OLD | NEW |
| (Empty) |
1 // Copyright (c) 2016, 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 library runtime.tools.kernel_service; | |
5 | |
6 // This is an interface to the Dart Kernel parser and Kernel binary generator. | |
7 // | |
8 // It is used by the kernel-isolate to load Dart source code and generate | |
9 // Kernel binary format. | |
10 // | |
11 // This is either invoked as the root script of the Kernel isolate when used | |
12 // as a part of | |
13 // | |
14 // dart --dfe=runtime/tools/kernel-service.dart ... | |
15 // | |
16 // invocation or it is invoked as a standalone script to perform batch mode | |
17 // compilation requested via an HTTP interface | |
18 // | |
19 // dart runtime/tools/kernel-service.dart --batch | |
20 // | |
21 // The port for the batch mode worker is controlled by DFE_WORKER_PORT | |
22 // environment declarations (set by -DDFE_WORKER_PORT=... command line flag). | |
23 // When not set (or set to 0) an ephemeral port returned by the OS is used | |
24 // instead. | |
25 // | |
26 // When this script is used as a Kernel isolate root script and DFE_WORKER_PORT | |
27 // is set to non-zero value then Kernel isolate will forward all compilation | |
28 // requests it receives to the batch worker on the given port. | |
29 // | |
30 | |
31 import 'dart:async'; | |
32 import 'dart:convert'; | |
33 import 'dart:io'; | |
34 import 'dart:isolate'; | |
35 import 'dart:typed_data'; | |
36 | |
37 import 'package:kernel/analyzer/loader.dart'; | |
38 import 'package:kernel/binary/ast_to_binary.dart'; | |
39 import 'package:kernel/kernel.dart'; | |
40 import 'package:kernel/target/targets.dart'; | |
41 | |
42 const bool verbose = const bool.fromEnvironment('DFE_VERBOSE') ?? false; | |
43 const int workerPort = const int.fromEnvironment('DFE_WORKER_PORT') ?? 0; | |
44 | |
45 class DataSink implements Sink<List<int>> { | |
46 final BytesBuilder builder = new BytesBuilder(); | |
47 | |
48 void add(List<int> data) { | |
49 builder.add(data); | |
50 } | |
51 | |
52 void close() { | |
53 // Nothing to do. | |
54 } | |
55 } | |
56 | |
57 // Note: these values must match Dart_KernelCompilationStatus in dart_api.h. | |
58 const int STATUS_OK = 0; // Compilation was successful. | |
59 const int STATUS_ERROR = 1; // Compilation failed with a compile time error. | |
60 const int STATUS_CRASH = 2; // Compiler crashed. | |
61 | |
62 abstract class CompilationResult { | |
63 List toResponse(); | |
64 } | |
65 | |
66 class CompilationOk extends CompilationResult { | |
67 final Uint8List binary; | |
68 | |
69 CompilationOk(this.binary); | |
70 | |
71 List toResponse() => [STATUS_OK, binary]; | |
72 | |
73 String toString() => "CompilationOk(${binary.length} bytes)"; | |
74 } | |
75 | |
76 abstract class CompilationFail extends CompilationResult { | |
77 String get errorString; | |
78 | |
79 Map<String, dynamic> toJson(); | |
80 | |
81 static CompilationFail fromJson(Map m) { | |
82 switch (m['status']) { | |
83 case STATUS_ERROR: | |
84 return new CompilationError(m['errors']); | |
85 case STATUS_CRASH: | |
86 return new CompilationCrash(m['exception'], m['stack']); | |
87 } | |
88 } | |
89 } | |
90 | |
91 class CompilationError extends CompilationFail { | |
92 final List<String> errors; | |
93 | |
94 CompilationError(this.errors); | |
95 | |
96 List toResponse() => [STATUS_ERROR, errorString]; | |
97 | |
98 Map<String, dynamic> toJson() => { | |
99 "status": STATUS_ERROR, | |
100 "errors": errors, | |
101 }; | |
102 | |
103 String get errorString => errors.take(10).join('\n'); | |
104 | |
105 String toString() => "CompilationError(${errorString})"; | |
106 } | |
107 | |
108 class CompilationCrash extends CompilationFail { | |
109 final String exception; | |
110 final String stack; | |
111 | |
112 CompilationCrash(this.exception, this.stack); | |
113 | |
114 List toResponse() => [STATUS_CRASH, errorString]; | |
115 | |
116 Map<String, dynamic> toJson() => { | |
117 "status": STATUS_CRASH, | |
118 "exception": exception, | |
119 "stack": stack, | |
120 }; | |
121 | |
122 String get errorString => "${exception}\n${stack}"; | |
123 | |
124 String toString() => "CompilationCrash(${errorString})"; | |
125 } | |
126 | |
127 Future<CompilationResult> parseScriptImpl(DartLoaderBatch batch_loader, | |
128 Uri fileName, String packageConfig, String sdkPath) async { | |
129 if (!FileSystemEntity.isFileSync(fileName.path)) { | |
130 throw "Input file '${fileName.path}' does not exist."; | |
131 } | |
132 | |
133 if (!FileSystemEntity.isDirectorySync(sdkPath)) { | |
134 throw "Patched sdk directory not found at $sdkPath"; | |
135 } | |
136 | |
137 Target target = getTarget("vm", new TargetFlags(strongMode: false)); | |
138 DartOptions dartOptions = new DartOptions( | |
139 strongMode: false, | |
140 strongModeSdk: false, | |
141 sdk: sdkPath, | |
142 packagePath: packageConfig, | |
143 customUriMappings: const {}, | |
144 declaredVariables: const {}); | |
145 DartLoader loader = | |
146 await batch_loader.getLoader(new Repository(), dartOptions); | |
147 var program = loader.loadProgram(fileName, target: target); | |
148 | |
149 var errors = loader.errors; | |
150 if (errors.isNotEmpty) { | |
151 return new CompilationError(loader.errors.toList()); | |
152 } | |
153 | |
154 // Link program into one file, cf. --link option in dartk. | |
155 target.transformProgram(program); | |
156 | |
157 // Write the program to a list of bytes and return it. | |
158 var sink = new DataSink(); | |
159 new BinaryPrinter(sink).writeProgramFile(program); | |
160 return new CompilationOk(sink.builder.takeBytes()); | |
161 } | |
162 | |
163 Future<CompilationResult> parseScript(DartLoaderBatch loader, Uri fileName, | |
164 String packageConfig, String sdkPath) async { | |
165 try { | |
166 return await parseScriptImpl(loader, fileName, packageConfig, sdkPath); | |
167 } catch (err, stack) { | |
168 return new CompilationCrash(err.toString(), stack.toString()); | |
169 } | |
170 } | |
171 | |
172 Future _processLoadRequestImpl(String inputFileUrl) async { | |
173 Uri scriptUri = Uri.parse(inputFileUrl); | |
174 | |
175 // Because we serve both Loader and bootstrapping requests we need to | |
176 // duplicate the logic from _resolveScriptUri(...) here and attempt to | |
177 // resolve schemaless uris using current working directory. | |
178 if (scriptUri.scheme == '') { | |
179 // Script does not have a scheme, assume that it is a path, | |
180 // resolve it against the working directory. | |
181 scriptUri = Directory.current.uri.resolveUri(scriptUri); | |
182 } | |
183 | |
184 if (scriptUri.scheme != 'file') { | |
185 // TODO: reuse loader code to support other schemes. | |
186 throw "Expected 'file' scheme for a script uri: got ${scriptUri.scheme}"; | |
187 } | |
188 | |
189 final Uri packagesUri = (Platform.packageConfig != null) | |
190 ? Uri.parse(Platform.packageConfig) | |
191 : await _findPackagesFile(scriptUri); | |
192 if (packagesUri == null) { | |
193 throw "Could not find .packages"; | |
194 } | |
195 | |
196 final Uri patchedSdk = | |
197 Uri.parse(Platform.resolvedExecutable).resolve("patched_sdk"); | |
198 | |
199 if (verbose) { | |
200 print("""DFE: Requesting compilation { | |
201 scriptUri: ${scriptUri} | |
202 packagesUri: ${packagesUri} | |
203 patchedSdk: ${patchedSdk} | |
204 }"""); | |
205 } | |
206 | |
207 if (workerPort != 0) { | |
208 return await requestParse(scriptUri, packagesUri, patchedSdk); | |
209 } else { | |
210 return await parseScript( | |
211 new DartLoaderBatch(), scriptUri, packagesUri.path, patchedSdk.path); | |
212 } | |
213 } | |
214 | |
215 | |
216 // Process a request from the runtime. See KernelIsolate::CompileToKernel in | |
217 // kernel_isolate.cc and Loader::SendKernelRequest in loader.cc. | |
218 Future _processLoadRequest(request) async { | |
219 if (verbose) { | |
220 print("DFE: request: $request"); | |
221 print("DFE: Platform.packageConfig: ${Platform.packageConfig}"); | |
222 print("DFE: Platform.resolvedExecutable: ${Platform.resolvedExecutable}"); | |
223 } | |
224 | |
225 final int tag = request[0]; | |
226 final SendPort port = request[1]; | |
227 final String inputFileUrl = request[2]; | |
228 | |
229 var result; | |
230 try { | |
231 result = await _processLoadRequestImpl(inputFileUrl); | |
232 } catch (error, stack) { | |
233 result = new CompilationCrash(error.toString(), stack.toString()); | |
234 } | |
235 | |
236 if (verbose) { | |
237 print("DFE:> ${result}"); | |
238 } | |
239 | |
240 // Check whether this is a Loader request or a bootstrapping request from | |
241 // KernelIsolate::CompileToKernel. | |
242 final isBootstrapRequest = tag == null; | |
243 if (isBootstrapRequest) { | |
244 port.send(result.toResponse()); | |
245 } else { | |
246 // See loader.cc for the code that handles these replies. | |
247 if (result is CompilationOk) { | |
248 port.send([tag, inputFileUrl, inputFileUrl, null, result]); | |
249 } else { | |
250 port.send([-tag, inputFileUrl, inputFileUrl, null, result.errorString]); | |
251 } | |
252 } | |
253 } | |
254 | |
255 Future<CompilationResult> requestParse( | |
256 Uri scriptUri, Uri packagesUri, Uri patchedSdk) async { | |
257 if (verbose) { | |
258 print( | |
259 "DFE: forwarding request to worker at http://localhost:${workerPort}/"); | |
260 } | |
261 | |
262 HttpClient client = new HttpClient(); | |
263 final rq = await client | |
264 .postUrl(new Uri(host: 'localhost', port: workerPort, scheme: 'http')); | |
265 rq.headers.contentType = ContentType.JSON; | |
266 rq.write(JSON.encode({ | |
267 "inputFileUrl": scriptUri.toString(), | |
268 "packagesUri": packagesUri.toString(), | |
269 "patchedSdk": patchedSdk.toString(), | |
270 })); | |
271 final rs = await rq.close(); | |
272 try { | |
273 if (rs.statusCode == HttpStatus.OK) { | |
274 final BytesBuilder bb = new BytesBuilder(); | |
275 await rs.forEach(bb.add); | |
276 return new CompilationOk(bb.takeBytes()); | |
277 } else { | |
278 return CompilationFail.fromJson(JSON.decode(await UTF8.decodeStream(rs))); | |
279 } | |
280 } finally { | |
281 await client.close(); | |
282 } | |
283 } | |
284 | |
285 void startBatchServer() { | |
286 final loader = new DartLoaderBatch(); | |
287 HttpServer.bind(InternetAddress.LOOPBACK_IP_V6, workerPort).then((server) { | |
288 print('READY ${server.port}'); | |
289 server.listen((HttpRequest request) async { | |
290 final rq = JSON.decode(await UTF8.decodeStream(request)); | |
291 | |
292 final Uri scriptUri = Uri.parse(rq['inputFileUrl']); | |
293 final Uri packagesUri = Uri.parse(rq['packagesUri']); | |
294 final Uri patchedSdk = Uri.parse(rq['patchedSdk']); | |
295 | |
296 final CompilationResult result = await parseScript( | |
297 loader, scriptUri, packagesUri.path, patchedSdk.path); | |
298 | |
299 if (result is CompilationOk) { | |
300 request.response.statusCode = HttpStatus.OK; | |
301 request.response.headers.contentType = ContentType.BINARY; | |
302 request.response.add(result.binary); | |
303 request.response.close(); | |
304 } else { | |
305 request.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR; | |
306 request.response.headers.contentType = ContentType.TEXT; | |
307 request.response.write(JSON.encode(result.toJson())); | |
308 request.response.close(); | |
309 } | |
310 }); | |
311 ProcessSignal.SIGTERM.watch().first.then((_) => server.close()); | |
312 }); | |
313 } | |
314 | |
315 main([args]) { | |
316 if (args?.length == 1 && args[0] == '--batch') { | |
317 startBatchServer(); | |
318 } else { | |
319 // Entry point for the Kernel isolate. | |
320 return new RawReceivePort()..handler = _processLoadRequest; | |
321 } | |
322 } | |
323 | |
324 // This duplicates functionality from the Loader which we can't easily | |
325 // access from here. | |
326 Uri _findPackagesFile(Uri base) async { | |
327 var dir = new File.fromUri(base).parent; | |
328 while (true) { | |
329 final packagesFile = dir.uri.resolve(".packages"); | |
330 if (await new File.fromUri(packagesFile).exists()) { | |
331 return packagesFile; | |
332 } | |
333 if (dir.parent.path == dir.path) { | |
334 break; | |
335 } | |
336 dir = dir.parent; | |
337 } | |
338 return null; | |
339 } | |
OLD | NEW |