| // 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 <String>[], | |
| ); | |
| 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: <String>['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 <String>[], | |
| ); | |
| args.addMultiOption( | |
| 'keep-class-names-implementing', | |
| help: | |
| 'Prevents obfuscation of the class names of any class implementing ' | |
| 'the given class.', | |
| defaultsTo: const <String>[], | |
| ); | |
| 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<int> 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<String>? 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<String>? experimentalFlags = options['enable-experiment']; | |
| final Map<String, String> environmentDefines = {}; | |
| final List<String> 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 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<Uri> additionalSources = sources.map(resolveInputUri).toList(); | |
| final CompilerOptions compilerOptions = new CompilerOptions() | |
| ..sdkSummary = platformKernelUri | |
| ..fileSystem = fileSystem | |
| ..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: aot || linkPlatform, | |
| 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<Library> loadedLibraries; | |
| final ClassHierarchy? classHierarchy; | |
| final CoreTypes? coreTypes; | |
| final Iterable<Uri>? compiledSources; | |
| final Uri? usedPackageConfig; | |
| 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<Uri> additionalSources; | |
| final Uri? nativeAssets; | |
| final Uri? recordedUsages; | |
| final bool requireMain; | |
| final bool includePlatform; | |
| final List<String> deleteToStringPackageUris; | |
| final List<String> keepClassNamesImplementing; | |
| final bool aot; | |
| final Uri? dynamicInterface; | |
| final String? dumpDetailedDynamicInterface; | |
| final Map<String, String> 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 <Uri>[], | |
| this.nativeAssets, | |
| this.recordedUsages, | |
| this.requireMain = true, | |
| this.includePlatform = false, | |
| this.deleteToStringPackageUris = const <String>[], | |
| this.keepClassNamesImplementing = const <String>[], | |
| this.aot = false, | |
| this.dynamicInterface, | |
| this.dumpDetailedDynamicInterface, | |
| Map<String, String>? 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<KernelCompilationResults> 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, | |
| ); | |
| final dynamicInterface = args.dynamicInterface; | |
| List<Uri> additionalSources = [ | |
| ...args.additionalSources, | |
| ...await getDynamicInterfaceUserLibraryUris( | |
| args.options!.fileSystem, | |
| 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<Uri>? compiledSources = component?.uriToSource.keys; | |
| Set<Library> 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, | |
| ); | |
| } | |
| Future<Iterable<Uri>> getDynamicInterfaceUserLibraryUris( | |
| FileSystem fileSystem, | |
| Uri? dynamicInterface, | |
| ) async { | |
| if (dynamicInterface == null) return const <Uri>[]; | |
| final fileUri = await asFileUri(fileSystem, dynamicInterface); | |
| final contents = File(fileUri.toFilePath()).readAsStringSync(); | |
| final dynamicInterfaceYamlFile = DynamicInterfaceYamlFile(contents); | |
| return dynamicInterfaceYamlFile.getUserLibraryUris(dynamicInterface); | |
| } | |
| Set<Library> createLoadedLibrariesSet( | |
| List<Component>? loadedComponents, | |
| Component? sdkComponent, { | |
| bool includePlatform = false, | |
| }) { | |
| final Set<Library> 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<String, List<Map<String, String>>>? 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<T> runWithFrontEndCompilerContext<T>( | |
| Uri source, | |
| CompilerOptions compilerOptions, | |
| Component component, | |
| Future<T> 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<Uri?, List<CfeDiagnosticMessage>> compilationMessages = | |
| <Uri?, List<CfeDiagnosticMessage>>{}; | |
| final void Function(String) println; | |
| ErrorPrinter( | |
| this.verbosity, { | |
| this.previousErrorHandler, | |
| this.println = print, | |
| }); | |
| void call(CfeDiagnosticMessage message) { | |
| final sourceUri = getMessageUri(message); | |
| (compilationMessages[sourceUri] ??= <CfeDiagnosticMessage>[]).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<String> dFlags, | |
| Map<String, String> 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<String>? multiRootFileSystemRoots, { | |
| bool allowHttp = false, | |
| }) { | |
| FileSystem fileSystem = StandardFileSystem.instance; | |
| if (allowHttp) { | |
| fileSystem = HttpAwareFileSystem(fileSystem); | |
| } | |
| if (multiRootFileSystemRoots != null && | |
| multiRootFileSystemRoots.isNotEmpty && | |
| multiRootFileSystemScheme != null) { | |
| final rootUris = <Uri>[]; | |
| 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<Uri> 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<Uri> 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 = <String>[]; | |
| 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<Library> 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<Library> 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<void> forEachPackage( | |
| KernelCompilationResults results, | |
| Future<void> action(String package, List<Library> libraries), { | |
| required bool mainFirst, | |
| }) async { | |
| final Component component = results.component!; | |
| final Set<Library> loadedLibraries = results.loadedLibraries; | |
| sortComponent(component); | |
| final Map<String, List<Library>> packages = <String, List<Library>>{}; | |
| packages['main'] = <Library>[]; // 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, () => <Library>[]).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<void> writeDepfile( | |
| FileSystem fileSystem, | |
| Iterable<Uri> 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<void> createFarManifest( | |
| String output, | |
| String? dataDir, | |
| String packageManifestFilename, | |
| ) async { | |
| List<String> 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<Component> get loadedComponents => const <Component>[]; | |
| @override | |
| CoreTypes? get coreTypes => null; | |
| @override | |
| ClassHierarchy? get classHierarchy => null; | |
| } | |
| Future<CompilerResult> 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() {} |