OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, 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 library source.pub_package_map_provider; | |
6 | |
7 import 'dart:collection'; | |
8 import 'dart:convert'; | |
9 import 'dart:core' hide Resource; | |
10 import 'dart:io' as io; | |
11 | |
12 import 'package:analyzer/file_system/file_system.dart'; | |
13 import 'package:analyzer/source/package_map_provider.dart'; | |
14 import 'package:analyzer/src/generated/engine.dart'; | |
15 import 'package:analyzer/src/generated/sdk_io.dart'; | |
16 | |
17 /** | |
18 * The function used to run pub list. | |
19 */ | |
20 typedef io.ProcessResult RunPubList(Folder folder); | |
21 | |
22 /** | |
23 * Implementation of PackageMapProvider that operates by executing pub. | |
24 */ | |
25 class PubPackageMapProvider implements PackageMapProvider { | |
26 static const String PUB_LIST_COMMAND = 'list-package-dirs'; | |
27 | |
28 /** | |
29 * The name of the 'pubspec.lock' file, which we assume is the dependency | |
30 * in the event that [PUB_LIST_COMMAND] fails. | |
31 */ | |
32 static const String PUBSPEC_LOCK_NAME = 'pubspec.lock'; | |
33 | |
34 /** | |
35 * [ResourceProvider] that is used to create the [Folder]s that populate the | |
36 * package map. | |
37 */ | |
38 final ResourceProvider resourceProvider; | |
39 | |
40 /** | |
41 * Sdk that we use to find the pub executable. | |
42 */ | |
43 final DirectoryBasedDartSdk sdk; | |
44 | |
45 /** | |
46 * The function used to run pub list. | |
47 */ | |
48 RunPubList _runPubList; | |
49 | |
50 /** | |
51 * Construct a new instance. | |
52 * A [RunPubList] implementation may be injected for testing | |
53 */ | |
54 PubPackageMapProvider(this.resourceProvider, this.sdk, [this._runPubList]) { | |
55 if (_runPubList == null) { | |
56 _runPubList = _runPubListDefault; | |
57 } | |
58 } | |
59 | |
60 @override | |
61 PackageMapInfo computePackageMap(Folder folder) { | |
62 // TODO(paulberry) make this asynchronous so that we can (a) do other | |
63 // analysis while it's in progress, and (b) time out if it takes too long | |
64 // to respond. | |
65 io.ProcessResult result; | |
66 try { | |
67 result = _runPubList(folder); | |
68 } on io.ProcessException catch (exception, stackTrace) { | |
69 AnalysisEngine.instance.logger.logInformation( | |
70 "Error running pub $PUB_LIST_COMMAND\n$exception\n$stackTrace"); | |
71 } | |
72 if (result == null || result.exitCode != 0) { | |
73 String exitCode = | |
74 result != null ? 'exit code ${result.exitCode}' : 'null'; | |
75 AnalysisEngine.instance.logger | |
76 .logInformation("pub $PUB_LIST_COMMAND failed: $exitCode"); | |
77 return computePackageMapError(folder); | |
78 } | |
79 try { | |
80 PackageMapInfo packageMap = | |
81 parsePackageMap(JSON.decode(result.stdout), folder); | |
82 return packageMap; | |
83 } catch (exception, stackTrace) { | |
84 AnalysisEngine.instance.logger.logError( | |
85 "Malformed output from pub $PUB_LIST_COMMAND\n$exception\n$stackTrace"
); | |
86 } | |
87 | |
88 return computePackageMapError(folder); | |
89 } | |
90 | |
91 /** | |
92 * Create a PackageMapInfo object representing an error condition. | |
93 */ | |
94 PackageMapInfo computePackageMapError(Folder folder) { | |
95 // Even if an error occurs, we still need to know the dependencies, so that | |
96 // we'll know when to try running "pub list-package-dirs" again. | |
97 // Unfortunately, "pub list-package-dirs" doesn't tell us dependencies when | |
98 // an error occurs, so just assume there is one dependency, "pubspec.lock". | |
99 List<String> dependencies = <String>[ | |
100 resourceProvider.pathContext.join(folder.path, PUBSPEC_LOCK_NAME) | |
101 ]; | |
102 return new PackageMapInfo(null, dependencies.toSet()); | |
103 } | |
104 | |
105 /** | |
106 * Decode the JSON output from pub into a package map. Paths in the | |
107 * output are considered relative to [folder]. | |
108 */ | |
109 PackageMapInfo parsePackageMap(Map obj, Folder folder) { | |
110 // The output of pub looks like this: | |
111 // { | |
112 // "packages": { | |
113 // "foo": "path/to/foo", | |
114 // "bar": ["path/to/bar1", "path/to/bar2"], | |
115 // "myapp": "path/to/myapp", // self link is included | |
116 // }, | |
117 // "input_files": [ | |
118 // "path/to/myapp/pubspec.lock" | |
119 // ] | |
120 // } | |
121 Map<String, List<Folder>> packageMap = new HashMap<String, List<Folder>>(); | |
122 Map packages = obj['packages']; | |
123 processPaths(String packageName, List paths) { | |
124 List<Folder> folders = <Folder>[]; | |
125 for (var path in paths) { | |
126 if (path is String) { | |
127 Resource resource = folder.getChildAssumingFolder(path); | |
128 if (resource is Folder) { | |
129 folders.add(resource); | |
130 } | |
131 } | |
132 } | |
133 if (folders.isNotEmpty) { | |
134 packageMap[packageName] = folders; | |
135 } | |
136 } | |
137 packages.forEach((key, value) { | |
138 if (value is String) { | |
139 processPaths(key, [value]); | |
140 } else if (value is List) { | |
141 processPaths(key, value); | |
142 } | |
143 }); | |
144 Set<String> dependencies = new Set<String>(); | |
145 List inputFiles = obj['input_files']; | |
146 if (inputFiles != null) { | |
147 for (var path in inputFiles) { | |
148 if (path is String) { | |
149 dependencies.add(folder.canonicalizePath(path)); | |
150 } | |
151 } | |
152 } | |
153 return new PackageMapInfo(packageMap, dependencies); | |
154 } | |
155 | |
156 /** | |
157 * Run pub list to determine the packages and input files. | |
158 */ | |
159 io.ProcessResult _runPubListDefault(Folder folder) { | |
160 String executablePath = sdk.pubExecutable.getAbsolutePath(); | |
161 List<String> arguments = [PUB_LIST_COMMAND]; | |
162 String workingDirectory = folder.path; | |
163 int subprocessId = AnalysisEngine.instance.instrumentationService | |
164 .logSubprocessStart(executablePath, arguments, workingDirectory); | |
165 io.ProcessResult result = io.Process.runSync(executablePath, arguments, | |
166 workingDirectory: workingDirectory); | |
167 AnalysisEngine.instance.instrumentationService.logSubprocessResult( | |
168 subprocessId, result.exitCode, result.stdout, result.stderr); | |
169 return result; | |
170 } | |
171 } | |
OLD | NEW |