Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(263)

Unified Diff: lib/src/compiler.dart

Issue 1235503010: fixes #219, able to compile multiple entry points (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: lib/src/compiler.dart
diff --git a/lib/src/compiler.dart b/lib/src/compiler.dart
new file mode 100644
index 0000000000000000000000000000000000000000..a018b9061c91d8dbeaf097ba783726711a477431
--- /dev/null
+++ b/lib/src/compiler.dart
@@ -0,0 +1,401 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+/// Command line tool to run the checker on a Dart program.
+library dev_compiler.src.compiler;
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:math' as math;
+import 'dart:io';
+
+import 'package:analyzer/src/generated/ast.dart' show CompilationUnit;
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/engine.dart'
+ show AnalysisEngine, AnalysisContext, ChangeSet, ParseDartTask;
+import 'package:analyzer/src/generated/error.dart'
+ show AnalysisError, ErrorSeverity, ErrorType;
+import 'package:analyzer/src/generated/error.dart';
+import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
+import 'package:analyzer/src/generated/source.dart' show Source;
+import 'package:analyzer/src/task/html.dart';
+import 'package:html/dom.dart' as html;
+import 'package:html/parser.dart' as html;
+import 'package:logging/logging.dart' show Level, Logger, LogRecord;
+import 'package:path/path.dart' as path;
+
+import 'package:dev_compiler/strong_mode.dart' show StrongModeOptions;
+
+import 'analysis_context.dart';
+import 'checker/checker.dart';
+import 'checker/rules.dart';
+import 'codegen/html_codegen.dart' as html_codegen;
+import 'codegen/js_codegen.dart';
+import 'info.dart'
+ show AnalyzerMessage, CheckerResults, LibraryInfo, LibraryUnit;
+import 'options.dart';
+import 'report.dart';
+
+/// Sets up the type checker logger to print a span that highlights error
+/// messages.
+StreamSubscription setupLogger(Level level, printFn) {
+ Logger.root.level = level;
+ return Logger.root.onRecord.listen((LogRecord rec) {
+ printFn('${rec.level.name.toLowerCase()}: ${rec.message}');
+ });
+}
+
+class BatchCompiler extends AbstractCompiler {
+ JSGenerator _jsGen;
+
+ /// Already compiled sources, so we don't compile them again.
+ final _compiled = new HashSet<LibraryElement>();
+
+ bool _failure = false;
+ bool get failure => _failure;
+
+ BatchCompiler(AnalysisContext context, CompilerOptions options,
+ {AnalysisErrorListener reporter})
+ : super(context, options, reporter) {
+ if (outputDir != null) {
+ _jsGen = new JSGenerator(this);
+ }
+ }
+
+ void reset() {
+ _compiled.clear();
+ }
+
+ /// Compiles every file in [options.inputs].
+ /// Returns true on successful compile.
+ bool run() {
+ var clock = new Stopwatch()..start();
+ options.inputs.forEach(compileFromUriString);
+ clock.stop();
+ var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
+ _log.fine('Compiled ${_compiled.length} libraries in ${time} s\n');
+
+ return !_failure;
+ }
+
+ void compileFromUriString(String uriString) {
+ compileFromUri(stringToUri(uriString));
+ }
+
+ void compileFromUri(Uri uri) {
+ var source = context.sourceFactory.forUri(Uri.encodeFull('$uri'));
+ if (source == null) throw new ArgumentError.value(
+ uri.toString(), 'uri', 'could not find source for');
+ compileSource(source);
+ }
+
+ void compileSource(Source source) {
+ if (AnalysisEngine.isHtmlFileName(source.uri.path)) {
+ compileHtml(source);
+ return;
+ }
+
+ compileLibrary(context.computeLibraryElement(source));
+ }
+
+ void compileLibrary(LibraryElement library) {
+ if (!_compiled.add(library)) return;
+ if (!options.checkSdk && library.source.uri.scheme == 'dart') return;
+
+ // TODO(jmesserly): in incremental mode, we can skip the transitive
+ // compile of imports/exports.
+ library.importedLibraries.forEach(compileLibrary);
+ library.exportedLibraries.forEach(compileLibrary);
+
+ var unitElements = [library.definingCompilationUnit]..addAll(library.parts);
+ var units = <CompilationUnit>[];
+
+ bool failureInLib = false;
+ for (var element in unitElements) {
+ var unit = context.resolveCompilationUnit(element.source, library);
+ units.add(unit);
+ failureInLib = logErrors(element.source) || failureInLib;
+ checker.visitCompilationUnit(unit);
+ if (checker.failure) failureInLib = true;
+ }
+
+ if (failureInLib) {
+ _failure = true;
+ if (!options.codegenOptions.forceCompile) return;
+ }
+
+ if (_jsGen != null) {
+ var unit = units.first;
+ var parts = units.skip(1).toList();
+
+ // TODO(jmesserly): this hack is to avoid compiling the same compilation
+ // unit to JS twice. We mutate the AST, so it's not safe to run more than
+ // once on the same unit.
+ if (unit.getProperty(_propertyName) == true) return;
+ unit.setProperty(_propertyName, true);
+
+ _jsGen.generateLibrary(new LibraryUnit(unit, parts));
+ }
+ }
+
+ static const String _propertyName = 'dev_compiler.BatchCompiler.isCompiled';
+
+ void compileHtml(Source source) {
+ // TODO(jmesserly): reuse DartScriptsTask instead of copy/paste.
+ var contents = context.getContents(source);
+ var document = html.parse(contents.data, generateSpans: true);
+ var scripts = document.querySelectorAll('script[type="application/dart"]');
+
+ var loadedLibs = new LinkedHashSet<Uri>();
+
+ for (var script in scripts) {
+ Source scriptSource = null;
+ var srcAttr = script.attributes['src'];
+ if (srcAttr == null) {
+ if (script.hasContent()) {
+ var fragments = <ScriptFragment>[];
+ for (var node in script.nodes) {
+ if (node is html.Text) {
+ var start = node.sourceSpan.start;
+ fragments.add(new ScriptFragment(
+ start.offset, start.line, start.column, node.data));
+ }
+ }
+ scriptSource = new DartScript(source, fragments);
+ }
+ } else if (AnalysisEngine.isDartFileName(srcAttr)) {
+ scriptSource = context.sourceFactory.resolveUri(source, srcAttr);
+ }
+
+ if (scriptSource != null) {
+ var lib = context.computeLibraryElement(scriptSource);
+ compileLibrary(lib);
+ script.replaceWith(_linkLibraries(lib, loadedLibs));
+ }
+ }
+
+ // TODO(jmesserly): we need to clean this up so we aren't treating these
+ // as a special case.
+ for (var file in defaultRuntimeFiles) {
+ var input = path.join(options.runtimeDir, file);
+ var output = path.join(outputDir, runtimeFileOutput(file));
+ new Directory(path.dirname(output)).createSync(recursive: true);
+ new File(input).copySync(output);
+ }
+
+ new File(getOutputPath(source.uri)).openSync(mode: FileMode.WRITE)
+ ..writeStringSync(document.outerHtml)
+ ..writeStringSync('\n')
+ ..closeSync();
+ }
+
+ html.DocumentFragment _linkLibraries(
+ LibraryElement mainLib, LinkedHashSet<Uri> loaded) {
+ var alreadyLoaded = loaded.length;
+ _collectLibraries(mainLib, loaded);
+
+ var newLibs = loaded.skip(alreadyLoaded);
+ var df = new html.DocumentFragment();
+ for (var path in defaultRuntimeFiles) {
+ df.append(html_codegen.libraryInclude(runtimeFileOutput(path)));
+ }
+ for (var uri in newLibs) {
+ if (uri.scheme == 'dart') continue;
+ df.append(html_codegen.libraryInclude(getModulePath(uri)));
+ }
+ df.append(html_codegen.invokeMain(getModuleName(mainLib.source.uri)));
+ return df;
+ }
+
+ void _collectLibraries(LibraryElement lib, LinkedHashSet<Uri> loaded) {
+ var uri = lib.source.uri;
+ if (!loaded.add(uri)) return;
+ for (var l in lib.importedLibraries) _collectLibraries(l, loaded);
+ for (var l in lib.exportedLibraries) _collectLibraries(l, loaded);
+ // Move the item to the end of the list.
+ loaded.remove(uri);
+ loaded.add(uri);
+ }
+
+ String runtimeFileOutput(String file) =>
+ path.join('dev_compiler', 'runtime', file);
+}
+
+abstract class AbstractCompiler {
+ final CompilerOptions options;
+ final AnalysisContext context;
+ final CodeChecker checker;
+
+ AbstractCompiler(AnalysisContext context, CompilerOptions options,
+ [AnalysisErrorListener reporter])
+ : context = context,
+ options = options,
+ checker = createChecker(context.typeProvider, options.strongOptions,
+ reporter == null ? AnalysisErrorListener.NULL_LISTENER : reporter) {
+ enableDevCompilerInference(context, options.strongOptions);
+ }
+
+ static CodeChecker createChecker(TypeProvider typeProvider,
+ StrongModeOptions options, AnalysisErrorListener reporter) {
+ return new CodeChecker(
+ new RestrictedRules(typeProvider, options: options), reporter, options);
+ }
+
+ String get outputDir => options.codegenOptions.outputDir;
+ TypeRules get rules => checker.rules;
+ AnalysisErrorListener get reporter => checker.reporter;
+
+ Uri stringToUri(String uriString) {
+ var uri = uriString.startsWith('dart:') || uriString.startsWith('package:')
+ ? Uri.parse(uriString)
+ : new Uri.file(uriString);
+ return uri;
+ }
+
+ /// Directory presumed to be the common prefix for all input file:// URIs.
+ /// Used when computing output paths.
+ ///
+ /// For example:
+ /// dartdevc -o out foo/a.dart bar/b.dart
+ ///
+ /// Will produce:
+ /// out/foo/a.dart
+ /// out/bar/b.dart
+ ///
+ /// This is only used if at least one of [options.codegenOptions.inputs] is
+ /// a file URI.
+ // TODO(jmesserly): do we need an option for this?
vsm 2015/07/16 23:56:39 Might be nice at some point, but easy enough to ad
+ // Other ideas: we could look up and see what package the file is in, treat
+ // that as a base path. We could also use the current working directory as
+ // the base.
+ String get inputBaseDir {
+ if (_fileUriCommonBaseDir == null) {
+ List<String> common = null;
+ for (var uri in options.inputs.map(stringToUri)) {
+ if (uri.scheme != 'file') continue;
+
+ var segments = path.split(path.dirname(uri.path));
+ if (common == null) {
+ common = segments;
+ } else {
+ int len = math.min(common.length, segments.length);
+ while (len > 0 && common[len - 1] != segments[len - 1]) {
+ len--;
+ }
+ common.length = len;
+ }
+ }
+ _fileUriCommonBaseDir = common == null ? '' : path.joinAll(common);
+ }
+ return _fileUriCommonBaseDir;
+ }
+ String _fileUriCommonBaseDir;
+
+ String getOutputPath(Uri uri) => path.join(outputDir, getModulePath(uri));
+
+ /// Like [getModuleName] but includes the file extension, either .js or .html.
+ String getModulePath(Uri uri) {
+ var ext = path.extension(uri.path);
+ if (ext == '.dart' || ext == '' && uri.scheme == 'dart') ext = '.js';
+ return getModuleName(uri) + ext;
+ }
+
+ /// Gets the module name, without extension. For example:
+ ///
+ /// * dart:core -> dart/core
+ /// * file:foo/bar/baz.dart -> foo/bar/baz
+ /// * package:qux/qux.dart -> qux/qux
vsm 2015/07/16 23:56:39 at some point, we might want to prefix the package
+ ///
+ /// For file: URLs this will also make them relative to [inputBaseDir].
+ String getModuleName(Uri uri) {
+ var filepath = path.withoutExtension(uri.path);
+ if (uri.scheme == 'dart') {
+ filepath = 'dart/$filepath';
+ } else if (uri.scheme == 'file') {
+ filepath = path.relative(filepath, from: inputBaseDir);
+ } else {
+ assert(uri.scheme == 'package');
+ // filepath is good here, we want the output to start with a directory
+ // matching the package name.
+ }
+ return filepath;
+ }
+
+ /// Log any errors encountered when resolving [source] and return whether any
+ /// errors were found.
+ bool logErrors(Source source) {
+ List<AnalysisError> errors = context.computeErrors(source);
+ bool failure = false;
+ if (errors.isNotEmpty) {
+ for (var error in errors) {
+ // Always skip TODOs.
+ if (error.errorCode.type == ErrorType.TODO) continue;
+
+ // Skip hints for now. In the future these could be turned on via flags.
+ if (error.errorCode.errorSeverity.ordinal <
+ ErrorSeverity.WARNING.ordinal) {
+ continue;
+ }
+
+ // All analyzer warnings or errors are errors for DDC.
+ failure = true;
+ reporter.onError(error);
+ }
+ }
+ return failure;
+ }
+}
+
+AnalysisErrorListener createErrorReporter(
+ AnalysisContext context, CompilerOptions options) {
+ return options.dumpInfo
+ ? new SummaryReporter(context, options.logLevel)
+ : new LogReporter(context, useColors: options.useColors);
+}
+
+// TODO(jmesserly): find a better home for these.
+/// Curated order to minimize lazy classes needed by dart:core and its
+/// transitive SDK imports.
+const corelibOrder = const [
+ 'dart.core',
+ 'dart.collection',
+ 'dart._internal',
+ 'dart.math',
+ 'dart._interceptors',
+ 'dart.async',
+ 'dart._foreign_helper',
+ 'dart._js_embedded_names',
+ 'dart._js_helper',
+ 'dart.isolate',
+ 'dart.typed_data',
+ 'dart._native_typed_data',
+ 'dart._isolate_helper',
+ 'dart._js_primitives',
+ 'dart.convert',
+ 'dart.mirrors',
+ 'dart._js_mirrors',
+ 'dart.js'
+ // _foreign_helper is not included, as it only defines the JS builtin that
+ // the compiler handles at compile time.
+];
+
+/// Runtime files added to all applications when running the compiler in the
+/// command line.
+final defaultRuntimeFiles = () {
+ var files = [
+ 'harmony_feature_check.js',
+ 'dart_utils.js',
+ 'dart_library.js',
+ '_errors.js',
+ '_types.js',
+ '_rtti.js',
+ '_classes.js',
+ '_operations.js',
+ 'dart_runtime.js',
+ ];
+ files.addAll(corelibOrder.map((l) => l.replaceAll('.', '/') + '.js'));
+ return files;
+}();
+
+final _log = new Logger('dev_compiler.src.compiler');

Powered by Google App Engine
This is Rietveld 408576698