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

Side by Side 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: pretty url 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /// Command line tool to run the checker on a Dart program.
6 library dev_compiler.src.compiler;
7
8 import 'dart:async';
9 import 'dart:collection';
10 import 'dart:math' as math;
11 import 'dart:io';
12
13 import 'package:analyzer/src/generated/element.dart';
14 import 'package:analyzer/src/generated/engine.dart'
15 show AnalysisEngine, AnalysisContext, ChangeSet, ParseDartTask;
16 import 'package:analyzer/src/generated/error.dart'
17 show AnalysisError, ErrorSeverity, ErrorType;
18 import 'package:analyzer/src/generated/error.dart';
19 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
20 import 'package:analyzer/src/generated/source.dart' show Source;
21 import 'package:analyzer/src/task/html.dart';
22 import 'package:html/dom.dart' as html;
23 import 'package:html/parser.dart' as html;
24 import 'package:logging/logging.dart' show Level, Logger, LogRecord;
25 import 'package:path/path.dart' as path;
26
27 import 'package:dev_compiler/strong_mode.dart' show StrongModeOptions;
28
29 import 'analysis_context.dart';
30 import 'checker/checker.dart';
31 import 'checker/rules.dart';
32 import 'codegen/html_codegen.dart' as html_codegen;
33 import 'codegen/js_codegen.dart';
34 import 'info.dart'
35 show AnalyzerMessage, CheckerResults, LibraryInfo, LibraryUnit;
36 import 'options.dart';
37 import 'report.dart';
38
39 /// Sets up the type checker logger to print a span that highlights error
40 /// messages.
41 StreamSubscription setupLogger(Level level, printFn) {
42 Logger.root.level = level;
43 return Logger.root.onRecord.listen((LogRecord rec) {
44 printFn('${rec.level.name.toLowerCase()}: ${rec.message}');
45 });
46 }
47
48 class BatchCompiler extends AbstractCompiler {
49 JSGenerator _jsGen;
50
51 /// Already compiled sources, so we don't compile them again.
52 final _compiled = new HashSet<LibraryElement>();
53
54 bool _failure = false;
55 bool get failure => _failure;
56
57 BatchCompiler(AnalysisContext context, CompilerOptions options,
58 {AnalysisErrorListener reporter})
59 : super(context, options, reporter) {
60 if (outputDir != null) {
61 _jsGen = new JSGenerator(this);
62 }
63 }
64
65 void reset() {
66 _compiled.clear();
67 }
68
69 /// Compiles every file in [options.inputs].
70 /// Returns true on successful compile.
71 bool run() {
72 var clock = new Stopwatch()..start();
73 options.inputs.forEach(compileFromUriString);
74 clock.stop();
75 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
76 _log.fine('Compiled ${_compiled.length} libraries in ${time} s\n');
77
78 return !_failure;
79 }
80
81 void compileFromUriString(String uriString) {
82 compileFromUri(stringToUri(uriString));
83 }
84
85 void compileFromUri(Uri uri) {
86 var source = context.sourceFactory.forUri(Uri.encodeFull('$uri'));
87 if (source == null) throw new ArgumentError.value(
88 uri.toString(), 'uri', 'could not find source for');
89 compileSource(source);
90 }
91
92 void compileSource(Source source) {
93 if (AnalysisEngine.isHtmlFileName(source.uri.path)) {
94 compileHtml(source);
95 return;
96 }
97
98 compileLibrary(context.computeLibraryElement(source));
99 }
100
101 void compileLibrary(LibraryElement library) {
102 if (!_compiled.add(library)) return;
103 if (!options.checkSdk && library.source.uri.scheme == 'dart') return;
104
105 // TODO(jmesserly): in incremental mode, we can skip the transitive
106 // compile of imports/exports.
107 library.importedLibraries.forEach(compileLibrary);
108 library.exportedLibraries.forEach(compileLibrary);
109
110 var unitElements = [library.definingCompilationUnit]..addAll(library.parts);
111 var units = [];
112
113 bool failureInLib = false;
114 for (var element in unitElements) {
115 var unit = context.resolveCompilationUnit(element.source, library);
116 units.add(unit);
117 failureInLib = logErrors(element.source) || failureInLib;
118 checker.visitCompilationUnit(unit);
119 if (checker.failure) failureInLib = true;
120 }
121
122 if (failureInLib) {
123 _failure = true;
124 if (!options.codegenOptions.forceCompile) return;
125 }
126
127 if (_jsGen != null) {
128 _jsGen.generateLibrary(
129 new LibraryUnit(units.first, units.skip(1).toList()));
130 }
131 }
132
133 void compileHtml(Source source) {
134 // TODO(jmesserly): reuse DartScriptsTask instead of copy/paste.
135 var contents = context.getContents(source);
136 var document = html.parse(contents.data, generateSpans: true);
137 var scripts = document.querySelectorAll('script[type="application/dart"]');
138
139 var loadedLibs = new LinkedHashSet<Uri>();
140
141 for (var script in scripts) {
142 Source scriptSource = null;
143 var srcAttr = script.attributes['src'];
144 if (srcAttr == null) {
145 if (script.hasContent()) {
146 var fragments = <ScriptFragment>[];
147 for (var node in script.nodes) {
148 if (node is html.Text) {
149 var start = node.sourceSpan.start;
150 fragments.add(new ScriptFragment(
151 start.offset, start.line, start.column, node.data));
152 }
153 }
154 scriptSource = new DartScript(source, fragments);
155 }
156 } else if (AnalysisEngine.isDartFileName(srcAttr)) {
157 scriptSource = context.sourceFactory.resolveUri(source, srcAttr);
158 }
159
160 if (scriptSource != null) {
161 var lib = context.computeLibraryElement(scriptSource);
162 compileLibrary(lib);
163 script.replaceWith(_linkLibraries(lib, loadedLibs));
164 }
165 }
166
167 // TODO(jmesserly): we need to clean this up so we aren't treating these
168 // as a special case.
169 for (var file in defaultRuntimeFiles) {
170 var input = path.join(options.runtimeDir, file);
171 var output = path.join(outputDir, runtimeFileOutput(file));
172 new Directory(path.dirname(output)).createSync(recursive: true);
173 new File(input).copySync(output);
174 }
175
176 new File(getOutputPath(source.uri)).openSync(mode: FileMode.WRITE)
177 ..writeStringSync(document.outerHtml)
178 ..writeStringSync('\n')
179 ..closeSync();
180 }
181
182 html.DocumentFragment _linkLibraries(
183 LibraryElement mainLib, LinkedHashSet<Uri> loaded) {
184 var alreadyLoaded = loaded.length;
185 _collectLibraries(mainLib, loaded);
186
187 var newLibs = loaded.skip(alreadyLoaded);
188 var df = new html.DocumentFragment();
189 for (var path in defaultRuntimeFiles) {
190 df.append(html_codegen.libraryInclude(runtimeFileOutput(path)));
191 }
192 for (var uri in newLibs) {
193 if (uri.scheme == 'dart') continue;
194 df.append(html_codegen.libraryInclude(getModulePath(uri)));
195 }
196 df.append(html_codegen.invokeMain(getModuleName(mainLib.source.uri)));
197 return df;
198 }
199
200 void _collectLibraries(LibraryElement lib, LinkedHashSet<Uri> loaded) {
201 var uri = lib.source.uri;
202 if (!loaded.add(uri)) return;
203 for (var l in lib.importedLibraries) _collectLibraries(l, loaded);
204 for (var l in lib.exportedLibraries) _collectLibraries(l, loaded);
205 // Move the item to the end of the list.
206 loaded.remove(uri);
207 loaded.add(uri);
208 }
209
210 String runtimeFileOutput(String file) =>
211 path.join('dev_compiler', 'runtime', file);
212 }
213
214 abstract class AbstractCompiler {
215 final CompilerOptions options;
216 final AnalysisContext context;
217 final CodeChecker checker;
218
219 AbstractCompiler(AnalysisContext context, CompilerOptions options,
220 [AnalysisErrorListener reporter])
221 : context = context,
222 options = options,
223 checker = createChecker(context.typeProvider, options.strongOptions,
224 reporter == null ? AnalysisErrorListener.NULL_LISTENER : reporter) {
225 enableDevCompilerInference(context, options.strongOptions);
226 }
227
228 static CodeChecker createChecker(TypeProvider typeProvider,
229 StrongModeOptions options, AnalysisErrorListener reporter) {
230 return new CodeChecker(
231 new RestrictedRules(typeProvider, options: options), reporter, options);
232 }
233
234 String get outputDir => options.codegenOptions.outputDir;
235 TypeRules get rules => checker.rules;
236 AnalysisErrorListener get reporter => checker.reporter;
237
238 Uri stringToUri(String uriString) {
239 var uri = uriString.startsWith('dart:') || uriString.startsWith('package:')
240 ? Uri.parse(uriString)
241 : new Uri.file(uriString);
242 return uri;
243 }
244
245 /// Directory presumed to be the common prefix for all input file:// URIs.
246 /// Used when computing output paths.
247 ///
248 /// For example:
249 /// dartdevc -o out foo/a.dart bar/b.dart
250 ///
251 /// Will produce:
252 /// out/foo/a.dart
253 /// out/bar/b.dart
254 ///
255 /// This is only used if at least one of [options.codegenOptions.inputs] is
256 /// a file URI.
257 // TODO(jmesserly): do we need an option for this?
258 // Other ideas: we could look up and see what package the file is in, treat
259 // that as a base path. We could also use the current working directory as
260 // the base.
261 String get inputBaseDir {
262 if (_fileUriCommonBaseDir == null) {
263 List<String> common = null;
264 for (var uri in options.inputs.map(stringToUri)) {
265 if (uri.scheme != 'file') continue;
266
267 var segments = path.split(path.dirname(uri.path));
268 if (common == null) {
269 common = segments;
270 } else {
271 int len = math.min(common.length, segments.length);
272 while (len > 0 && common[len - 1] != segments[len - 1]) {
273 len--;
274 }
275 common.length = len;
276 }
277 }
278 _fileUriCommonBaseDir = common == null ? '' : path.joinAll(common);
279 }
280 return _fileUriCommonBaseDir;
281 }
282 String _fileUriCommonBaseDir;
283
284 String getOutputPath(Uri uri) => path.join(outputDir, getModulePath(uri));
285
286 /// Like [getModuleName] but includes the file extension, either .js or .html.
287 String getModulePath(Uri uri) {
288 var ext = path.extension(uri.path);
289 if (ext == '.dart' || ext == '' && uri.scheme == 'dart') ext = '.js';
290 return getModuleName(uri) + ext;
291 }
292
293 /// Gets the module name, without extension. For example:
294 ///
295 /// * dart:core -> dart/core
296 /// * file:foo/bar/baz.dart -> foo/bar/baz
297 /// * package:qux/qux.dart -> qux/qux
298 ///
299 /// For file: URLs this will also make them relative to [inputBaseDir].
300 String getModuleName(Uri uri) {
301 var filepath = path.withoutExtension(uri.path);
302 if (uri.scheme == 'dart') {
303 filepath = 'dart/$filepath';
304 } else if (uri.scheme == 'file') {
305 filepath = path.relative(filepath, from: inputBaseDir);
306 } else {
307 assert(uri.scheme == 'package');
308 // filepath is good here, we want the output to start with a directory
309 // matching the package name.
310 }
311 return filepath;
312 }
313
314 /// Log any errors encountered when resolving [source] and return whether any
315 /// errors were found.
316 bool logErrors(Source source) {
317 List<AnalysisError> errors = context.computeErrors(source);
318 bool failure = false;
319 if (errors.isNotEmpty) {
320 for (var error in errors) {
321 // Always skip TODOs.
322 if (error.errorCode.type == ErrorType.TODO) continue;
323
324 // Skip hints for now. In the future these could be turned on via flags.
325 if (error.errorCode.errorSeverity.ordinal <
326 ErrorSeverity.WARNING.ordinal) {
327 continue;
328 }
329
330 // All analyzer warnings or errors are errors for DDC.
331 failure = true;
332 reporter.onError(error);
333 }
334 }
335 return failure;
336 }
337 }
338
339 AnalysisErrorListener createErrorReporter(
340 AnalysisContext context, CompilerOptions options) {
341 return options.dumpInfo
342 ? new SummaryReporter(context, options.logLevel)
343 : new LogReporter(context, useColors: options.useColors);
344 }
345
346 // TODO(jmesserly): find a better home for these.
347 /// Curated order to minimize lazy classes needed by dart:core and its
348 /// transitive SDK imports.
349 const corelibOrder = const [
350 'dart.core',
351 'dart.collection',
352 'dart._internal',
353 'dart.math',
354 'dart._interceptors',
355 'dart.async',
356 'dart._foreign_helper',
357 'dart._js_embedded_names',
358 'dart._js_helper',
359 'dart.isolate',
360 'dart.typed_data',
361 'dart._native_typed_data',
362 'dart._isolate_helper',
363 'dart._js_primitives',
364 'dart.convert',
365 'dart.mirrors',
366 'dart._js_mirrors',
367 'dart.js'
368 // _foreign_helper is not included, as it only defines the JS builtin that
369 // the compiler handles at compile time.
370 ];
371
372 /// Runtime files added to all applications when running the compiler in the
373 /// command line.
374 final defaultRuntimeFiles = () {
375 var files = [
376 'harmony_feature_check.js',
377 'dart_utils.js',
378 'dart_library.js',
379 '_errors.js',
380 '_types.js',
381 '_rtti.js',
382 '_classes.js',
383 '_operations.js',
384 'dart_runtime.js',
385 ];
386 files.addAll(corelibOrder.map((l) => l.replaceAll('.', '/') + '.js'));
387 return files;
388 }();
389
390 final _log = new Logger('dev_compiler.src.compiler');
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698