| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| library frontend_server; |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io' hide FileSystemEntity; |
| |
| import 'package:args/args.dart'; |
| // front_end/src imports below that require lint `ignore_for_file` |
| // are a temporary state of things until frontend team builds better api |
| // that would replace api used below. This api was made private in |
| // an effort to discourage further use. |
| // ignore_for_file: implementation_imports |
| import 'package:front_end/src/api_prototype/compiler_options.dart' |
| show CompilerOptions, parseExperimentalFlags; |
| import 'package:front_end/src/api_unstable/vm.dart'; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/binary/ast_to_binary.dart'; |
| import 'package:kernel/binary/limited_ast_to_binary.dart'; |
| import 'package:kernel/kernel.dart' |
| show Component, loadComponentSourceFromBytes; |
| import 'package:path/path.dart' as path; |
| import 'package:usage/uuid/uuid.dart'; |
| |
| import 'package:vm/incremental_compiler.dart' show IncrementalCompiler; |
| import 'package:vm/kernel_front_end.dart' |
| show |
| asFileUri, |
| compileToKernel, |
| parseCommandLineDefines, |
| convertFileOrUriArgumentToUri, |
| createFrontEndTarget, |
| createFrontEndFileSystem, |
| setVMEnvironmentDefines, |
| writeDepfile; |
| |
| ArgParser argParser = new ArgParser(allowTrailingOptions: true) |
| ..addFlag('train', |
| help: 'Run through sample command line to produce snapshot', |
| negatable: false) |
| ..addFlag('incremental', |
| help: 'Run compiler in incremental mode', defaultsTo: false) |
| ..addOption('sdk-root', |
| help: 'Path to sdk root', |
| defaultsTo: '../../out/android_debug/flutter_patched_sdk') |
| ..addOption('platform', help: 'Platform kernel filename') |
| ..addFlag('aot', |
| help: 'Run compiler in AOT mode (enables whole-program transformations)', |
| defaultsTo: false) |
| // TODO(alexmarkov): Cleanup uses in Flutter and remove these obsolete flags. |
| ..addFlag('strong', help: 'Obsolete', defaultsTo: true) |
| ..addFlag('tfa', |
| help: |
| 'Enable global type flow analysis and related transformations in AOT mode.', |
| defaultsTo: false) |
| ..addFlag('protobuf-tree-shaker', |
| help: 'Enable protobuf tree shaker transformation in AOT mode.', |
| defaultsTo: false) |
| ..addFlag('link-platform', |
| help: |
| 'When in batch mode, link platform kernel file into result kernel file.' |
| ' Intended use is to satisfy different loading strategies implemented' |
| ' by gen_snapshot(which needs platform embedded) vs' |
| ' Flutter engine(which does not)', |
| defaultsTo: true) |
| ..addOption('import-dill', |
| help: 'Import libraries from existing dill file', defaultsTo: null) |
| ..addOption('output-dill', |
| help: 'Output path for the generated dill', defaultsTo: null) |
| ..addOption('output-incremental-dill', |
| help: 'Output path for the generated incremental dill', defaultsTo: null) |
| ..addOption('depfile', |
| help: 'Path to output Ninja depfile. Only used in batch mode.') |
| ..addOption('packages', |
| help: '.packages file to use for compilation', defaultsTo: null) |
| ..addOption('target', |
| help: 'Target model that determines what core libraries are available', |
| allowed: <String>['vm', 'flutter', 'flutter_runner', 'dart_runner'], |
| defaultsTo: 'vm') |
| ..addMultiOption('filesystem-root', |
| help: 'File path that is used as a root in virtual filesystem used in' |
| ' compiled kernel files. When used --output-dill should be provided' |
| ' as well.', |
| hide: true) |
| ..addOption('filesystem-scheme', |
| help: |
| 'Scheme that is used in virtual filesystem set up via --filesystem-root' |
| ' option', |
| defaultsTo: 'org-dartlang-root', |
| hide: true) |
| ..addFlag('verbose', help: 'Enables verbose output from the compiler.') |
| ..addOption('initialize-from-dill', |
| help: 'Normally the output dill is used to specify which dill to ' |
| 'initialize from, but it can be overwritten here.', |
| defaultsTo: null, |
| hide: true) |
| ..addMultiOption('define', |
| abbr: 'D', |
| help: 'The values for the environment constants (e.g. -Dkey=value).') |
| ..addFlag('embed-source-text', |
| help: 'Includes sources into generated dill file. Having sources' |
| ' allows to effectively use observatory to debug produced' |
| ' application, produces better stack traces on exceptions.', |
| defaultsTo: true) |
| ..addFlag('unsafe-package-serialization', |
| help: 'Potentially unsafe: Does not allow for invalidating packages, ' |
| 'additionally the output dill file might include more libraries than ' |
| 'needed. The use case is test-runs, where invalidation is not really ' |
| 'used, and where dill filesize does not matter, and the gain is ' |
| 'improved speed.', |
| defaultsTo: false, |
| hide: true) |
| ..addFlag('track-widget-creation', |
| help: 'Run a kernel transformer to track creation locations for widgets.', |
| defaultsTo: false) |
| ..addMultiOption('enable-experiment', |
| help: 'Comma separated list of experimental features, eg set-literals.', |
| hide: true); |
| |
| String usage = ''' |
| Usage: server [options] [input.dart] |
| |
| If filename or uri pointing to the entrypoint is provided on the command line, |
| then the server compiles it, generates dill file and exits. |
| If no entrypoint is provided on the command line, server waits for |
| instructions from stdin. |
| |
| Instructions: |
| - compile <input.dart> |
| - recompile [<input.dart>] <boundary-key> |
| <invalidated file uri> |
| <invalidated file uri> |
| ... |
| <boundary-key> |
| - accept |
| - quit |
| |
| Output: |
| - result <boundary-key> |
| <compiler output> |
| <boundary-key> [<output.dill>] |
| |
| Options: |
| ${argParser.usage} |
| '''; |
| |
| enum _State { |
| READY_FOR_INSTRUCTION, |
| RECOMPILE_LIST, |
| COMPILE_EXPRESSION_EXPRESSION, |
| COMPILE_EXPRESSION_DEFS, |
| COMPILE_EXPRESSION_TYPEDEFS, |
| COMPILE_EXPRESSION_LIBRARY_URI, |
| COMPILE_EXPRESSION_KLASS, |
| COMPILE_EXPRESSION_IS_STATIC |
| } |
| |
| /// Actions that every compiler should implement. |
| abstract class CompilerInterface { |
| /// Compile given Dart program identified by `entryPoint` with given list of |
| /// `options`. When `generator` parameter is omitted, new instance of |
| /// `IncrementalKernelGenerator` is created by this method. Main use for this |
| /// parameter is for mocking in tests. |
| /// Returns [true] if compilation was successful and produced no errors. |
| Future<bool> compile( |
| String entryPoint, |
| ArgResults options, { |
| IncrementalCompiler generator, |
| }); |
| |
| /// Assuming some Dart program was previously compiled, recompile it again |
| /// taking into account some changed(invalidated) sources. |
| Future<Null> recompileDelta({String entryPoint}); |
| |
| /// Accept results of previous compilation so that next recompilation cycle |
| /// won't recompile sources that were previously reported as changed. |
| void acceptLastDelta(); |
| |
| /// Rejects results of previous compilation and sets compiler back to last |
| /// accepted state. |
| Future<void> rejectLastDelta(); |
| |
| /// This let's compiler know that source file identifed by `uri` was changed. |
| void invalidate(Uri uri); |
| |
| /// Resets incremental compiler accept/reject status so that next time |
| /// recompile is requested, complete kernel file is produced. |
| void resetIncrementalCompiler(); |
| |
| /// Compiles [expression] with free variables listed in [definitions], |
| /// free type variables listed in [typedDefinitions]. "Free" means their |
| /// values are through evaluation API call, rather than coming from running |
| /// application. |
| /// If [klass] is not [null], expression is compiled in context of [klass] |
| /// class. |
| /// If [klass] is [null],expression is compiled at top-level of |
| /// [libraryUrl] library. If [klass] is not [null], [isStatic] determines |
| /// whether expression can refer to [this] or not. |
| Future<Null> compileExpression( |
| String expression, |
| List<String> definitions, |
| List<String> typeDefinitions, |
| String libraryUri, |
| String klass, |
| bool isStatic); |
| |
| /// Communicates an error [msg] to the client. |
| void reportError(String msg); |
| } |
| |
| abstract class ProgramTransformer { |
| void transform(Component component); |
| } |
| |
| /// Class that for test mocking purposes encapsulates creation of [BinaryPrinter]. |
| class BinaryPrinterFactory { |
| /// Creates new [BinaryPrinter] to write to [targetSink]. |
| BinaryPrinter newBinaryPrinter(Sink<List<int>> targetSink) { |
| return new LimitedBinaryPrinter(targetSink, (_) => true /* predicate */, |
| false /* excludeUriToSource */); |
| } |
| } |
| |
| class FrontendCompiler implements CompilerInterface { |
| FrontendCompiler(this._outputStream, |
| {this.printerFactory, |
| this.transformer, |
| this.unsafePackageSerialization}) { |
| _outputStream ??= stdout; |
| printerFactory ??= new BinaryPrinterFactory(); |
| } |
| |
| StringSink _outputStream; |
| BinaryPrinterFactory printerFactory; |
| bool unsafePackageSerialization; |
| |
| CompilerOptions _compilerOptions; |
| FileSystem _fileSystem; |
| Uri _mainSource; |
| ArgResults _options; |
| |
| IncrementalCompiler _generator; |
| String _kernelBinaryFilename; |
| String _kernelBinaryFilenameIncremental; |
| String _kernelBinaryFilenameFull; |
| String _initializeFromDill; |
| |
| Set<Uri> previouslyReportedDependencies = Set<Uri>(); |
| |
| final ProgramTransformer transformer; |
| |
| final List<String> errors = new List<String>(); |
| |
| @override |
| Future<bool> compile( |
| String entryPoint, |
| ArgResults options, { |
| IncrementalCompiler generator, |
| }) async { |
| _options = options; |
| _fileSystem = createFrontEndFileSystem( |
| options['filesystem-scheme'], options['filesystem-root']); |
| _mainSource = _getFileOrUri(entryPoint); |
| _kernelBinaryFilenameFull = _options['output-dill'] ?? '$entryPoint.dill'; |
| _kernelBinaryFilenameIncremental = _options['output-incremental-dill'] ?? |
| (_options['output-dill'] != null |
| ? '${_options['output-dill']}.incremental.dill' |
| : '$entryPoint.incremental.dill'); |
| _kernelBinaryFilename = _kernelBinaryFilenameFull; |
| _initializeFromDill = |
| _options['initialize-from-dill'] ?? _kernelBinaryFilenameFull; |
| final String boundaryKey = new Uuid().generateV4(); |
| _outputStream.writeln('result $boundaryKey'); |
| final Uri sdkRoot = _ensureFolderPath(options['sdk-root']); |
| final String platformKernelDill = |
| options['platform'] ?? 'platform_strong.dill'; |
| final CompilerOptions compilerOptions = new CompilerOptions() |
| ..sdkRoot = sdkRoot |
| ..fileSystem = _fileSystem |
| ..packagesFileUri = _getFileOrUri(_options['packages']) |
| ..sdkSummary = sdkRoot.resolve(platformKernelDill) |
| ..verbose = options['verbose'] |
| ..embedSourceText = options['embed-source-text'] |
| ..experimentalFlags = parseExperimentalFlags( |
| options['enable-experiment'], (msg) => errors.add(msg)) |
| ..onDiagnostic = (DiagnosticMessage message) { |
| bool printMessage; |
| switch (message.severity) { |
| case Severity.error: |
| case Severity.internalProblem: |
| printMessage = true; |
| errors.addAll(message.plainTextFormatted); |
| break; |
| case Severity.warning: |
| printMessage = true; |
| break; |
| case Severity.errorLegacyWarning: |
| case Severity.context: |
| case Severity.ignored: |
| throw 'Unexpected severity: ${message.severity}'; |
| } |
| if (printMessage) { |
| printDiagnosticMessage(message, _outputStream.writeln); |
| } |
| }; |
| |
| if (options.wasParsed('filesystem-root')) { |
| if (_options['output-dill'] == null) { |
| print('When --filesystem-root is specified it is required to specify' |
| ' --output-dill option that points to physical file system location' |
| ' of a target dill file.'); |
| return false; |
| } |
| } |
| |
| final Map<String, String> environmentDefines = {}; |
| if (!parseCommandLineDefines( |
| options['define'], environmentDefines, usage)) { |
| return false; |
| } |
| |
| compilerOptions.target = createFrontEndTarget( |
| options['target'], |
| trackWidgetCreation: options['track-widget-creation'], |
| ); |
| if (compilerOptions.target == null) { |
| print('Failed to create front-end target ${options['target']}.'); |
| return false; |
| } |
| |
| final String importDill = options['import-dill']; |
| if (importDill != null) { |
| compilerOptions.inputSummaries = <Uri>[ |
| Uri.base.resolveUri(new Uri.file(importDill)) |
| ]; |
| } |
| |
| Component component; |
| if (options['incremental']) { |
| _compilerOptions = compilerOptions; |
| setVMEnvironmentDefines(environmentDefines, _compilerOptions); |
| |
| _compilerOptions.omitPlatform = false; |
| _generator = |
| generator ?? _createGenerator(new Uri.file(_initializeFromDill)); |
| await invalidateIfInitializingFromDill(); |
| component = await _runWithPrintRedirection(() => _generator.compile()); |
| } else { |
| if (options['link-platform']) { |
| // TODO(aam): Remove linkedDependencies once platform is directly embedded |
| // into VM snapshot and http://dartbug.com/30111 is fixed. |
| compilerOptions.linkedDependencies = <Uri>[ |
| sdkRoot.resolve(platformKernelDill) |
| ]; |
| } |
| component = await _runWithPrintRedirection(() => compileToKernel( |
| _mainSource, compilerOptions, |
| aot: options['aot'], |
| useGlobalTypeFlowAnalysis: options['tfa'], |
| environmentDefines: environmentDefines, |
| useProtobufTreeShaker: options['protobuf-tree-shaker'])); |
| } |
| if (component != null) { |
| if (transformer != null) { |
| transformer.transform(component); |
| } |
| |
| await writeDillFile(component, _kernelBinaryFilename, |
| filterExternal: importDill != null); |
| |
| _outputStream.writeln(boundaryKey); |
| await _outputDependenciesDelta(component); |
| _outputStream |
| .writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}'); |
| final String depfile = options['depfile']; |
| if (depfile != null) { |
| await writeDepfile(compilerOptions.fileSystem, component, |
| _kernelBinaryFilename, depfile); |
| } |
| |
| _kernelBinaryFilename = _kernelBinaryFilenameIncremental; |
| } else |
| _outputStream.writeln(boundaryKey); |
| return errors.isEmpty; |
| } |
| |
| void _outputDependenciesDelta(Component component) async { |
| Set<Uri> uris = new Set<Uri>(); |
| for (Uri uri in component.uriToSource.keys) { |
| // Skip empty or corelib dependencies. |
| if (uri == null || uri.scheme == 'org-dartlang-sdk') continue; |
| uris.add(uri); |
| } |
| for (Uri uri in uris) { |
| if (previouslyReportedDependencies.contains(uri)) { |
| continue; |
| } |
| _outputStream.writeln('+${await asFileUri(_fileSystem, uri)}'); |
| } |
| for (Uri uri in previouslyReportedDependencies) { |
| if (!uris.contains(uri)) { |
| _outputStream.writeln('-${await asFileUri(_fileSystem, uri)}'); |
| } |
| } |
| previouslyReportedDependencies = uris; |
| } |
| |
| writeDillFile(Component component, String filename, |
| {bool filterExternal: false}) async { |
| final IOSink sink = new File(filename).openWrite(); |
| final BinaryPrinter printer = filterExternal |
| ? new LimitedBinaryPrinter( |
| sink, (lib) => !lib.isExternal, true /* excludeUriToSource */) |
| : printerFactory.newBinaryPrinter(sink); |
| |
| component.libraries.sort((Library l1, Library l2) { |
| return "${l1.fileUri}".compareTo("${l2.fileUri}"); |
| }); |
| |
| component.computeCanonicalNames(); |
| for (Library library in component.libraries) { |
| library.additionalExports.sort((Reference r1, Reference r2) { |
| return "${r1.canonicalName}".compareTo("${r2.canonicalName}"); |
| }); |
| } |
| if (unsafePackageSerialization == true) { |
| writePackagesToSinkAndTrimComponent(component, sink); |
| } |
| |
| printer.writeComponentFile(component); |
| await sink.close(); |
| } |
| |
| Future<Null> invalidateIfInitializingFromDill() async { |
| if (_kernelBinaryFilename != _kernelBinaryFilenameFull) return null; |
| // If the generator is initialized, it's not going to initialize from dill |
| // again anyway, so there's no reason to spend time invalidating what should |
| // be invalidated by the normal approach anyway. |
| if (_generator.initialized) return null; |
| |
| final File f = new File(_initializeFromDill); |
| if (!f.existsSync()) return null; |
| |
| Component component; |
| try { |
| component = loadComponentSourceFromBytes(f.readAsBytesSync()); |
| } catch (e) { |
| // If we cannot load the dill file we shouldn't initialize from it. |
| _generator = _createGenerator(null); |
| return null; |
| } |
| |
| nextUri: |
| for (Uri uri in component.uriToSource.keys) { |
| if (uri == null || '$uri' == '') continue nextUri; |
| |
| final List<int> oldBytes = component.uriToSource[uri].source; |
| FileSystemEntity entity; |
| try { |
| entity = _compilerOptions.fileSystem.entityForUri(uri); |
| } catch (_) { |
| // Ignore errors that might be caused by non-file uris. |
| continue nextUri; |
| } |
| |
| bool exists; |
| try { |
| exists = await entity.exists(); |
| } catch (e) { |
| exists = false; |
| } |
| |
| if (!exists) { |
| _generator.invalidate(uri); |
| continue nextUri; |
| } |
| final List<int> newBytes = await entity.readAsBytes(); |
| if (oldBytes.length != newBytes.length) { |
| _generator.invalidate(uri); |
| continue nextUri; |
| } |
| for (int i = 0; i < oldBytes.length; ++i) { |
| if (oldBytes[i] != newBytes[i]) { |
| _generator.invalidate(uri); |
| continue nextUri; |
| } |
| } |
| } |
| } |
| |
| @override |
| Future<Null> recompileDelta({String entryPoint}) async { |
| final String boundaryKey = new Uuid().generateV4(); |
| _outputStream.writeln('result $boundaryKey'); |
| await invalidateIfInitializingFromDill(); |
| if (entryPoint != null) { |
| _mainSource = _getFileOrUri(entryPoint); |
| } |
| errors.clear(); |
| final Component deltaProgram = |
| await _generator.compile(entryPoint: _mainSource); |
| |
| if (deltaProgram != null && transformer != null) { |
| transformer.transform(deltaProgram); |
| } |
| await writeDillFile(deltaProgram, _kernelBinaryFilename); |
| _outputStream.writeln(boundaryKey); |
| await _outputDependenciesDelta(deltaProgram); |
| _outputStream |
| .writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}'); |
| _kernelBinaryFilename = _kernelBinaryFilenameIncremental; |
| } |
| |
| @override |
| Future<Null> compileExpression( |
| String expression, |
| List<String> definitions, |
| List<String> typeDefinitions, |
| String libraryUri, |
| String klass, |
| bool isStatic) async { |
| final String boundaryKey = new Uuid().generateV4(); |
| _outputStream.writeln('result $boundaryKey'); |
| Procedure procedure = await _generator.compileExpression( |
| expression, definitions, typeDefinitions, libraryUri, klass, isStatic); |
| if (procedure != null) { |
| final IOSink sink = new File(_kernelBinaryFilename).openWrite(); |
| sink.add(serializeProcedure(procedure)); |
| await sink.close(); |
| _outputStream |
| .writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}'); |
| _kernelBinaryFilename = _kernelBinaryFilenameIncremental; |
| } else { |
| _outputStream.writeln(boundaryKey); |
| } |
| } |
| |
| @override |
| void reportError(String msg) { |
| final String boundaryKey = new Uuid().generateV4(); |
| _outputStream.writeln('result $boundaryKey'); |
| _outputStream.writeln(msg); |
| _outputStream.writeln(boundaryKey); |
| } |
| |
| /// Map of already serialized dill data. All uris in a serialized component |
| /// maps to the same blob of data. Used by |
| /// [writePackagesToSinkAndTrimComponent]. |
| Map<Uri, List<int>> cachedPackageLibraries = new Map<Uri, List<int>>(); |
| |
| /// Map of dependencies for already serialized dill data. |
| /// E.g. if blob1 dependents on blob2, but only using a single file from blob1 |
| /// that does not dependent on blob2, blob2 would not be included leaving the |
| /// dill file in a weird state that could cause the VM to crash if asked to |
| /// forcefully compile everything. Used by |
| /// [writePackagesToSinkAndTrimComponent]. |
| Map<Uri, List<Uri>> cachedPackageDependencies = new Map<Uri, List<Uri>>(); |
| |
| writePackagesToSinkAndTrimComponent( |
| Component deltaProgram, Sink<List<int>> ioSink) { |
| if (deltaProgram == null) return; |
| |
| List<Library> packageLibraries = new List<Library>(); |
| List<Library> libraries = new List<Library>(); |
| deltaProgram.computeCanonicalNames(); |
| |
| for (var lib in deltaProgram.libraries) { |
| Uri uri = lib.importUri; |
| if (uri.scheme == "package") { |
| packageLibraries.add(lib); |
| } else { |
| libraries.add(lib); |
| } |
| } |
| deltaProgram.libraries |
| ..clear() |
| ..addAll(libraries); |
| |
| Map<String, List<Library>> newPackages = new Map<String, List<Library>>(); |
| Set<List<int>> alreadyAdded = new Set<List<int>>(); |
| |
| addDataAndDependentData(List<int> data, Uri uri) { |
| if (alreadyAdded.add(data)) { |
| ioSink.add(data); |
| // Now also add all dependencies. |
| for (Uri dep in cachedPackageDependencies[uri]) { |
| addDataAndDependentData(cachedPackageLibraries[dep], dep); |
| } |
| } |
| } |
| |
| for (Library lib in packageLibraries) { |
| List<int> data = cachedPackageLibraries[lib.fileUri]; |
| if (data != null) { |
| addDataAndDependentData(data, lib.fileUri); |
| } else { |
| String package = lib.importUri.pathSegments.first; |
| newPackages[package] ??= <Library>[]; |
| newPackages[package].add(lib); |
| } |
| } |
| |
| for (String package in newPackages.keys) { |
| List<Library> libraries = newPackages[package]; |
| Component singleLibrary = new Component( |
| libraries: libraries, |
| uriToSource: deltaProgram.uriToSource, |
| nameRoot: deltaProgram.root); |
| ByteSink byteSink = new ByteSink(); |
| final BinaryPrinter printer = printerFactory.newBinaryPrinter(byteSink); |
| printer.writeComponentFile(singleLibrary); |
| |
| // Record things this package blob dependent on. |
| Set<Uri> libraryUris = new Set<Uri>(); |
| for (Library lib in libraries) { |
| libraryUris.add(lib.fileUri); |
| } |
| Set<Uri> deps = new Set<Uri>(); |
| for (Library lib in libraries) { |
| for (LibraryDependency dep in lib.dependencies) { |
| Library dependencyLibrary = dep.importedLibraryReference.asLibrary; |
| if (dependencyLibrary.importUri.scheme != "package") continue; |
| Uri dependencyLibraryUri = |
| dep.importedLibraryReference.asLibrary.fileUri; |
| if (libraryUris.contains(dependencyLibraryUri)) continue; |
| deps.add(dependencyLibraryUri); |
| } |
| } |
| |
| List<int> data = byteSink.builder.takeBytes(); |
| for (Library lib in libraries) { |
| cachedPackageLibraries[lib.fileUri] = data; |
| cachedPackageDependencies[lib.fileUri] = new List<Uri>.from(deps); |
| } |
| ioSink.add(data); |
| } |
| } |
| |
| @override |
| void acceptLastDelta() { |
| _generator.accept(); |
| } |
| |
| @override |
| Future<void> rejectLastDelta() async { |
| await _generator.reject(); |
| final String boundaryKey = new Uuid().generateV4(); |
| _outputStream.writeln('result $boundaryKey'); |
| _outputStream.writeln(boundaryKey); |
| } |
| |
| @override |
| void invalidate(Uri uri) { |
| _generator.invalidate(uri); |
| } |
| |
| @override |
| void resetIncrementalCompiler() { |
| _generator.resetDeltaState(); |
| _kernelBinaryFilename = _kernelBinaryFilenameFull; |
| } |
| |
| Uri _getFileOrUri(String fileOrUri) => |
| convertFileOrUriArgumentToUri(_fileSystem, fileOrUri); |
| |
| IncrementalCompiler _createGenerator(Uri initializeFromDillUri) { |
| return new IncrementalCompiler(_compilerOptions, _mainSource, |
| initializeFromDillUri: initializeFromDillUri); |
| } |
| |
| Uri _ensureFolderPath(String path) { |
| String uriPath = new Uri.file(path).toString(); |
| if (!uriPath.endsWith('/')) { |
| uriPath = '$uriPath/'; |
| } |
| return Uri.base.resolve(uriPath); |
| } |
| |
| /// Runs the given function [f] in a Zone that redirects all prints into |
| /// [_outputStream]. |
| Future<T> _runWithPrintRedirection<T>(Future<T> f()) { |
| return runZoned(() => new Future<T>(f), |
| zoneSpecification: new ZoneSpecification( |
| print: (Zone self, ZoneDelegate parent, Zone zone, String line) => |
| _outputStream.writeln(line))); |
| } |
| } |
| |
| /// A [Sink] that directly writes data into a byte builder. |
| class ByteSink implements Sink<List<int>> { |
| final BytesBuilder builder = new BytesBuilder(); |
| |
| void add(List<int> data) { |
| builder.add(data); |
| } |
| |
| void close() {} |
| } |
| |
| class _CompileExpressionRequest { |
| String expression; |
| // Note that FE will reject a compileExpression command by returning a null |
| // procedure when defs or typeDefs include an illegal identifier. |
| List<String> defs = <String>[]; |
| List<String> typeDefs = <String>[]; |
| String library; |
| String klass; |
| bool isStatic; |
| } |
| |
| /// Listens for the compilation commands on [input] stream. |
| /// This supports "interactive" recompilation mode of execution. |
| void listenAndCompile(CompilerInterface compiler, Stream<List<int>> input, |
| ArgResults options, Completer<int> completer, |
| {IncrementalCompiler generator}) { |
| _State state = _State.READY_FOR_INSTRUCTION; |
| _CompileExpressionRequest compileExpressionRequest; |
| String boundaryKey; |
| String recompileEntryPoint; |
| input |
| .transform(utf8.decoder) |
| .transform(const LineSplitter()) |
| .listen((String string) async { |
| switch (state) { |
| case _State.READY_FOR_INSTRUCTION: |
| const String COMPILE_INSTRUCTION_SPACE = 'compile '; |
| const String RECOMPILE_INSTRUCTION_SPACE = 'recompile '; |
| const String COMPILE_EXPRESSION_INSTRUCTION_SPACE = |
| 'compile-expression '; |
| if (string.startsWith(COMPILE_INSTRUCTION_SPACE)) { |
| final String entryPoint = |
| string.substring(COMPILE_INSTRUCTION_SPACE.length); |
| await compiler.compile(entryPoint, options, generator: generator); |
| } else if (string.startsWith(RECOMPILE_INSTRUCTION_SPACE)) { |
| // 'recompile [<entryPoint>] <boundarykey>' |
| // where <boundarykey> can't have spaces |
| final String remainder = |
| string.substring(RECOMPILE_INSTRUCTION_SPACE.length); |
| final int spaceDelim = remainder.lastIndexOf(' '); |
| if (spaceDelim > -1) { |
| recompileEntryPoint = remainder.substring(0, spaceDelim); |
| boundaryKey = remainder.substring(spaceDelim + 1); |
| } else { |
| boundaryKey = remainder; |
| } |
| state = _State.RECOMPILE_LIST; |
| } else if (string.startsWith(COMPILE_EXPRESSION_INSTRUCTION_SPACE)) { |
| // 'compile-expression <boundarykey> |
| // expression |
| // definitions (one per line) |
| // ... |
| // <boundarykey> |
| // type-defintions (one per line) |
| // ... |
| // <boundarykey> |
| // <libraryUri: String> |
| // <klass: String> |
| // <isStatic: true|false> |
| compileExpressionRequest = new _CompileExpressionRequest(); |
| boundaryKey = |
| string.substring(COMPILE_EXPRESSION_INSTRUCTION_SPACE.length); |
| state = _State.COMPILE_EXPRESSION_EXPRESSION; |
| } else if (string == 'accept') { |
| compiler.acceptLastDelta(); |
| } else if (string == 'reject') { |
| await compiler.rejectLastDelta(); |
| } else if (string == 'reset') { |
| compiler.resetIncrementalCompiler(); |
| } else if (string == 'quit') { |
| completer.complete(0); |
| } |
| break; |
| case _State.RECOMPILE_LIST: |
| if (string == boundaryKey) { |
| compiler.recompileDelta(entryPoint: recompileEntryPoint); |
| state = _State.READY_FOR_INSTRUCTION; |
| } else |
| compiler.invalidate(Uri.base.resolve(string)); |
| break; |
| case _State.COMPILE_EXPRESSION_EXPRESSION: |
| compileExpressionRequest.expression = string; |
| state = _State.COMPILE_EXPRESSION_DEFS; |
| break; |
| case _State.COMPILE_EXPRESSION_DEFS: |
| if (string == boundaryKey) { |
| state = _State.COMPILE_EXPRESSION_TYPEDEFS; |
| } else { |
| compileExpressionRequest.defs.add(string); |
| } |
| break; |
| case _State.COMPILE_EXPRESSION_TYPEDEFS: |
| if (string == boundaryKey) { |
| state = _State.COMPILE_EXPRESSION_LIBRARY_URI; |
| } else { |
| compileExpressionRequest.typeDefs.add(string); |
| } |
| break; |
| case _State.COMPILE_EXPRESSION_LIBRARY_URI: |
| compileExpressionRequest.library = string; |
| state = _State.COMPILE_EXPRESSION_KLASS; |
| break; |
| case _State.COMPILE_EXPRESSION_KLASS: |
| compileExpressionRequest.klass = string.isEmpty ? null : string; |
| state = _State.COMPILE_EXPRESSION_IS_STATIC; |
| break; |
| case _State.COMPILE_EXPRESSION_IS_STATIC: |
| if (string == 'true' || string == 'false') { |
| compileExpressionRequest.isStatic = string == 'true'; |
| compiler.compileExpression( |
| compileExpressionRequest.expression, |
| compileExpressionRequest.defs, |
| compileExpressionRequest.typeDefs, |
| compileExpressionRequest.library, |
| compileExpressionRequest.klass, |
| compileExpressionRequest.isStatic); |
| } else { |
| compiler |
| .reportError('Got $string. Expected either "true" or "false"'); |
| } |
| state = _State.READY_FOR_INSTRUCTION; |
| break; |
| } |
| }); |
| } |
| |
| /// Entry point for this module, that creates `_FrontendCompiler` instance and |
| /// processes user input. |
| /// `compiler` is an optional parameter so it can be replaced with mocked |
| /// version for testing. |
| Future<int> starter( |
| List<String> args, { |
| CompilerInterface compiler, |
| Stream<List<int>> input, |
| StringSink output, |
| IncrementalCompiler generator, |
| BinaryPrinterFactory binaryPrinterFactory, |
| }) async { |
| ArgResults options; |
| try { |
| options = argParser.parse(args); |
| } catch (error) { |
| print('ERROR: $error\n'); |
| print(usage); |
| return 1; |
| } |
| |
| if (options['train']) { |
| final String sdkRoot = options['sdk-root']; |
| final String platform = options['platform']; |
| final Directory temp = |
| Directory.systemTemp.createTempSync('train_frontend_server'); |
| try { |
| final String outputTrainingDill = path.join(temp.path, 'app.dill'); |
| final List<String> args = <String>[ |
| '--incremental', |
| '--sdk-root=$sdkRoot', |
| '--output-dill=$outputTrainingDill', |
| ]; |
| if (platform != null) { |
| args.add('--platform=${new Uri.file(platform)}'); |
| } |
| options = argParser.parse(args); |
| compiler ??= |
| new FrontendCompiler(output, printerFactory: binaryPrinterFactory); |
| |
| await compiler.compile(Platform.script.toFilePath(), options, |
| generator: generator); |
| compiler.acceptLastDelta(); |
| await compiler.recompileDelta(); |
| compiler.acceptLastDelta(); |
| compiler.resetIncrementalCompiler(); |
| await compiler.recompileDelta(); |
| compiler.acceptLastDelta(); |
| await compiler.recompileDelta(); |
| compiler.acceptLastDelta(); |
| return 0; |
| } finally { |
| temp.deleteSync(recursive: true); |
| } |
| } |
| |
| compiler ??= new FrontendCompiler(output, |
| printerFactory: binaryPrinterFactory, |
| unsafePackageSerialization: options["unsafe-package-serialization"]); |
| |
| if (options.rest.isNotEmpty) { |
| return await compiler.compile(options.rest[0], options, |
| generator: generator) |
| ? 0 |
| : 254; |
| } |
| |
| Completer<int> completer = new Completer<int>(); |
| listenAndCompile(compiler, input ?? stdin, options, completer, |
| generator: generator); |
| return completer.future; |
| } |