Chromium Code Reviews| Index: tool/global_compile.dart |
| diff --git a/tool/global_compile.dart b/tool/global_compile.dart |
| index c6a658c018711b99bea45890cb8c12525c36182e..41b0875785c26de35595cd3ded914196045c8e94 100644 |
| --- a/tool/global_compile.dart |
| +++ b/tool/global_compile.dart |
| @@ -3,6 +3,7 @@ |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| +import 'dart:async'; |
| import 'dart:io'; |
| import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
| @@ -13,75 +14,219 @@ import 'package:dev_compiler/src/analyzer/context.dart' |
| show createAnalysisContextWithSources, AnalyzerOptions; |
| import 'package:path/path.dart' as path; |
| +const ENTRY = "main"; |
|
Jennifer Messerly
2016/05/10 20:43:00
style nit: this should not be upper camel case :)
|
| + |
| void main(List<String> args) { |
| // Parse flags. |
| var parser = new ArgParser() |
| - ..addOption('out', abbr: 'o', defaultsTo: 'out.js') |
| - ..addFlag('unsafe-force-compile', negatable: false) |
| - ..addOption('package-root', abbr: 'p', defaultsTo: 'packages/'); |
| + ..addOption('out', |
| + help: 'Output file (defaults to "out.js")', |
| + abbr: 'o', |
| + defaultsTo: 'out.js') |
| + ..addFlag('unsafe-force-compile', |
|
Jennifer Messerly
2016/05/10 20:43:00
I may have asked this before but, shouldn't this j
|
| + help: 'Generate code with undefined behavior', negatable: false) |
| + ..addOption('package-root', |
| + help: 'Directory containing packages', |
| + abbr: 'p', |
| + defaultsTo: 'packages/') |
| + ..addFlag('log', help: 'Show individual build commands') |
| + ..addOption('tmp', |
|
Jennifer Messerly
2016/05/10 20:43:00
any reason to not always use system temp? I'm tryi
|
| + help: |
| + 'Directory for temporary artifacts (defaults to a system tmp directory)'); |
|
Jennifer Messerly
2016/05/10 20:43:00
long line
|
| var options = parser.parse(args); |
| if (options.rest.length != 1) { |
| throw 'Expected a single dart entrypoint.'; |
| } |
| var entry = options.rest.first; |
| - var outfile = options['out']; |
| - var packageRoot = options['package-root']; |
| - var unsafe = options['unsafe-force-compile']; |
| + var outfile = options['out'] as String; |
| + var packageRoot = options['package-root'] as String; |
| + var unsafe = options['unsafe-force-compile'] as bool; |
| + var log = options['log'] as bool; |
| + var tmp = options['tmp'] as String; |
| - // Build an invocation to dartdevc. |
| + // Build an invocation to dartdevc |
| var dartPath = Platform.resolvedExecutable; |
| var ddcPath = path.dirname(path.dirname(Platform.script.toFilePath())); |
| - var command = [ |
| + var template = [ |
| '$ddcPath/bin/dartdevc.dart', |
| 'compile', |
| '--no-source-map', // Invalid as we're just concatenating files below |
| '-p', |
| - packageRoot, |
| - '-o', |
| - outfile |
| + packageRoot |
| ]; |
| if (unsafe) { |
| - command.add('--unsafe-force-compile'); |
| + template.add('--unsafe-force-compile'); |
| } |
| // Compute the transitive closure |
| - var watch = new Stopwatch()..start(); |
| + var total = new Stopwatch()..start(); |
| + var partial = new Stopwatch()..start(); |
| + |
| + // TODO(vsm): We're using the analyzer just to compute the import/export/part |
| + // dependence graph. This is expensive. Is there a lighterweight way to do |
| + // this? |
|
Jennifer Messerly
2016/05/10 20:43:00
yes indeed there is! See
https://github.com/dart-
|
| var context = createAnalysisContextWithSources(new AnalyzerOptions()); |
| - var inputSet = new Set<String>(); |
| - transitiveFiles(inputSet, context, entry, Directory.current.path); |
| - command.addAll(inputSet); |
| - var result = Process.runSync(dartPath, command); |
| + transitiveFiles(context, entry, Directory.current.path); |
| + orderModules(); |
| + computeTransitiveDependences(); |
| - if (result.exitCode == 0) { |
| - print(result.stdout); |
| - } else { |
| - print('ERROR:'); |
| - print(result.stdout); |
| - print(result.stderr); |
| - exit(1); |
| - } |
| - var time = watch.elapsedMilliseconds / 1000; |
| - print('Successfully compiled ${inputSet.length} files in $time seconds'); |
| + var graphTime = partial.elapsedMilliseconds / 1000; |
| + print('Computed global build graph in $graphTime seconds'); |
| - // Prepend Dart runtime files to the output. |
| + // Prepend Dart runtime files to the output |
| var out = new File(outfile); |
| - var code = out.readAsStringSync(); |
| var dartLibrary = |
| new File(path.join(ddcPath, 'lib', 'runtime', 'dart_library.js')) |
| .readAsStringSync(); |
| + out.writeAsStringSync(dartLibrary); |
| var dartSdk = new File(path.join(ddcPath, 'lib', 'runtime', 'dart_sdk.js')) |
| .readAsStringSync(); |
| - out.writeAsStringSync(dartLibrary); |
| out.writeAsStringSync(dartSdk, mode: FileMode.APPEND); |
| - out.writeAsStringSync(code, mode: FileMode.APPEND); |
| - |
| - // Append the entry point invocation. |
| - var moduleName = path.basenameWithoutExtension(outfile); |
| - var libraryName = |
| - path.withoutExtension(entry).replaceAll(path.separator, '__'); |
| - out.writeAsStringSync('dart_library.start("$moduleName", "$libraryName");\n', |
| - mode: FileMode.APPEND); |
| + |
| + // Linearize module concatenation for deterministic output |
| + var last = new Future.value(); |
| + for (var module in orderedModules) { |
| + linearizerMap[module] = last; |
| + var completer = new Completer(); |
| + completerMap[module] = completer; |
| + last = completer.future; |
| + } |
| + |
| + // Build modules asynchronously |
|
Jennifer Messerly
2016/05/10 20:43:00
Would it be easier to generate a Makefile and just
|
| + var tmpdir = (tmp == null) |
| + ? Directory.systemTemp |
| + .createTempSync(outfile.replaceAll(path.separator, '__')) |
| + : new Directory(tmp)..createSync(); |
| + for (var module in orderedModules) { |
| + var file = tmpdir.path + path.separator + module + '.js'; |
| + var command = new List.from(template)..addAll(['-o', file]); |
| + var dependences = transitiveDependenceMap[module]; |
| + for (var dependence in dependences) { |
| + var summary = tmpdir.path + path.separator + dependence + '.sum'; |
| + command.addAll(['-s', summary]); |
| + } |
| + var infiles = fileMap[module]; |
| + command.addAll(infiles); |
| + |
| + var immediateDeps = |
| + dependenceMap.containsKey(module) ? dependenceMap[module] : <String>[]; |
| + var waitList = immediateDeps.map((dep) => readyMap[dep]); |
| + var future = Future.wait(waitList); |
| + readyMap[module] = future.then((_) { |
| + var ready = Process.run(dartPath, command); |
| + if (log) { |
| + print(command.join(' ')); |
| + } |
| + return ready.then((result) { |
|
Jennifer Messerly
2016/05/10 20:43:00
this could be an `await`
https://www.dartlang.org/
|
| + if (result.exitCode != 0) { |
| + print('ERROR: compiling $module'); |
| + print(result.stdout); |
| + print(result.stderr); |
| + out.deleteSync(); |
| + exit(1); |
| + } |
| + print('Compiled $module (${infiles.length} files)'); |
| + print(result.stdout); |
| + |
| + // Schedule module append once the previous module is written |
| + var codefile = new File(file); |
| + linearizerMap[module] |
| + .then((_) => codefile.readAsString()) |
| + .then((code) => |
| + out.writeAsString(code, mode: FileMode.APPEND, flush: true)) |
| + .then((_) => completerMap[module].complete()); |
| + }); |
| + }); |
| + } |
| + |
| + last.then((_) { |
|
Jennifer Messerly
2016/05/10 20:43:00
same this could be async/await
|
| + var time = total.elapsedMilliseconds / 1000; |
| + print('Successfully compiled ${inputSet.length} files in $time seconds'); |
| + |
| + // Append the entry point invocation. |
| + var libraryName = |
| + path.withoutExtension(entry).replaceAll(path.separator, '__'); |
| + out.writeAsStringSync('dart_library.start("$ENTRY", "$libraryName");\n', |
| + mode: FileMode.APPEND); |
| + }); |
| +} |
| + |
| +final inputSet = new Set<String>(); |
| +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
|
| +final transitiveDependenceMap = new Map<String, Set<String>>(); |
| +final fileMap = new Map<String, Set<String>>(); |
| + |
| +final readyMap = new Map<String, Future>(); |
| +final linearizerMap = new Map<String, Future>(); |
| +final completerMap = new Map<String, Completer>(); |
| + |
| +final orderedModules = new List<String>(); |
| +final visitedModules = new Set<String>(); |
| + |
| +void orderModules( |
|
Jennifer Messerly
2016/05/10 20:43:00
fyi ... I didn't look at all this build graph stuf
|
| + [String module = ENTRY, List<String> stack, Set<String> visited]) { |
| + if (stack == null) { |
| + assert(visited == null); |
| + stack = new List<String>(); |
| + visited = new Set<String>(); |
| + } |
| + if (visited.contains(module)) return; |
| + visited.add(module); |
| + if (stack.contains(module)) { |
| + print(stack); |
| + throw 'Circular dependence on $module'; |
| + } |
| + stack.add(module); |
| + var dependences = dependenceMap[module]; |
| + if (dependences != null) { |
| + for (var dependence in dependences) { |
| + orderModules(dependence, stack, visited); |
| + } |
| + } |
| + orderedModules.add(module); |
| + assert(module == stack.last); |
| + stack.removeLast(); |
| +} |
| + |
| +void computeTransitiveDependences() { |
| + for (var module in orderedModules) { |
| + var transitiveSet = new Set<String>(); |
| + if (dependenceMap.containsKey(module)) { |
| + transitiveSet.addAll(dependenceMap[module]); |
| + for (var dependence in dependenceMap[module]) { |
| + transitiveSet.addAll(transitiveDependenceMap[dependence]); |
| + } |
| + } |
| + transitiveDependenceMap[module] = transitiveSet; |
| + } |
| +} |
| + |
| +String getModule(String uri) { |
| + var sourceUri = Uri.parse(uri); |
| + if (sourceUri.scheme == 'dart') { |
| + return 'dart'; |
| + } else if (sourceUri.scheme == 'package') { |
| + return path.split(sourceUri.path)[0]; |
| + } else { |
| + return ENTRY; |
| + } |
| +} |
| + |
| +bool processFile(String file) { |
| + inputSet.add(file); |
| + |
| + var module = getModule(file); |
| + fileMap.putIfAbsent(module, () => new Set<String>()); |
| + return fileMap[module].add(file); |
| +} |
| + |
| +void processDependence(String from, String to) { |
| + var fromModule = getModule(from); |
| + var toModule = getModule(to); |
| + if (fromModule == toModule || toModule == 'dart') return; |
| + dependenceMap.putIfAbsent(fromModule, () => new Set<String>()); |
| + dependenceMap[fromModule].add(toModule); |
| } |
| String canonicalize(String uri, String root) { |
| @@ -93,12 +238,11 @@ String canonicalize(String uri, String root) { |
| return sourceUri.toString(); |
| } |
| -void transitiveFiles(Set<String> results, AnalysisContext context, |
| - String entryPoint, String root) { |
| +void transitiveFiles(AnalysisContext context, String entryPoint, String root) { |
| entryPoint = canonicalize(entryPoint, root); |
| if (entryPoint.startsWith('dart:')) return; |
| var entryDir = path.dirname(entryPoint); |
| - if (results.add(entryPoint)) { |
| + if (processFile(entryPoint)) { |
| // Process this |
| var source = context.sourceFactory.forUri(entryPoint); |
| if (source == null) { |
| @@ -108,13 +252,15 @@ void transitiveFiles(Set<String> results, AnalysisContext context, |
| var library = context.computeLibraryElement(source); |
| for (var entry in library.imports) { |
| if (entry.uri == null) continue; |
| - transitiveFiles(results, context, entry.uri, entryDir); |
| + processDependence(entryPoint, canonicalize(entry.uri, entryDir)); |
| + transitiveFiles(context, entry.uri, entryDir); |
| } |
| for (var entry in library.exports) { |
| - transitiveFiles(results, context, entry.uri, entryDir); |
| + processDependence(entryPoint, canonicalize(entry.uri, entryDir)); |
| + transitiveFiles(context, entry.uri, entryDir); |
| } |
| for (var part in library.parts) { |
| - results.add(canonicalize(part.uri, entryDir)); |
| + processFile(canonicalize(part.uri, entryDir)); |
| } |
| } |
| } |