// 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. /// Defines the VM-specific translation of Dart source code to kernel binaries. library; import 'dart:async'; import 'dart:convert' as convert show json; import 'dart:io' show File, IOSink; import 'dart:typed_data'; import 'package:args/args.dart' show ArgParser, ArgResults; import 'package:build_integration/file_system/multi_root.dart' show MultiRootFileSystem, MultiRootFileSystemEntity; import 'package:crypto/crypto.dart'; import 'package:front_end/src/api_unstable/vm.dart' show CompilerContext, CompilerOptions, CompilerResult, InvocationMode, CfeDiagnosticMessage, DiagnosticMessageHandler, FileSystem, FileSystemEntity, ProcessedOptions, CfeSeverity, StandardFileSystem, Verbosity, getMessageUri, kernelForModule, kernelForProgram, parseExperimentalArguments, parseExperimentalFlags, printDiagnosticMessage, resolveInputUri; import 'package:front_end/src/api_prototype/dynamic_module_validator.dart' show DynamicInterfaceYamlFile; import 'package:kernel/ast.dart' show Component, Library; import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter; import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; import 'package:kernel/core_types.dart' show CoreTypes; import 'package:kernel/kernel.dart' show loadComponentFromBinary; import 'package:kernel/target/targets.dart' show Target, TargetFlags, getTarget; import 'package:package_config/package_config.dart' show loadPackageConfigUri; import 'http_filesystem.dart' show HttpAwareFileSystem; import 'modular/target/install.dart' show installAdditionalTargets; import 'modular/transformations/call_site_annotator.dart' as call_site_annotator; import 'native_assets/synthesizer.dart'; import 'target_os.dart'; import 'transformations/deferred_loading.dart' as deferred_loading; import 'transformations/devirtualization.dart' as devirtualization show transformComponent; import 'transformations/dynamic_interface_annotator.dart' as dynamic_interface_annotator show annotateComponent; import 'transformations/mixin_deduplication.dart' as mixin_deduplication show transformComponent; import 'transformations/no_dynamic_invocations_annotator.dart' as no_dynamic_invocations_annotator show transformComponent; import 'transformations/obfuscation_prohibitions_annotator.dart' as obfuscationProhibitions; import 'transformations/record_use/record_use.dart' as record_use; import 'transformations/to_string_transformer.dart' as to_string_transformer; import 'transformations/type_flow/transformer.dart' as globalTypeFlow show transformComponent; import 'transformations/unreachable_code_elimination.dart' as unreachable_code_elimination; import 'transformations/vm_constant_evaluator.dart' as vm_constant_evaluator; /// Declare options consumed by [runCompiler]. void declareCompilerOptions(ArgParser args) { args.addOption( 'platform', help: 'Path to vm_platform.dill file', defaultsTo: null, ); args.addOption( 'packages', help: 'Path to .dart_tool/package_config.json file', defaultsTo: null, ); args.addOption( 'output', abbr: 'o', help: 'Path to resulting dill file', defaultsTo: null, ); args.addFlag( 'aot', help: 'Produce kernel file for AOT compilation (enables global transformations).', defaultsTo: false, ); args.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, ); args.addFlag('compact-async', help: 'Obsolete, ignored.', hide: true); args.addOption('depfile', help: 'Path to output Ninja depfile'); args.addOption( 'depfile-target', help: 'Override the target in the generated depfile', hide: true, ); args.addOption( 'from-dill', help: 'Read existing dill file instead of compiling from sources', defaultsTo: null, ); args.addFlag( 'link-platform', help: 'Include platform into resulting kernel file.', defaultsTo: true, ); args.addFlag( 'minimal-kernel', help: 'Produce minimal tree-shaken kernel file.', defaultsTo: false, ); args.addFlag( 'embed-sources', help: 'Embed source files in the generated kernel component', defaultsTo: true, ); args.addMultiOption( 'filesystem-root', help: 'A base path for the multi-root virtual file system.' ' If multi-root file system is used, the input script and .dart_tool/package_config.json file should be specified using URI.', ); args.addOption( 'filesystem-scheme', help: 'The URI scheme for the multi-root virtual filesystem.', ); args.addMultiOption( 'source', help: 'List additional source files to include into compilation.', defaultsTo: const [], ); args.addOption( 'native-assets', help: 'Provide the native-assets mapping for @Native external functions.', ); args.addOption( 'recorded-usages-file', help: 'The path to store the recorded usages.', ); args.addOption( 'target', help: 'Target model that determines what core libraries are available', allowed: ['vm', 'flutter', 'flutter_runner', 'dart_runner'], defaultsTo: 'vm', ); args.addFlag( 'tfa', help: 'Enable global type flow analysis and related transformations in AOT mode.', defaultsTo: true, ); args.addOption( 'target-os', help: 'Compile for a specific target operating system when in AOT mode.', allowed: TargetOS.names, ); args.addFlag( 'rta', help: 'Use rapid type analysis for faster compilation in AOT mode.', defaultsTo: true, ); args.addFlag( 'tree-shake-write-only-fields', help: 'Enable tree shaking of fields which are only written in AOT mode.', defaultsTo: true, ); args.addFlag( 'protobuf-tree-shaker-v2', help: 'Enable protobuf tree shaker v2 in AOT mode.', defaultsTo: false, ); args.addFlag( 'protobuf-tree-shaker-mixins', help: 'Include protobuf messages with mixins in the tree shaker pass.', defaultsTo: false, ); args.addMultiOption( 'define', abbr: 'D', help: 'The values for the environment constants (e.g. -Dkey=value).', ); args.addFlag( 'enable-asserts', help: 'Whether asserts will be enabled.', defaultsTo: false, ); args.addFlag( 'sound-null-safety', help: 'Respect the nullability of types at runtime.', defaultsTo: true, hide: true, ); args.addFlag( 'split-output-by-packages', help: 'Split resulting kernel file into multiple files (one per package).', defaultsTo: false, ); args.addOption( 'component-name', help: 'Name of the Fuchsia component', defaultsTo: null, ); args.addOption( 'data-dir', help: 'Name of the subdirectory of //data for output files', ); args.addOption( 'dynamic-interface', help: 'Path to dynamic module interface yaml file.', ); args.addOption( 'dump-detailed-dynamic-interface', help: 'Path to output detailed dynamic interface.', ); args.addOption('manifest', help: 'Path to output Fuchsia package manifest'); args.addMultiOption( 'enable-experiment', help: 'Comma separated list of experimental features to enable.', ); args.addFlag( 'help', abbr: 'h', negatable: false, help: 'Print this help message.', ); args.addFlag( 'track-creation-locations', help: 'Run a kernel transformer to track creation locations for widgets.', defaultsTo: false, aliases: [ // TODO(http://dartbug.com/63225): Remove this once flutter is migrated // to the new flag. 'track-widget-creation', ], ); args.addMultiOption( 'delete-tostring-package-uri', help: 'Replaces implementations of `toString` with `super.toString()` for ' 'specified package', valueHelp: 'dart:ui', defaultsTo: const [], ); args.addMultiOption( 'keep-class-names-implementing', help: 'Prevents obfuscation of the class names of any class implementing ' 'the given class.', defaultsTo: const [], ); args.addOption( 'invocation-modes', help: 'Provides information to the front end about how it is invoked.', defaultsTo: '', ); args.addOption( 'verbosity', help: 'Sets the verbosity level used for filtering messages during ' 'compilation.', defaultsTo: Verbosity.defaultValue, ); } /// Create ArgParser and populate it with options consumed by [runCompiler]. ArgParser createCompilerArgParser() { final ArgParser argParser = new ArgParser(allowTrailingOptions: true); declareCompilerOptions(argParser); return argParser; } const int successExitCode = 0; const int badUsageExitCode = 1; const int compileTimeErrorExitCode = 254; /// Run kernel compiler tool with given [options] and [usage] /// and return exit code. Future runCompiler(ArgResults options, String usage) async { final String? platformKernel = options['platform']; if (options['help']) { print(usage); return successExitCode; } final String? nativeAssetsPath = options['native-assets']; final String? recordedUsagesFile = options['recorded-usages-file']; final bool splitOutputByPackages = options['split-output-by-packages']; final String? input = options.rest.singleOrNull; if ((input == null && (nativeAssetsPath == null || splitOutputByPackages)) || (platformKernel == null)) { print(usage); return badUsageExitCode; } final String outputFileName = options['output'] ?? "$input.dill"; final String? packages = options['packages']; final String targetName = options['target']; final String? fileSystemScheme = options['filesystem-scheme']; final String? depfile = options['depfile']; final String? depfileTarget = options['depfile-target']; final String? fromDillFile = options['from-dill']; final List? fileSystemRoots = options['filesystem-root']; final String? targetOS = options['target-os']; final bool aot = options['aot']; final bool tfa = options['tfa']; final bool rta = options['rta']; final bool linkPlatform = options['link-platform']; final bool embedSources = options['embed-sources']; final bool enableAsserts = options['enable-asserts']; final bool useProtobufTreeShakerV2 = options['protobuf-tree-shaker-v2']; final bool protobufTreeShakerMixins = options['protobuf-tree-shaker-mixins']; final String? manifestFilename = options['manifest']; final String? dataDir = options['component-name'] ?? options['data-dir']; final bool? supportMirrors = options['support-mirrors']; final bool minimalKernel = options['minimal-kernel']; final bool treeShakeWriteOnlyFields = options['tree-shake-write-only-fields']; final List? experimentalFlags = options['enable-experiment']; final Map environmentDefines = {}; final List sources = options['source']; if (!parseCommandLineDefines(options['define'], environmentDefines, usage)) { return badUsageExitCode; } final bool soundNullSafety = options['sound-null-safety']; if (!soundNullSafety) { print('Error: --no-sound-null-safety is not supported.'); return badUsageExitCode; } if (aot) { if (!linkPlatform) { print('Error: --no-link-platform option cannot be used with --aot'); return badUsageExitCode; } if (splitOutputByPackages) { print( 'Error: --split-output-by-packages option cannot be used with --aot', ); return badUsageExitCode; } } if (supportMirrors == true) { if (aot) { print('Error: --support-mirrors option cannot be used with --aot'); return badUsageExitCode; } if (minimalKernel) { print( 'Error: --support-mirrors option cannot be used with ' '--minimal-kernel', ); return badUsageExitCode; } } final fileSystem = createFrontEndFileSystem( fileSystemScheme, fileSystemRoots, ); final Uri? packagesUri = packages != null ? resolveInputUri(packages) : null; final platformKernelUri = Uri.base.resolveUri(new Uri.file(platformKernel)); final List additionalDills = []; if (aot || linkPlatform) { additionalDills.add(platformKernelUri); } final verbosity = Verbosity.parseArgument(options['verbosity']); final errorPrinter = new ErrorPrinter(verbosity); final errorDetector = new ErrorDetector( previousErrorHandler: errorPrinter.call, ); final Uri? nativeAssetsUri = nativeAssetsPath == null ? null : resolveInputUri(nativeAssetsPath); final Uri? recordedUsagesUri = recordedUsagesFile == null ? null : resolveInputUri(recordedUsagesFile); final String? dynamicInterfaceFilePath = options['dynamic-interface']; final Uri? dynamicInterfaceUri = dynamicInterfaceFilePath == null ? null : resolveInputUri(dynamicInterfaceFilePath); final String? dumpDetailedDynamicInterface = options['dump-detailed-dynamic-interface']; Uri? mainUri; if (input != null) { mainUri = resolveInputUri(input); if (packagesUri != null) { mainUri = await convertToPackageUri(fileSystem, mainUri, packagesUri); } } final List additionalSources = sources.map(resolveInputUri).toList(); final CompilerOptions compilerOptions = new CompilerOptions() ..sdkSummary = platformKernelUri ..fileSystem = fileSystem ..additionalDills = additionalDills ..packagesFileUri = packagesUri ..explicitExperimentalFlags = parseExperimentalFlags( parseExperimentalArguments(experimentalFlags), onError: print, ) ..onDiagnostic = (CfeDiagnosticMessage m) { errorDetector(m); } ..embedSourceText = embedSources ..invocationModes = InvocationMode.parseArguments( options['invocation-modes'], ) ..verbosity = verbosity; compilerOptions.target = createFrontEndTarget( targetName, trackCreationLocations: options['track-creation-locations'], supportMirrors: supportMirrors ?? !(aot || minimalKernel), constKeepLocalsIndicator: !(aot || minimalKernel), ); if (compilerOptions.target == null) { print('Failed to create front-end target $targetName.'); return badUsageExitCode; } final results = await compileToKernel( KernelCompilationArguments( source: mainUri, options: compilerOptions, additionalSources: additionalSources, nativeAssets: nativeAssetsUri, recordedUsages: recordedUsagesUri, includePlatform: additionalDills.isNotEmpty, deleteToStringPackageUris: options['delete-tostring-package-uri'], keepClassNamesImplementing: options['keep-class-names-implementing'], dynamicInterface: dynamicInterfaceUri, dumpDetailedDynamicInterface: dumpDetailedDynamicInterface, aot: aot, useGlobalTypeFlowAnalysis: tfa, useRapidTypeAnalysis: rta, environmentDefines: environmentDefines, enableAsserts: enableAsserts, useProtobufTreeShakerV2: useProtobufTreeShakerV2, protobufTreeShakerMixins: protobufTreeShakerMixins, minimalKernel: minimalKernel, treeShakeWriteOnlyFields: treeShakeWriteOnlyFields, targetOS: targetOS, fromDillFile: fromDillFile, ), ); errorPrinter.printCompilationMessages(); final Component? component = results.component; final Library? nativeAssetsLibrary = results.nativeAssetsLibrary; if (errorDetector.hasCompilationErrors || (component == null && nativeAssetsLibrary == null)) { return compileTimeErrorExitCode; } final IOSink sink = new File(outputFileName).openWrite(); if (component != null) { final BinaryPrinter printer = new BinaryPrinter( sink, libraryFilter: (lib) => !results.loadedLibraries.contains(lib), ); 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; } printer.writeComponentFile(component); } if ((nativeAssetsLibrary != null && (!aot || component == null))) { // If no Dart component, write as separate dill. // If Dart component in JIT, write as concatenated dill, to not mess with // the incremental compiler. final BinaryPrinter printer = new BinaryPrinter(sink); printer.writeComponentFile(Component(libraries: [nativeAssetsLibrary])); } await sink.close(); if (depfile != null) { final usedPackageConfig = results.usedPackageConfig; await writeDepfile( fileSystem, [ if (usedPackageConfig != null) usedPackageConfig, ...results.compiledSources!, ], depfileTarget ?? outputFileName, depfile, ); } if (splitOutputByPackages) { await writeOutputSplitByPackages( mainUri!, compilerOptions, results, outputFileName, ); } if (manifestFilename != null) { await createFarManifest(outputFileName, dataDir, manifestFilename); } return successExitCode; } /// Results of [compileToKernel]: generated kernel [Component] and /// collection of compiled sources. class KernelCompilationResults { final Component? component; final Library? nativeAssetsLibrary; /// Set of libraries loaded from .dill, with or without the SDK depending on /// the compilation settings. final Set loadedLibraries; final ClassHierarchy? classHierarchy; final CoreTypes? coreTypes; final Iterable? compiledSources; final Uri? usedPackageConfig; KernelCompilationResults( this.component, this.loadedLibraries, this.classHierarchy, this.coreTypes, this.compiledSources, ) : nativeAssetsLibrary = null, usedPackageConfig = null; KernelCompilationResults.named({ this.component, this.loadedLibraries = const {}, this.classHierarchy, this.coreTypes, this.compiledSources, this.nativeAssetsLibrary, this.usedPackageConfig, }); } // Arguments for [compileToKernel]. class KernelCompilationArguments { final Uri? source; final CompilerOptions? options; final List additionalSources; final Uri? nativeAssets; final Uri? recordedUsages; final bool requireMain; final bool includePlatform; final List deleteToStringPackageUris; final List keepClassNamesImplementing; final bool aot; final Uri? dynamicInterface; final String? dumpDetailedDynamicInterface; final Map environmentDefines; // Should be mutable. final bool enableAsserts; final bool useGlobalTypeFlowAnalysis; final bool useRapidTypeAnalysis; final bool treeShakeWriteOnlyFields; final bool useProtobufTreeShakerV2; final bool protobufTreeShakerMixins; final bool minimalKernel; final String? targetOS; final String? fromDillFile; KernelCompilationArguments({ this.source, this.options, this.additionalSources = const [], this.nativeAssets, this.recordedUsages, this.requireMain = true, this.includePlatform = false, this.deleteToStringPackageUris = const [], this.keepClassNamesImplementing = const [], this.aot = false, this.dynamicInterface, this.dumpDetailedDynamicInterface, Map? environmentDefines, this.enableAsserts = true, this.useGlobalTypeFlowAnalysis = false, this.useRapidTypeAnalysis = true, this.treeShakeWriteOnlyFields = false, this.useProtobufTreeShakerV2 = false, this.protobufTreeShakerMixins = false, this.minimalKernel = false, this.targetOS, this.fromDillFile, }) : environmentDefines = environmentDefines ?? {}; } /// Generates a kernel representation of the program whose main library is in /// the given [args.source]. Intended for whole program (non-modular) compilation. /// /// VM-specific replacement of [kernelForProgram]. /// /// Either [arg.source], or [args.nativeAssets], or both must be non-null. Future compileToKernel( KernelCompilationArguments args, ) async { final options = args.options!; // Replace error handler to detect if there are compilation errors. final errorDetector = new ErrorDetector( previousErrorHandler: options.onDiagnostic, ); options.onDiagnostic = errorDetector.call; final nativeAssetsLibrary = await NativeAssetsSynthesizer.synthesizeLibraryFromYamlFile( args.nativeAssets, errorDetector, ); if (args.source == null) { return KernelCompilationResults.named( nativeAssetsLibrary: nativeAssetsLibrary, ); } final target = options.target!; options.environmentDefines = target.updateEnvironmentDefines( args.environmentDefines, ); List additionalSources = args.additionalSources; final dynamicInterface = args.dynamicInterface; if (dynamicInterface != null) { final fileUri = await asFileUri(args.options!.fileSystem, dynamicInterface); final contents = File(fileUri.toFilePath()).readAsStringSync(); final dynamicInterfaceYamlFile = DynamicInterfaceYamlFile(contents); additionalSources = [ ...additionalSources, ...dynamicInterfaceYamlFile.getUserLibraryUris(dynamicInterface), ]; } CompilerResult? compilerResult; final fromDillFile = args.fromDillFile; Uri? usedPackageConfig; if (fromDillFile != null) { compilerResult = await loadKernel( options.fileSystem, resolveInputUri(fromDillFile), ); } else { final processedOptions = new ProcessedOptions( options: options, inputs: [args.source!, ...additionalSources], ); compilerResult = await CompilerContext.runWithOptions(processedOptions, ( CompilerContext context, ) async { return args.requireMain ? await kernelForProgram( args.source!, options, additionalSources: additionalSources, ) : await kernelForModule([ args.source!, ...additionalSources, ], options); }); usedPackageConfig = await processedOptions.resolvePackagesFileUri(); } final Component? component = compilerResult?.component; Iterable? compiledSources = component?.uriToSource.keys; Set loadedLibraries = createLoadedLibrariesSet( compilerResult?.loadedComponents, compilerResult?.sdkComponent, includePlatform: args.includePlatform, ); if (args.deleteToStringPackageUris.isNotEmpty && component != null) { to_string_transformer.transformComponent( component, args.deleteToStringPackageUris, ); } // Run global transformations only if component is correct. if (component != null) { if (args.aot || args.minimalKernel) { await runGlobalTransformations(target, component, errorDetector, args); if (args.minimalKernel) { // compiledSources is component.uriToSource.keys. // Make a copy of compiledSources to detach it from // component.uriToSource which is cleared below. compiledSources = compiledSources!.toList(); component.metadata.clear(); component.uriToSource.clear(); } } else if (dynamicInterface != null) { // Even though the dynamic_interface_annotator is a global transformation, // we apply it here to partially test dynamic modules with JIT when // loading full kernel snapshots. We only use this to insert all // dynamically-callable pragmas needed to perform runtime checks around // dynamic calls. // // Note: this is not sufficient to support JIT in the general case. Here // we don't handle how JIT uses a different code-path for loading the // platform libraries, we also don't support loading code from sources to // compile in front-end server, or incremental updates used in hot-reload, // and we don't support concatenating dill files (since the global // transformation will not see relations established when combining // libraries). To properly support JIT we will need a modular strategy in // the long run. await _runDynamicInterfaceAnnotator( component, new CoreTypes(component), args, ); } } // Restore error handler (in case 'options' are reused). options.onDiagnostic = errorDetector.previousErrorHandler; return KernelCompilationResults.named( component: component, nativeAssetsLibrary: nativeAssetsLibrary, loadedLibraries: loadedLibraries, classHierarchy: compilerResult?.classHierarchy, coreTypes: compilerResult?.coreTypes, compiledSources: compiledSources, usedPackageConfig: usedPackageConfig, ); } Set createLoadedLibrariesSet( List? loadedComponents, Component? sdkComponent, { bool includePlatform = false, }) { final Set loadedLibraries = {}; if (loadedComponents != null) { for (Component c in loadedComponents) { for (Library lib in c.libraries) { loadedLibraries.add(lib); } } } if (sdkComponent != null) { if (includePlatform) { for (Library lib in sdkComponent.libraries) { loadedLibraries.remove(lib); } } else { for (Library lib in sdkComponent.libraries) { loadedLibraries.add(lib); } } } return loadedLibraries; } Future runGlobalTransformations( Target target, Component component, ErrorDetector errorDetector, KernelCompilationArguments args, ) async { assert(!target.flags.supportMirrors); if (errorDetector.hasCompilationErrors) return; final coreTypes = new CoreTypes(component); // The dynamic_interface_annotator transformation annotates AST nodes with // pragmas and should precede other transformations looking at pragmas // (such as mixin_deduplication and TFA). if (args.dynamicInterface != null) { await _runDynamicInterfaceAnnotator(component, coreTypes, args); } // TODO(alexmarkov,cstefantsova): Consider doing canonicalization of // identical mixin applications when creating mixin applications in frontend, // so all backends (and all transformation passes from the very beginning) // can benefit from mixin de-duplication. // At least, in addition to VM/AOT case we should run this transformation // when building a platform dill file for VM/JIT case. mixin_deduplication.transformComponent(component, coreTypes, target); // Perform unreachable code elimination, which should be performed before // type flow analysis so TFA won't take unreachable code into account. final targetOS = args.targetOS; final os = targetOS != null ? TargetOS.fromString(targetOS)! : null; final evaluator = vm_constant_evaluator.VMConstantEvaluator.create( target, component, os, enableAsserts: args.enableAsserts, environmentDefines: args.environmentDefines, coreTypes: coreTypes, ); unreachable_code_elimination.transformComponent( target, component, evaluator, args.enableAsserts, ); if (args.useGlobalTypeFlowAnalysis) { globalTypeFlow.transformComponent( target, coreTypes, component, treeShakeSignatures: !args.minimalKernel, treeShakeWriteOnlyFields: args.treeShakeWriteOnlyFields, treeShakeProtobufs: args.useProtobufTreeShakerV2, treeShakeProtobufMixins: args.protobufTreeShakerMixins, useRapidTypeAnalysis: args.useRapidTypeAnalysis, ); } else { devirtualization.transformComponent(coreTypes, component); no_dynamic_invocations_annotator.transformComponent(component); } // TODO(35069): avoid recomputing CSA by reading it from the platform files. void ignoreAmbiguousSupertypes(cls, a, b) {} final hierarchy = new ClassHierarchy( component, coreTypes, onAmbiguousSupertypes: ignoreAmbiguousSupertypes, ); call_site_annotator.transformLibraries( component, component.libraries, coreTypes, hierarchy, ); // We don't know yet whether gen_snapshot will want to do obfuscation, but if // it does it will need the obfuscation prohibitions. obfuscationProhibitions.transformComponent( component, coreTypes, target, hierarchy, args.keepClassNamesImplementing, ); deferred_loading.transformComponent(component, coreTypes, target); final recordedUsagesFile = args.recordedUsages; if (recordedUsagesFile != null) { assert(args.source != null); record_use.transformComponent(component, recordedUsagesFile); } } Future _runDynamicInterfaceAnnotator( Component component, CoreTypes coreTypes, KernelCompilationArguments args, ) async { final dynamicInterface = args.dynamicInterface!; final fileUri = await asFileUri(args.options!.fileSystem, dynamicInterface); final dumpDetailedDynamicInterface = args.dumpDetailedDynamicInterface; Map>>? detailedDynamicInterfaceJson = (dumpDetailedDynamicInterface != null) ? {} : null; dynamic_interface_annotator.annotateComponent( File(fileUri.toFilePath()).readAsStringSync(), dynamicInterface, component, coreTypes, detailedDynamicInterfaceJson: detailedDynamicInterfaceJson, ); if (dumpDetailedDynamicInterface != null) { File( dumpDetailedDynamicInterface, ).writeAsStringSync(convert.json.encode(detailedDynamicInterfaceJson)); } } /// Runs given [action] with [CompilerContext]. This is needed to /// be able to report compile-time errors. Future runWithFrontEndCompilerContext( Uri source, CompilerOptions compilerOptions, Component component, Future action(), ) async { final processedOptions = new ProcessedOptions( options: compilerOptions, inputs: [source], ); // Run within the context, so we have uri source tokens... return await CompilerContext.runWithOptions(processedOptions, ( CompilerContext context, ) async { // To make the fileUri/fileOffset -> line/column mapping, we need to // pre-fill the map. context.uriToSource.addAll(component.uriToSource); return action(); }); } class ErrorDetector { final DiagnosticMessageHandler? previousErrorHandler; bool hasCompilationErrors = false; ErrorDetector({this.previousErrorHandler}); void call(CfeDiagnosticMessage message) { if (message.severity == CfeSeverity.error) { hasCompilationErrors = true; } previousErrorHandler?.call(message); } } class ErrorPrinter { final Verbosity verbosity; final DiagnosticMessageHandler? previousErrorHandler; final Map> compilationMessages = >{}; final void Function(String) println; ErrorPrinter( this.verbosity, { this.previousErrorHandler, this.println = print, }); void call(CfeDiagnosticMessage message) { final sourceUri = getMessageUri(message); (compilationMessages[sourceUri] ??= []).add(message); previousErrorHandler?.call(message); } void printCompilationMessages() { final sortedUris = compilationMessages.keys.toList() ..sort((a, b) { // Sort messages without a corresponding uri before the location based // messages, since these related to the whole compilation. if (a != null && b != null) { return '$a'.compareTo('$b'); } else if (a != null) { return 1; } else if (b != null) { return -1; } return 0; }); for (final Uri? sourceUri in sortedUris) { for (final CfeDiagnosticMessage message in compilationMessages[sourceUri]!) { if (Verbosity.shouldPrint(verbosity, message)) { printDiagnosticMessage(message, println); } } } } } bool parseCommandLineDefines( List dFlags, Map environmentDefines, String usage, ) { for (final String dflag in dFlags) { final equalsSignIndex = dflag.indexOf('='); if (equalsSignIndex < 0) { // Ignored. } else if (equalsSignIndex > 0) { final key = dflag.substring(0, equalsSignIndex); final value = dflag.substring(equalsSignIndex + 1); environmentDefines[key] = value; } else { print('The environment constant options must have a key (was: "$dflag")'); print(usage); return false; } } return true; } /// Create front-end target with given name. Target? createFrontEndTarget( String targetName, { bool trackCreationLocations = false, bool supportMirrors = true, bool includeUnsupportedPlatformLibraryStubs = false, bool? constKeepLocalsIndicator, bool isClosureContextLoweringEnabled = false, }) { // Make sure VM-specific targets are available. installAdditionalTargets(); final TargetFlags targetFlags = new TargetFlags( trackCreationLocations: trackCreationLocations, supportMirrors: supportMirrors, includeUnsupportedPlatformLibraryStubs: includeUnsupportedPlatformLibraryStubs, constKeepLocalsIndicator: constKeepLocalsIndicator, isClosureContextLoweringEnabled: isClosureContextLoweringEnabled, ); return getTarget(targetName, targetFlags); } /// Create a front-end file system. /// /// If requested, create a virtual multi-root file system and/or an http aware /// file system. FileSystem createFrontEndFileSystem( String? multiRootFileSystemScheme, List? multiRootFileSystemRoots, { bool allowHttp = false, }) { FileSystem fileSystem = StandardFileSystem.instance; if (allowHttp) { fileSystem = HttpAwareFileSystem(fileSystem); } if (multiRootFileSystemRoots != null && multiRootFileSystemRoots.isNotEmpty && multiRootFileSystemScheme != null) { final rootUris = []; for (String root in multiRootFileSystemRoots) { rootUris.add(resolveInputUri(root)); } fileSystem = new MultiRootFileSystem( multiRootFileSystemScheme, rootUris, fileSystem, ); } return fileSystem; } /// Convert a URI which may use virtual file system schema to a real file URI. Future asFileUri(FileSystem fileSystem, Uri uri) async { FileSystemEntity fse = fileSystem.entityForUri(uri); if (fse is MultiRootFileSystemEntity) { fse = await fse.delegate; } return fse.uri; } /// Convert URI to a package URI if it is inside one of the packages. /// TODO(alexmarkov) Remove this conversion after Fuchsia build rules are fixed. Future convertToPackageUri( FileSystem fileSystem, Uri uri, Uri packagesUri, ) async { if (uri.scheme == 'package') { return uri; } // Convert virtual URI to a real file URI. final Uri fileUri = await asFileUri(fileSystem, uri); try { final packageConfig = await loadPackageConfigUri( await asFileUri(fileSystem, packagesUri), ); return packageConfig.toPackageUri(fileUri) ?? uri; } catch (_) { // Can't read packages file - silently give up. return uri; } } /// Write a separate kernel binary for each package. The name of the /// output kernel binary is '[outputFileName]-$package.dilp'. /// The list of package names is written into a file '[outputFileName]-packages'. Future writeOutputSplitByPackages( Uri source, CompilerOptions compilerOptions, KernelCompilationResults compilationResults, String outputFileName, ) async { final packages = []; final Component component = compilationResults.component!; await runWithFrontEndCompilerContext( source, compilerOptions, component, () async { // When loading a kernel file list, flutter_runner and dart_runner expect // 'main' to be last. await forEachPackage(compilationResults, ( String package, List libraries, ) async { packages.add(package); final String filename = '$outputFileName-$package.dilp'; final IOSink sink = new File(filename).openWrite(); final BinaryPrinter printer = new BinaryPrinter( sink, libraryFilter: (lib) => packageFor(lib, compilationResults.loadedLibraries) == package, ); printer.writeComponentFile(component); await sink.close(); }, mainFirst: false); }, ); final IOSink packagesList = new File('$outputFileName-packages').openWrite(); for (String package in packages) { packagesList.writeln(package); } await packagesList.close(); } String? packageFor(Library lib, Set loadedLibraries) { // Core libraries are not written into any package kernel binaries. if (loadedLibraries.contains(lib)) return null; // Packages are written into their own kernel binaries. Uri uri = lib.importUri; if (uri.scheme == 'package') return uri.pathSegments.first; // Everything else (e.g., file: or data: imports) is lumped into the main // kernel binary. return 'main'; } /// Sort the libraries etc in the component. Helps packages to produce identical /// output when their parts are imported in different orders in different /// contexts. void sortComponent(Component component) { component.libraries.sort((Library a, Library b) { return a.importUri.toString().compareTo(b.importUri.toString()); }); component.computeCanonicalNames(); for (Library lib in component.libraries) { lib.additionalExports.sort(); } } Future forEachPackage( KernelCompilationResults results, Future action(String package, List libraries), { required bool mainFirst, }) async { final Component component = results.component!; final Set loadedLibraries = results.loadedLibraries; sortComponent(component); final Map> packages = >{}; packages['main'] = []; // Always create 'main'. for (Library lib in component.libraries) { final String? package = packageFor(lib, loadedLibraries); // Ignore external libraries. if (package == null) { continue; } packages.putIfAbsent(package, () => []).add(lib); } final mainLibraries = packages.remove('main')!; if (mainFirst) { await action('main', mainLibraries); } final mainMethod = component.mainMethod; final problemsAsJson = component.problemsAsJson; component.setMainMethodAndMode(null, true); component.problemsAsJson = null; for (String package in packages.keys) { await action(package, packages[package]!); } component.setMainMethodAndMode(mainMethod?.reference, true); component.problemsAsJson = problemsAsJson; if (!mainFirst) { await action('main', mainLibraries); } } String _escapePath(String path) { return path.replaceAll('\\', '\\\\').replaceAll(' ', '\\ '); } /// Create ninja dependencies file, as described in /// https://ninja-build.org/manual.html#_depfile Future writeDepfile( FileSystem fileSystem, Iterable compiledSources, String output, String depfile, ) async { final IOSink file = new File(depfile).openWrite(); file.write(_escapePath(output)); file.write(':'); // TODO(https://dartbug.com/55246): track macro deps when available. for (Uri dep in compiledSources) { // Skip corelib dependencies. if (dep.scheme == 'org-dartlang-sdk') continue; Uri uri = await asFileUri(fileSystem, dep); file.write(' '); file.write(_escapePath(uri.toFilePath())); } file.write('\n'); await file.close(); } Future createFarManifest( String output, String? dataDir, String packageManifestFilename, ) async { List packages = await File('$output-packages').readAsLines(); // Make sure the 'main' package is the last (convention with package loader). packages.remove('main'); packages.add('main'); final IOSink packageManifest = File(packageManifestFilename).openWrite(); final String kernelListFilename = '$packageManifestFilename.dilplist'; final IOSink kernelList = File(kernelListFilename).openWrite(); for (String package in packages) { final String filenameInPackage = '$package.dilp'; final String filenameInBuild = '$output-$package.dilp'; packageManifest.write( 'data/$dataDir/$filenameInPackage=$filenameInBuild\n', ); kernelList.write('$filenameInPackage\n'); } await kernelList.close(); final String frameworkVersionFilename = '$packageManifestFilename.frameworkversion'; final IOSink frameworkVersion = File(frameworkVersionFilename).openWrite(); for (String package in [ 'collection', 'flutter', 'meta', 'typed_data', 'vector_math', ]) { Digest? digest; if (packages.contains(package)) { final filenameInBuild = '$output-$package.dilp'; final bytes = await File(filenameInBuild).readAsBytes(); digest = sha256.convert(bytes); } frameworkVersion.write('$package=$digest\n'); } await frameworkVersion.close(); packageManifest.write('data/$dataDir/app.dilplist=$kernelListFilename\n'); packageManifest.write( 'data/$dataDir/app.frameworkversion=$frameworkVersionFilename\n', ); await packageManifest.close(); } class CompilerResultLoadedFromKernel implements CompilerResult { final Component component; final Component sdkComponent = Component(); CompilerResultLoadedFromKernel(this.component); @override Uint8List? get summary => null; @override List get loadedComponents => const []; @override CoreTypes? get coreTypes => null; @override ClassHierarchy? get classHierarchy => null; } Future loadKernel( FileSystem fileSystem, Uri dillFileUri, ) async { final component = loadComponentFromBinary( (await asFileUri(fileSystem, dillFileUri)).toFilePath(), ); return CompilerResultLoadedFromKernel(component); } // Used by kernel_front_end_test.dart main() {}