// 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/analyzer.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/context/builder.dart'; import 'package:analyzer/src/dart/analysis/driver.dart'; import 'package:analyzer/src/dart/analysis/file_state.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/api_prototype/byte_store.dart'; import 'package:front_end/src/base/performance_logger.dart'; import 'package:front_end/src/codegen/tools.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, [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 resultDescriptors = new Set(); 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`, and execute [callback] on the getter names. */ void findResultDescriptorLists( AstNode node, void callback(String descriptorListName)) { Set resultDescriptorLists = new Set(); 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 resultDescriptors = new Set(); 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 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 generateGraphData() async { if (!hasInitializedPlugins) { AnalysisEngine.instance.processRequiredPlugins(); hasInitializedPlugins = true; } List lines = []; resourceProvider = PhysicalResourceProvider.INSTANCE; DartSdk sdk = new FolderBasedDartSdk(resourceProvider, FolderBasedDartSdk.defaultSdkDirectory(resourceProvider)); ContextBuilderOptions builderOptions = new ContextBuilderOptions(); if (Platform.packageRoot != null) { builderOptions.defaultPackagesDirectoryPath = Uri.parse(Platform.packageRoot).toFilePath(); } else 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 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.element.getType('EnginePlugin'); extensionPointIdType = enginePluginUnit.element.getType('ExtensionPointId').type; CompilationUnit dartDartUnit = await getUnit(dartDartPath); CompilationUnit taskUnit = await getUnit(taskPath); taskUnitElement = taskUnit.element; Set results = new Set(); Set resultLists = new Set(); for (CompilationUnitMember dartUnitMember in dartDartUnit.declarations) { if (dartUnitMember is ClassDeclaration) { ClassDeclaration clazz = dartUnitMember; if (!clazz.isAbstract && clazz.element.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 generateHtml() async { var data = await generateGraphData(); return ''' Analysis Task Dependency Graph '''; } Future getUnit(String path) async { var result = await driver.getResult(path); return result.unit; } Future 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); } } }