OLD | NEW |
| (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 library analyzer_cli.src.driver; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:convert'; | |
9 import 'dart:io' as io; | |
10 | |
11 import 'package:analyzer/error/error.dart'; | |
12 import 'package:analyzer/file_system/file_system.dart' as file_system; | |
13 import 'package:analyzer/file_system/file_system.dart'; | |
14 import 'package:analyzer/file_system/physical_file_system.dart'; | |
15 import 'package:analyzer/plugin/resolver_provider.dart'; | |
16 import 'package:analyzer/source/package_map_provider.dart'; | |
17 import 'package:analyzer/source/package_map_resolver.dart'; | |
18 import 'package:analyzer/source/pub_package_map_provider.dart'; | |
19 import 'package:analyzer/source/sdk_ext.dart'; | |
20 import 'package:analyzer/src/context/builder.dart'; | |
21 import 'package:analyzer/src/dart/analysis/byte_store.dart'; | |
22 import 'package:analyzer/src/dart/analysis/driver.dart'; | |
23 import 'package:analyzer/src/dart/analysis/file_state.dart'; | |
24 import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
25 import 'package:analyzer/src/generated/constant.dart'; | |
26 import 'package:analyzer/src/generated/engine.dart'; | |
27 import 'package:analyzer/src/generated/interner.dart'; | |
28 import 'package:analyzer/src/generated/java_engine.dart'; | |
29 import 'package:analyzer/src/generated/sdk.dart'; | |
30 import 'package:analyzer/src/generated/source.dart'; | |
31 import 'package:analyzer/src/generated/source_io.dart'; | |
32 import 'package:analyzer/src/generated/utilities_general.dart' | |
33 show PerformanceTag; | |
34 import 'package:analyzer/src/source/source_resource.dart'; | |
35 import 'package:analyzer/src/summary/idl.dart'; | |
36 import 'package:analyzer/src/summary/package_bundle_reader.dart'; | |
37 import 'package:analyzer/src/summary/summary_sdk.dart' show SummaryBasedDartSdk; | |
38 import 'package:analyzer_cli/src/analyzer_impl.dart'; | |
39 import 'package:analyzer_cli/src/build_mode.dart'; | |
40 import 'package:analyzer_cli/src/error_formatter.dart'; | |
41 import 'package:analyzer_cli/src/error_severity.dart'; | |
42 import 'package:analyzer_cli/src/options.dart'; | |
43 import 'package:analyzer_cli/src/perf_report.dart'; | |
44 import 'package:analyzer_cli/starter.dart' show CommandLineStarter; | |
45 import 'package:linter/src/rules.dart' as linter; | |
46 import 'package:package_config/discovery.dart' as pkg_discovery; | |
47 import 'package:package_config/packages.dart' show Packages; | |
48 import 'package:package_config/packages_file.dart' as pkgfile show parse; | |
49 import 'package:package_config/src/packages_impl.dart' show MapPackages; | |
50 import 'package:path/path.dart' as path; | |
51 import 'package:plugin/manager.dart'; | |
52 import 'package:plugin/plugin.dart'; | |
53 import 'package:yaml/yaml.dart'; | |
54 | |
55 /// Shared IO sink for standard error reporting. | |
56 /// | |
57 /// *Visible for testing.* | |
58 StringSink errorSink = io.stderr; | |
59 | |
60 /// Shared IO sink for standard out reporting. | |
61 /// | |
62 /// *Visible for testing.* | |
63 StringSink outSink = io.stdout; | |
64 | |
65 /// Test this option map to see if it specifies lint rules. | |
66 bool containsLintRuleEntry(Map<String, YamlNode> options) { | |
67 var linterNode = options['linter']; | |
68 return linterNode is YamlMap && linterNode.containsKey('rules'); | |
69 } | |
70 | |
71 typedef Future<ErrorSeverity> _BatchRunnerHandler(List<String> args); | |
72 | |
73 class Driver implements CommandLineStarter { | |
74 static final PerformanceTag _analyzeAllTag = | |
75 new PerformanceTag("Driver._analyzeAll"); | |
76 | |
77 static ByteStore analysisDriverMemoryByteStore = new MemoryByteStore(); | |
78 | |
79 /// The plugins that are defined outside the `analyzer_cli` package. | |
80 List<Plugin> _userDefinedPlugins = <Plugin>[]; | |
81 | |
82 /// The context that was most recently created by a call to [_analyzeAll], or | |
83 /// `null` if [_analyzeAll] hasn't been called yet. | |
84 InternalAnalysisContext _context; | |
85 | |
86 AnalysisDriver analysisDriver; | |
87 | |
88 /// The total number of source files loaded by an AnalysisContext. | |
89 int _analyzedFileCount = 0; | |
90 | |
91 /// If [_context] is not `null`, the [CommandLineOptions] that guided its | |
92 /// creation. | |
93 CommandLineOptions _previousOptions; | |
94 | |
95 @override | |
96 ResolverProvider packageResolverProvider; | |
97 | |
98 /// SDK instance. | |
99 DartSdk sdk; | |
100 | |
101 /** | |
102 * The resource provider used to access the file system. | |
103 */ | |
104 file_system.ResourceProvider resourceProvider = | |
105 PhysicalResourceProvider.INSTANCE; | |
106 | |
107 /// Collected analysis statistics. | |
108 final AnalysisStats stats = new AnalysisStats(); | |
109 | |
110 /// This Driver's current analysis context. | |
111 /// | |
112 /// *Visible for testing.* | |
113 AnalysisContext get context => _context; | |
114 | |
115 @override | |
116 void set userDefinedPlugins(List<Plugin> plugins) { | |
117 _userDefinedPlugins = plugins ?? <Plugin>[]; | |
118 } | |
119 | |
120 @override | |
121 Future<Null> start(List<String> args) async { | |
122 if (_context != null) { | |
123 throw new StateError("start() can only be called once"); | |
124 } | |
125 int startTime = new DateTime.now().millisecondsSinceEpoch; | |
126 | |
127 StringUtilities.INTERNER = new MappedInterner(); | |
128 | |
129 _processPlugins(); | |
130 | |
131 // Parse commandline options. | |
132 CommandLineOptions options = CommandLineOptions.parse(args); | |
133 | |
134 // Do analysis. | |
135 if (options.buildMode) { | |
136 ErrorSeverity severity = _buildModeAnalyze(options); | |
137 // In case of error propagate exit code. | |
138 if (severity == ErrorSeverity.ERROR) { | |
139 io.exitCode = severity.ordinal; | |
140 } | |
141 } else if (options.shouldBatch) { | |
142 _BatchRunner.runAsBatch(args, (List<String> args) async { | |
143 CommandLineOptions options = CommandLineOptions.parse(args); | |
144 return await _analyzeAll(options); | |
145 }); | |
146 } else { | |
147 ErrorSeverity severity = await _analyzeAll(options); | |
148 // In case of error propagate exit code. | |
149 if (severity == ErrorSeverity.ERROR) { | |
150 io.exitCode = severity.ordinal; | |
151 } | |
152 } | |
153 | |
154 if (_context != null) { | |
155 _analyzedFileCount += _context.sources.length; | |
156 } | |
157 | |
158 if (options.perfReport != null) { | |
159 String json = makePerfReport( | |
160 startTime, currentTimeMillis, options, _analyzedFileCount, stats); | |
161 new io.File(options.perfReport).writeAsStringSync(json); | |
162 } | |
163 } | |
164 | |
165 Future<ErrorSeverity> _analyzeAll(CommandLineOptions options) async { | |
166 PerformanceTag previous = _analyzeAllTag.makeCurrent(); | |
167 try { | |
168 return await _analyzeAllImpl(options); | |
169 } finally { | |
170 previous.makeCurrent(); | |
171 } | |
172 } | |
173 | |
174 /// Perform analysis according to the given [options]. | |
175 Future<ErrorSeverity> _analyzeAllImpl(CommandLineOptions options) async { | |
176 if (!options.machineFormat) { | |
177 List<String> fileNames = options.sourceFiles.map((String file) { | |
178 file = path.normalize(file); | |
179 if (file == '.') { | |
180 file = path.basename(path.current); | |
181 } else if (file == '..') { | |
182 file = path.basename(path.normalize(path.absolute(file))); | |
183 } | |
184 return file; | |
185 }).toList(); | |
186 | |
187 outSink.writeln("Analyzing ${fileNames.join(', ')}..."); | |
188 } | |
189 | |
190 // Create a context, or re-use the previous one. | |
191 try { | |
192 _createAnalysisContext(options); | |
193 } on _DriverError catch (error) { | |
194 outSink.writeln(error.msg); | |
195 return ErrorSeverity.ERROR; | |
196 } | |
197 | |
198 // Add all the files to be analyzed en masse to the context. Skip any | |
199 // files that were added earlier (whether explicitly or implicitly) to | |
200 // avoid causing those files to be unnecessarily re-read. | |
201 Set<Source> knownSources = context.sources.toSet(); | |
202 Set<Source> sourcesToAnalyze = new Set<Source>(); | |
203 ChangeSet changeSet = new ChangeSet(); | |
204 for (String sourcePath in options.sourceFiles) { | |
205 sourcePath = sourcePath.trim(); | |
206 | |
207 // Collect files for analysis. | |
208 // Note that these files will all be analyzed in the same context. | |
209 // This should be updated when the ContextManager re-work is complete | |
210 // (See: https://github.com/dart-lang/sdk/issues/24133) | |
211 Iterable<io.File> files = _collectFiles(sourcePath); | |
212 if (files.isEmpty) { | |
213 errorSink.writeln('No dart files found at: $sourcePath'); | |
214 io.exitCode = ErrorSeverity.ERROR.ordinal; | |
215 return ErrorSeverity.ERROR; | |
216 } | |
217 | |
218 for (io.File file in files) { | |
219 Source source = _computeLibrarySource(file.absolute.path); | |
220 if (!knownSources.contains(source)) { | |
221 changeSet.addedSource(source); | |
222 } | |
223 sourcesToAnalyze.add(source); | |
224 } | |
225 | |
226 if (analysisDriver == null) { | |
227 context.applyChanges(changeSet); | |
228 } | |
229 } | |
230 | |
231 // Analyze the libraries. | |
232 ErrorSeverity allResult = ErrorSeverity.NONE; | |
233 List<Uri> libUris = <Uri>[]; | |
234 Set<Source> partSources = new Set<Source>(); | |
235 | |
236 SeverityProcessor defaultSeverityProcessor = (AnalysisError error) { | |
237 return determineProcessedSeverity( | |
238 error, options, _context.analysisOptions); | |
239 }; | |
240 | |
241 // We currently print out to stderr to ensure that when in batch mode we | |
242 // print to stderr, this is because the prints from batch are made to | |
243 // stderr. The reason that options.shouldBatch isn't used is because when | |
244 // the argument flags are constructed in BatchRunner and passed in from | |
245 // batch mode which removes the batch flag to prevent the "cannot have the | |
246 // batch flag and source file" error message. | |
247 ErrorFormatter formatter; | |
248 if (options.machineFormat) { | |
249 formatter = new MachineErrorFormatter(errorSink, options, stats, | |
250 severityProcessor: defaultSeverityProcessor); | |
251 } else { | |
252 formatter = new HumanErrorFormatter(outSink, options, stats, | |
253 severityProcessor: defaultSeverityProcessor); | |
254 } | |
255 | |
256 for (Source source in sourcesToAnalyze) { | |
257 SourceKind sourceKind = analysisDriver != null | |
258 ? await analysisDriver.getSourceKind(source.fullName) | |
259 : context.computeKindOf(source); | |
260 if (sourceKind == SourceKind.PART) { | |
261 partSources.add(source); | |
262 continue; | |
263 } | |
264 ErrorSeverity status = await _runAnalyzer(source, options, formatter); | |
265 allResult = allResult.max(status); | |
266 libUris.add(source.uri); | |
267 } | |
268 | |
269 formatter.flush(); | |
270 | |
271 // Check that each part has a corresponding source in the input list. | |
272 for (Source partSource in partSources) { | |
273 bool found = false; | |
274 if (analysisDriver != null) { | |
275 var partFile = | |
276 analysisDriver.fsState.getFileForPath(partSource.fullName); | |
277 if (libUris.contains(partFile.library?.uri)) { | |
278 found = true; | |
279 } | |
280 } else { | |
281 for (var lib in context.getLibrariesContaining(partSource)) { | |
282 if (libUris.contains(lib.uri)) { | |
283 found = true; | |
284 } | |
285 } | |
286 } | |
287 if (!found) { | |
288 errorSink.writeln( | |
289 "${partSource.fullName} is a part and cannot be analyzed."); | |
290 errorSink.writeln("Please pass in a library that contains this part."); | |
291 io.exitCode = ErrorSeverity.ERROR.ordinal; | |
292 allResult = allResult.max(ErrorSeverity.ERROR); | |
293 } | |
294 } | |
295 | |
296 if (!options.machineFormat) { | |
297 stats.print(outSink); | |
298 } | |
299 | |
300 return allResult; | |
301 } | |
302 | |
303 /// Perform analysis in build mode according to the given [options]. | |
304 ErrorSeverity _buildModeAnalyze(CommandLineOptions options) { | |
305 return _analyzeAllTag.makeCurrentWhile(() { | |
306 if (options.buildModePersistentWorker) { | |
307 new AnalyzerWorkerLoop.std(resourceProvider, | |
308 dartSdkPath: options.dartSdkPath) | |
309 .run(); | |
310 } else { | |
311 return new BuildMode(resourceProvider, options, stats).analyze(); | |
312 } | |
313 }); | |
314 } | |
315 | |
316 /// Determine whether the context created during a previous call to | |
317 /// [_analyzeAll] can be re-used in order to analyze using [options]. | |
318 bool _canContextBeReused(CommandLineOptions options) { | |
319 // TODO(paulberry): add a command-line option that disables context re-use. | |
320 if (_context == null) { | |
321 return false; | |
322 } | |
323 if (options.packageRootPath != _previousOptions.packageRootPath) { | |
324 return false; | |
325 } | |
326 if (options.packageConfigPath != _previousOptions.packageConfigPath) { | |
327 return false; | |
328 } | |
329 if (!_equalMaps( | |
330 options.definedVariables, _previousOptions.definedVariables)) { | |
331 return false; | |
332 } | |
333 if (options.log != _previousOptions.log) { | |
334 return false; | |
335 } | |
336 if (options.disableHints != _previousOptions.disableHints) { | |
337 return false; | |
338 } | |
339 if (options.enableStrictCallChecks != | |
340 _previousOptions.enableStrictCallChecks) { | |
341 return false; | |
342 } | |
343 if (options.enableAssertInitializer != | |
344 _previousOptions.enableAssertInitializer) { | |
345 return false; | |
346 } | |
347 if (options.showPackageWarnings != _previousOptions.showPackageWarnings) { | |
348 return false; | |
349 } | |
350 if (options.showPackageWarningsPrefix != | |
351 _previousOptions.showPackageWarningsPrefix) { | |
352 return false; | |
353 } | |
354 if (options.showSdkWarnings != _previousOptions.showSdkWarnings) { | |
355 return false; | |
356 } | |
357 if (options.lints != _previousOptions.lints) { | |
358 return false; | |
359 } | |
360 if (options.strongMode != _previousOptions.strongMode) { | |
361 return false; | |
362 } | |
363 if (options.enableSuperMixins != _previousOptions.enableSuperMixins) { | |
364 return false; | |
365 } | |
366 if (!_equalLists( | |
367 options.buildSummaryInputs, _previousOptions.buildSummaryInputs)) { | |
368 return false; | |
369 } | |
370 if (options.disableCacheFlushing != _previousOptions.disableCacheFlushing) { | |
371 return false; | |
372 } | |
373 return true; | |
374 } | |
375 | |
376 /// Decide on the appropriate policy for which files need to be fully parsed | |
377 /// and which files need to be diet parsed, based on [options], and return an | |
378 /// [AnalyzeFunctionBodiesPredicate] that implements this policy. | |
379 AnalyzeFunctionBodiesPredicate _chooseDietParsingPolicy( | |
380 CommandLineOptions options) { | |
381 if (options.shouldBatch) { | |
382 // As analyzer is currently implemented, once a file has been diet | |
383 // parsed, it can't easily be un-diet parsed without creating a brand new | |
384 // context and losing caching. In batch mode, we can't predict which | |
385 // files we'll need to generate errors and warnings for in the future, so | |
386 // we can't safely diet parse anything. | |
387 return (Source source) => true; | |
388 } | |
389 | |
390 return (Source source) { | |
391 if (options.sourceFiles.contains(source.fullName)) { | |
392 return true; | |
393 } else if (source.uri.scheme == 'dart') { | |
394 return options.showSdkWarnings; | |
395 } else { | |
396 // TODO(paulberry): diet parse 'package:' imports when we don't want | |
397 // diagnostics. (Full parse is still needed for "self" packages.) | |
398 return true; | |
399 } | |
400 }; | |
401 } | |
402 | |
403 /// Decide on the appropriate method for resolving URIs based on the given | |
404 /// [options] and [customUrlMappings] settings, and return a | |
405 /// [SourceFactory] that has been configured accordingly. | |
406 /// When [includeSdkResolver] is `false`, return a temporary [SourceFactory] | |
407 /// for the purpose of resolved analysis options file `include:` directives. | |
408 /// In this situation, [analysisOptions] is ignored and can be `null`. | |
409 SourceFactory _chooseUriResolutionPolicy( | |
410 CommandLineOptions options, | |
411 Map<file_system.Folder, YamlMap> embedderMap, | |
412 _PackageInfo packageInfo, | |
413 SummaryDataStore summaryDataStore, | |
414 bool includeSdkResolver, | |
415 AnalysisOptions analysisOptions) { | |
416 // Create a custom package resolver if one has been specified. | |
417 if (packageResolverProvider != null) { | |
418 file_system.Folder folder = resourceProvider.getResource('.'); | |
419 UriResolver resolver = packageResolverProvider(folder); | |
420 if (resolver != null) { | |
421 // TODO(brianwilkerson) This doesn't handle sdk extensions. | |
422 List<UriResolver> resolvers = <UriResolver>[]; | |
423 if (includeSdkResolver) { | |
424 resolvers.add(new DartUriResolver(sdk)); | |
425 } | |
426 resolvers | |
427 .add(new InSummaryUriResolver(resourceProvider, summaryDataStore)); | |
428 resolvers.add(resolver); | |
429 resolvers.add(new file_system.ResourceUriResolver(resourceProvider)); | |
430 return new SourceFactory(resolvers); | |
431 } | |
432 } | |
433 | |
434 UriResolver packageUriResolver; | |
435 | |
436 if (options.packageRootPath != null) { | |
437 ContextBuilderOptions builderOptions = new ContextBuilderOptions(); | |
438 builderOptions.defaultPackagesDirectoryPath = options.packageRootPath; | |
439 ContextBuilder builder = new ContextBuilder(resourceProvider, null, null, | |
440 options: builderOptions); | |
441 packageUriResolver = new PackageMapUriResolver(resourceProvider, | |
442 builder.convertPackagesToMap(builder.createPackageMap(''))); | |
443 } else if (options.packageConfigPath == null) { | |
444 // TODO(pq): remove? | |
445 if (packageInfo.packageMap == null) { | |
446 // Fall back to pub list-package-dirs. | |
447 PubPackageMapProvider pubPackageMapProvider = | |
448 new PubPackageMapProvider(resourceProvider, sdk); | |
449 file_system.Resource cwd = resourceProvider.getResource('.'); | |
450 PackageMapInfo packageMapInfo = | |
451 pubPackageMapProvider.computePackageMap(cwd); | |
452 Map<String, List<file_system.Folder>> packageMap = | |
453 packageMapInfo.packageMap; | |
454 | |
455 // Only create a packageUriResolver if pub list-package-dirs succeeded. | |
456 // If it failed, that's not a problem; it simply means we have no way | |
457 // to resolve packages. | |
458 if (packageMapInfo.packageMap != null) { | |
459 packageUriResolver = | |
460 new PackageMapUriResolver(resourceProvider, packageMap); | |
461 } | |
462 } | |
463 } | |
464 | |
465 // Now, build our resolver list. | |
466 List<UriResolver> resolvers = []; | |
467 | |
468 // 'dart:' URIs come first. | |
469 | |
470 // Setup embedding. | |
471 if (includeSdkResolver) { | |
472 EmbedderSdk embedderSdk = new EmbedderSdk(resourceProvider, embedderMap); | |
473 if (embedderSdk.libraryMap.size() == 0) { | |
474 // The embedder uri resolver has no mappings. Use the default Dart SDK | |
475 // uri resolver. | |
476 resolvers.add(new DartUriResolver(sdk)); | |
477 } else { | |
478 // The embedder uri resolver has mappings, use it instead of the default | |
479 // Dart SDK uri resolver. | |
480 embedderSdk.analysisOptions = analysisOptions; | |
481 resolvers.add(new DartUriResolver(embedderSdk)); | |
482 } | |
483 } | |
484 | |
485 // Next SdkExts. | |
486 if (packageInfo.packageMap != null) { | |
487 resolvers.add(new SdkExtUriResolver(packageInfo.packageMap)); | |
488 } | |
489 | |
490 // Then package URIs from summaries. | |
491 resolvers.add(new InSummaryUriResolver(resourceProvider, summaryDataStore)); | |
492 | |
493 // Then package URIs. | |
494 if (packageUriResolver != null) { | |
495 resolvers.add(packageUriResolver); | |
496 } | |
497 | |
498 // Finally files. | |
499 resolvers.add(new file_system.ResourceUriResolver(resourceProvider)); | |
500 | |
501 return new SourceFactory(resolvers, packageInfo.packages); | |
502 } | |
503 | |
504 // TODO(devoncarew): This needs to respect analysis_options excludes. | |
505 | |
506 /// Collect all analyzable files at [filePath], recursively if it's a | |
507 /// directory, ignoring links. | |
508 Iterable<io.File> _collectFiles(String filePath) { | |
509 List<io.File> files = <io.File>[]; | |
510 io.File file = new io.File(filePath); | |
511 if (file.existsSync()) { | |
512 files.add(file); | |
513 } else { | |
514 io.Directory directory = new io.Directory(filePath); | |
515 if (directory.existsSync()) { | |
516 for (io.FileSystemEntity entry | |
517 in directory.listSync(recursive: true, followLinks: false)) { | |
518 String relative = path.relative(entry.path, from: directory.path); | |
519 if (AnalysisEngine.isDartFileName(entry.path) && | |
520 !_isInHiddenDir(relative)) { | |
521 files.add(entry); | |
522 } | |
523 } | |
524 } | |
525 } | |
526 return files; | |
527 } | |
528 | |
529 /// Convert the given [sourcePath] (which may be relative to the current | |
530 /// working directory) to a [Source] object that can be fed to the analysis | |
531 /// context. | |
532 Source _computeLibrarySource(String sourcePath) { | |
533 sourcePath = _normalizeSourcePath(sourcePath); | |
534 File sourceFile = resourceProvider.getFile(sourcePath); | |
535 Source source = sdk.fromFileUri(sourceFile.toUri()); | |
536 if (source != null) { | |
537 return source; | |
538 } | |
539 source = new FileSource(sourceFile, sourceFile.toUri()); | |
540 Uri uri = _context.sourceFactory.restoreUri(source); | |
541 if (uri == null) { | |
542 return source; | |
543 } | |
544 return new FileSource(sourceFile, uri); | |
545 } | |
546 | |
547 /// Create an analysis context that is prepared to analyze sources according | |
548 /// to the given [options], and store it in [_context]. | |
549 void _createAnalysisContext(CommandLineOptions options) { | |
550 if (_canContextBeReused(options)) { | |
551 return; | |
552 } | |
553 _previousOptions = options; | |
554 | |
555 // Save stats from previous context before clobbering it. | |
556 if (_context != null) { | |
557 _analyzedFileCount += _context.sources.length; | |
558 } | |
559 | |
560 // Find package info. | |
561 _PackageInfo packageInfo = _findPackages(options); | |
562 | |
563 // Process embedders. | |
564 Map<file_system.Folder, YamlMap> embedderMap = | |
565 new EmbedderYamlLocator(packageInfo.packageMap).embedderYamls; | |
566 | |
567 // Scan for SDK extenders. | |
568 bool hasSdkExt = _hasSdkExt(packageInfo.packageMap?.values); | |
569 | |
570 // No summaries in the presence of embedders or extenders. | |
571 bool useSummaries = embedderMap.isEmpty && !hasSdkExt; | |
572 | |
573 if (!useSummaries && options.buildSummaryInputs.isNotEmpty) { | |
574 throw new _DriverError( | |
575 'Summaries are not yet supported when using Flutter.'); | |
576 } | |
577 | |
578 // Read any input summaries. | |
579 SummaryDataStore summaryDataStore = new SummaryDataStore( | |
580 useSummaries ? options.buildSummaryInputs : <String>[]); | |
581 | |
582 AnalysisOptionsImpl analysisOptions = | |
583 createAnalysisOptionsForCommandLineOptions(resourceProvider, options); | |
584 analysisOptions.analyzeFunctionBodiesPredicate = | |
585 _chooseDietParsingPolicy(options); | |
586 | |
587 // Once options and embedders are processed, setup the SDK. | |
588 _setupSdk(options, useSummaries, analysisOptions); | |
589 | |
590 PackageBundle sdkBundle = sdk.getLinkedBundle(); | |
591 if (sdkBundle != null) { | |
592 summaryDataStore.addBundle(null, sdkBundle); | |
593 } | |
594 | |
595 // Choose a package resolution policy and a diet parsing policy based on | |
596 // the command-line options. | |
597 SourceFactory sourceFactory = _chooseUriResolutionPolicy(options, | |
598 embedderMap, packageInfo, summaryDataStore, true, analysisOptions); | |
599 | |
600 // Create a context. | |
601 _context = AnalysisEngine.instance.createAnalysisContext(); | |
602 setupAnalysisContext(_context, options, analysisOptions); | |
603 _context.sourceFactory = sourceFactory; | |
604 | |
605 if (options.enableNewAnalysisDriver) { | |
606 PerformanceLog log = new PerformanceLog(null); | |
607 AnalysisDriverScheduler scheduler = new AnalysisDriverScheduler(log); | |
608 analysisDriver = new AnalysisDriver( | |
609 scheduler, | |
610 log, | |
611 resourceProvider, | |
612 analysisDriverMemoryByteStore, | |
613 new FileContentOverlay(), | |
614 null, | |
615 context.sourceFactory, | |
616 context.analysisOptions); | |
617 analysisDriver.results.listen((_) {}); | |
618 analysisDriver.exceptions.listen((_) {}); | |
619 scheduler.start(); | |
620 } else { | |
621 if (sdkBundle != null) { | |
622 _context.resultProvider = | |
623 new InputPackagesResultProvider(_context, summaryDataStore); | |
624 } | |
625 } | |
626 } | |
627 | |
628 /// Return discovered packagespec, or `null` if none is found. | |
629 Packages _discoverPackagespec(Uri root) { | |
630 try { | |
631 Packages packages = pkg_discovery.findPackagesFromFile(root); | |
632 if (packages != Packages.noPackages) { | |
633 return packages; | |
634 } | |
635 } catch (_) { | |
636 // Ignore and fall through to null. | |
637 } | |
638 | |
639 return null; | |
640 } | |
641 | |
642 _PackageInfo _findPackages(CommandLineOptions options) { | |
643 if (packageResolverProvider != null) { | |
644 // The resolver provider will do all the work later. | |
645 return new _PackageInfo(null, null); | |
646 } | |
647 | |
648 Packages packages; | |
649 Map<String, List<file_system.Folder>> packageMap; | |
650 | |
651 if (options.packageConfigPath != null) { | |
652 String packageConfigPath = options.packageConfigPath; | |
653 Uri fileUri = new Uri.file(packageConfigPath); | |
654 try { | |
655 io.File configFile = new io.File.fromUri(fileUri).absolute; | |
656 List<int> bytes = configFile.readAsBytesSync(); | |
657 Map<String, Uri> map = pkgfile.parse(bytes, configFile.uri); | |
658 packages = new MapPackages(map); | |
659 packageMap = _getPackageMap(packages); | |
660 } catch (e) { | |
661 printAndFail( | |
662 'Unable to read package config data from $packageConfigPath: $e'); | |
663 } | |
664 } else if (options.packageRootPath != null) { | |
665 packageMap = _PackageRootPackageMapBuilder | |
666 .buildPackageMap(options.packageRootPath); | |
667 } else { | |
668 file_system.Resource cwd = resourceProvider.getResource('.'); | |
669 // Look for .packages. | |
670 packages = _discoverPackagespec(new Uri.directory(cwd.path)); | |
671 packageMap = _getPackageMap(packages); | |
672 } | |
673 | |
674 return new _PackageInfo(packages, packageMap); | |
675 } | |
676 | |
677 Map<String, List<file_system.Folder>> _getPackageMap(Packages packages) { | |
678 if (packages == null) { | |
679 return null; | |
680 } | |
681 | |
682 Map<String, List<file_system.Folder>> folderMap = | |
683 new Map<String, List<file_system.Folder>>(); | |
684 packages.asMap().forEach((String packagePath, Uri uri) { | |
685 folderMap[packagePath] = [resourceProvider.getFolder(path.fromUri(uri))]; | |
686 }); | |
687 return folderMap; | |
688 } | |
689 | |
690 bool _hasSdkExt(Iterable<List<file_system.Folder>> folders) { | |
691 if (folders != null) { | |
692 //TODO: ideally share this traversal with SdkExtUriResolver | |
693 for (Iterable<file_system.Folder> libDirs in folders) { | |
694 if (libDirs.any((file_system.Folder libDir) => | |
695 libDir.getChild(SdkExtUriResolver.SDK_EXT_NAME).exists)) { | |
696 return true; | |
697 } | |
698 } | |
699 } | |
700 return false; | |
701 } | |
702 | |
703 /// Returns `true` if this relative path is a hidden directory. | |
704 bool _isInHiddenDir(String relative) => | |
705 path.split(relative).any((part) => part.startsWith(".")); | |
706 | |
707 void _processPlugins() { | |
708 List<Plugin> plugins = <Plugin>[]; | |
709 plugins.addAll(AnalysisEngine.instance.requiredPlugins); | |
710 plugins.addAll(_userDefinedPlugins); | |
711 | |
712 ExtensionManager manager = new ExtensionManager(); | |
713 manager.processPlugins(plugins); | |
714 | |
715 linter.registerLintRules(); | |
716 } | |
717 | |
718 /// Analyze a single source. | |
719 Future<ErrorSeverity> _runAnalyzer(Source source, CommandLineOptions options, | |
720 ErrorFormatter formatter) async { | |
721 int startTime = currentTimeMillis; | |
722 AnalyzerImpl analyzer = new AnalyzerImpl(_context.analysisOptions, _context, | |
723 analysisDriver, source, options, stats, startTime); | |
724 ErrorSeverity errorSeverity = await analyzer.analyze(formatter); | |
725 if (errorSeverity == ErrorSeverity.ERROR) { | |
726 io.exitCode = errorSeverity.ordinal; | |
727 } | |
728 if (options.warningsAreFatal && errorSeverity == ErrorSeverity.WARNING) { | |
729 io.exitCode = errorSeverity.ordinal; | |
730 } | |
731 return errorSeverity; | |
732 } | |
733 | |
734 void _setupSdk(CommandLineOptions options, bool useSummaries, | |
735 AnalysisOptions analysisOptions) { | |
736 if (sdk == null) { | |
737 if (options.dartSdkSummaryPath != null) { | |
738 sdk = new SummaryBasedDartSdk( | |
739 options.dartSdkSummaryPath, options.strongMode); | |
740 } else { | |
741 String dartSdkPath = options.dartSdkPath; | |
742 FolderBasedDartSdk dartSdk = new FolderBasedDartSdk(resourceProvider, | |
743 resourceProvider.getFolder(dartSdkPath), options.strongMode); | |
744 dartSdk.useSummary = useSummaries && | |
745 options.sourceFiles.every((String sourcePath) { | |
746 sourcePath = path.absolute(sourcePath); | |
747 sourcePath = path.normalize(sourcePath); | |
748 return !path.isWithin(dartSdkPath, sourcePath); | |
749 }); | |
750 dartSdk.analysisOptions = analysisOptions; | |
751 sdk = dartSdk; | |
752 } | |
753 } | |
754 } | |
755 | |
756 static AnalysisOptionsImpl createAnalysisOptionsForCommandLineOptions( | |
757 ResourceProvider resourceProvider, CommandLineOptions options) { | |
758 if (options.analysisOptionsFile != null) { | |
759 file_system.File file = | |
760 resourceProvider.getFile(options.analysisOptionsFile); | |
761 if (!file.exists) { | |
762 printAndFail('Options file not found: ${options.analysisOptionsFile}', | |
763 exitCode: ErrorSeverity.ERROR.ordinal); | |
764 } | |
765 } | |
766 | |
767 String contextRoot; | |
768 if (options.sourceFiles.isEmpty) { | |
769 contextRoot = path.current; | |
770 } else { | |
771 contextRoot = options.sourceFiles[0]; | |
772 if (!path.isAbsolute(contextRoot)) { | |
773 contextRoot = path.absolute(contextRoot); | |
774 } | |
775 } | |
776 | |
777 void verbosePrint(String text) { | |
778 outSink.writeln(text); | |
779 } | |
780 | |
781 AnalysisOptionsImpl contextOptions = new ContextBuilder( | |
782 resourceProvider, null, null, | |
783 options: options.contextBuilderOptions) | |
784 .getAnalysisOptions(contextRoot, | |
785 verbosePrint: options.verbose ? verbosePrint : null); | |
786 | |
787 contextOptions.trackCacheDependencies = false; | |
788 contextOptions.disableCacheFlushing = options.disableCacheFlushing; | |
789 contextOptions.hint = !options.disableHints; | |
790 contextOptions.generateImplicitErrors = options.showPackageWarnings; | |
791 contextOptions.generateSdkErrors = options.showSdkWarnings; | |
792 contextOptions.enableAssertInitializer = options.enableAssertInitializer; | |
793 | |
794 return contextOptions; | |
795 } | |
796 | |
797 static void setAnalysisContextOptions( | |
798 file_system.ResourceProvider resourceProvider, | |
799 AnalysisContext context, | |
800 CommandLineOptions options, | |
801 void configureContextOptions(AnalysisOptionsImpl contextOptions)) { | |
802 AnalysisOptionsImpl analysisOptions = | |
803 createAnalysisOptionsForCommandLineOptions(resourceProvider, options); | |
804 configureContextOptions(analysisOptions); | |
805 setupAnalysisContext(context, options, analysisOptions); | |
806 } | |
807 | |
808 static void setupAnalysisContext(AnalysisContext context, | |
809 CommandLineOptions options, AnalysisOptionsImpl analysisOptions) { | |
810 Map<String, String> definedVariables = options.definedVariables; | |
811 if (definedVariables.isNotEmpty) { | |
812 DeclaredVariables declaredVariables = context.declaredVariables; | |
813 definedVariables.forEach((String variableName, String value) { | |
814 declaredVariables.define(variableName, value); | |
815 }); | |
816 } | |
817 | |
818 if (options.log) { | |
819 AnalysisEngine.instance.logger = new StdLogger(); | |
820 } | |
821 | |
822 // Set context options. | |
823 context.analysisOptions = analysisOptions; | |
824 } | |
825 | |
826 /// Perform a deep comparison of two string lists. | |
827 static bool _equalLists(List<String> l1, List<String> l2) { | |
828 if (l1.length != l2.length) { | |
829 return false; | |
830 } | |
831 for (int i = 0; i < l1.length; i++) { | |
832 if (l1[i] != l2[i]) { | |
833 return false; | |
834 } | |
835 } | |
836 return true; | |
837 } | |
838 | |
839 /// Perform a deep comparison of two string maps. | |
840 static bool _equalMaps(Map<String, String> m1, Map<String, String> m2) { | |
841 if (m1.length != m2.length) { | |
842 return false; | |
843 } | |
844 for (String key in m1.keys) { | |
845 if (!m2.containsKey(key) || m1[key] != m2[key]) { | |
846 return false; | |
847 } | |
848 } | |
849 return true; | |
850 } | |
851 | |
852 /// Convert [sourcePath] into an absolute path. | |
853 static String _normalizeSourcePath(String sourcePath) => | |
854 path.normalize(new io.File(sourcePath).absolute.path); | |
855 } | |
856 | |
857 /// Provides a framework to read command line options from stdin and feed them | |
858 /// to a callback. | |
859 class _BatchRunner { | |
860 /// Run the tool in 'batch' mode, receiving command lines through stdin and | |
861 /// returning pass/fail status through stdout. This feature is intended for | |
862 /// use in unit testing. | |
863 static void runAsBatch(List<String> sharedArgs, _BatchRunnerHandler handler) { | |
864 outSink.writeln('>>> BATCH START'); | |
865 Stopwatch stopwatch = new Stopwatch(); | |
866 stopwatch.start(); | |
867 int testsFailed = 0; | |
868 int totalTests = 0; | |
869 ErrorSeverity batchResult = ErrorSeverity.NONE; | |
870 // Read line from stdin. | |
871 Stream cmdLine = | |
872 io.stdin.transform(UTF8.decoder).transform(new LineSplitter()); | |
873 cmdLine.listen((String line) async { | |
874 // Maybe finish. | |
875 if (line.isEmpty) { | |
876 var time = stopwatch.elapsedMilliseconds; | |
877 outSink.writeln( | |
878 '>>> BATCH END (${totalTests - testsFailed}/$totalTests) ${time}ms')
; | |
879 io.exitCode = batchResult.ordinal; | |
880 } | |
881 // Prepare arguments. | |
882 var lineArgs = line.split(new RegExp('\\s+')); | |
883 var args = new List<String>(); | |
884 args.addAll(sharedArgs); | |
885 args.addAll(lineArgs); | |
886 args.remove('-b'); | |
887 args.remove('--batch'); | |
888 // Analyze single set of arguments. | |
889 try { | |
890 totalTests++; | |
891 ErrorSeverity result = await handler(args); | |
892 bool resultPass = result != ErrorSeverity.ERROR; | |
893 if (!resultPass) { | |
894 testsFailed++; | |
895 } | |
896 batchResult = batchResult.max(result); | |
897 // Write stderr end token and flush. | |
898 errorSink.writeln('>>> EOF STDERR'); | |
899 String resultPassString = resultPass ? 'PASS' : 'FAIL'; | |
900 outSink.writeln( | |
901 '>>> TEST $resultPassString ${stopwatch.elapsedMilliseconds}ms'); | |
902 } catch (e, stackTrace) { | |
903 errorSink.writeln(e); | |
904 errorSink.writeln(stackTrace); | |
905 errorSink.writeln('>>> EOF STDERR'); | |
906 outSink.writeln('>>> TEST CRASH'); | |
907 } | |
908 }); | |
909 } | |
910 } | |
911 | |
912 class _DriverError implements Exception { | |
913 String msg; | |
914 _DriverError(this.msg); | |
915 } | |
916 | |
917 class _PackageInfo { | |
918 Packages packages; | |
919 Map<String, List<file_system.Folder>> packageMap; | |
920 _PackageInfo(this.packages, this.packageMap); | |
921 } | |
922 | |
923 /// [SdkExtUriResolver] needs a Map from package name to folder. In the case | |
924 /// that the analyzer is invoked with a --package-root option, we need to | |
925 /// manually create this mapping. Given [packageRootPath], | |
926 /// [_PackageRootPackageMapBuilder] creates a simple mapping from package name | |
927 /// to full path on disk (resolving any symbolic links). | |
928 class _PackageRootPackageMapBuilder { | |
929 static Map<String, List<file_system.Folder>> buildPackageMap( | |
930 String packageRootPath) { | |
931 var packageRoot = new io.Directory(packageRootPath); | |
932 if (!packageRoot.existsSync()) { | |
933 throw new _DriverError( | |
934 'Package root directory ($packageRootPath) does not exist.'); | |
935 } | |
936 var packages = packageRoot.listSync(followLinks: false); | |
937 var result = new Map<String, List<file_system.Folder>>(); | |
938 for (var package in packages) { | |
939 var packageName = path.basename(package.path); | |
940 var realPath = package.resolveSymbolicLinksSync(); | |
941 result[packageName] = [ | |
942 PhysicalResourceProvider.INSTANCE.getFolder(realPath) | |
943 ]; | |
944 } | |
945 return result; | |
946 } | |
947 } | |
OLD | NEW |