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 * The ".dot" file is output to standard out. To convert it to a pdf, store it | |
11 * in a file (e.g. "tasks.dot"), and post-process it with | |
12 * "dot tasks.dot -Tpdf -O". | |
13 * | |
14 * TODO(paulberry): | |
15 * - Add general.dart and html.dart for completeness. | |
16 * - Use Graphviz's "record" feature to produce more compact output | |
17 * (http://www.graphviz.org/content/node-shapes#record) | |
18 * - Produce a warning if a result descriptor is found which isn't the output | |
19 * of exactly one task. | |
20 * - Convert this tool to use package_config to find the package map. | |
21 */ | |
22 library task_dependency_graph; | |
23 | |
24 import 'dart:io' hide File; | |
25 | |
26 import 'package:analyzer/analyzer.dart'; | |
27 import 'package:analyzer/file_system/file_system.dart'; | |
28 import 'package:analyzer/file_system/physical_file_system.dart'; | |
29 import 'package:analyzer/src/generated/constant.dart'; | |
30 import 'package:analyzer/src/generated/element.dart'; | |
31 import 'package:analyzer/src/generated/engine.dart'; | |
32 import 'package:analyzer/src/generated/java_io.dart'; | |
33 import 'package:analyzer/src/generated/sdk.dart'; | |
34 import 'package:analyzer/src/generated/sdk_io.dart'; | |
35 import 'package:analyzer/src/generated/source.dart'; | |
36 import 'package:analyzer/src/generated/source_io.dart'; | |
37 import 'package:path/path.dart' as path; | |
38 | |
39 main() { | |
40 new Driver().run(); | |
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 String rootDir; | |
54 | |
55 /** | |
56 * Starting at [node], find all calls to registerExtension() which refer to | |
57 * the given [extensionIdVariable], and execute [callback] for the associated | |
58 * result descriptors. | |
59 */ | |
60 void findExtensions(AstNode node, TopLevelVariableElement extensionIdVariable, | |
61 void callback(descriptorName)) { | |
62 Set<PropertyAccessorElement> resultDescriptors = | |
63 new Set<PropertyAccessorElement>(); | |
64 node.accept(new ExtensionFinder( | |
65 resultDescriptorType, extensionIdVariable, resultDescriptors.add)); | |
66 for (PropertyAccessorElement resultDescriptor in resultDescriptors) { | |
67 callback(resultDescriptor.name); | |
68 } | |
69 } | |
70 | |
71 /** | |
72 * Starting at [node], find all references to a getter of type | |
73 * `List<ResultDescriptor>`, and execute [callback] on the getter names. | |
74 */ | |
75 void findResultDescriptorLists( | |
76 AstNode node, void callback(String descriptorListName)) { | |
77 Set<PropertyAccessorElement> resultDescriptorLists = | |
78 new Set<PropertyAccessorElement>(); | |
79 node.accept(new GetterFinder( | |
80 listOfResultDescriptorType, resultDescriptorLists.add)); | |
81 for (PropertyAccessorElement resultDescriptorList | |
82 in resultDescriptorLists) { | |
83 // We only care about result descriptor lists associated with getters in | |
84 // the engine plugin class. | |
85 if (resultDescriptorList.enclosingElement != enginePluginClass) { | |
86 continue; | |
87 } | |
88 callback(resultDescriptorList.name); | |
89 } | |
90 } | |
91 | |
92 void findResultDescriptors( | |
93 AstNode node, void callback(String descriptorName)) { | |
94 Set<PropertyAccessorElement> resultDescriptors = | |
95 new Set<PropertyAccessorElement>(); | |
96 node.accept(new GetterFinder(resultDescriptorType, resultDescriptors.add)); | |
97 for (PropertyAccessorElement resultDescriptor in resultDescriptors) { | |
98 callback(resultDescriptor.name); | |
99 } | |
100 } | |
101 | |
102 /** | |
103 * Find the root directory of the analyzer package by proceeding | |
104 * upward to the 'tool' dir, and then going up one more directory. | |
105 */ | |
106 String findRoot(String pathname) { | |
107 while (path.basename(pathname) != 'tool') { | |
108 String parent = path.dirname(pathname); | |
109 if (parent.length >= pathname.length) { | |
110 throw new Exception("Can't find root directory"); | |
111 } | |
112 pathname = parent; | |
113 } | |
114 return path.dirname(pathname); | |
115 } | |
116 | |
117 CompilationUnit getUnit(Source source) => | |
118 context.resolveCompilationUnit2(source, source); | |
119 | |
120 void run() { | |
121 rootDir = findRoot(Platform.script.toFilePath(windows: Platform.isWindows)); | |
122 resourceProvider = PhysicalResourceProvider.INSTANCE; | |
123 DartSdk sdk = DirectoryBasedDartSdk.defaultSdk; | |
124 context = AnalysisEngine.instance.createAnalysisContext(); | |
125 JavaFile packagesDir = new JavaFile(path.join(rootDir, 'packages')); | |
126 List<UriResolver> uriResolvers = [ | |
127 new DartUriResolver(sdk), | |
128 new PackageUriResolver(<JavaFile>[packagesDir]), | |
129 new FileUriResolver() | |
130 ]; | |
131 context.sourceFactory = new SourceFactory(uriResolvers); | |
132 Source dartDartSource = | |
133 setupSource(path.join('lib', 'src', 'task', 'dart.dart')); | |
134 Source taskSource = setupSource(path.join('lib', 'plugin', 'task.dart')); | |
135 Source modelSource = setupSource(path.join('lib', 'task', 'model.dart')); | |
136 Source enginePluginSource = | |
137 setupSource(path.join('lib', 'src', 'plugin', 'engine_plugin.dart')); | |
138 CompilationUnitElement modelElement = getUnit(modelSource).element; | |
139 InterfaceType analysisTaskType = modelElement.getType('AnalysisTask').type; | |
140 DartType dynamicType = context.typeProvider.dynamicType; | |
141 resultDescriptorType = modelElement | |
142 .getType('ResultDescriptor') | |
143 .type | |
144 .substitute4([dynamicType]); | |
145 listOfResultDescriptorType = | |
146 context.typeProvider.listType.substitute4([resultDescriptorType]); | |
147 CompilationUnitElement enginePluginUnitElement = | |
148 getUnit(enginePluginSource).element; | |
149 enginePluginClass = enginePluginUnitElement.getType('EnginePlugin'); | |
150 extensionPointIdType = | |
151 enginePluginUnitElement.getType('ExtensionPointId').type; | |
152 CompilationUnit dartDartUnit = getUnit(dartDartSource); | |
153 CompilationUnitElement dartDartUnitElement = dartDartUnit.element; | |
154 CompilationUnit taskUnit = getUnit(taskSource); | |
155 taskUnitElement = taskUnit.element; | |
156 print('digraph G {'); | |
157 Set<String> results = new Set<String>(); | |
158 Set<String> resultLists = new Set<String>(); | |
159 for (ClassElement cls in dartDartUnitElement.types) { | |
160 if (!cls.isAbstract && cls.type.isSubtypeOf(analysisTaskType)) { | |
161 String task = cls.name; | |
162 // TODO(paulberry): node is deprecated. What am I supposed to do | |
163 // instead? | |
164 AstNode buildInputsAst = cls.getMethod('buildInputs').node; | |
165 findResultDescriptors(buildInputsAst, (String input) { | |
166 results.add(input); | |
167 print(' $input -> $task'); | |
168 }); | |
169 findResultDescriptorLists(buildInputsAst, (String input) { | |
170 resultLists.add(input); | |
171 print(' $input -> $task'); | |
172 }); | |
173 findResultDescriptors(cls.getField('DESCRIPTOR').node, (String output) { | |
174 results.add(output); | |
175 print(' $task -> $output'); | |
176 }); | |
177 } | |
178 } | |
179 AstNode enginePluginAst = enginePluginUnitElement.node; | |
180 for (String resultList in resultLists) { | |
181 print(' $resultList [shape=hexagon]'); | |
182 TopLevelVariableElement extensionIdVariable = _getExtensionId(resultList); | |
183 findExtensions(enginePluginAst, extensionIdVariable, (String extension) { | |
184 results.add(extension); | |
185 print(' $extension -> $resultList'); | |
186 }); | |
187 } | |
188 for (String result in results) { | |
189 print(' $result [shape=box]'); | |
190 } | |
191 print('}'); | |
192 } | |
193 | |
194 Source setupSource(String filename) { | |
195 String filePath = path.join(rootDir, filename); | |
196 File file = resourceProvider.getResource(filePath); | |
197 Source source = file.createSource(); | |
198 Uri restoredUri = context.sourceFactory.restoreUri(source); | |
199 if (restoredUri != null) { | |
200 source = file.createSource(restoredUri); | |
201 } | |
202 ChangeSet changeSet = new ChangeSet(); | |
203 changeSet.addedSource(source); | |
204 context.applyChanges(changeSet); | |
205 return source; | |
206 } | |
207 | |
208 /** | |
209 * Find the result list getter having name [resultListGetterName] in the | |
210 * [EnginePlugin] class, and use the [ExtensionPointId] annotation on that | |
211 * getter to find the associated [TopLevelVariableElement] which can be used | |
212 * to register extensions for that getter. | |
213 */ | |
214 TopLevelVariableElement _getExtensionId(String resultListGetterName) { | |
215 PropertyAccessorElement getter = | |
216 enginePluginClass.getGetter(resultListGetterName); | |
217 for (ElementAnnotation annotation in getter.metadata) { | |
218 // TODO(paulberry): we should be using part of the public API rather than | |
219 // just casting to ElementAnnotationImpl. | |
220 ElementAnnotationImpl annotationImpl = annotation; | |
221 DartObjectImpl annotationValue = annotationImpl.evaluationResult.value; | |
222 if (annotationValue.type.isSubtypeOf(extensionPointIdType)) { | |
223 String extensionPointId = | |
224 annotationValue.fields['extensionPointId'].value; | |
225 for (TopLevelVariableElement variable | |
226 in taskUnitElement.topLevelVariables) { | |
227 if (variable.name == extensionPointId) { | |
228 return variable; | |
229 } | |
230 } | |
231 } | |
232 } | |
233 throw new Exception( | |
234 'Could not find extension ID corresponding to $resultListGetterName'); | |
235 } | |
236 } | |
237 | |
238 /** | |
239 * Visitor that finds calls that register extension points. Specifically, we | |
240 * look for calls of the form `method(extensionIdVariable, resultDescriptor)`, | |
241 * where `resultDescriptor` has type [resultDescriptorType], and we pass the | |
242 * corresponding result descriptor names to [callback]. | |
243 */ | |
244 class ExtensionFinder extends GeneralizingAstVisitor { | |
245 final InterfaceType resultDescriptorType; | |
246 final TopLevelVariableElement extensionIdVariable; | |
247 final GetterFinderCallback callback; | |
248 | |
249 ExtensionFinder( | |
250 this.resultDescriptorType, this.extensionIdVariable, this.callback); | |
251 | |
252 @override | |
253 visitIdentifier(Identifier node) { | |
254 Element element = node.staticElement; | |
255 if (element is PropertyAccessorElement && | |
256 element.isGetter && | |
257 element.variable == extensionIdVariable) { | |
258 AstNode parent = node.parent; | |
259 if (parent is ArgumentList && | |
260 parent.arguments.length == 2 && | |
261 parent.arguments[0] == node) { | |
262 Expression extension = parent.arguments[1]; | |
263 if (extension is Identifier) { | |
264 Element element = extension.staticElement; | |
265 if (element is PropertyAccessorElement && | |
266 element.isGetter && | |
267 element.returnType.isSubtypeOf(resultDescriptorType)) { | |
268 callback(element); | |
269 return; | |
270 } | |
271 } | |
272 } | |
273 throw new Exception('Could not decode extension setup: $parent'); | |
274 } | |
275 } | |
276 } | |
277 | |
278 /** | |
279 * Visitor that finds references to getters having a specific type (or a | |
280 * subtype of that type) | |
281 */ | |
282 class GetterFinder extends GeneralizingAstVisitor { | |
283 final InterfaceType type; | |
284 final GetterFinderCallback callback; | |
285 | |
286 GetterFinder(this.type, this.callback); | |
287 | |
288 @override | |
289 visitIdentifier(Identifier node) { | |
290 Element element = node.staticElement; | |
291 if (element is PropertyAccessorElement && | |
292 element.isGetter && | |
293 element.returnType.isSubtypeOf(type)) { | |
294 callback(element); | |
295 } | |
296 } | |
297 } | |
OLD | NEW |