Index: pkg/analyzer_cli/lib/src/bootloader.dart |
diff --git a/pkg/analyzer_cli/lib/src/bootloader.dart b/pkg/analyzer_cli/lib/src/bootloader.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..726004cb3206003e7956ef189d6553396230ea7c |
--- /dev/null |
+++ b/pkg/analyzer_cli/lib/src/bootloader.dart |
@@ -0,0 +1,196 @@ |
+// 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.bootloader; |
+ |
+import 'dart:async'; |
+import 'dart:isolate'; |
+ |
+import 'package:analyzer/file_system/physical_file_system.dart'; |
+import 'package:analyzer/source/analysis_options_provider.dart'; |
+import 'package:analyzer/src/context/context.dart'; |
+import 'package:analyzer/src/generated/engine.dart' as engine; |
+import 'package:analyzer/src/plugin/plugin_configuration.dart'; |
+import 'package:analyzer_cli/src/driver.dart'; |
+import 'package:analyzer_cli/src/options.dart'; |
+import 'package:source_span/source_span.dart'; |
+import 'package:yaml/src/yaml_node.dart'; |
+ |
+const _analyzerPackageName = 'analyzer'; |
+ |
+/// Return non-null if there is a validation issue with this plugin. |
+String validate(PluginInfo plugin) { |
+ var missing = <String>[]; |
+ if (plugin.className == null) { |
+ missing.add('class name'); |
+ } |
+ if (plugin.libraryUri == null) { |
+ missing.add('library uri'); |
+ } |
+ if (missing.isEmpty) { |
+ // All good. |
+ return null; |
+ } |
+ return 'Plugin ${plugin.name} skipped, config missing: ${missing.join(", ")}'; |
+} |
+ |
+List<PluginInfo> _validate(Iterable<PluginInfo> plugins) { |
+ List<PluginInfo> validated = <PluginInfo>[]; |
+ plugins.forEach((PluginInfo plugin) { |
+ String validation = validate(plugin); |
+ if (validation != null) { |
+ errorSink.writeln(validation); |
+ } else { |
+ validated.add(plugin); |
+ } |
+ }); |
+ return validated; |
+} |
+ |
+/// Source code assembler. |
+class Assembler { |
+ /// Plugins to configure. |
+ final Iterable<PluginInfo> plugins; |
+ |
+ /// Create an assembler for the given plugin [config]. |
+ Assembler(this.plugins); |
+ |
+ /// A string enumerating required package `import`s. |
+ String get enumerateImports => |
+ plugins.map((PluginInfo p) => "import '${p.libraryUri}';").join('\n'); |
+ |
+ /// A string listing initialized plugin instances. |
+ String get pluginList => |
+ plugins.map((PluginInfo p) => 'new ${p.className}()').join(', '); |
+ |
+ /// Create a file containing a `main()` suitable for loading in spawned |
+ /// isolate. |
+ String createMain() => _generateMain(); |
+ |
+ String _generateMain() => """ |
+// 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. |
+ |
+// This code was auto-generated, is not intended to be edited, and is subject to |
+// significant change. Please see the README file for more information. |
+ |
+import 'package:analyzer_cli/src/driver.dart'; |
+ |
+$enumerateImports |
+ |
+void main(List<String> args) { |
+ var starter = new Driver(); |
+ starter.userDefinedPlugins = [$pluginList]; |
+ starter.start(args); |
+} |
+"""; |
+} |
+ |
+/// Given environment information extracted from command-line `args`, creates a |
+/// a loadable analyzer "image". |
+class BootLoader { |
+ /// Emits an error message to [errorSink] if plugin config can't be read. |
+ static final ErrorHandler _pluginConfigErrorHandler = (Exception e) { |
+ String details; |
+ if (e is PluginConfigFormatException) { |
+ details = e.message; |
+ var node = e.yamlNode; |
+ if (node is YamlNode) { |
+ SourceLocation location = node.span.start; |
+ details += ' (line ${location.line}, column ${location.column})'; |
+ } |
+ } else { |
+ details = e.toString(); |
+ } |
+ |
+ errorSink.writeln('Plugin configuration skipped: $details'); |
+ }; |
+ |
+ /// Reads plugin config info from `.analysis_options`. |
+ PluginConfigOptionsProcessor _pluginOptionsProcessor = |
+ new PluginConfigOptionsProcessor(_pluginConfigErrorHandler); |
+ |
+ /// Create a loadable analyzer image configured with plugins derived from |
+ /// the given analyzer command-line `args`. |
+ Image createImage(List<String> args) { |
+ // Parse commandline options. |
+ CommandLineOptions options = CommandLineOptions.parse(args); |
+ |
+ // Process analysis options file (and notify all interested parties). |
+ _processAnalysisOptions(options); |
+ |
+ // TODO(pquitslund): Pass in .packages info |
+ return new Image(_pluginOptionsProcessor.config, |
+ args: args, packageRootPath: options.packageRootPath); |
+ } |
+ |
+ void _processAnalysisOptions(CommandLineOptions options) { |
+ // Determine options file path. |
+ var filePath = options.analysisOptionsFile ?? |
+ engine.AnalysisEngine.ANALYSIS_OPTIONS_FILE; |
+ try { |
+ var file = PhysicalResourceProvider.INSTANCE.getFile(filePath); |
+ AnalysisOptionsProvider analysisOptionsProvider = |
+ new AnalysisOptionsProvider(); |
+ Map<String, YamlNode> options = |
+ analysisOptionsProvider.getOptionsFromFile(file); |
+ //TODO(pq): thread in proper context. |
+ var temporaryContext = new AnalysisContextImpl(); |
+ _pluginOptionsProcessor.optionsProcessed(temporaryContext, options); |
+ } on Exception catch (e) { |
+ _pluginOptionsProcessor.onError(e); |
+ } |
+ } |
+} |
+ |
+/// A loadable "image" of a a configured analyzer instance. |
+class Image { |
+ /// (Optional) package root path. |
+ final String packageRootPath; |
+ |
+ /// (Optional) package map. |
+ final Map<String, Uri> packages; |
+ |
+ /// (Optional) args to be passed on to the loaded main. |
+ final List<String> args; |
+ |
+ /// Plugin configuration. |
+ final PluginConfig config; |
+ |
+ /// Create an image with the given [config] and optionally [packages], |
+ /// [packageRootPath], and command line [args]. |
+ Image(this.config, {this.packages, this.packageRootPath, this.args}); |
+ |
+ /// Load this image. |
+ /// |
+ /// Loading an image consists in assembling an analyzer `main()`, configured |
+ /// to include the appropriate analyzer plugins as specified in |
+ /// `.analyzer_options` which is then run in a spawned isolate. |
+ Future load() { |
+ List<PluginInfo> plugins = _validate(config.plugins); |
+ String mainSource = new Assembler(plugins).createMain(); |
+ |
+ Completer completer = new Completer(); |
+ ReceivePort exitListener = new ReceivePort(); |
+ exitListener.listen((data) { |
+ completer.complete(); |
+ exitListener.close(); |
+ }); |
+ |
+ Uri uri = |
+ Uri.parse('data:application/dart;charset=utf-8,${Uri.encodeComponent( |
+ mainSource)}'); |
+ |
+ // TODO(pquitslund): update once .packages are supported. |
+ String packageRoot = |
+ packageRootPath != null ? packageRootPath : './packages'; |
+ Uri packageUri = new Uri.file(packageRoot); |
+ |
+ Isolate.spawnUri(uri, args, null /* msg */, |
+ packageRoot: packageUri, onExit: exitListener.sendPort); |
+ |
+ return completer.future; |
+ } |
+} |