OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * This file contains code to output a description of tasks and their |
| 7 * dependencies in ".dot" format. Prior to running, the user should run "pub |
| 8 * get" in the analyzer directory to ensure that a "packages" folder exists. |
| 9 * |
| 10 * TODO(paulberry): |
| 11 * - Add general.dart and html.dart for completeness. |
| 12 * - Use Graphviz's "record" feature to produce more compact output |
| 13 * (http://www.graphviz.org/content/node-shapes#record) |
| 14 * - Produce a warning if a result descriptor is found which isn't the output |
| 15 * of exactly one task. |
| 16 * - Convert this tool to use package_config to find the package map. |
| 17 */ |
| 18 library task_dependency_graph.generate; |
| 19 |
| 20 import 'dart:io' hide File; |
| 21 import 'dart:io' as io; |
| 22 |
| 23 import 'package:analyzer/analyzer.dart'; |
| 24 import 'package:analyzer/file_system/file_system.dart'; |
| 25 import 'package:analyzer/file_system/physical_file_system.dart'; |
| 26 import 'package:analyzer/src/generated/constant.dart'; |
| 27 import 'package:analyzer/src/generated/element.dart'; |
| 28 import 'package:analyzer/src/generated/engine.dart'; |
| 29 import 'package:analyzer/src/generated/java_io.dart'; |
| 30 import 'package:analyzer/src/generated/sdk.dart'; |
| 31 import 'package:analyzer/src/generated/sdk_io.dart'; |
| 32 import 'package:analyzer/src/generated/source.dart'; |
| 33 import 'package:analyzer/src/generated/source_io.dart'; |
| 34 import 'package:path/path.dart' as path; |
| 35 |
| 36 /** |
| 37 * Generate the target .dot file. |
| 38 */ |
| 39 main() { |
| 40 new Driver().generateFile(); |
| 41 } |
| 42 |
| 43 typedef void GetterFinderCallback(PropertyAccessorElement element); |
| 44 |
| 45 class Driver { |
| 46 PhysicalResourceProvider resourceProvider; |
| 47 AnalysisContext context; |
| 48 InterfaceType resultDescriptorType; |
| 49 InterfaceType listOfResultDescriptorType; |
| 50 ClassElement enginePluginClass; |
| 51 CompilationUnitElement taskUnitElement; |
| 52 InterfaceType extensionPointIdType; |
| 53 final String rootDir; |
| 54 |
| 55 Driver() |
| 56 : rootDir = |
| 57 findRoot(Platform.script.toFilePath(windows: Platform.isWindows)); |
| 58 |
| 59 /** |
| 60 * Get an [io.File] object corresponding to the file in which the generated |
| 61 * graph should be output. |
| 62 */ |
| 63 io.File get file => new io.File( |
| 64 path.join(rootDir, 'tool', 'task_dependency_graph', 'tasks.dot')); |
| 65 |
| 66 /** |
| 67 * Determine if the output [file] contains the expected contents. |
| 68 */ |
| 69 bool checkFile() { |
| 70 String expectedContents = generateFileContents(); |
| 71 String actualContents = file.readAsStringSync(); |
| 72 // Normalize Windows line endings to Unix line endings so that the |
| 73 // comparison doesn't fail on Windows. |
| 74 actualContents = actualContents.replaceAll('\r\n', '\n'); |
| 75 return expectedContents == actualContents; |
| 76 } |
| 77 |
| 78 /** |
| 79 * Starting at [node], find all calls to registerExtension() which refer to |
| 80 * the given [extensionIdVariable], and execute [callback] for the associated |
| 81 * result descriptors. |
| 82 */ |
| 83 void findExtensions(AstNode node, TopLevelVariableElement extensionIdVariable, |
| 84 void callback(descriptorName)) { |
| 85 Set<PropertyAccessorElement> resultDescriptors = |
| 86 new Set<PropertyAccessorElement>(); |
| 87 node.accept(new ExtensionFinder( |
| 88 resultDescriptorType, extensionIdVariable, resultDescriptors.add)); |
| 89 for (PropertyAccessorElement resultDescriptor in resultDescriptors) { |
| 90 callback(resultDescriptor.name); |
| 91 } |
| 92 } |
| 93 |
| 94 /** |
| 95 * Starting at [node], find all references to a getter of type |
| 96 * `List<ResultDescriptor>`, and execute [callback] on the getter names. |
| 97 */ |
| 98 void findResultDescriptorLists( |
| 99 AstNode node, void callback(String descriptorListName)) { |
| 100 Set<PropertyAccessorElement> resultDescriptorLists = |
| 101 new Set<PropertyAccessorElement>(); |
| 102 node.accept(new GetterFinder( |
| 103 listOfResultDescriptorType, resultDescriptorLists.add)); |
| 104 for (PropertyAccessorElement resultDescriptorList |
| 105 in resultDescriptorLists) { |
| 106 // We only care about result descriptor lists associated with getters in |
| 107 // the engine plugin class. |
| 108 if (resultDescriptorList.enclosingElement != enginePluginClass) { |
| 109 continue; |
| 110 } |
| 111 callback(resultDescriptorList.name); |
| 112 } |
| 113 } |
| 114 |
| 115 void findResultDescriptors( |
| 116 AstNode node, void callback(String descriptorName)) { |
| 117 Set<PropertyAccessorElement> resultDescriptors = |
| 118 new Set<PropertyAccessorElement>(); |
| 119 node.accept(new GetterFinder(resultDescriptorType, resultDescriptors.add)); |
| 120 for (PropertyAccessorElement resultDescriptor in resultDescriptors) { |
| 121 callback(resultDescriptor.name); |
| 122 } |
| 123 } |
| 124 |
| 125 /** |
| 126 * Generate the task dependency graph and write it to the output [file]. |
| 127 */ |
| 128 void generateFile() { |
| 129 String fileContents = generateFileContents(); |
| 130 file.writeAsStringSync(fileContents); |
| 131 } |
| 132 |
| 133 /** |
| 134 * Generate the task dependency graph and return it as a [String]. |
| 135 */ |
| 136 String generateFileContents() { |
| 137 List<String> lines = <String>[]; |
| 138 resourceProvider = PhysicalResourceProvider.INSTANCE; |
| 139 DartSdk sdk = DirectoryBasedDartSdk.defaultSdk; |
| 140 context = AnalysisEngine.instance.createAnalysisContext(); |
| 141 String packageRootPath; |
| 142 if (Platform.packageRoot.isNotEmpty) { |
| 143 packageRootPath = Platform.packageRoot; |
| 144 } else { |
| 145 packageRootPath = path.join(rootDir, 'packages'); |
| 146 } |
| 147 JavaFile packagesDir = new JavaFile(packageRootPath); |
| 148 List<UriResolver> uriResolvers = [ |
| 149 new DartUriResolver(sdk), |
| 150 new PackageUriResolver(<JavaFile>[packagesDir]), |
| 151 new FileUriResolver() |
| 152 ]; |
| 153 context.sourceFactory = new SourceFactory(uriResolvers); |
| 154 Source dartDartSource = |
| 155 setupSource(path.join('lib', 'src', 'task', 'dart.dart')); |
| 156 Source taskSource = setupSource(path.join('lib', 'plugin', 'task.dart')); |
| 157 Source modelSource = setupSource(path.join('lib', 'task', 'model.dart')); |
| 158 Source enginePluginSource = |
| 159 setupSource(path.join('lib', 'src', 'plugin', 'engine_plugin.dart')); |
| 160 CompilationUnitElement modelElement = getUnit(modelSource).element; |
| 161 InterfaceType analysisTaskType = modelElement.getType('AnalysisTask').type; |
| 162 DartType dynamicType = context.typeProvider.dynamicType; |
| 163 resultDescriptorType = modelElement |
| 164 .getType('ResultDescriptor') |
| 165 .type |
| 166 .substitute4([dynamicType]); |
| 167 listOfResultDescriptorType = |
| 168 context.typeProvider.listType.substitute4([resultDescriptorType]); |
| 169 CompilationUnitElement enginePluginUnitElement = |
| 170 getUnit(enginePluginSource).element; |
| 171 enginePluginClass = enginePluginUnitElement.getType('EnginePlugin'); |
| 172 extensionPointIdType = |
| 173 enginePluginUnitElement.getType('ExtensionPointId').type; |
| 174 CompilationUnit dartDartUnit = getUnit(dartDartSource); |
| 175 CompilationUnitElement dartDartUnitElement = dartDartUnit.element; |
| 176 CompilationUnit taskUnit = getUnit(taskSource); |
| 177 taskUnitElement = taskUnit.element; |
| 178 Set<String> results = new Set<String>(); |
| 179 Set<String> resultLists = new Set<String>(); |
| 180 for (ClassElement cls in dartDartUnitElement.types) { |
| 181 if (!cls.isAbstract && cls.type.isSubtypeOf(analysisTaskType)) { |
| 182 String task = cls.name; |
| 183 AstNode buildInputsAst = cls.getMethod('buildInputs').computeNode(); |
| 184 findResultDescriptors(buildInputsAst, (String input) { |
| 185 results.add(input); |
| 186 lines.add(' $input -> $task'); |
| 187 }); |
| 188 findResultDescriptorLists(buildInputsAst, (String input) { |
| 189 resultLists.add(input); |
| 190 lines.add(' $input -> $task'); |
| 191 }); |
| 192 findResultDescriptors(cls.getField('DESCRIPTOR').computeNode(), (String
out) { |
| 193 results.add(out); |
| 194 lines.add(' $task -> $out'); |
| 195 }); |
| 196 } |
| 197 } |
| 198 AstNode enginePluginAst = enginePluginUnitElement.computeNode(); |
| 199 for (String resultList in resultLists) { |
| 200 lines.add(' $resultList [shape=hexagon]'); |
| 201 TopLevelVariableElement extensionIdVariable = _getExtensionId(resultList); |
| 202 findExtensions(enginePluginAst, extensionIdVariable, (String extension) { |
| 203 results.add(extension); |
| 204 lines.add(' $extension -> $resultList'); |
| 205 }); |
| 206 } |
| 207 for (String result in results) { |
| 208 lines.add(' $result [shape=box]'); |
| 209 } |
| 210 lines.sort(); |
| 211 return ''' |
| 212 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 213 // for details. All rights reserved. Use of this source code is governed by a |
| 214 // BSD-style license that can be found in the LICENSE file. |
| 215 // |
| 216 // This file has been automatically generated. Please do not edit it manually. |
| 217 // To regenerate the file, use the script |
| 218 // "pkg/analyzer/tool/task_dependency_graph/generate.dart". |
| 219 // |
| 220 // To render this graph using Graphviz (www.graphviz.org) use the command: |
| 221 // "dot tasks.dot -Tpdf -O". |
| 222 digraph G { |
| 223 ${lines.join('\n')} |
| 224 } |
| 225 '''; |
| 226 } |
| 227 |
| 228 CompilationUnit getUnit(Source source) => |
| 229 context.resolveCompilationUnit2(source, source); |
| 230 |
| 231 Source setupSource(String filename) { |
| 232 String filePath = path.join(rootDir, filename); |
| 233 File file = resourceProvider.getResource(filePath); |
| 234 Source source = file.createSource(); |
| 235 Uri restoredUri = context.sourceFactory.restoreUri(source); |
| 236 if (restoredUri != null) { |
| 237 source = file.createSource(restoredUri); |
| 238 } |
| 239 ChangeSet changeSet = new ChangeSet(); |
| 240 changeSet.addedSource(source); |
| 241 context.applyChanges(changeSet); |
| 242 return source; |
| 243 } |
| 244 |
| 245 /** |
| 246 * Find the result list getter having name [resultListGetterName] in the |
| 247 * [EnginePlugin] class, and use the [ExtensionPointId] annotation on that |
| 248 * getter to find the associated [TopLevelVariableElement] which can be used |
| 249 * to register extensions for that getter. |
| 250 */ |
| 251 TopLevelVariableElement _getExtensionId(String resultListGetterName) { |
| 252 PropertyAccessorElement getter = |
| 253 enginePluginClass.getGetter(resultListGetterName); |
| 254 for (ElementAnnotation annotation in getter.metadata) { |
| 255 DartObjectImpl annotationValue = annotation.constantValue; |
| 256 if (annotationValue.type.isSubtypeOf(extensionPointIdType)) { |
| 257 String extensionPointId = |
| 258 annotationValue.fields['extensionPointId'].toStringValue(); |
| 259 for (TopLevelVariableElement variable |
| 260 in taskUnitElement.topLevelVariables) { |
| 261 if (variable.name == extensionPointId) { |
| 262 return variable; |
| 263 } |
| 264 } |
| 265 } |
| 266 } |
| 267 throw new Exception( |
| 268 'Could not find extension ID corresponding to $resultListGetterName'); |
| 269 } |
| 270 |
| 271 /** |
| 272 * Find the root directory of the analyzer package by proceeding |
| 273 * upward to the 'tool' dir, and then going up one more directory. |
| 274 */ |
| 275 static String findRoot(String pathname) { |
| 276 while (path.basename(pathname) != 'tool') { |
| 277 String parent = path.dirname(pathname); |
| 278 if (parent.length >= pathname.length) { |
| 279 throw new Exception("Can't find root directory"); |
| 280 } |
| 281 pathname = parent; |
| 282 } |
| 283 return path.dirname(pathname); |
| 284 } |
| 285 } |
| 286 |
| 287 /** |
| 288 * Visitor that finds calls that register extension points. Specifically, we |
| 289 * look for calls of the form `method(extensionIdVariable, resultDescriptor)`, |
| 290 * where `resultDescriptor` has type [resultDescriptorType], and we pass the |
| 291 * corresponding result descriptor names to [callback]. |
| 292 */ |
| 293 class ExtensionFinder extends GeneralizingAstVisitor { |
| 294 final InterfaceType resultDescriptorType; |
| 295 final TopLevelVariableElement extensionIdVariable; |
| 296 final GetterFinderCallback callback; |
| 297 |
| 298 ExtensionFinder( |
| 299 this.resultDescriptorType, this.extensionIdVariable, this.callback); |
| 300 |
| 301 @override |
| 302 visitIdentifier(Identifier node) { |
| 303 Element element = node.staticElement; |
| 304 if (element is PropertyAccessorElement && |
| 305 element.isGetter && |
| 306 element.variable == extensionIdVariable) { |
| 307 AstNode parent = node.parent; |
| 308 if (parent is ArgumentList && |
| 309 parent.arguments.length == 2 && |
| 310 parent.arguments[0] == node) { |
| 311 Expression extension = parent.arguments[1]; |
| 312 if (extension is Identifier) { |
| 313 Element element = extension.staticElement; |
| 314 if (element is PropertyAccessorElement && |
| 315 element.isGetter && |
| 316 element.returnType.isSubtypeOf(resultDescriptorType)) { |
| 317 callback(element); |
| 318 return; |
| 319 } |
| 320 } |
| 321 } |
| 322 throw new Exception('Could not decode extension setup: $parent'); |
| 323 } |
| 324 } |
| 325 } |
| 326 |
| 327 /** |
| 328 * Visitor that finds references to getters having a specific type (or a |
| 329 * subtype of that type) |
| 330 */ |
| 331 class GetterFinder extends GeneralizingAstVisitor { |
| 332 final InterfaceType type; |
| 333 final GetterFinderCallback callback; |
| 334 |
| 335 GetterFinder(this.type, this.callback); |
| 336 |
| 337 @override |
| 338 visitIdentifier(Identifier node) { |
| 339 Element element = node.staticElement; |
| 340 if (element is PropertyAccessorElement && |
| 341 element.isGetter && |
| 342 element.returnType.isSubtypeOf(type)) { |
| 343 callback(element); |
| 344 } |
| 345 } |
| 346 } |
OLD | NEW |