| // 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. |
| */ |
| import 'dart:async'; |
| import 'dart:io' hide File; |
| import 'dart:io' as io; |
| |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/file_system/physical_file_system.dart'; |
| import 'package:analyzer/src/codegen/tools.dart'; |
| import 'package:analyzer/src/context/builder.dart'; |
| import 'package:analyzer/src/dart/analysis/byte_store.dart'; |
| import 'package:analyzer/src/dart/analysis/driver.dart'; |
| import 'package:analyzer/src/dart/analysis/file_state.dart'; |
| import 'package:analyzer/src/dart/analysis/performance_logger.dart'; |
| import 'package:analyzer/src/dart/sdk/sdk.dart'; |
| import 'package:analyzer/src/file_system/file_system.dart'; |
| import 'package:analyzer/src/generated/constant.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer/src/generated/sdk.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/generated/source_io.dart'; |
| import 'package:analyzer/src/source/package_map_resolver.dart'; |
| import 'package:front_end/src/testing/package_root.dart' as package_root; |
| import 'package:path/path.dart' as path; |
| import 'package:path/path.dart'; |
| |
| /** |
| * Generate the target .dot file. |
| */ |
| main() async { |
| String pkgPath = normalize(join(package_root.packageRoot, 'analyzer')); |
| await GeneratedContent.generateAll( |
| pkgPath, <GeneratedContent>[target, htmlTarget]); |
| } |
| |
| final GeneratedFile htmlTarget = new GeneratedFile( |
| 'doc/tasks.html', (String pkgPath) => new Driver(pkgPath).generateHtml()); |
| |
| final GeneratedFile target = new GeneratedFile( |
| 'tool/task_dependency_graph/tasks.dot', |
| (String pkgPath) => new Driver(pkgPath).generateFileContents()); |
| |
| typedef void GetterFinderCallback(PropertyAccessorElement element); |
| |
| class Driver { |
| static bool hasInitializedPlugins = false; |
| PhysicalResourceProvider resourceProvider; |
| AnalysisDriver driver; |
| InterfaceType resultDescriptorType; |
| InterfaceType listOfResultDescriptorType; |
| ClassElement enginePluginClass; |
| CompilationUnitElement taskUnitElement; |
| InterfaceType extensionPointIdType; |
| |
| final String rootDir; |
| |
| Driver(String pkgPath) : rootDir = new Directory(pkgPath).absolute.path; |
| |
| /** |
| * 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')); |
| |
| /** |
| * 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(String 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 return it as a [String]. |
| */ |
| Future<String> generateFileContents() async { |
| String data = await generateGraphData(); |
| 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 { |
| $data |
| } |
| '''; |
| } |
| |
| Future<String> generateGraphData() async { |
| if (!hasInitializedPlugins) { |
| AnalysisEngine.instance.processRequiredPlugins(); |
| hasInitializedPlugins = true; |
| } |
| List<String> lines = <String>[]; |
| resourceProvider = PhysicalResourceProvider.INSTANCE; |
| DartSdk sdk = new FolderBasedDartSdk(resourceProvider, |
| FolderBasedDartSdk.defaultSdkDirectory(resourceProvider)); |
| |
| ContextBuilderOptions builderOptions = new ContextBuilderOptions(); |
| if (Platform.packageConfig != null) { |
| builderOptions.defaultPackageFilePath = |
| Uri.parse(Platform.packageConfig).toFilePath(); |
| } else { |
| // Let the context builder use the default algorithm for package |
| // resolution. |
| } |
| |
| ContextBuilder builder = new ContextBuilder(resourceProvider, null, null, |
| options: builderOptions); |
| List<UriResolver> uriResolvers = [ |
| new DartUriResolver(sdk), |
| new PackageMapUriResolver(resourceProvider, |
| builder.convertPackagesToMap(builder.createPackageMap(''))), |
| new ResourceUriResolver(resourceProvider) |
| ]; |
| |
| var logger = new PerformanceLog(null); |
| var scheduler = new AnalysisDriverScheduler(logger); |
| driver = new AnalysisDriver( |
| scheduler, |
| logger, |
| resourceProvider, |
| new MemoryByteStore(), |
| new FileContentOverlay(), |
| null, |
| new SourceFactory(uriResolvers), |
| new AnalysisOptionsImpl()); |
| scheduler.start(); |
| |
| TypeProvider typeProvider = await driver.currentSession.typeProvider; |
| |
| String dartDartPath = path.join(rootDir, 'lib', 'src', 'task', 'dart.dart'); |
| String taskPath = path.join(rootDir, 'lib', 'src', 'plugin', 'task.dart'); |
| String modelPath = |
| path.join(rootDir, 'lib', 'src', 'task', 'api', 'model.dart'); |
| String enginePluginPath = |
| path.join(rootDir, 'lib', 'src', 'plugin', 'engine_plugin.dart'); |
| |
| CompilationUnitElement modelElement = await getUnitElement(modelPath); |
| InterfaceType analysisTaskType = modelElement.getType('AnalysisTask').type; |
| DartType dynamicType = typeProvider.dynamicType; |
| resultDescriptorType = modelElement |
| .getType('ResultDescriptor') |
| .type |
| .instantiate([dynamicType]); |
| listOfResultDescriptorType = |
| typeProvider.listType.instantiate([resultDescriptorType]); |
| CompilationUnit enginePluginUnit = await getUnit(enginePluginPath); |
| enginePluginClass = |
| enginePluginUnit.declaredElement.getType('EnginePlugin'); |
| extensionPointIdType = |
| enginePluginUnit.declaredElement.getType('ExtensionPointId').type; |
| CompilationUnit dartDartUnit = await getUnit(dartDartPath); |
| CompilationUnit taskUnit = await getUnit(taskPath); |
| taskUnitElement = taskUnit.declaredElement; |
| Set<String> results = new Set<String>(); |
| Set<String> resultLists = new Set<String>(); |
| for (CompilationUnitMember dartUnitMember in dartDartUnit.declarations) { |
| if (dartUnitMember is ClassDeclaration) { |
| ClassDeclaration clazz = dartUnitMember; |
| if (!clazz.isAbstract && |
| clazz.declaredElement.type.isSubtypeOf(analysisTaskType)) { |
| String task = clazz.name.name; |
| |
| MethodDeclaration buildInputsAst; |
| VariableDeclaration descriptorField; |
| for (ClassMember classMember in clazz.members) { |
| if (classMember is MethodDeclaration && |
| classMember.name.name == 'buildInputs') { |
| buildInputsAst = classMember; |
| } |
| if (classMember is FieldDeclaration) { |
| for (VariableDeclaration field in classMember.fields.variables) { |
| if (field.name.name == 'DESCRIPTOR') { |
| descriptorField = field; |
| } |
| } |
| } |
| } |
| |
| findResultDescriptors(buildInputsAst, (String input) { |
| results.add(input); |
| lines.add(' $input -> $task'); |
| }); |
| findResultDescriptorLists(buildInputsAst, (String input) { |
| resultLists.add(input); |
| lines.add(' $input -> $task'); |
| }); |
| findResultDescriptors(descriptorField, (String out) { |
| results.add(out); |
| lines.add(' $task -> $out'); |
| }); |
| } |
| } |
| } |
| for (String resultList in resultLists) { |
| lines.add(' $resultList [shape=hexagon]'); |
| TopLevelVariableElement extensionIdVariable = _getExtensionId(resultList); |
| findExtensions(enginePluginUnit, extensionIdVariable, (String extension) { |
| results.add(extension); |
| lines.add(' $extension -> $resultList'); |
| }); |
| } |
| for (String result in results) { |
| lines.add(' $result [shape=box]'); |
| } |
| lines.sort(); |
| return lines.join('\n'); |
| } |
| |
| Future<String> generateHtml() async { |
| var data = await generateGraphData(); |
| return ''' |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Analysis Task Dependency Graph</title> |
| <link rel="stylesheet" href="support/style.css"> |
| <script src="support/viz.js"></script> |
| <script type="application/dart" src="support/web_app.dart.js"></script> |
| <script src="support/dart.js"></script> |
| </head> |
| <body> |
| <button id="zoomBtn">Zoom</button> |
| <script type="text/vnd.graphviz" id="dot"> |
| digraph G { |
| tooltip="Analysis Task Dependency Graph"; |
| node [fontname=Helvetica]; |
| edge [fontname=Helvetica, fontcolor=gray]; |
| $data |
| } |
| </script> |
| </body> |
| </html> |
| '''; |
| } |
| |
| Future<CompilationUnit> getUnit(String path) async { |
| var result = await driver.getResult(path); |
| return result.unit; |
| } |
| |
| Future<CompilationUnitElement> getUnitElement(String path) async { |
| UnitElementResult result = await driver.getUnitElement(path); |
| return result.element; |
| } |
| |
| /** |
| * 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'); |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| } |