| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:io' hide FileSystemException; |
| |
| import 'package:args/args.dart'; |
| |
| import 'package:front_end/src/api_unstable/vm.dart'; |
| import 'package:front_end/src/scheme_based_file_system.dart' |
| show SchemeBasedFileSystem; |
| |
| import 'package:build_integration/file_system/multi_root.dart' |
| show MultiRootFileSystem, MultiRootFileSystemEntity; |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/binary/ast_to_binary.dart'; |
| import 'package:kernel/binary/limited_ast_to_binary.dart'; |
| import 'package:kernel/target/targets.dart'; |
| |
| import 'package:vm/kernel_front_end.dart' |
| show compileToKernel, ErrorDetector, ErrorPrinter; |
| import 'package:vm/target/dart_runner.dart' show DartRunnerTarget; |
| import 'package:vm/target/flutter_runner.dart' show FlutterRunnerTarget; |
| |
| ArgParser _argParser = new ArgParser(allowTrailingOptions: true) |
| ..addOption('sdk-root', help: 'Path to runner_patched_sdk') |
| ..addOption('multi-root-scheme', help: 'The URI scheme for the multi root') |
| ..addMultiOption('multi-root', |
| help: 'A base for the multi root. Can be given multiple times to build an overlay') |
| ..addFlag('aot', |
| help: 'Run compiler in AOT mode (enables whole-program transformations)', |
| defaultsTo: false) |
| ..addFlag('tfa', |
| help: 'Run global type flow analysis', defaultsTo: false) |
| ..addFlag('drop-ast', |
| help: 'Drop AST for members with bytecode', defaultsTo: false) |
| ..addOption('component-name', help: 'Name of the component') |
| ..addFlag('embed-sources', |
| help: 'Embed sources in the output dill file', defaultsTo: false) |
| ..addFlag('gen-bytecode', help: 'Generate bytecode', defaultsTo: false) |
| ..addOption('depfile', help: 'Path to output Ninja depfile') |
| ..addOption('manifest', help: 'Path to output Fuchsia package manifest') |
| ..addOption('output', help: 'Path to output dill file') |
| ..addOption('packages', help: 'Path to .packages file') |
| ..addOption('target', help: 'Kernel target name') |
| ..addFlag('verbose', help: 'Run in verbose mode'); |
| |
| String _usage = ''' |
| Usage: compiler [options] input.dart |
| |
| Options: |
| ${_argParser.usage} |
| '''; |
| |
| Uri _ensureFolderPath(String path) { |
| String uriPath = new Uri.file(path).toString(); |
| if (!uriPath.endsWith('/')) { |
| uriPath = '$uriPath/'; |
| } |
| return Uri.base.resolve(uriPath); |
| } |
| |
| Future<Uri> _asFileUri(FileSystem fileSystem, Uri uri) async { |
| FileSystemEntity fse = fileSystem.entityForUri(uri); |
| if (fse is MultiRootFileSystemEntity) { |
| MultiRootFileSystemEntity mrfse = fse; |
| fse = await mrfse.delegate; |
| } |
| return fse.uri; |
| } |
| |
| Future<void> main(List<String> args) async { |
| ArgResults options; |
| try { |
| options = _argParser.parse(args); |
| if (!options.rest.isNotEmpty) { |
| throw new Exception('Must specify input.dart'); |
| } |
| } on Exception catch (error) { |
| print('ERROR: $error\n'); |
| print(_usage); |
| exitCode = 1; |
| return; |
| } |
| |
| final Uri sdkRoot = _ensureFolderPath(options['sdk-root']); |
| final Uri packagesUri = Uri.base.resolve(options['packages']); |
| final bool aot = options['aot']; |
| final bool tfa = options['tfa']; |
| final bool embedSources = options['embed-sources']; |
| final String targetName = options['target']; |
| final bool genBytecode = options['gen-bytecode']; |
| final bool dropAST = options['drop-ast']; |
| final String componentName = options['component-name']; |
| final bool verbose = options['verbose']; |
| |
| Uri mainUri = Uri.parse(options.rest[0]); |
| |
| Uri platformKernelDill = sdkRoot.resolve('platform_strong.dill'); |
| |
| TargetFlags targetFlags = new TargetFlags(syncAsync: true); |
| Target target; |
| switch (targetName) { |
| case 'dart_runner': |
| target = new DartRunnerTarget(targetFlags); |
| break; |
| case 'flutter_runner': |
| target = new FlutterRunnerTarget(targetFlags); |
| break; |
| default: |
| print('Unknown target: $targetName'); |
| exitCode = 1; |
| return; |
| } |
| |
| FileSystem fileSystem = StandardFileSystem.instance; |
| String multiRootScheme = options['multi-root-scheme']; |
| if (multiRootScheme != null) { |
| final rootUris = <Uri>[]; |
| for (String root in options['multi-root']) { |
| rootUris.add(Uri.base.resolveUri(new Uri.file(root))); |
| } |
| final multiRootFS = new MultiRootFileSystem(multiRootScheme, rootUris, fileSystem); |
| fileSystem = new SchemeBasedFileSystem({ |
| 'file': fileSystem, |
| 'data': fileSystem, |
| '': fileSystem, |
| multiRootScheme: multiRootFS, |
| }); |
| } |
| |
| // fuchsia-source:///x/y/main.dart -> file:///a/b/x/y/main.dart |
| String mainUriString = (await _asFileUri(fileSystem, mainUri)).toString(); |
| // file:///a/b/x/y/main.dart -> package:x.y/main.dart |
| for (var line in await new File( |
| (await _asFileUri(fileSystem, packagesUri)).toFilePath()) |
| .readAsLines()) { |
| var colon = line.indexOf(':'); |
| if (colon == -1) |
| continue; |
| var packageName = line.substring(0, colon); |
| String packagePath; |
| try { |
| packagePath = (await _asFileUri( |
| fileSystem, packagesUri.resolve(line.substring(colon + 1)))) |
| .toString(); |
| } on FileSystemException { |
| // Can't resolve package path. |
| continue; |
| } |
| if (mainUriString.startsWith(packagePath)) { |
| mainUri = Uri.parse('package:$packageName/${mainUriString.substring(packagePath.length)}'); |
| break; |
| } |
| } |
| |
| final errorPrinter = new ErrorPrinter(); |
| final errorDetector = new ErrorDetector(previousErrorHandler: errorPrinter); |
| final CompilerOptions compilerOptions = new CompilerOptions() |
| ..sdkSummary = platformKernelDill |
| ..fileSystem = fileSystem |
| ..packagesFileUri = packagesUri |
| ..target = target |
| ..embedSourceText = embedSources |
| ..onDiagnostic = (DiagnosticMessage m) { |
| printDiagnosticMessage(m, stderr.writeln); |
| errorDetector(m); |
| } |
| ..verbose = verbose; |
| |
| if (aot) { |
| // Link in the platform to produce an output with no external references. |
| compilerOptions.linkedDependencies = <Uri>[platformKernelDill]; |
| } |
| |
| Component component = await compileToKernel( |
| mainUri, |
| compilerOptions, |
| aot: aot, |
| useGlobalTypeFlowAnalysis: tfa, |
| genBytecode: genBytecode, |
| dropAST: dropAST, |
| ); |
| |
| errorPrinter.printCompilationMessages(mainUri); |
| if (errorDetector.hasCompilationErrors || (component == null)) { |
| exitCode = 1; |
| return; |
| } |
| |
| // Single-file output. |
| final String kernelBinaryFilename = options['output']; |
| if (kernelBinaryFilename != null) { |
| final IOSink sink = new File(kernelBinaryFilename).openWrite(); |
| final BinaryPrinter printer = new LimitedBinaryPrinter(sink, |
| (Library lib) => true, false /* excludeUriToSource */); |
| printer.writeComponentFile(component); |
| await sink.close(); |
| |
| final String depfile = options['depfile']; |
| if (depfile != null) { |
| await writeDepfile(fileSystem, component, kernelBinaryFilename, depfile); |
| } |
| } |
| |
| // Multiple-file output. |
| final String manifestFilename = options['manifest']; |
| if (manifestFilename != null) { |
| await writePackages(component, kernelBinaryFilename, manifestFilename, componentName); |
| } |
| } |
| |
| String escapePath(String path) { |
| return path.replaceAll('\\', '\\\\').replaceAll(' ', '\\ '); |
| } |
| |
| List<Uri> getDependencies(Component component) { |
| var deps = <Uri>[]; |
| for (Library lib in component.libraries) { |
| if (lib.importUri.scheme == 'dart') { |
| continue; |
| } |
| deps.add(lib.fileUri); |
| for (LibraryPart part in lib.parts) { |
| final Uri fileUri = lib.fileUri.resolve(part.partUri); |
| deps.add(fileUri); |
| } |
| } |
| return deps; |
| } |
| |
| // https://ninja-build.org/manual.html#_depfile |
| Future<void> writeDepfile(FileSystem fileSystem, |
| Component component, String output, String depfile) async { |
| var file = new File(depfile).openWrite(); |
| file.write(escapePath(output)); |
| file.write(':'); |
| for (Uri dep in getDependencies(component)) { |
| Uri uri = await _asFileUri(fileSystem, dep); |
| file.write(' '); |
| file.write(escapePath(uri.toFilePath())); |
| } |
| file.write('\n'); |
| await file.close(); |
| } |
| |
| Future writePackages(Component component, String output, String packageManifestFilename, String componentName) async { |
| // Package sharing: make the encoding not depend on the order in which parts |
| // of a package are loaded. |
| component.libraries.sort((Library a, Library b) { |
| return a.importUri.toString().compareTo(b.importUri.toString()); |
| }); |
| for (Library lib in component.libraries) { |
| lib.additionalExports.sort((Reference a, Reference b) { |
| return a.canonicalName.toString().compareTo(b.canonicalName.toString()); |
| }); |
| } |
| |
| final IOSink packageManifest = new File(packageManifestFilename).openWrite(); |
| final String kernelListFilename = '$packageManifestFilename.dilplist'; |
| final IOSink kernelList = new File(kernelListFilename).openWrite(); |
| |
| final packages = new Set<String>(); |
| for (Library lib in component.libraries) { |
| packages.add(packageFor(lib)); |
| } |
| packages.remove('main'); |
| packages.remove(null); |
| |
| for (String package in packages) { |
| await writePackage(component, output, package, packageManifest, kernelList, componentName); |
| } |
| await writePackage(component, output, 'main', packageManifest, kernelList, componentName); |
| |
| packageManifest.write('data/$componentName/app.dilplist=$kernelListFilename\n'); |
| await packageManifest.close(); |
| await kernelList.close(); |
| } |
| |
| Future writePackage(Component component, String output, String package, |
| IOSink packageManifest, IOSink kernelList, String componentName) async { |
| final String filenameInPackage = '$package.dilp'; |
| final String filenameInBuild = '$output-$package.dilp'; |
| final IOSink sink = new File(filenameInBuild).openWrite(); |
| |
| var main = component.mainMethod; |
| if (package != 'main') { |
| // Package sharing: remove the information about the importer from package |
| // dilps. |
| component.mainMethod = null; |
| } |
| final BinaryPrinter printer = new LimitedBinaryPrinter(sink, |
| (lib) => packageFor(lib) == package, false /* excludeUriToSource */); |
| printer.writeComponentFile(component); |
| component.mainMethod = main; |
| |
| await sink.close(); |
| |
| packageManifest.write('data/$componentName/$filenameInPackage=$filenameInBuild\n'); |
| kernelList.write('$filenameInPackage\n'); |
| } |
| |
| String packageFor(Library lib) { |
| // Core libraries are not written into any dilp. |
| if (lib.isExternal) |
| return null; |
| |
| // Packages are written into their own dilp. |
| 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 dilp. |
| return 'main'; |
| } |