Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env dart | 1 #!/usr/bin/env dart |
| 2 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 2 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 3 // for details. All rights reserved. Use of this source code is governed by a | 3 // for details. All rights reserved. Use of this source code is governed by a |
| 4 // BSD-style license that can be found in the LICENSE file. | 4 // BSD-style license that can be found in the LICENSE file. |
| 5 | 5 |
| 6 import 'dart:async'; | |
| 6 import 'dart:io'; | 7 import 'dart:io'; |
| 7 | 8 |
| 8 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; | 9 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
| 9 import 'package:args/args.dart' show ArgParser; | 10 import 'package:args/args.dart' show ArgParser; |
| 10 import 'package:dev_compiler/src/analyzer/context.dart' | 11 import 'package:dev_compiler/src/analyzer/context.dart' |
| 11 show createAnalysisContextWithSources; | 12 show createAnalysisContextWithSources; |
| 12 import 'package:dev_compiler/src/analyzer/context.dart' | 13 import 'package:dev_compiler/src/analyzer/context.dart' |
| 13 show createAnalysisContextWithSources, AnalyzerOptions; | 14 show createAnalysisContextWithSources, AnalyzerOptions; |
| 14 import 'package:path/path.dart' as path; | 15 import 'package:path/path.dart' as path; |
| 15 | 16 |
| 17 const ENTRY = "main"; | |
|
Jennifer Messerly
2016/05/10 20:43:00
style nit: this should not be upper camel case :)
| |
| 18 | |
| 16 void main(List<String> args) { | 19 void main(List<String> args) { |
| 17 // Parse flags. | 20 // Parse flags. |
| 18 var parser = new ArgParser() | 21 var parser = new ArgParser() |
| 19 ..addOption('out', abbr: 'o', defaultsTo: 'out.js') | 22 ..addOption('out', |
| 20 ..addFlag('unsafe-force-compile', negatable: false) | 23 help: 'Output file (defaults to "out.js")', |
| 21 ..addOption('package-root', abbr: 'p', defaultsTo: 'packages/'); | 24 abbr: 'o', |
| 25 defaultsTo: 'out.js') | |
| 26 ..addFlag('unsafe-force-compile', | |
|
Jennifer Messerly
2016/05/10 20:43:00
I may have asked this before but, shouldn't this j
| |
| 27 help: 'Generate code with undefined behavior', negatable: false) | |
| 28 ..addOption('package-root', | |
| 29 help: 'Directory containing packages', | |
| 30 abbr: 'p', | |
| 31 defaultsTo: 'packages/') | |
| 32 ..addFlag('log', help: 'Show individual build commands') | |
| 33 ..addOption('tmp', | |
|
Jennifer Messerly
2016/05/10 20:43:00
any reason to not always use system temp? I'm tryi
| |
| 34 help: | |
| 35 'Directory for temporary artifacts (defaults to a system tmp directo ry)'); | |
|
Jennifer Messerly
2016/05/10 20:43:00
long line
| |
| 22 | 36 |
| 23 var options = parser.parse(args); | 37 var options = parser.parse(args); |
| 24 if (options.rest.length != 1) { | 38 if (options.rest.length != 1) { |
| 25 throw 'Expected a single dart entrypoint.'; | 39 throw 'Expected a single dart entrypoint.'; |
| 26 } | 40 } |
| 27 var entry = options.rest.first; | 41 var entry = options.rest.first; |
| 28 var outfile = options['out']; | 42 var outfile = options['out'] as String; |
| 29 var packageRoot = options['package-root']; | 43 var packageRoot = options['package-root'] as String; |
| 30 var unsafe = options['unsafe-force-compile']; | 44 var unsafe = options['unsafe-force-compile'] as bool; |
| 31 | 45 var log = options['log'] as bool; |
| 32 // Build an invocation to dartdevc. | 46 var tmp = options['tmp'] as String; |
| 47 | |
| 48 // Build an invocation to dartdevc | |
| 33 var dartPath = Platform.resolvedExecutable; | 49 var dartPath = Platform.resolvedExecutable; |
| 34 var ddcPath = path.dirname(path.dirname(Platform.script.toFilePath())); | 50 var ddcPath = path.dirname(path.dirname(Platform.script.toFilePath())); |
| 35 var command = [ | 51 var template = [ |
| 36 '$ddcPath/bin/dartdevc.dart', | 52 '$ddcPath/bin/dartdevc.dart', |
| 37 'compile', | 53 'compile', |
| 38 '--no-source-map', // Invalid as we're just concatenating files below | 54 '--no-source-map', // Invalid as we're just concatenating files below |
| 39 '-p', | 55 '-p', |
| 40 packageRoot, | 56 packageRoot |
| 41 '-o', | |
| 42 outfile | |
| 43 ]; | 57 ]; |
| 44 if (unsafe) { | 58 if (unsafe) { |
| 45 command.add('--unsafe-force-compile'); | 59 template.add('--unsafe-force-compile'); |
| 46 } | 60 } |
| 47 | 61 |
| 48 // Compute the transitive closure | 62 // Compute the transitive closure |
| 49 var watch = new Stopwatch()..start(); | 63 var total = new Stopwatch()..start(); |
| 64 var partial = new Stopwatch()..start(); | |
| 65 | |
| 66 // TODO(vsm): We're using the analyzer just to compute the import/export/part | |
| 67 // dependence graph. This is expensive. Is there a lighterweight way to do | |
| 68 // this? | |
|
Jennifer Messerly
2016/05/10 20:43:00
yes indeed there is! See
https://github.com/dart-
| |
| 50 var context = createAnalysisContextWithSources(new AnalyzerOptions()); | 69 var context = createAnalysisContextWithSources(new AnalyzerOptions()); |
| 51 var inputSet = new Set<String>(); | 70 transitiveFiles(context, entry, Directory.current.path); |
| 52 transitiveFiles(inputSet, context, entry, Directory.current.path); | 71 orderModules(); |
| 53 command.addAll(inputSet); | 72 computeTransitiveDependences(); |
| 54 var result = Process.runSync(dartPath, command); | 73 |
| 55 | 74 var graphTime = partial.elapsedMilliseconds / 1000; |
| 56 if (result.exitCode == 0) { | 75 print('Computed global build graph in $graphTime seconds'); |
| 57 print(result.stdout); | 76 |
| 58 } else { | 77 // Prepend Dart runtime files to the output |
| 59 print('ERROR:'); | |
| 60 print(result.stdout); | |
| 61 print(result.stderr); | |
| 62 exit(1); | |
| 63 } | |
| 64 var time = watch.elapsedMilliseconds / 1000; | |
| 65 print('Successfully compiled ${inputSet.length} files in $time seconds'); | |
| 66 | |
| 67 // Prepend Dart runtime files to the output. | |
| 68 var out = new File(outfile); | 78 var out = new File(outfile); |
| 69 var code = out.readAsStringSync(); | |
| 70 var dartLibrary = | 79 var dartLibrary = |
| 71 new File(path.join(ddcPath, 'lib', 'runtime', 'dart_library.js')) | 80 new File(path.join(ddcPath, 'lib', 'runtime', 'dart_library.js')) |
| 72 .readAsStringSync(); | 81 .readAsStringSync(); |
| 82 out.writeAsStringSync(dartLibrary); | |
| 73 var dartSdk = new File(path.join(ddcPath, 'lib', 'runtime', 'dart_sdk.js')) | 83 var dartSdk = new File(path.join(ddcPath, 'lib', 'runtime', 'dart_sdk.js')) |
| 74 .readAsStringSync(); | 84 .readAsStringSync(); |
| 75 out.writeAsStringSync(dartLibrary); | |
| 76 out.writeAsStringSync(dartSdk, mode: FileMode.APPEND); | 85 out.writeAsStringSync(dartSdk, mode: FileMode.APPEND); |
| 77 out.writeAsStringSync(code, mode: FileMode.APPEND); | 86 |
| 78 | 87 // Linearize module concatenation for deterministic output |
| 79 // Append the entry point invocation. | 88 var last = new Future.value(); |
| 80 var moduleName = path.basenameWithoutExtension(outfile); | 89 for (var module in orderedModules) { |
| 81 var libraryName = | 90 linearizerMap[module] = last; |
| 82 path.withoutExtension(entry).replaceAll(path.separator, '__'); | 91 var completer = new Completer(); |
| 83 out.writeAsStringSync('dart_library.start("$moduleName", "$libraryName");\n', | 92 completerMap[module] = completer; |
| 84 mode: FileMode.APPEND); | 93 last = completer.future; |
| 94 } | |
| 95 | |
| 96 // Build modules asynchronously | |
|
Jennifer Messerly
2016/05/10 20:43:00
Would it be easier to generate a Makefile and just
| |
| 97 var tmpdir = (tmp == null) | |
| 98 ? Directory.systemTemp | |
| 99 .createTempSync(outfile.replaceAll(path.separator, '__')) | |
| 100 : new Directory(tmp)..createSync(); | |
| 101 for (var module in orderedModules) { | |
| 102 var file = tmpdir.path + path.separator + module + '.js'; | |
| 103 var command = new List.from(template)..addAll(['-o', file]); | |
| 104 var dependences = transitiveDependenceMap[module]; | |
| 105 for (var dependence in dependences) { | |
| 106 var summary = tmpdir.path + path.separator + dependence + '.sum'; | |
| 107 command.addAll(['-s', summary]); | |
| 108 } | |
| 109 var infiles = fileMap[module]; | |
| 110 command.addAll(infiles); | |
| 111 | |
| 112 var immediateDeps = | |
| 113 dependenceMap.containsKey(module) ? dependenceMap[module] : <String>[]; | |
| 114 var waitList = immediateDeps.map((dep) => readyMap[dep]); | |
| 115 var future = Future.wait(waitList); | |
| 116 readyMap[module] = future.then((_) { | |
| 117 var ready = Process.run(dartPath, command); | |
| 118 if (log) { | |
| 119 print(command.join(' ')); | |
| 120 } | |
| 121 return ready.then((result) { | |
|
Jennifer Messerly
2016/05/10 20:43:00
this could be an `await`
https://www.dartlang.org/
| |
| 122 if (result.exitCode != 0) { | |
| 123 print('ERROR: compiling $module'); | |
| 124 print(result.stdout); | |
| 125 print(result.stderr); | |
| 126 out.deleteSync(); | |
| 127 exit(1); | |
| 128 } | |
| 129 print('Compiled $module (${infiles.length} files)'); | |
| 130 print(result.stdout); | |
| 131 | |
| 132 // Schedule module append once the previous module is written | |
| 133 var codefile = new File(file); | |
| 134 linearizerMap[module] | |
| 135 .then((_) => codefile.readAsString()) | |
| 136 .then((code) => | |
| 137 out.writeAsString(code, mode: FileMode.APPEND, flush: true)) | |
| 138 .then((_) => completerMap[module].complete()); | |
| 139 }); | |
| 140 }); | |
| 141 } | |
| 142 | |
| 143 last.then((_) { | |
|
Jennifer Messerly
2016/05/10 20:43:00
same this could be async/await
| |
| 144 var time = total.elapsedMilliseconds / 1000; | |
| 145 print('Successfully compiled ${inputSet.length} files in $time seconds'); | |
| 146 | |
| 147 // Append the entry point invocation. | |
| 148 var libraryName = | |
| 149 path.withoutExtension(entry).replaceAll(path.separator, '__'); | |
| 150 out.writeAsStringSync('dart_library.start("$ENTRY", "$libraryName");\n', | |
| 151 mode: FileMode.APPEND); | |
| 152 }); | |
| 153 } | |
| 154 | |
| 155 final inputSet = new Set<String>(); | |
| 156 final dependenceMap = new Map<String, Set<String>>(); | |
|
Jennifer Messerly
2016/05/10 20:43:00
all of these maps do make me wonder if we should h
| |
| 157 final transitiveDependenceMap = new Map<String, Set<String>>(); | |
| 158 final fileMap = new Map<String, Set<String>>(); | |
| 159 | |
| 160 final readyMap = new Map<String, Future>(); | |
| 161 final linearizerMap = new Map<String, Future>(); | |
| 162 final completerMap = new Map<String, Completer>(); | |
| 163 | |
| 164 final orderedModules = new List<String>(); | |
| 165 final visitedModules = new Set<String>(); | |
| 166 | |
| 167 void orderModules( | |
|
Jennifer Messerly
2016/05/10 20:43:00
fyi ... I didn't look at all this build graph stuf
| |
| 168 [String module = ENTRY, List<String> stack, Set<String> visited]) { | |
| 169 if (stack == null) { | |
| 170 assert(visited == null); | |
| 171 stack = new List<String>(); | |
| 172 visited = new Set<String>(); | |
| 173 } | |
| 174 if (visited.contains(module)) return; | |
| 175 visited.add(module); | |
| 176 if (stack.contains(module)) { | |
| 177 print(stack); | |
| 178 throw 'Circular dependence on $module'; | |
| 179 } | |
| 180 stack.add(module); | |
| 181 var dependences = dependenceMap[module]; | |
| 182 if (dependences != null) { | |
| 183 for (var dependence in dependences) { | |
| 184 orderModules(dependence, stack, visited); | |
| 185 } | |
| 186 } | |
| 187 orderedModules.add(module); | |
| 188 assert(module == stack.last); | |
| 189 stack.removeLast(); | |
| 190 } | |
| 191 | |
| 192 void computeTransitiveDependences() { | |
| 193 for (var module in orderedModules) { | |
| 194 var transitiveSet = new Set<String>(); | |
| 195 if (dependenceMap.containsKey(module)) { | |
| 196 transitiveSet.addAll(dependenceMap[module]); | |
| 197 for (var dependence in dependenceMap[module]) { | |
| 198 transitiveSet.addAll(transitiveDependenceMap[dependence]); | |
| 199 } | |
| 200 } | |
| 201 transitiveDependenceMap[module] = transitiveSet; | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 String getModule(String uri) { | |
| 206 var sourceUri = Uri.parse(uri); | |
| 207 if (sourceUri.scheme == 'dart') { | |
| 208 return 'dart'; | |
| 209 } else if (sourceUri.scheme == 'package') { | |
| 210 return path.split(sourceUri.path)[0]; | |
| 211 } else { | |
| 212 return ENTRY; | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 bool processFile(String file) { | |
| 217 inputSet.add(file); | |
| 218 | |
| 219 var module = getModule(file); | |
| 220 fileMap.putIfAbsent(module, () => new Set<String>()); | |
| 221 return fileMap[module].add(file); | |
| 222 } | |
| 223 | |
| 224 void processDependence(String from, String to) { | |
| 225 var fromModule = getModule(from); | |
| 226 var toModule = getModule(to); | |
| 227 if (fromModule == toModule || toModule == 'dart') return; | |
| 228 dependenceMap.putIfAbsent(fromModule, () => new Set<String>()); | |
| 229 dependenceMap[fromModule].add(toModule); | |
| 85 } | 230 } |
| 86 | 231 |
| 87 String canonicalize(String uri, String root) { | 232 String canonicalize(String uri, String root) { |
| 88 var sourceUri = Uri.parse(uri); | 233 var sourceUri = Uri.parse(uri); |
| 89 if (sourceUri.scheme == '') { | 234 if (sourceUri.scheme == '') { |
| 90 sourceUri = path.toUri( | 235 sourceUri = path.toUri( |
| 91 path.isAbsolute(uri) ? path.absolute(uri) : path.join(root, uri)); | 236 path.isAbsolute(uri) ? path.absolute(uri) : path.join(root, uri)); |
| 92 } | 237 } |
| 93 return sourceUri.toString(); | 238 return sourceUri.toString(); |
| 94 } | 239 } |
| 95 | 240 |
| 96 void transitiveFiles(Set<String> results, AnalysisContext context, | 241 void transitiveFiles(AnalysisContext context, String entryPoint, String root) { |
| 97 String entryPoint, String root) { | |
| 98 entryPoint = canonicalize(entryPoint, root); | 242 entryPoint = canonicalize(entryPoint, root); |
| 99 if (entryPoint.startsWith('dart:')) return; | 243 if (entryPoint.startsWith('dart:')) return; |
| 100 var entryDir = path.dirname(entryPoint); | 244 var entryDir = path.dirname(entryPoint); |
| 101 if (results.add(entryPoint)) { | 245 if (processFile(entryPoint)) { |
| 102 // Process this | 246 // Process this |
| 103 var source = context.sourceFactory.forUri(entryPoint); | 247 var source = context.sourceFactory.forUri(entryPoint); |
| 104 if (source == null) { | 248 if (source == null) { |
| 105 throw new Exception('could not create a source for $entryPoint.' | 249 throw new Exception('could not create a source for $entryPoint.' |
| 106 ' The file name is in the wrong format or was not found.'); | 250 ' The file name is in the wrong format or was not found.'); |
| 107 } | 251 } |
| 108 var library = context.computeLibraryElement(source); | 252 var library = context.computeLibraryElement(source); |
| 109 for (var entry in library.imports) { | 253 for (var entry in library.imports) { |
| 110 if (entry.uri == null) continue; | 254 if (entry.uri == null) continue; |
| 111 transitiveFiles(results, context, entry.uri, entryDir); | 255 processDependence(entryPoint, canonicalize(entry.uri, entryDir)); |
| 256 transitiveFiles(context, entry.uri, entryDir); | |
| 112 } | 257 } |
| 113 for (var entry in library.exports) { | 258 for (var entry in library.exports) { |
| 114 transitiveFiles(results, context, entry.uri, entryDir); | 259 processDependence(entryPoint, canonicalize(entry.uri, entryDir)); |
| 260 transitiveFiles(context, entry.uri, entryDir); | |
| 115 } | 261 } |
| 116 for (var part in library.parts) { | 262 for (var part in library.parts) { |
| 117 results.add(canonicalize(part.uri, entryDir)); | 263 processFile(canonicalize(part.uri, entryDir)); |
| 118 } | 264 } |
| 119 } | 265 } |
| 120 } | 266 } |
| OLD | NEW |