blob: cbda719e8f3c071ce572fe11cc17425f84e67b00 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// ignore_for_file: implementation_imports, constant_identifier_names
// 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.
library frontend_server;
import 'dart:async';
import 'dart:convert';
import 'dart:io' show File, IOSink, stdout;
import 'dart:typed_data' show BytesBuilder;
import 'package:args/args.dart';
import 'package:dev_compiler/dev_compiler.dart'
show
DevCompilerTarget,
ExpressionCompiler,
parseModuleFormat,
ProgramCompiler;
import 'package:front_end/src/api_unstable/ddc.dart' as ddc
show IncrementalCompiler;
import 'package:front_end/src/api_unstable/vm.dart';
import 'package:kernel/ast.dart' show Library, Procedure, LibraryDependency;
import 'package:kernel/binary/ast_to_binary.dart';
import 'package:kernel/kernel.dart'
show Component, loadComponentSourceFromBytes;
import 'package:kernel/target/targets.dart' show targets, TargetFlags;
import 'package:package_config/package_config.dart';
import 'package:usage/uuid/uuid.dart';
import 'package:vm/incremental_compiler.dart' show IncrementalCompiler;
import 'package:vm/kernel_front_end.dart';
import 'package:vm/target_os.dart'; // For possible --target-os values.
import 'src/javascript_bundle.dart';
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)
..addOption('target-os',
help: 'Compile to a specific target operating system.',
allowed: TargetOS.names)
..addFlag('support-mirrors',
help: 'Whether dart:mirrors is supported. By default dart:mirrors is '
'supported when --aot and --minimal-kernel are not used.',
defaultsTo: null)
..addFlag('compact-async', help: 'Obsolete, ignored.', hide: true)
..addFlag('tfa',
help: 'Enable global type flow analysis and related transformations '
'in AOT mode.',
defaultsTo: false)
..addFlag('rta',
help: 'Use rapid type analysis for faster compilation in AOT mode.',
defaultsTo: true)
..addFlag('tree-shake-write-only-fields',
help: 'Enable tree shaking of fields which are only written in AOT mode.',
defaultsTo: true)
..addFlag('protobuf-tree-shaker-v2',
help: 'Enable protobuf tree shaker v2 in AOT mode.', defaultsTo: false)
..addFlag('minimal-kernel',
help: 'Produce minimal tree-shaken kernel file.', 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('from-dill',
help: 'Read existing dill file instead of compiling from sources',
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: '.dart_tool/package_config.json file to use for compilation',
defaultsTo: null)
..addMultiOption('source',
help: 'List additional source files to include into compilation.',
defaultsTo: const <String>[])
..addOption('native-assets',
help: 'Provide the native-assets mapping for @Native external functions.')
..addOption('target',
help: 'Target model that determines what core libraries are available',
allowed: <String>[
'vm',
'flutter',
'flutter_runner',
'dart_runner',
'dartdevc'
],
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)
..addOption('binary-protocol-address',
hide: true,
help: 'The server will establish TCP connection to this address, and'
' will exchange binary requests and responses with the client.')
..addFlag('enable-http-uris',
defaultsTo: false, hide: true, help: 'Enables support for http uris.')
..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)
..addFlag('assume-initialize-from-dill-up-to-date',
help: 'Normally the dill used for initializing is checked against the '
"files it was compiled against. If we somehow know that it's "
'up-to-date we can skip it safely. Under normal circumstances this '
"isn't safe though.",
defaultsTo: false,
hide: true)
..addMultiOption('define',
abbr: 'D',
help: 'The values for the environment constants (e.g. -Dkey=value).',
splitCommas: false)
..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: '*Deprecated* '
'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 file size does not matter, and the gain is '
'improved speed.',
defaultsTo: false,
hide: true)
..addFlag('incremental-serialization',
help: 'Re-use previously serialized data when serializing. '
'The output dill file might include more libraries than strictly '
'needed, but the serialization phase will generally be much faster.',
defaultsTo: true,
negatable: true,
hide: true)
..addFlag('track-widget-creation',
help: 'Run a kernel transformer to track creation locations for widgets.',
defaultsTo: false)
..addMultiOption(
'delete-tostring-package-uri',
help: 'Replaces implementations of `toString` with `super.toString()` for '
'specified package',
valueHelp: 'dart:ui',
defaultsTo: const <String>[],
)
..addMultiOption(
'keep-class-names-implementing',
help: 'Prevents obfuscation of the class names of any class implementing '
'the given class.',
defaultsTo: const <String>[],
)
..addFlag('enable-asserts',
help: 'Whether asserts will be enabled.', defaultsTo: false)
..addFlag('sound-null-safety',
help: 'Respect the nullability of types at runtime.', defaultsTo: true)
..addMultiOption('enable-experiment',
help: 'Comma separated list of experimental features, e.g. set-literals.',
hide: true)
..addFlag('split-output-by-packages',
help:
'Split resulting kernel file into multiple files (one per package).',
defaultsTo: false)
..addOption('component-name', help: 'Name of the Fuchsia component')
..addOption('data-dir',
help: 'Name of the subdirectory of //data for output files')
..addOption('far-manifest', help: 'Path to output Fuchsia package manifest')
..addOption('libraries-spec',
help: 'A path or uri to the libraries specification JSON file')
..addFlag('debugger-module-names',
help: 'Use debugger-friendly modules names', defaultsTo: false)
..addFlag('experimental-emit-debug-metadata',
help: 'Emit module and library metadata for the debugger',
defaultsTo: false)
..addFlag('emit-debug-symbols',
help: 'Emit debug symbols for the debugger', defaultsTo: false)
..addOption('dartdevc-module-format',
help: 'The module format to use on for the dartdevc compiler',
defaultsTo: 'amd')
..addFlag('dartdevc-canary',
help: 'Enable canary features in dartdevc compiler', defaultsTo: false)
..addFlag('print-incremental-dependencies',
help: 'Print list of sources added and removed from compilation',
defaultsTo: true)
..addOption('resident-info-file-name',
help: 'Allowing for incremental compilation of changes when using the '
'Dart CLI. '
'Stores server information in this file for accessing later',
hide: true)
..addOption('verbosity',
help: 'Sets the verbosity level of the compilation',
defaultsTo: Verbosity.defaultValue,
allowed: Verbosity.allowedValues,
allowedHelp: Verbosity.allowedValuesHelp);
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,
// compileExpression
COMPILE_EXPRESSION_EXPRESSION,
COMPILE_EXPRESSION_DEFS,
COMPILE_EXPRESSION_DEFTYPES,
COMPILE_EXPRESSION_TYPEDEFS,
COMPILE_EXPRESSION_TYPEBOUNDS,
COMPILE_EXPRESSION_TYPEDEFAULTS,
COMPILE_EXPRESSION_LIBRARY_URI,
COMPILE_EXPRESSION_KLASS,
COMPILE_EXPRESSION_METHOD,
COMPILE_EXPRESSION_IS_STATIC,
// compileExpressionToJs
COMPILE_EXPRESSION_TO_JS_LIBRARYURI,
COMPILE_EXPRESSION_TO_JS_LINE,
COMPILE_EXPRESSION_TO_JS_COLUMN,
COMPILE_EXPRESSION_TO_JS_JSMODULES,
COMPILE_EXPRESSION_TO_JS_JSFRAMEVALUES,
COMPILE_EXPRESSION_TO_JS_MODULENAME,
COMPILE_EXPRESSION_TO_JS_EXPRESSION,
// Json input
JSON_INPUT,
}
/// 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,
});
/// Sets the native assets mapping to be embedded in the kernel.
Future<bool> setNativeAssets(String nativeAssets);
/// Assuming some Dart program was previously compiled, recompile it again
/// taking into account some changed(invalidated) sources.
Future<void> 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 identified 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<void> compileExpression(
String expression,
List<String> definitions,
List<String> definitionTypes,
List<String> typeDefinitions,
List<String> typeBounds,
List<String> typeDefaults,
String libraryUri,
String? klass,
String? method,
int offset,
String? scriptUri,
bool isStatic);
/// Compiles [expression] in library [libraryUri] and file [scriptUri]
/// at [line]:[column] to JavaScript in [moduleName].
///
/// [libraryUri] and [scriptUri] can be the same, but if for instance
/// evaluating expressions in a part file the [libraryUri] will be the uri of
/// the "part of" file whereas [scriptUri] will be the uri of the part.
///
/// [line] and [column] are 1-based.
///
/// Values listed in [jsFrameValues] are substituted for their names in the
/// [expression].
///
/// Ensures that all [jsModules] are loaded and accessible inside the
/// expression.
///
/// Example values of parameters:
/// [moduleName] is of the form '/packages/hello_world_main.dart'
/// [jsFrameValues] is a map from js variable name to its primitive value
/// or another variable name, for example
/// { 'x': '1', 'y': 'y', 'o': 'null' }
/// [jsModules] is a map from variable name to the module name, where
/// variable name is the name originally used in JavaScript to contain the
/// module object, for example:
/// { 'dart':'dart_sdk', 'main': '/packages/hello_world_main.dart' }
Future<void> compileExpressionToJs(
String libraryUri,
String? scriptUri,
int line,
int column,
Map<String, String> jsModules,
Map<String, String> jsFrameValues,
String moduleName,
String expression);
/// 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 BinaryPrinter(targetSink);
}
}
class FrontendCompiler implements CompilerInterface {
FrontendCompiler(
StringSink? outputStream, {
BinaryPrinterFactory? printerFactory,
this.transformer,
this.unsafePackageSerialization,
this.incrementalSerialization = true,
this.useDebuggerModuleNames = false,
this.emitDebugMetadata = false,
this.emitDebugSymbols = false,
this.canaryFeatures = false,
}) : _outputStream = outputStream ?? stdout,
printerFactory = printerFactory ?? new BinaryPrinterFactory();
/// Fields with initializers
final List<String> errors = <String>[];
Set<Uri> previouslyReportedDependencies = <Uri>{};
/// Initialized in the constructor
bool emitDebugMetadata;
bool emitDebugSymbols;
bool incrementalSerialization;
final StringSink _outputStream;
BinaryPrinterFactory printerFactory;
bool useDebuggerModuleNames;
bool canaryFeatures;
/// Initialized in [compile].
late List<Uri> _additionalSources;
late bool _assumeInitializeFromDillUpToDate;
late CompilerOptions _compilerOptions;
late FileSystem _fileSystem;
late IncrementalCompiler _generator;
late String _initializeFromDill;
late String _kernelBinaryFilename;
late String _kernelBinaryFilenameIncremental;
late String _kernelBinaryFilenameFull;
late Uri _mainSource;
late ArgResults _options;
late bool _printIncrementalDependencies;
late ProcessedOptions _processedOptions;
/// Initialized in [compile] from options, or (re)set in [setNativeAssets].
Uri? _nativeAssets;
/// Cached compilation of [_nativeAssets].
///
/// Managed by [_compileNativeAssets] and [setNativeAssets].
Library? _nativeAssetsLibrary;
/// Initialized in [writeJavaScriptBundle].
IncrementalJavaScriptBundler? _bundler;
/// Nullable fields
final ProgramTransformer? transformer;
bool? unsafePackageSerialization;
void _onDiagnostic(DiagnosticMessage message) {
switch (message.severity) {
case Severity.error:
case Severity.internalProblem:
errors.addAll(message.plainTextFormatted);
break;
case Severity.warning:
case Severity.info:
break;
case Severity.context:
case Severity.ignored:
throw 'Unexpected severity: ${message.severity}';
}
if (Verbosity.shouldPrint(_compilerOptions.verbosity, message)) {
printDiagnosticMessage(message, _outputStream.writeln);
}
}
void _installDartdevcTarget() {
targets['dartdevc'] = (TargetFlags flags) => new DevCompilerTarget(flags);
}
@override
Future<bool> compile(
String entryPoint,
ArgResults options, {
IncrementalCompiler? generator,
}) async {
_options = options;
_fileSystem = createFrontEndFileSystem(
options['filesystem-scheme'], options['filesystem-root'],
allowHttp: options['enable-http-uris']);
_mainSource = resolveInputUri(entryPoint);
_additionalSources =
(options['source'] as List<String>).map(resolveInputUri).toList();
final String? nativeAssets = options['native-assets'] as String?;
_nativeAssets = nativeAssets != null ? resolveInputUri(nativeAssets) : null;
_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;
_assumeInitializeFromDillUpToDate =
_options['assume-initialize-from-dill-up-to-date'] ?? false;
_printIncrementalDependencies = _options['print-incremental-dependencies'];
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 String? packagesOption = _options['packages'];
final bool soundNullSafety = _options['sound-null-safety'];
final CompilerOptions compilerOptions = new CompilerOptions()
..sdkRoot = sdkRoot
..fileSystem = _fileSystem
..packagesFileUri =
packagesOption != null ? resolveInputUri(packagesOption) : null
..sdkSummary = sdkRoot.resolve(platformKernelDill)
..verbose = options['verbose']
..embedSourceText = options['embed-source-text']
..explicitExperimentalFlags = parseExperimentalFlags(
parseExperimentalArguments(options['enable-experiment']),
onError: (msg) => errors.add(msg))
..nnbdMode = soundNullSafety ? NnbdMode.Strong : NnbdMode.Weak
..onDiagnostic = _onDiagnostic
..verbosity = Verbosity.parseArgument(options['verbosity'],
onError: (msg) => errors.add(msg));
_compilerOptions = compilerOptions;
if (options.wasParsed('libraries-spec')) {
compilerOptions.librariesSpecificationUri =
resolveInputUri(options['libraries-spec']);
}
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;
}
if (options['aot']) {
if (!options['link-platform']) {
print('Error: --no-link-platform option cannot be used with --aot');
return false;
}
if (options['split-output-by-packages']) {
print('Error: --split-output-by-packages option cannot be used '
'with --aot');
return false;
}
if (options['incremental']) {
print('Error: --incremental option cannot be used with --aot');
return false;
}
if (options['import-dill'] != null) {
print('Error: --import-dill option cannot be used with --aot');
return false;
}
}
if (options['target-os'] != null) {
if (!options['aot']) {
print('Error: --target-os option must be used with --aot');
return false;
}
}
if (options['support-mirrors'] == true) {
if (options['aot']) {
print('Error: --support-mirrors option cannot be used with --aot');
return false;
}
if (options['minimal-kernel']) {
print('Error: --support-mirrors option cannot be used with '
'--minimal-kernel');
return false;
}
}
if (options['incremental']) {
if (options['from-dill'] != null) {
print('Error: --from-dill option cannot be used with --incremental');
return false;
}
}
// Initialize additional supported kernel targets.
_installDartdevcTarget();
compilerOptions.target = createFrontEndTarget(
options['target'],
trackWidgetCreation: options['track-widget-creation'],
soundNullSafety: compilerOptions.nnbdMode == NnbdMode.Strong,
supportMirrors: options['support-mirrors'] ??
!(options['aot'] || options['minimal-kernel']),
);
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.additionalDills = <Uri>[
Uri.base.resolveUri(new Uri.file(importDill))
];
}
_processedOptions = new ProcessedOptions(options: compilerOptions);
KernelCompilationResults? results;
IncrementalSerializer? incrementalSerializer;
if (options['incremental']) {
_compilerOptions.environmentDefines =
_compilerOptions.target!.updateEnvironmentDefines(environmentDefines);
_compilerOptions.omitPlatform = false;
_generator =
generator ?? _createGenerator(new Uri.file(_initializeFromDill));
await invalidateIfInitializingFromDill();
IncrementalCompilerResult compilerResult =
await _runWithPrintRedirection(() => _generator.compile());
Component component = compilerResult.component;
await _compileNativeAssets();
results = new KernelCompilationResults.named(
component: component,
nativeAssetsLibrary: _nativeAssetsLibrary,
classHierarchy: compilerResult.classHierarchy,
coreTypes: compilerResult.coreTypes,
compiledSources: component.uriToSource.keys,
);
incrementalSerializer = _generator.incrementalSerializer;
} 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.additionalDills = <Uri>[
sdkRoot.resolve(platformKernelDill)
];
}
results = await _runWithPrintRedirection(() => compileToKernel(
_mainSource, compilerOptions,
additionalSources: _additionalSources,
nativeAssets: _nativeAssets,
includePlatform: options['link-platform'],
deleteToStringPackageUris: options['delete-tostring-package-uri'],
keepClassNamesImplementing: options['keep-class-names-implementing'],
aot: options['aot'],
targetOS: options['target-os'],
useGlobalTypeFlowAnalysis: options['tfa'],
useRapidTypeAnalysis: options['rta'],
environmentDefines: environmentDefines,
enableAsserts: options['enable-asserts'],
useProtobufTreeShakerV2: options['protobuf-tree-shaker-v2'],
minimalKernel: options['minimal-kernel'],
treeShakeWriteOnlyFields: options['tree-shake-write-only-fields'],
fromDillFile: options['from-dill']));
}
if (results!.component != null) {
transformer?.transform(results.component!);
if (_compilerOptions.target!.name == 'dartdevc') {
await writeJavaScriptBundle(results, _kernelBinaryFilename,
options['filesystem-scheme'], options['dartdevc-module-format'],
fullComponent: true);
}
await writeDillFile(
results,
_kernelBinaryFilename,
filterExternal: importDill != null || options['minimal-kernel'],
incrementalSerializer: incrementalSerializer,
aot: options['aot'],
);
_outputStream.writeln(boundaryKey);
final Iterable<Uri> compiledSources = results.compiledSources!;
await _outputDependenciesDelta(compiledSources);
_outputStream
.writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}');
final String? depfile = options['depfile'];
if (depfile != null) {
await writeDepfile(compilerOptions.fileSystem, compiledSources,
_kernelBinaryFilename, depfile);
}
_kernelBinaryFilename = _kernelBinaryFilenameIncremental;
} else {
_outputStream.writeln(boundaryKey);
}
results = null; // Fix leak: Probably variation of http://dartbug.com/36983.
return errors.isEmpty;
}
@override
Future<bool> setNativeAssets(String nativeAssets) async {
_nativeAssetsLibrary = null; // Purge compiled cache.
_nativeAssets = resolveInputUri(nativeAssets);
return true;
}
/// Compiles [_nativeAssets] into [_nativeAssetsLibrary].
///
/// [compile] and [recompileDelta] invoke this, and bundles the cached
/// [_nativeAssetsLibrary] in the dill file.
Future<void> _compileNativeAssets() async {
final Uri? nativeAssets = _nativeAssets;
if (nativeAssets == null || _nativeAssetsLibrary != null) {
return;
}
final KernelCompilationResults results =
await _runWithPrintRedirection(() => compileToKernel(
null,
_compilerOptions,
nativeAssets: _nativeAssets,
environmentDefines: {},
));
_nativeAssetsLibrary = results.nativeAssetsLibrary;
}
Future<void> _outputDependenciesDelta(Iterable<Uri> compiledSources) async {
if (!_printIncrementalDependencies) {
return;
}
Set<Uri> uris = {};
for (Uri uri in compiledSources) {
// Skip empty or corelib dependencies.
if (uri.isScheme('org-dartlang-sdk')) continue;
uris.add(uri);
}
for (Uri uri in uris) {
if (previouslyReportedDependencies.contains(uri)) {
continue;
}
try {
_outputStream.writeln('+${await asFileUri(_fileSystem, uri)}');
} on FileSystemException {
// Ignore errors from invalid import uris.
}
}
for (Uri uri in previouslyReportedDependencies) {
if (uris.contains(uri)) {
continue;
}
try {
_outputStream.writeln('-${await asFileUri(_fileSystem, uri)}');
} on FileSystemException {
// Ignore errors from invalid import uris.
}
}
previouslyReportedDependencies = uris;
}
/// Write a JavaScript bundle containing the provided component.
Future<void> writeJavaScriptBundle(KernelCompilationResults results,
String filename, String fileSystemScheme, String moduleFormat,
{required bool fullComponent}) async {
PackageConfig packageConfig = await loadPackageConfigUri(
_compilerOptions.packagesFileUri ??
new File('.dart_tool/package_config.json').absolute.uri);
bool soundNullSafety = _compilerOptions.nnbdMode == NnbdMode.Strong;
final Component component = results.component!;
final IncrementalJavaScriptBundler bundler =
_bundler ??= new IncrementalJavaScriptBundler(
_compilerOptions.fileSystem,
results.loadedLibraries,
fileSystemScheme,
useDebuggerModuleNames: useDebuggerModuleNames,
emitDebugMetadata: emitDebugMetadata,
moduleFormat: moduleFormat,
soundNullSafety: soundNullSafety,
canaryFeatures: canaryFeatures,
);
if (fullComponent) {
await bundler.initialize(component, _mainSource, packageConfig);
} else {
await bundler.invalidate(
component,
_generator.lastKnownGoodResult!.component,
_mainSource,
packageConfig);
}
// Create JavaScript bundler.
final File sourceFile = new File('$filename.sources');
final File manifestFile = new File('$filename.json');
final File sourceMapsFile = new File('$filename.map');
final File metadataFile = new File('$filename.metadata');
final File symbolsFile = new File('$filename.symbols');
if (!sourceFile.parent.existsSync()) {
sourceFile.parent.createSync(recursive: true);
}
final IOSink sourceFileSink = sourceFile.openWrite();
final IOSink manifestFileSink = manifestFile.openWrite();
final IOSink sourceMapsFileSink = sourceMapsFile.openWrite();
final IOSink? metadataFileSink =
emitDebugMetadata ? metadataFile.openWrite() : null;
final IOSink? symbolsFileSink =
emitDebugSymbols ? symbolsFile.openWrite() : null;
final Map<String, ProgramCompiler> kernel2JsCompilers =
await bundler.compile(
results.classHierarchy!,
results.coreTypes!,
packageConfig,
sourceFileSink,
manifestFileSink,
sourceMapsFileSink,
metadataFileSink,
symbolsFileSink);
cachedProgramCompilers.addAll(kernel2JsCompilers);
await Future.wait([
sourceFileSink.close(),
manifestFileSink.close(),
sourceMapsFileSink.close(),
if (metadataFileSink != null) metadataFileSink.close(),
if (symbolsFileSink != null) symbolsFileSink.close(),
]);
}
Future<void> writeDillFile(
KernelCompilationResults results,
String filename, {
bool filterExternal = false,
IncrementalSerializer? incrementalSerializer,
bool aot = false,
}) async {
final Component component = results.component!;
final Library? nativeAssetsLibrary = results.nativeAssetsLibrary;
if (aot && nativeAssetsLibrary != null) {
// If Dart component in AOT, write the vm:native-assets library _inside_
// the Dart component.
// TODO(https://dartbug.com/50152): Support AOT dill concatenation.
component.libraries.add(nativeAssetsLibrary);
nativeAssetsLibrary.parent = component;
}
final IOSink sink = new File(filename).openWrite();
final Set<Library> loadedLibraries = results.loadedLibraries;
final BinaryPrinter printer = filterExternal
? new BinaryPrinter(sink,
libraryFilter: (lib) => !loadedLibraries.contains(lib),
includeSources: false)
: printerFactory.newBinaryPrinter(sink);
sortComponent(component);
if (incrementalSerializer != null) {
incrementalSerializer.writePackagesToSinkAndTrimComponent(
component, sink);
} else if (unsafePackageSerialization == true) {
writePackagesToSinkAndTrimComponent(component, sink);
}
printer.writeComponentFile(component);
if (nativeAssetsLibrary != null && !aot) {
final BinaryPrinter printer = new BinaryPrinter(sink);
printer.writeComponentFile(new Component(
libraries: [nativeAssetsLibrary],
mode: nativeAssetsLibrary.nonNullableByDefaultCompiledMode,
));
}
await sink.close();
if (_options['split-output-by-packages']) {
await writeOutputSplitByPackages(
_mainSource, _compilerOptions, results, filename);
}
final String? manifestFilename = _options['far-manifest'];
if (manifestFilename != null) {
final String output = _options['output-dill'];
final String? dataDir = _options.options.contains('component-name')
? _options['component-name']
: _options['data-dir'];
await createFarManifest(output, dataDir, manifestFilename);
}
}
Future<void> invalidateIfInitializingFromDill() async {
if (_assumeInitializeFromDillUpToDate) return;
if (_kernelBinaryFilename != _kernelBinaryFilenameFull) return;
// 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;
final File f = new File(_initializeFromDill);
if (!f.existsSync()) return;
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;
}
nextUri:
for (Uri uri in component.uriToSource.keys) {
if ('$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<void> recompileDelta({String? entryPoint}) async {
final String boundaryKey = new Uuid().generateV4();
_outputStream.writeln('result $boundaryKey');
await invalidateIfInitializingFromDill();
if (entryPoint != null) {
_mainSource = resolveInputUri(entryPoint);
}
errors.clear();
IncrementalCompilerResult deltaProgramResult = await _generator
.compile(entryPoints: [_mainSource, ..._additionalSources]);
Component deltaProgram = deltaProgramResult.component;
transformer?.transform(deltaProgram);
await _compileNativeAssets();
KernelCompilationResults results = new KernelCompilationResults.named(
component: deltaProgram,
classHierarchy: deltaProgramResult.classHierarchy,
coreTypes: deltaProgramResult.coreTypes,
compiledSources: deltaProgram.uriToSource.keys,
nativeAssetsLibrary: _nativeAssetsLibrary,
);
if (_compilerOptions.target!.name == 'dartdevc') {
await writeJavaScriptBundle(results, _kernelBinaryFilename,
_options['filesystem-scheme'], _options['dartdevc-module-format'],
fullComponent: false);
} else {
await writeDillFile(results, _kernelBinaryFilename,
incrementalSerializer: _generator.incrementalSerializer);
}
_outputStream.writeln(boundaryKey);
await _outputDependenciesDelta(results.compiledSources!);
_outputStream
.writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}');
_kernelBinaryFilename = _kernelBinaryFilenameIncremental;
}
@override
Future<void> compileExpression(
String expression,
List<String> definitions,
List<String> definitionTypes,
List<String> typeDefinitions,
List<String> typeBounds,
List<String> typeDefaults,
String libraryUri,
String? klass,
String? method,
int offset,
String? scriptUri,
bool isStatic) async {
final String boundaryKey = new Uuid().generateV4();
_outputStream.writeln('result $boundaryKey');
Procedure? procedure = await _generator.compileExpression(
expression,
definitions,
definitionTypes,
typeDefinitions,
typeBounds,
typeDefaults,
libraryUri,
klass,
method,
offset,
scriptUri,
isStatic);
if (procedure != null) {
Component component = createExpressionEvaluationComponent(procedure);
final IOSink sink = new File(_kernelBinaryFilename).openWrite();
sink.add(serializeComponent(component));
await sink.close();
_outputStream
.writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}');
_kernelBinaryFilename = _kernelBinaryFilenameIncremental;
} else {
_outputStream.writeln(boundaryKey);
}
}
/// Program compilers per module.
///
/// Produced during initial compilation of the module to JavaScript,
/// cached to be used for expression compilation in [compileExpressionToJs].
final Map<String, ProgramCompiler> cachedProgramCompilers = {};
@override
Future<void> compileExpressionToJs(
String libraryUri,
String? scriptUri,
int line,
int column,
Map<String, String> jsModules,
Map<String, String> jsFrameValues,
String moduleName,
String expression) async {
_generator.accept();
errors.clear();
if (_bundler == null) {
reportError('JavaScript bundler is null');
return;
}
if (!cachedProgramCompilers.containsKey(moduleName)) {
reportError('Cannot find kernel2js compiler for $moduleName.');
return;
}
final String boundaryKey = new Uuid().generateV4();
_outputStream.writeln('result $boundaryKey');
_processedOptions.ticker
.logMs('Compiling expression to JavaScript in $moduleName');
final ProgramCompiler kernel2jsCompiler =
cachedProgramCompilers[moduleName]!;
IncrementalCompilerResult compilerResult = _generator.lastKnownGoodResult!;
Component component = compilerResult.component;
component.computeCanonicalNames();
_processedOptions.ticker.logMs('Computed component');
final ExpressionCompiler expressionCompiler = new ExpressionCompiler(
_compilerOptions,
parseModuleFormat(_options['dartdevc-module-format'] as String),
errors,
_generator.generator as ddc.IncrementalCompiler,
kernel2jsCompiler,
component,
);
final String? procedure = await expressionCompiler.compileExpressionToJs(
libraryUri, scriptUri, line, column, jsFrameValues, expression);
final String result = errors.isNotEmpty ? errors[0] : procedure!;
// TODO(annagrin): kernelBinaryFilename is too specific
// rename to _outputFileName?
await new File(_kernelBinaryFilename).writeAsString(result);
_processedOptions.ticker.logMs('Compiled expression to JavaScript');
_outputStream
.writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}');
// TODO(annagrin): do we need to add asserts/error reporting if
// initial compilation didn't happen and _kernelBinaryFilename
// is different from below?
if (procedure != null) {
_kernelBinaryFilename = _kernelBinaryFilenameIncremental;
}
}
@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 = <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 = <Uri, List<Uri>>{};
void writePackagesToSinkAndTrimComponent(
Component deltaProgram, Sink<List<int>> ioSink) {
List<Library> packageLibraries = <Library>[];
List<Library> libraries = <Library>[];
deltaProgram.computeCanonicalNames();
for (Library lib in deltaProgram.libraries) {
Uri uri = lib.importUri;
if (uri.isScheme("package")) {
packageLibraries.add(lib);
} else {
libraries.add(lib);
}
}
deltaProgram.libraries
..clear()
..addAll(libraries);
Map<String, List<Library>> newPackages = <String, List<Library>>{};
Set<List<int>> alreadyAdded = <List<int>>{};
void 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>[]).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);
singleLibrary.setMainMethodAndMode(null, false, deltaProgram.mode);
ByteSink byteSink = new ByteSink();
final BinaryPrinter printer = printerFactory.newBinaryPrinter(byteSink);
printer.writeComponentFile(singleLibrary);
// Record things this package blob dependent on.
Set<Uri> libraryUris = <Uri>{};
for (Library lib in libraries) {
libraryUris.add(lib.fileUri);
}
Set<Uri> deps = <Uri>{};
for (Library lib in libraries) {
for (LibraryDependency dep in lib.dependencies) {
Library dependencyLibrary = dep.importedLibraryReference.asLibrary;
if (!dependencyLibrary.importUri.isScheme("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>.of(deps);
}
ioSink.add(data);
}
}
@override
void acceptLastDelta() {
_generator.accept();
}
@override
Future<void> rejectLastDelta() async {
final String boundaryKey = new Uuid().generateV4();
_outputStream.writeln('result $boundaryKey');
await _generator.reject();
_outputStream.writeln(boundaryKey);
}
@override
void invalidate(Uri uri) {
_generator.invalidate(uri);
}
@override
void resetIncrementalCompiler() {
_generator.resetDeltaState();
_kernelBinaryFilename = _kernelBinaryFilenameFull;
}
IncrementalCompiler _createGenerator(Uri? initializeFromDillUri) {
return new IncrementalCompiler(
_compilerOptions, [_mainSource, ..._additionalSources],
initializeFromDillUri: initializeFromDillUri,
incrementalSerialization: incrementalSerialization);
}
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> Function() 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();
@override
void add(List<int> data) {
builder.add(data);
}
@override
void close() {}
}
class _CompileExpressionRequest {
late 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> defTypes = <String>[];
List<String> typeDefs = <String>[];
List<String> typeBounds = <String>[];
List<String> typeDefaults = <String>[];
late String library;
String? klass;
String? method;
int offset = -1;
String? scriptUri;
late bool isStatic;
}
class _CompileExpressionToJsRequest {
late String libraryUri;
late int line;
late int column;
Map<String, String> jsModules = <String, String>{};
Map<String, String> jsFrameValues = <String, String>{};
late String moduleName;
late String expression;
}
/// Listens for the compilation commands on [input] stream.
/// This supports "interactive" recompilation mode of execution.
StreamSubscription<String> listenAndCompile(CompilerInterface compiler,
Stream<List<int>> input, ArgResults options, Completer<int> completer,
{IncrementalCompiler? generator}) {
_State state = _State.READY_FOR_INSTRUCTION;
late _CompileExpressionRequest compileExpressionRequest;
late _CompileExpressionToJsRequest compileExpressionToJsRequest;
late String boundaryKey;
StringBuffer? previousJsonString;
String? recompileEntryPoint;
return 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 NATIVE_ASSETS_INSTRUCTION_SPACE = 'native-assets ';
const String COMPILE_EXPRESSION_INSTRUCTION_SPACE =
'compile-expression ';
const String COMPILE_EXPRESSION_TO_JS_INSTRUCTION_SPACE =
'compile-expression-to-js ';
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(NATIVE_ASSETS_INSTRUCTION_SPACE)) {
final String nativeAssets =
string.substring(NATIVE_ASSETS_INSTRUCTION_SPACE.length);
await compiler.setNativeAssets(nativeAssets);
} else if (string
.startsWith(COMPILE_EXPRESSION_TO_JS_INSTRUCTION_SPACE)) {
// 'compile-expression-to-js <boundarykey>
// libraryUri
// line
// column
// jsModules (one k-v pair per line)
// ...
// <boundarykey>
// jsFrameValues (one k-v pair per line)
// ...
// <boundarykey>
// moduleName
// expression
compileExpressionToJsRequest = new _CompileExpressionToJsRequest();
boundaryKey = string
.substring(COMPILE_EXPRESSION_TO_JS_INSTRUCTION_SPACE.length);
state = _State.COMPILE_EXPRESSION_TO_JS_LIBRARYURI;
} else if (string.startsWith(COMPILE_EXPRESSION_INSTRUCTION_SPACE)) {
// 'compile-expression <boundarykey>
// expression
// definitions (one per line)
// ...
// <boundarykey>
// definitionTypes (one per line)
// ...
// <boundarykey>
// type-definitions (one per line)
// ...
// <boundarykey>
// type-bounds (one per line)
// ...
// <boundarykey>
// type-defaults (one per line)
// ...
// <boundarykey>
// <libraryUri: String>
// <klass: String>
// <method: String>
// <isStatic: true|false>
compileExpressionRequest = new _CompileExpressionRequest();
boundaryKey =
string.substring(COMPILE_EXPRESSION_INSTRUCTION_SPACE.length);
state = _State.COMPILE_EXPRESSION_EXPRESSION;
} else if (string == 'JSON_INPUT') {
state = _State.JSON_INPUT;
} else if (string == 'accept') {
compiler.acceptLastDelta();
} else if (string == 'reject') {
await compiler.rejectLastDelta();
} else if (string == 'reset') {
compiler.resetIncrementalCompiler();
} else if (string == 'quit') {
if (!completer.isCompleted) {
completer.complete(0);
}
}
break;
case _State.RECOMPILE_LIST:
if (string == boundaryKey) {
await 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_DEFTYPES;
} else {
compileExpressionRequest.defs.add(string);
}
break;
case _State.COMPILE_EXPRESSION_DEFTYPES:
if (string == boundaryKey) {
state = _State.COMPILE_EXPRESSION_TYPEDEFS;
} else {
compileExpressionRequest.defTypes.add(string);
}
break;
case _State.COMPILE_EXPRESSION_TYPEDEFS:
if (string == boundaryKey) {
state = _State.COMPILE_EXPRESSION_TYPEBOUNDS;
} else {
compileExpressionRequest.typeDefs.add(string);
}
break;
case _State.COMPILE_EXPRESSION_TYPEBOUNDS:
if (string == boundaryKey) {
state = _State.COMPILE_EXPRESSION_TYPEDEFAULTS;
} else {
compileExpressionRequest.typeBounds.add(string);
}
break;
case _State.COMPILE_EXPRESSION_TYPEDEFAULTS:
if (string == boundaryKey) {
state = _State.COMPILE_EXPRESSION_LIBRARY_URI;
} else {
compileExpressionRequest.typeDefaults.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_METHOD;
break;
case _State.COMPILE_EXPRESSION_METHOD:
compileExpressionRequest.method = 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';
await compiler.compileExpression(
compileExpressionRequest.expression,
compileExpressionRequest.defs,
compileExpressionRequest.defTypes,
compileExpressionRequest.typeDefs,
compileExpressionRequest.typeBounds,
compileExpressionRequest.typeDefaults,
compileExpressionRequest.library,
compileExpressionRequest.klass,
compileExpressionRequest.method,
compileExpressionRequest.offset,
compileExpressionRequest.scriptUri,
compileExpressionRequest.isStatic);
} else {
compiler
.reportError('Got $string. Expected either "true" or "false"');
}
state = _State.READY_FOR_INSTRUCTION;
break;
case _State.COMPILE_EXPRESSION_TO_JS_LIBRARYURI:
compileExpressionToJsRequest.libraryUri = string;
state = _State.COMPILE_EXPRESSION_TO_JS_LINE;
break;
case _State.COMPILE_EXPRESSION_TO_JS_LINE:
compileExpressionToJsRequest.line = int.parse(string);
state = _State.COMPILE_EXPRESSION_TO_JS_COLUMN;
break;
case _State.COMPILE_EXPRESSION_TO_JS_COLUMN:
compileExpressionToJsRequest.column = int.parse(string);
state = _State.COMPILE_EXPRESSION_TO_JS_JSMODULES;
break;
case _State.COMPILE_EXPRESSION_TO_JS_JSMODULES:
if (string == boundaryKey) {
state = _State.COMPILE_EXPRESSION_TO_JS_JSFRAMEVALUES;
} else {
List<String> list = string.split(':');
String key = list[0];
String value = list[1];
compileExpressionToJsRequest.jsModules[key] = value;
}
break;
case _State.COMPILE_EXPRESSION_TO_JS_JSFRAMEVALUES:
if (string == boundaryKey) {
state = _State.COMPILE_EXPRESSION_TO_JS_MODULENAME;
} else {
List<String> list = string.split(':');
String key = list[0];
String value = list[1];
compileExpressionToJsRequest.jsFrameValues[key] = value;
}
break;
case _State.COMPILE_EXPRESSION_TO_JS_MODULENAME:
compileExpressionToJsRequest.moduleName = string;
state = _State.COMPILE_EXPRESSION_TO_JS_EXPRESSION;
break;
case _State.COMPILE_EXPRESSION_TO_JS_EXPRESSION:
compileExpressionToJsRequest.expression = string;
await compiler.compileExpressionToJs(
compileExpressionToJsRequest.libraryUri,
null /* not supported here - use json! */,
compileExpressionToJsRequest.line,
compileExpressionToJsRequest.column,
compileExpressionToJsRequest.jsModules,
compileExpressionToJsRequest.jsFrameValues,
compileExpressionToJsRequest.moduleName,
compileExpressionToJsRequest.expression);
state = _State.READY_FOR_INSTRUCTION;
break;
case _State.JSON_INPUT:
state = _State.READY_FOR_INSTRUCTION;
/// TODO(jensj): Find/make a better way to combine json if it's
/// finished after the first line.
Map<String, dynamic>? jsonDecoded;
String data;
bool ok = false;
if (previousJsonString == null) {
data = string;
} else {
previousJsonString!.write(string);
data = previousJsonString.toString();
}
try {
jsonDecoded = jsonDecode(data) as Map<String, dynamic>;
previousJsonString = null;
ok = true;
} catch (e) {
if (e is FormatException && e.offset == data.length) {
// Need more input.
if (previousJsonString == null) {
previousJsonString = new StringBuffer()..write(string);
}
state = _State.JSON_INPUT;
} else {
// Invalid input.
compiler.reportError('Json input error: $e');
previousJsonString = null;
}
}
if (ok) {
await processJsonInput(jsonDecoded!, compiler);
}
}
});
}
Future<void> processJsonInput(
Map<String, dynamic> topLevelData, CompilerInterface compiler) async {
final dynamic type = topLevelData["type"];
if (type is! String) {
compiler.reportError("No valid 'type' data in json input.");
return;
}
final dynamic data = topLevelData["data"];
if (data is! Map) {
compiler.reportError("No valid 'data' data in json input.");
return;
}
// Note: If deprecating keys they should probably be "soft-deprecated" by
// allowing it to be send for a while, i.e. we should probably remove it from
// this set.
Set<String> unusedKeys = new Set<String>.from(data.keys);
List<String> errorMessages = [];
E? getValue<E>(String key) {
unusedKeys.remove(key);
try {
return data[key] as E;
} catch (e) {
errorMessages.add("'$key' was invalid: $e");
return null;
}
}
List<String>? getList(String key) {
unusedKeys.remove(key);
try {
if (!data.containsKey(key)) return null;
List<dynamic> list = data[key] as List;
return new List<String>.from(list);
} catch (e) {
errorMessages.add("'$key' was invalid: $e");
return null;
}
}
Map<String, String>? getMap(String key) {
unusedKeys.remove(key);
try {
if (!data.containsKey(key)) return null;
Map<dynamic, dynamic> map = data[key] as Map;
return new Map<String, String>.from(map);
} catch (e) {
errorMessages.add("'$key' was invalid: $e");
return null;
}
}
// TODO(jensj): Use shared constants for these.
if (type == "COMPILE_EXPRESSION") {
String expression = getValue<String>("expression") ?? "";
List<String> definitions = getList("definitions") ?? [];
List<String> definitionTypes = getList("definitionTypes") ?? [];
List<String> typeDefinitions = getList("typeDefinitions") ?? [];
List<String> typeBounds = getList("typeBounds") ?? [];
List<String> typeDefaults = getList("typeDefaults") ?? [];
String libraryUri = getValue<String>("libraryUri") ?? "";
String? klass = getValue<String?>("class");
String? method = getValue<String?>("method");
int offset = getValue<int?>("offset") ?? -1;
String? scriptUri = getValue<String?>("scriptUri");
bool isStatic = getValue<bool>("static") ?? true;
if (errorMessages.isNotEmpty) {
compiler.reportError("Errors: $errorMessages.");
return;
}
if (unusedKeys.isNotEmpty) {
compiler.reportError("Errors: Unused data sent: $unusedKeys.");
}
await compiler.compileExpression(
expression,
definitions,
definitionTypes,
typeDefinitions,
typeBounds,
typeDefaults,
libraryUri,
klass,
method,
offset,
scriptUri,
isStatic);
} else if (type == "COMPILE_EXPRESSION_JS") {
String expression = getValue<String>("expression") ?? "";
String libraryUri = getValue<String>("libraryUri") ?? "";
String? scriptUri = getValue<String?>("scriptUri");
int line = getValue<int>("line") ?? -1;
int column = getValue<int>("column") ?? -1;
Map<String, String> jsModules = getMap("jsModules") ?? {};
Map<String, String> jsFrameValues = getMap("jsFrameValues") ?? {};
String moduleName = getValue<String>("moduleName") ?? "";
if (errorMessages.isNotEmpty) {
compiler.reportError("Errors: $errorMessages.");
return;
}
if (unusedKeys.isNotEmpty) {
compiler.reportError("Errors: Send over unused data: $unusedKeys.");
}
await compiler.compileExpressionToJs(
libraryUri,
scriptUri,
line,
column,
jsModules,
jsFrameValues,
moduleName,
expression,
);
} else {
compiler.reportError("Unsupported type '$type' in json input.");
return;
}
}