| 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 |