Index: pkg/analyzer_cli/lib/src/driver.dart |
diff --git a/pkg/analyzer_cli/lib/src/driver.dart b/pkg/analyzer_cli/lib/src/driver.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..752cb1e87597f572754ca4169c4936a35a1a6f7d |
--- /dev/null |
+++ b/pkg/analyzer_cli/lib/src/driver.dart |
@@ -0,0 +1,641 @@ |
+// 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. |
+ |
+library analyzer_cli.src.driver; |
+ |
+import 'dart:async'; |
+import 'dart:convert'; |
+import 'dart:io'; |
+ |
+import 'package:analyzer/file_system/file_system.dart' as fileSystem; |
+import 'package:analyzer/file_system/physical_file_system.dart'; |
+import 'package:analyzer/plugin/options.dart'; |
+import 'package:analyzer/source/analysis_options_provider.dart'; |
+import 'package:analyzer/source/package_map_provider.dart'; |
+import 'package:analyzer/source/package_map_resolver.dart'; |
+import 'package:analyzer/source/pub_package_map_provider.dart'; |
+import 'package:analyzer/source/sdk_ext.dart'; |
+import 'package:analyzer/src/generated/constant.dart'; |
+import 'package:analyzer/src/generated/engine.dart'; |
+import 'package:analyzer/src/generated/error.dart'; |
+import 'package:analyzer/src/generated/interner.dart'; |
+import 'package:analyzer/src/generated/java_engine.dart'; |
+import 'package:analyzer/src/generated/java_io.dart'; |
+import 'package:analyzer/src/generated/sdk_io.dart'; |
+import 'package:analyzer/src/generated/source.dart'; |
+import 'package:analyzer/src/generated/source_io.dart'; |
+import 'package:analyzer/src/services/lint.dart'; |
+import 'package:analyzer/src/task/options.dart'; |
+import 'package:analyzer_cli/src/analyzer_impl.dart'; |
+import 'package:analyzer_cli/src/options.dart'; |
+import 'package:linter/src/plugin/linter_plugin.dart'; |
+import 'package:package_config/discovery.dart' as pkgDiscovery; |
+import 'package:package_config/packages.dart' show Packages; |
+import 'package:package_config/packages_file.dart' as pkgfile show parse; |
+import 'package:package_config/src/packages_impl.dart' show MapPackages; |
+import 'package:path/path.dart' as path; |
+import 'package:plugin/plugin.dart'; |
+import 'package:yaml/yaml.dart'; |
+ |
+/// The maximum number of sources for which AST structures should be kept in the |
+/// cache. |
+const int _maxCacheSize = 512; |
+ |
+/// Shared IO sink for standard error reporting. |
+/// |
+/// *Visible for testing.* |
+StringSink errorSink = stderr; |
+ |
+/// Shared IO sink for standard out reporting. |
+/// |
+/// *Visible for testing.* |
+StringSink outSink = stdout; |
+ |
+/// Test this option map to see if it specifies lint rules. |
+bool containsLintRuleEntry(Map<String, YamlNode> options) { |
+ var linterNode = options['linter']; |
+ return linterNode is YamlMap && linterNode.containsKey('rules'); |
+} |
+ |
+typedef ErrorSeverity _BatchRunnerHandler(List<String> args); |
+ |
+class Driver { |
+ /// The plugins that are defined outside the `analyzer_cli` package. |
+ List<Plugin> _userDefinedPlugins = <Plugin>[]; |
+ |
+ /// Indicates whether the analyzer is running in batch mode. |
+ bool _isBatch; |
+ |
+ /// The context that was most recently created by a call to [_analyzeAll], or |
+ /// `null` if [_analyzeAll] hasn't been called yet. |
+ AnalysisContext _context; |
+ |
+ /// If [_context] is not `null`, the [CommandLineOptions] that guided its |
+ /// creation. |
+ CommandLineOptions _previousOptions; |
+ |
+ /// This Driver's current analysis context. |
+ /// |
+ /// *Visible for testing.* |
+ AnalysisContext get context => _context; |
+ |
+ /// Set the [plugins] that are defined outside the `analyzer_cli` package. |
+ void set userDefinedPlugins(List<Plugin> plugins) { |
+ _userDefinedPlugins = plugins == null ? <Plugin>[] : plugins; |
+ } |
+ |
+ /// Use the given command-line [args] to start this analysis driver. |
+ void start(List<String> args) { |
+ StringUtilities.INTERNER = new MappedInterner(); |
+ |
+ _processPlugins(); |
+ |
+ // Parse commandline options. |
+ CommandLineOptions options = CommandLineOptions.parse(args); |
+ |
+ // Cache options of interest to inform analysis. |
+ _setupEnv(options); |
+ |
+ // Do analysis. |
+ if (_isBatch) { |
+ _BatchRunner.runAsBatch(args, (List<String> args) { |
+ CommandLineOptions options = CommandLineOptions.parse(args); |
+ return _analyzeAll(options); |
+ }); |
+ } else { |
+ ErrorSeverity severity = _analyzeAll(options); |
+ // In case of error propagate exit code. |
+ if (severity == ErrorSeverity.ERROR) { |
+ exitCode = severity.ordinal; |
+ } |
+ } |
+ } |
+ |
+ /// Perform analysis according to the given [options]. |
+ ErrorSeverity _analyzeAll(CommandLineOptions options) { |
+ if (!options.machineFormat) { |
+ outSink.writeln("Analyzing ${options.sourceFiles}..."); |
+ } |
+ |
+ // Create a context, or re-use the previous one. |
+ try { |
+ _createAnalysisContext(options); |
+ } on _DriverError catch (error) { |
+ outSink.writeln(error.msg); |
+ return ErrorSeverity.ERROR; |
+ } |
+ |
+ // Add all the files to be analyzed en masse to the context. Skip any |
+ // files that were added earlier (whether explicitly or implicitly) to |
+ // avoid causing those files to be unnecessarily re-read. |
+ Set<Source> knownSources = _context.sources.toSet(); |
+ List<Source> sourcesToAnalyze = <Source>[]; |
+ ChangeSet changeSet = new ChangeSet(); |
+ for (String sourcePath in options.sourceFiles) { |
+ sourcePath = sourcePath.trim(); |
+ // Check that file exists. |
+ if (!new File(sourcePath).existsSync()) { |
+ errorSink.writeln('File not found: $sourcePath'); |
+ exitCode = ErrorSeverity.ERROR.ordinal; |
+ //Fail fast; don't analyze more files |
+ return ErrorSeverity.ERROR; |
+ } |
+ // Check that file is Dart file. |
+ if (!AnalysisEngine.isDartFileName(sourcePath)) { |
+ errorSink.writeln('$sourcePath is not a Dart file'); |
+ exitCode = ErrorSeverity.ERROR.ordinal; |
+ // Fail fast; don't analyze more files. |
+ return ErrorSeverity.ERROR; |
+ } |
+ Source source = _computeLibrarySource(sourcePath); |
+ if (!knownSources.contains(source)) { |
+ changeSet.addedSource(source); |
+ } |
+ sourcesToAnalyze.add(source); |
+ } |
+ _context.applyChanges(changeSet); |
+ |
+ // Analyze the libraries. |
+ ErrorSeverity allResult = ErrorSeverity.NONE; |
+ var libUris = <Uri>[]; |
+ var parts = <Source>[]; |
+ for (Source source in sourcesToAnalyze) { |
+ if (context.computeKindOf(source) == SourceKind.PART) { |
+ parts.add(source); |
+ continue; |
+ } |
+ ErrorSeverity status = _runAnalyzer(source, options); |
+ allResult = allResult.max(status); |
+ libUris.add(source.uri); |
+ } |
+ |
+ // Check that each part has a corresponding source in the input list. |
+ for (Source part in parts) { |
+ bool found = false; |
+ for (var lib in context.getLibrariesContaining(part)) { |
+ if (libUris.contains(lib.uri)) { |
+ found = true; |
+ } |
+ } |
+ if (!found) { |
+ errorSink.writeln("${part.fullName} is a part and cannot be analyzed."); |
+ errorSink.writeln("Please pass in a library that contains this part."); |
+ exitCode = ErrorSeverity.ERROR.ordinal; |
+ allResult = allResult.max(ErrorSeverity.ERROR); |
+ } |
+ } |
+ |
+ return allResult; |
+ } |
+ |
+ /// Determine whether the context created during a previous call to |
+ /// [_analyzeAll] can be re-used in order to analyze using [options]. |
+ bool _canContextBeReused(CommandLineOptions options) { |
+ // TODO(paulberry): add a command-line option that disables context re-use. |
+ if (_context == null) { |
+ return false; |
+ } |
+ if (options.packageRootPath != _previousOptions.packageRootPath) { |
+ return false; |
+ } |
+ if (options.packageConfigPath != _previousOptions.packageConfigPath) { |
+ return false; |
+ } |
+ if (!_equalMaps( |
+ options.definedVariables, _previousOptions.definedVariables)) { |
+ return false; |
+ } |
+ if (options.log != _previousOptions.log) { |
+ return false; |
+ } |
+ if (options.disableHints != _previousOptions.disableHints) { |
+ return false; |
+ } |
+ if (options.enableStrictCallChecks != |
+ _previousOptions.enableStrictCallChecks) { |
+ return false; |
+ } |
+ if (options.showPackageWarnings != _previousOptions.showPackageWarnings) { |
+ return false; |
+ } |
+ if (options.showSdkWarnings != _previousOptions.showSdkWarnings) { |
+ return false; |
+ } |
+ if (options.lints != _previousOptions.lints) { |
+ return false; |
+ } |
+ if (options.strongMode != _previousOptions.strongMode) { |
+ return false; |
+ } |
+ if (options.enableSuperMixins != _previousOptions.enableSuperMixins) { |
+ return false; |
+ } |
+ return true; |
+ } |
+ |
+ /// Decide on the appropriate policy for which files need to be fully parsed |
+ /// and which files need to be diet parsed, based on [options], and return an |
+ /// [AnalyzeFunctionBodiesPredicate] that implements this policy. |
+ AnalyzeFunctionBodiesPredicate _chooseDietParsingPolicy( |
+ CommandLineOptions options) { |
+ if (_isBatch) { |
+ // As analyzer is currently implemented, once a file has been diet |
+ // parsed, it can't easily be un-diet parsed without creating a brand new |
+ // context and losing caching. In batch mode, we can't predict which |
+ // files we'll need to generate errors and warnings for in the future, so |
+ // we can't safely diet parse anything. |
+ return (Source source) => true; |
+ } |
+ |
+ // Determine the set of packages requiring a full parse. Use null to |
+ // represent the case where all packages require a full parse. |
+ Set<String> packagesRequiringFullParse; |
+ if (options.showPackageWarnings) { |
+ // We are showing warnings from all packages so all packages require a |
+ // full parse. |
+ packagesRequiringFullParse = null; |
+ } else { |
+ // We aren't showing warnings for dependent packages, but we may still |
+ // need to show warnings for "self" packages, so we need to do a full |
+ // parse in any package containing files mentioned on the command line. |
+ // TODO(paulberry): implement this. As a temporary workaround, we're |
+ // fully parsing all packages. |
+ packagesRequiringFullParse = null; |
+ } |
+ return (Source source) { |
+ if (source.uri.scheme == 'dart') { |
+ return options.showSdkWarnings; |
+ } else if (source.uri.scheme == 'package') { |
+ if (packagesRequiringFullParse == null) { |
+ return true; |
+ } else if (source.uri.pathSegments.length == 0) { |
+ // We should never see a URI like this, but fully parse it to be |
+ // safe. |
+ return true; |
+ } else { |
+ return packagesRequiringFullParse |
+ .contains(source.uri.pathSegments[0]); |
+ } |
+ } else { |
+ return true; |
+ } |
+ }; |
+ } |
+ |
+ /// Decide on the appropriate method for resolving URIs based on the given |
+ /// [options] and [customUrlMappings] settings, and return a |
+ /// [SourceFactory] that has been configured accordingly. |
+ SourceFactory _chooseUriResolutionPolicy(CommandLineOptions options) { |
+ Packages packages; |
+ Map<String, List<fileSystem.Folder>> packageMap; |
+ UriResolver packageUriResolver; |
+ |
+ // Process options, caching package resolution details. |
+ if (options.packageConfigPath != null) { |
+ String packageConfigPath = options.packageConfigPath; |
+ Uri fileUri = new Uri.file(packageConfigPath); |
+ try { |
+ File configFile = new File.fromUri(fileUri).absolute; |
+ List<int> bytes = configFile.readAsBytesSync(); |
+ Map<String, Uri> map = pkgfile.parse(bytes, configFile.uri); |
+ packages = new MapPackages(map); |
+ packageMap = _getPackageMap(packages); |
+ } catch (e) { |
+ printAndFail( |
+ 'Unable to read package config data from $packageConfigPath: $e'); |
+ } |
+ } else if (options.packageRootPath != null) { |
+ packageMap = _PackageRootPackageMapBuilder |
+ .buildPackageMap(options.packageRootPath); |
+ |
+ JavaFile packageDirectory = new JavaFile(options.packageRootPath); |
+ packageUriResolver = new PackageUriResolver([packageDirectory]); |
+ } else { |
+ fileSystem.Resource cwd = |
+ PhysicalResourceProvider.INSTANCE.getResource('.'); |
+ |
+ // Look for .packages. |
+ packages = _discoverPackagespec(new Uri.directory(cwd.path)); |
+ |
+ if (packages != null) { |
+ packageMap = _getPackageMap(packages); |
+ } else { |
+ // Fall back to pub list-package-dirs. |
+ |
+ PubPackageMapProvider pubPackageMapProvider = |
+ new PubPackageMapProvider(PhysicalResourceProvider.INSTANCE, sdk); |
+ PackageMapInfo packageMapInfo = |
+ pubPackageMapProvider.computePackageMap(cwd); |
+ packageMap = packageMapInfo.packageMap; |
+ |
+ // Only create a packageUriResolver if pub list-package-dirs succeeded. |
+ // If it failed, that's not a problem; it simply means we have no way |
+ // to resolve packages. |
+ if (packageMapInfo.packageMap != null) { |
+ packageUriResolver = new PackageMapUriResolver( |
+ PhysicalResourceProvider.INSTANCE, packageMap); |
+ } |
+ } |
+ } |
+ |
+ // Now, build our resolver list. |
+ |
+ // 'dart:' URIs come first. |
+ List<UriResolver> resolvers = [new DartUriResolver(sdk)]; |
+ |
+ // Next SdkExts. |
+ if (packageMap != null) { |
+ resolvers.add(new SdkExtUriResolver(packageMap)); |
+ } |
+ |
+ // Then package URIs. |
+ if (packageUriResolver != null) { |
+ resolvers.add(packageUriResolver); |
+ } |
+ |
+ // Finally files. |
+ resolvers.add(new FileUriResolver()); |
+ |
+ return new SourceFactory(resolvers, packages); |
+ } |
+ |
+ /// Convert the given [sourcePath] (which may be relative to the current |
+ /// working directory) to a [Source] object that can be fed to the analysis |
+ /// context. |
+ Source _computeLibrarySource(String sourcePath) { |
+ sourcePath = _normalizeSourcePath(sourcePath); |
+ JavaFile sourceFile = new JavaFile(sourcePath); |
+ Source source = sdk.fromFileUri(sourceFile.toURI()); |
+ if (source != null) { |
+ return source; |
+ } |
+ source = new FileBasedSource(sourceFile, sourceFile.toURI()); |
+ Uri uri = _context.sourceFactory.restoreUri(source); |
+ if (uri == null) { |
+ return source; |
+ } |
+ return new FileBasedSource(sourceFile, uri); |
+ } |
+ |
+ /// Create an analysis context that is prepared to analyze sources according |
+ /// to the given [options], and store it in [_context]. |
+ void _createAnalysisContext(CommandLineOptions options) { |
+ if (_canContextBeReused(options)) { |
+ return; |
+ } |
+ _previousOptions = options; |
+ // Choose a package resolution policy and a diet parsing policy based on |
+ // the command-line options. |
+ SourceFactory sourceFactory = _chooseUriResolutionPolicy(options); |
+ AnalyzeFunctionBodiesPredicate dietParsingPolicy = |
+ _chooseDietParsingPolicy(options); |
+ // Create a context using these policies. |
+ AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); |
+ |
+ context.sourceFactory = sourceFactory; |
+ |
+ Map<String, String> definedVariables = options.definedVariables; |
+ if (!definedVariables.isEmpty) { |
+ DeclaredVariables declaredVariables = context.declaredVariables; |
+ definedVariables.forEach((String variableName, String value) { |
+ declaredVariables.define(variableName, value); |
+ }); |
+ } |
+ |
+ if (options.log) { |
+ AnalysisEngine.instance.logger = new StdLogger(); |
+ } |
+ |
+ // Set context options. |
+ AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl(); |
+ contextOptions.cacheSize = _maxCacheSize; |
+ contextOptions.hint = !options.disableHints; |
+ contextOptions.enableStrictCallChecks = options.enableStrictCallChecks; |
+ contextOptions.enableSuperMixins = options.enableSuperMixins; |
+ contextOptions.analyzeFunctionBodiesPredicate = dietParsingPolicy; |
+ contextOptions.generateImplicitErrors = options.showPackageWarnings; |
+ contextOptions.generateSdkErrors = options.showSdkWarnings; |
+ contextOptions.lint = options.lints; |
+ contextOptions.strongMode = options.strongMode; |
+ context.analysisOptions = contextOptions; |
+ _context = context; |
+ |
+ // Process analysis options file (and notify all interested parties). |
+ _processAnalysisOptions(options, context); |
+ } |
+ |
+ /// Return discovered packagespec, or `null` if none is found. |
+ Packages _discoverPackagespec(Uri root) { |
+ try { |
+ Packages packages = pkgDiscovery.findPackagesFromFile(root); |
+ if (packages != Packages.noPackages) { |
+ return packages; |
+ } |
+ } catch (_) { |
+ // Ignore and fall through to null. |
+ } |
+ |
+ return null; |
+ } |
+ |
+ fileSystem.File _getOptionsFile(CommandLineOptions options) { |
+ fileSystem.File file; |
+ String filePath = options.analysisOptionsFile; |
+ if (filePath != null) { |
+ file = PhysicalResourceProvider.INSTANCE.getFile(filePath); |
+ if (!file.exists) { |
+ printAndFail('Options file not found: $filePath', |
+ exitCode: ErrorSeverity.ERROR.ordinal); |
+ } |
+ } else { |
+ filePath = AnalysisEngine.ANALYSIS_OPTIONS_FILE; |
+ file = PhysicalResourceProvider.INSTANCE.getFile(filePath); |
+ } |
+ return file; |
+ } |
+ |
+ Map<String, List<fileSystem.Folder>> _getPackageMap(Packages packages) { |
+ if (packages == null) { |
+ return null; |
+ } |
+ |
+ Map<String, List<fileSystem.Folder>> folderMap = |
+ new Map<String, List<fileSystem.Folder>>(); |
+ packages.asMap().forEach((String packagePath, Uri uri) { |
+ folderMap[packagePath] = [ |
+ PhysicalResourceProvider.INSTANCE.getFolder(path.fromUri(uri)) |
+ ]; |
+ }); |
+ return folderMap; |
+ } |
+ |
+ void _processAnalysisOptions( |
+ CommandLineOptions options, AnalysisContext context) { |
+ fileSystem.File file = _getOptionsFile(options); |
+ List<OptionsProcessor> optionsProcessors = |
+ AnalysisEngine.instance.optionsPlugin.optionsProcessors; |
+ try { |
+ AnalysisOptionsProvider analysisOptionsProvider = |
+ new AnalysisOptionsProvider(); |
+ Map<String, YamlNode> optionMap = |
+ analysisOptionsProvider.getOptionsFromFile(file); |
+ optionsProcessors.forEach( |
+ (OptionsProcessor p) => p.optionsProcessed(context, optionMap)); |
+ |
+ // Fill in lint rule defaults in case lints are enabled and rules are |
+ // not specified in an options file. |
+ if (options.lints && !containsLintRuleEntry(optionMap)) { |
+ setLints(context, linterPlugin.contributedRules); |
+ } |
+ |
+ // Ask engine to further process options. |
+ if (optionMap != null) { |
+ configureContextOptions(context, optionMap); |
+ } |
+ } on Exception catch (e) { |
+ optionsProcessors.forEach((OptionsProcessor p) => p.onError(e)); |
+ } |
+ } |
+ |
+ void _processPlugins() { |
+ List<Plugin> plugins = <Plugin>[]; |
+ plugins.add(linterPlugin); |
+ plugins.addAll(_userDefinedPlugins); |
+ AnalysisEngine.instance.userDefinedPlugins = plugins; |
+ |
+ // This ensures that AE extension manager processes plugins. |
+ AnalysisEngine.instance.taskManager; |
+ } |
+ |
+ /// Analyze a single source. |
+ ErrorSeverity _runAnalyzer(Source source, CommandLineOptions options) { |
+ int startTime = currentTimeMillis(); |
+ AnalyzerImpl analyzer = |
+ new AnalyzerImpl(_context, source, options, startTime); |
+ var errorSeverity = analyzer.analyzeSync(); |
+ if (errorSeverity == ErrorSeverity.ERROR) { |
+ exitCode = errorSeverity.ordinal; |
+ } |
+ if (options.warningsAreFatal && errorSeverity == ErrorSeverity.WARNING) { |
+ exitCode = errorSeverity.ordinal; |
+ } |
+ return errorSeverity; |
+ } |
+ |
+ void _setupEnv(CommandLineOptions options) { |
+ // In batch mode, SDK is specified on the main command line rather than in |
+ // the command lines sent to stdin. So process it before deciding whether |
+ // to activate batch mode. |
+ if (sdk == null) { |
+ sdk = new DirectoryBasedDartSdk(new JavaFile(options.dartSdkPath)); |
+ } |
+ _isBatch = options.shouldBatch; |
+ } |
+ |
+ /// Perform a deep comparison of two string maps. |
+ static bool _equalMaps(Map<String, String> m1, Map<String, String> m2) { |
+ if (m1.length != m2.length) { |
+ return false; |
+ } |
+ for (String key in m1.keys) { |
+ if (!m2.containsKey(key) || m1[key] != m2[key]) { |
+ return false; |
+ } |
+ } |
+ return true; |
+ } |
+ |
+ /// Convert [sourcePath] into an absolute path. |
+ static String _normalizeSourcePath(String sourcePath) => |
+ path.normalize(new File(sourcePath).absolute.path); |
+} |
+ |
+/// Provides a framework to read command line options from stdin and feed them |
+/// to a callback. |
+class _BatchRunner { |
+ /// Run the tool in 'batch' mode, receiving command lines through stdin and |
+ /// returning pass/fail status through stdout. This feature is intended for |
+ /// use in unit testing. |
+ static void runAsBatch(List<String> sharedArgs, _BatchRunnerHandler handler) { |
+ outSink.writeln('>>> BATCH START'); |
+ Stopwatch stopwatch = new Stopwatch(); |
+ stopwatch.start(); |
+ int testsFailed = 0; |
+ int totalTests = 0; |
+ ErrorSeverity batchResult = ErrorSeverity.NONE; |
+ // Read line from stdin. |
+ Stream cmdLine = |
+ stdin.transform(UTF8.decoder).transform(new LineSplitter()); |
+ cmdLine.listen((String line) { |
+ // Maybe finish. |
+ if (line.isEmpty) { |
+ var time = stopwatch.elapsedMilliseconds; |
+ outSink.writeln( |
+ '>>> BATCH END (${totalTests - testsFailed}/$totalTests) ${time}ms'); |
+ exitCode = batchResult.ordinal; |
+ } |
+ // Prepare aruments. |
+ var args; |
+ { |
+ var lineArgs = line.split(new RegExp('\\s+')); |
+ args = new List<String>(); |
+ args.addAll(sharedArgs); |
+ args.addAll(lineArgs); |
+ args.remove('-b'); |
+ args.remove('--batch'); |
+ } |
+ // Analyze single set of arguments. |
+ try { |
+ totalTests++; |
+ ErrorSeverity result = handler(args); |
+ bool resultPass = result != ErrorSeverity.ERROR; |
+ if (!resultPass) { |
+ testsFailed++; |
+ } |
+ batchResult = batchResult.max(result); |
+ // Write stderr end token and flush. |
+ errorSink.writeln('>>> EOF STDERR'); |
+ String resultPassString = resultPass ? 'PASS' : 'FAIL'; |
+ outSink.writeln( |
+ '>>> TEST $resultPassString ${stopwatch.elapsedMilliseconds}ms'); |
+ } catch (e, stackTrace) { |
+ errorSink.writeln(e); |
+ errorSink.writeln(stackTrace); |
+ errorSink.writeln('>>> EOF STDERR'); |
+ outSink.writeln('>>> TEST CRASH'); |
+ } |
+ }); |
+ } |
+} |
+ |
+class _DriverError implements Exception { |
+ String msg; |
+ _DriverError(this.msg); |
+} |
+ |
+/// [SdkExtUriResolver] needs a Map from package name to folder. In the case |
+/// that the analyzer is invoked with a --package-root option, we need to |
+/// manually create this mapping. Given [packageRootPath], |
+/// [_PackageRootPackageMapBuilder] creates a simple mapping from package name |
+/// to full path on disk (resolving any symbolic links). |
+class _PackageRootPackageMapBuilder { |
+ static Map<String, List<fileSystem.Folder>> buildPackageMap( |
+ String packageRootPath) { |
+ var packageRoot = new Directory(packageRootPath); |
+ if (!packageRoot.existsSync()) { |
+ throw new _DriverError( |
+ 'Package root directory ($packageRootPath) does not exist.'); |
+ } |
+ var packages = packageRoot.listSync(followLinks: false); |
+ var result = new Map<String, List<fileSystem.Folder>>(); |
+ for (var package in packages) { |
+ var packageName = path.basename(package.path); |
+ var realPath = package.resolveSymbolicLinksSync(); |
+ result[packageName] = [ |
+ PhysicalResourceProvider.INSTANCE.getFolder(realPath) |
+ ]; |
+ } |
+ return result; |
+ } |
+} |