Index: packages/analyzer/tool/task_dependency_graph/generate.dart |
diff --git a/packages/analyzer/tool/task_dependency_graph/generate.dart b/packages/analyzer/tool/task_dependency_graph/generate.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..235c2b9d032b7bee4592cab847b620d9135478b9 |
--- /dev/null |
+++ b/packages/analyzer/tool/task_dependency_graph/generate.dart |
@@ -0,0 +1,346 @@ |
+// 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 file contains code to output a description of tasks and their |
+ * dependencies in ".dot" format. Prior to running, the user should run "pub |
+ * get" in the analyzer directory to ensure that a "packages" folder exists. |
+ * |
+ * TODO(paulberry): |
+ * - Add general.dart and html.dart for completeness. |
+ * - Use Graphviz's "record" feature to produce more compact output |
+ * (http://www.graphviz.org/content/node-shapes#record) |
+ * - Produce a warning if a result descriptor is found which isn't the output |
+ * of exactly one task. |
+ * - Convert this tool to use package_config to find the package map. |
+ */ |
+library task_dependency_graph.generate; |
+ |
+import 'dart:io' hide File; |
+import 'dart:io' as io; |
+ |
+import 'package:analyzer/analyzer.dart'; |
+import 'package:analyzer/file_system/file_system.dart'; |
+import 'package:analyzer/file_system/physical_file_system.dart'; |
+import 'package:analyzer/src/generated/constant.dart'; |
+import 'package:analyzer/src/generated/element.dart'; |
+import 'package:analyzer/src/generated/engine.dart'; |
+import 'package:analyzer/src/generated/java_io.dart'; |
+import 'package:analyzer/src/generated/sdk.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:path/path.dart' as path; |
+ |
+/** |
+ * Generate the target .dot file. |
+ */ |
+main() { |
+ new Driver().generateFile(); |
+} |
+ |
+typedef void GetterFinderCallback(PropertyAccessorElement element); |
+ |
+class Driver { |
+ PhysicalResourceProvider resourceProvider; |
+ AnalysisContext context; |
+ InterfaceType resultDescriptorType; |
+ InterfaceType listOfResultDescriptorType; |
+ ClassElement enginePluginClass; |
+ CompilationUnitElement taskUnitElement; |
+ InterfaceType extensionPointIdType; |
+ final String rootDir; |
+ |
+ Driver() |
+ : rootDir = |
+ findRoot(Platform.script.toFilePath(windows: Platform.isWindows)); |
+ |
+ /** |
+ * Get an [io.File] object corresponding to the file in which the generated |
+ * graph should be output. |
+ */ |
+ io.File get file => new io.File( |
+ path.join(rootDir, 'tool', 'task_dependency_graph', 'tasks.dot')); |
+ |
+ /** |
+ * Determine if the output [file] contains the expected contents. |
+ */ |
+ bool checkFile() { |
+ String expectedContents = generateFileContents(); |
+ String actualContents = file.readAsStringSync(); |
+ // Normalize Windows line endings to Unix line endings so that the |
+ // comparison doesn't fail on Windows. |
+ actualContents = actualContents.replaceAll('\r\n', '\n'); |
+ return expectedContents == actualContents; |
+ } |
+ |
+ /** |
+ * Starting at [node], find all calls to registerExtension() which refer to |
+ * the given [extensionIdVariable], and execute [callback] for the associated |
+ * result descriptors. |
+ */ |
+ void findExtensions(AstNode node, TopLevelVariableElement extensionIdVariable, |
+ void callback(descriptorName)) { |
+ Set<PropertyAccessorElement> resultDescriptors = |
+ new Set<PropertyAccessorElement>(); |
+ node.accept(new ExtensionFinder( |
+ resultDescriptorType, extensionIdVariable, resultDescriptors.add)); |
+ for (PropertyAccessorElement resultDescriptor in resultDescriptors) { |
+ callback(resultDescriptor.name); |
+ } |
+ } |
+ |
+ /** |
+ * Starting at [node], find all references to a getter of type |
+ * `List<ResultDescriptor>`, and execute [callback] on the getter names. |
+ */ |
+ void findResultDescriptorLists( |
+ AstNode node, void callback(String descriptorListName)) { |
+ Set<PropertyAccessorElement> resultDescriptorLists = |
+ new Set<PropertyAccessorElement>(); |
+ node.accept(new GetterFinder( |
+ listOfResultDescriptorType, resultDescriptorLists.add)); |
+ for (PropertyAccessorElement resultDescriptorList |
+ in resultDescriptorLists) { |
+ // We only care about result descriptor lists associated with getters in |
+ // the engine plugin class. |
+ if (resultDescriptorList.enclosingElement != enginePluginClass) { |
+ continue; |
+ } |
+ callback(resultDescriptorList.name); |
+ } |
+ } |
+ |
+ void findResultDescriptors( |
+ AstNode node, void callback(String descriptorName)) { |
+ Set<PropertyAccessorElement> resultDescriptors = |
+ new Set<PropertyAccessorElement>(); |
+ node.accept(new GetterFinder(resultDescriptorType, resultDescriptors.add)); |
+ for (PropertyAccessorElement resultDescriptor in resultDescriptors) { |
+ callback(resultDescriptor.name); |
+ } |
+ } |
+ |
+ /** |
+ * Generate the task dependency graph and write it to the output [file]. |
+ */ |
+ void generateFile() { |
+ String fileContents = generateFileContents(); |
+ file.writeAsStringSync(fileContents); |
+ } |
+ |
+ /** |
+ * Generate the task dependency graph and return it as a [String]. |
+ */ |
+ String generateFileContents() { |
+ List<String> lines = <String>[]; |
+ resourceProvider = PhysicalResourceProvider.INSTANCE; |
+ DartSdk sdk = DirectoryBasedDartSdk.defaultSdk; |
+ context = AnalysisEngine.instance.createAnalysisContext(); |
+ String packageRootPath; |
+ if (Platform.packageRoot.isNotEmpty) { |
+ packageRootPath = Platform.packageRoot; |
+ } else { |
+ packageRootPath = path.join(rootDir, 'packages'); |
+ } |
+ JavaFile packagesDir = new JavaFile(packageRootPath); |
+ List<UriResolver> uriResolvers = [ |
+ new DartUriResolver(sdk), |
+ new PackageUriResolver(<JavaFile>[packagesDir]), |
+ new FileUriResolver() |
+ ]; |
+ context.sourceFactory = new SourceFactory(uriResolvers); |
+ Source dartDartSource = |
+ setupSource(path.join('lib', 'src', 'task', 'dart.dart')); |
+ Source taskSource = setupSource(path.join('lib', 'plugin', 'task.dart')); |
+ Source modelSource = setupSource(path.join('lib', 'task', 'model.dart')); |
+ Source enginePluginSource = |
+ setupSource(path.join('lib', 'src', 'plugin', 'engine_plugin.dart')); |
+ CompilationUnitElement modelElement = getUnit(modelSource).element; |
+ InterfaceType analysisTaskType = modelElement.getType('AnalysisTask').type; |
+ DartType dynamicType = context.typeProvider.dynamicType; |
+ resultDescriptorType = modelElement |
+ .getType('ResultDescriptor') |
+ .type |
+ .substitute4([dynamicType]); |
+ listOfResultDescriptorType = |
+ context.typeProvider.listType.substitute4([resultDescriptorType]); |
+ CompilationUnitElement enginePluginUnitElement = |
+ getUnit(enginePluginSource).element; |
+ enginePluginClass = enginePluginUnitElement.getType('EnginePlugin'); |
+ extensionPointIdType = |
+ enginePluginUnitElement.getType('ExtensionPointId').type; |
+ CompilationUnit dartDartUnit = getUnit(dartDartSource); |
+ CompilationUnitElement dartDartUnitElement = dartDartUnit.element; |
+ CompilationUnit taskUnit = getUnit(taskSource); |
+ taskUnitElement = taskUnit.element; |
+ Set<String> results = new Set<String>(); |
+ Set<String> resultLists = new Set<String>(); |
+ for (ClassElement cls in dartDartUnitElement.types) { |
+ if (!cls.isAbstract && cls.type.isSubtypeOf(analysisTaskType)) { |
+ String task = cls.name; |
+ AstNode buildInputsAst = cls.getMethod('buildInputs').computeNode(); |
+ findResultDescriptors(buildInputsAst, (String input) { |
+ results.add(input); |
+ lines.add(' $input -> $task'); |
+ }); |
+ findResultDescriptorLists(buildInputsAst, (String input) { |
+ resultLists.add(input); |
+ lines.add(' $input -> $task'); |
+ }); |
+ findResultDescriptors(cls.getField('DESCRIPTOR').computeNode(), (String out) { |
+ results.add(out); |
+ lines.add(' $task -> $out'); |
+ }); |
+ } |
+ } |
+ AstNode enginePluginAst = enginePluginUnitElement.computeNode(); |
+ for (String resultList in resultLists) { |
+ lines.add(' $resultList [shape=hexagon]'); |
+ TopLevelVariableElement extensionIdVariable = _getExtensionId(resultList); |
+ findExtensions(enginePluginAst, extensionIdVariable, (String extension) { |
+ results.add(extension); |
+ lines.add(' $extension -> $resultList'); |
+ }); |
+ } |
+ for (String result in results) { |
+ lines.add(' $result [shape=box]'); |
+ } |
+ lines.sort(); |
+ return ''' |
+// 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 file has been automatically generated. Please do not edit it manually. |
+// To regenerate the file, use the script |
+// "pkg/analyzer/tool/task_dependency_graph/generate.dart". |
+// |
+// To render this graph using Graphviz (www.graphviz.org) use the command: |
+// "dot tasks.dot -Tpdf -O". |
+digraph G { |
+${lines.join('\n')} |
+} |
+'''; |
+ } |
+ |
+ CompilationUnit getUnit(Source source) => |
+ context.resolveCompilationUnit2(source, source); |
+ |
+ Source setupSource(String filename) { |
+ String filePath = path.join(rootDir, filename); |
+ File file = resourceProvider.getResource(filePath); |
+ Source source = file.createSource(); |
+ Uri restoredUri = context.sourceFactory.restoreUri(source); |
+ if (restoredUri != null) { |
+ source = file.createSource(restoredUri); |
+ } |
+ ChangeSet changeSet = new ChangeSet(); |
+ changeSet.addedSource(source); |
+ context.applyChanges(changeSet); |
+ return source; |
+ } |
+ |
+ /** |
+ * Find the result list getter having name [resultListGetterName] in the |
+ * [EnginePlugin] class, and use the [ExtensionPointId] annotation on that |
+ * getter to find the associated [TopLevelVariableElement] which can be used |
+ * to register extensions for that getter. |
+ */ |
+ TopLevelVariableElement _getExtensionId(String resultListGetterName) { |
+ PropertyAccessorElement getter = |
+ enginePluginClass.getGetter(resultListGetterName); |
+ for (ElementAnnotation annotation in getter.metadata) { |
+ DartObjectImpl annotationValue = annotation.constantValue; |
+ if (annotationValue.type.isSubtypeOf(extensionPointIdType)) { |
+ String extensionPointId = |
+ annotationValue.fields['extensionPointId'].toStringValue(); |
+ for (TopLevelVariableElement variable |
+ in taskUnitElement.topLevelVariables) { |
+ if (variable.name == extensionPointId) { |
+ return variable; |
+ } |
+ } |
+ } |
+ } |
+ throw new Exception( |
+ 'Could not find extension ID corresponding to $resultListGetterName'); |
+ } |
+ |
+ /** |
+ * Find the root directory of the analyzer package by proceeding |
+ * upward to the 'tool' dir, and then going up one more directory. |
+ */ |
+ static String findRoot(String pathname) { |
+ while (path.basename(pathname) != 'tool') { |
+ String parent = path.dirname(pathname); |
+ if (parent.length >= pathname.length) { |
+ throw new Exception("Can't find root directory"); |
+ } |
+ pathname = parent; |
+ } |
+ return path.dirname(pathname); |
+ } |
+} |
+ |
+/** |
+ * Visitor that finds calls that register extension points. Specifically, we |
+ * look for calls of the form `method(extensionIdVariable, resultDescriptor)`, |
+ * where `resultDescriptor` has type [resultDescriptorType], and we pass the |
+ * corresponding result descriptor names to [callback]. |
+ */ |
+class ExtensionFinder extends GeneralizingAstVisitor { |
+ final InterfaceType resultDescriptorType; |
+ final TopLevelVariableElement extensionIdVariable; |
+ final GetterFinderCallback callback; |
+ |
+ ExtensionFinder( |
+ this.resultDescriptorType, this.extensionIdVariable, this.callback); |
+ |
+ @override |
+ visitIdentifier(Identifier node) { |
+ Element element = node.staticElement; |
+ if (element is PropertyAccessorElement && |
+ element.isGetter && |
+ element.variable == extensionIdVariable) { |
+ AstNode parent = node.parent; |
+ if (parent is ArgumentList && |
+ parent.arguments.length == 2 && |
+ parent.arguments[0] == node) { |
+ Expression extension = parent.arguments[1]; |
+ if (extension is Identifier) { |
+ Element element = extension.staticElement; |
+ if (element is PropertyAccessorElement && |
+ element.isGetter && |
+ element.returnType.isSubtypeOf(resultDescriptorType)) { |
+ callback(element); |
+ return; |
+ } |
+ } |
+ } |
+ throw new Exception('Could not decode extension setup: $parent'); |
+ } |
+ } |
+} |
+ |
+/** |
+ * Visitor that finds references to getters having a specific type (or a |
+ * subtype of that type) |
+ */ |
+class GetterFinder extends GeneralizingAstVisitor { |
+ final InterfaceType type; |
+ final GetterFinderCallback callback; |
+ |
+ GetterFinder(this.type, this.callback); |
+ |
+ @override |
+ visitIdentifier(Identifier node) { |
+ Element element = node.staticElement; |
+ if (element is PropertyAccessorElement && |
+ element.isGetter && |
+ element.returnType.isSubtypeOf(type)) { |
+ callback(element); |
+ } |
+ } |
+} |