build_aot.dart 11.1 KB
Newer Older
1 2 3 4 5 6 7 8 9
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:io';

import 'package:path/path.dart' as path;

10
import '../base/logger.dart';
11
import '../base/process.dart';
12
import '../base/utils.dart';
13
import '../build_info.dart';
14
import '../dart/package_map.dart';
15
import '../globals.dart';
16
import '../run.dart';
17
import 'build.dart';
18 19 20 21 22 23 24 25

const String _kDefaultAotOutputDir = 'build/aot';

// Files generated by the ahead-of-time snapshot builder.
const List<String> kAotSnapshotFiles = const <String>[
  'snapshot_aot_instr', 'snapshot_aot_isolate', 'snapshot_aot_rodata', 'snapshot_aot_vmisolate',
];

26
class BuildAotCommand extends BuildSubCommand {
27 28 29 30
  BuildAotCommand() {
    usesTargetOption();
    addBuildModeFlags();
    usesPubOption();
31 32 33 34 35
    argParser
      ..addOption('output-dir', defaultsTo: _kDefaultAotOutputDir)
      ..addOption('target-platform',
        defaultsTo: 'android-arm',
        allowed: <String>['android-arm', 'ios']
36 37
      )
      ..addFlag('interpreter');
38 39 40 41 42 43
  }

  @override
  final String name = 'aot';

  @override
44
  final String description = "Build an ahead-of-time compiled snapshot of your app's Dart code.";
45 46 47

  @override
  Future<int> runInProject() async {
48
    await super.runInProject();
49 50 51 52 53 54
    String targetPlatform = argResults['target-platform'];
    TargetPlatform platform = getTargetPlatformForName(targetPlatform);
    if (platform == null) {
      printError('Unknown platform: $targetPlatform');
      return 1;
    }
55 56 57 58

    String typeName = path.basename(tools.getEngineArtifactsDirectory(platform, getBuildMode()).path);
    Status status = logger.startProgress('Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...');
    String outputPath = await buildAotSnapshot(
59
      findMainDartFile(targetFile),
60
      platform,
61
      getBuildMode(),
62 63
      outputPath: argResults['output-dir'],
      interpreter: argResults['interpreter']
64
    );
65 66
    status.stop(showElapsedTime: true);

67 68 69
    if (outputPath == null)
      return 1;

70
    printStatus('Built to $outputPath${Platform.pathSeparator}.');
71 72 73 74
    return 0;
  }
}

75 76
String _getSdkExtensionPath(PackageMap packageMap, String package) {
  return path.dirname(packageMap.map[package].toFilePath());
77 78
}

79 80
/// Build an AOT snapshot. Return `null` (and log to `printError`) if the method
/// fails.
81
Future<String> buildAotSnapshot(
82
  String mainPath,
83
  TargetPlatform platform,
84
  BuildMode buildMode, {
85 86
  String outputPath: _kDefaultAotOutputDir,
  bool interpreter: false
87
}) async {
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
  try {
    return _buildAotSnapshot(
      mainPath,
      platform,
      buildMode,
      outputPath: outputPath,
      interpreter: interpreter
    );
  } on String catch (error) {
    // Catch the String exceptions thrown from the `runCheckedSync` methods below.
    printError(error);
    return null;
  }
}

103
Future<String> _buildAotSnapshot(
104 105 106 107 108
  String mainPath,
  TargetPlatform platform,
  BuildMode buildMode, {
  String outputPath: _kDefaultAotOutputDir,
  bool interpreter: false
109
}) async {
110
  if (!isAotBuildMode(buildMode) && !interpreter) {
111
    printError('${toTitleCase(getModeName(buildMode))} mode does not support AOT compilation.');
112 113 114
    return null;
  }

115 116 117 118 119
  if (platform != TargetPlatform.android_arm && platform != TargetPlatform.ios) {
    printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
    return null;
  }

120
  String entryPointsDir, dartEntryPointsDir, genSnapshot;
121 122 123 124

  String engineSrc = tools.engineSrcPath;
  if (engineSrc != null) {
    entryPointsDir  = path.join(engineSrc, 'sky', 'engine', 'bindings');
125
    dartEntryPointsDir = path.join(engineSrc, 'dart', 'runtime', 'bin');
126 127 128 129 130 131 132
    String engineOut = tools.getEngineArtifactsDirectory(platform, buildMode).path;
    if (platform == TargetPlatform.ios) {
      genSnapshot = path.join(engineOut, 'clang_x64', 'gen_snapshot');
    } else {
      String host32BitToolchain = getCurrentHostPlatform() == HostPlatform.darwin_x64 ? 'clang_i386' : 'clang_x86';
      genSnapshot = path.join(engineOut, host32BitToolchain, 'gen_snapshot');
    }
133
  } else {
134
    String artifactsDir = tools.getEngineArtifactsDirectory(platform, buildMode).path;
135
    entryPointsDir = artifactsDir;
136
    dartEntryPointsDir = entryPointsDir;
137 138 139 140 141 142
    if (platform == TargetPlatform.ios) {
      genSnapshot = path.join(artifactsDir, 'gen_snapshot');
    } else {
      String hostToolsDir = path.join(artifactsDir, getNameForHostPlatform(getCurrentHostPlatform()));
      genSnapshot = path.join(hostToolsDir, 'gen_snapshot');
    }
143
  }
144 145 146 147 148 149 150 151

  Directory outputDir = new Directory(outputPath);
  outputDir.createSync(recursive: true);
  String vmIsolateSnapshot = path.join(outputDir.path, 'snapshot_aot_vmisolate');
  String isolateSnapshot = path.join(outputDir.path, 'snapshot_aot_isolate');
  String instructionsBlob = path.join(outputDir.path, 'snapshot_aot_instr');
  String rodataBlob = path.join(outputDir.path, 'snapshot_aot_rodata');

152
  String vmEntryPoints = path.join(entryPointsDir, 'dart_vm_entry_points.txt');
153
  String ioEntryPoints = path.join(dartEntryPointsDir, 'dart_io_entries.txt');
154

155 156 157 158
  PackageMap packageMap = new PackageMap(PackageMap.globalPackagesPath);
  String packageMapError = packageMap.checkValid();
  if (packageMapError != null) {
    printError(packageMapError);
159 160 161
    return null;
  }

162
  String mojoPkg = _getSdkExtensionPath(packageMap, 'mojo');
Adam Barth's avatar
Adam Barth committed
163
  String mojoInternalPath = path.join(mojoPkg, 'sdk_ext', 'internal.dart');
164

165
  String skyEnginePkg = _getSdkExtensionPath(packageMap, 'sky_engine');
Adam Barth's avatar
Adam Barth committed
166
  String uiPath = path.join(skyEnginePkg, 'dart_ui', 'ui.dart');
167
  String jniPath = path.join(skyEnginePkg, 'dart_jni', 'jni.dart');
Adam Barth's avatar
Adam Barth committed
168
  String vmServicePath = path.join(skyEnginePkg, 'sdk_ext', 'dart', 'runtime', 'bin', 'vmservice', 'vmservice_io.dart');
169

170
  List<String> filePaths = <String>[
171 172
    genSnapshot,
    vmEntryPoints,
173
    ioEntryPoints,
174 175
    mojoInternalPath,
    uiPath,
176
    jniPath,
177
    vmServicePath,
178
  ];
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

  // These paths are used only on Android.
  String vmEntryPointsAndroid;

  // These paths are used only on iOS.
  String snapshotDartIOS;
  String assembly;

  switch (platform) {
    case TargetPlatform.android_arm:
    case TargetPlatform.android_x64:
    case TargetPlatform.android_x86:
      vmEntryPointsAndroid = path.join(entryPointsDir, 'dart_vm_entry_points_android.txt');
      filePaths.addAll(<String>[
        vmEntryPointsAndroid,
      ]);
      break;
    case TargetPlatform.ios:
      snapshotDartIOS = path.join(entryPointsDir, 'snapshot.dart');
      assembly = path.join(outputDir.path, 'snapshot_assembly.S');
      filePaths.addAll(<String>[
        snapshotDartIOS,
      ]);
      break;
    case TargetPlatform.darwin_x64:
    case TargetPlatform.linux_x64:
      assert(false);
  }

208 209 210 211 212 213
  List<String> missingFiles = filePaths.where((String p) => !FileSystemEntity.isFileSync(p)).toList();
  if (missingFiles.isNotEmpty) {
    printError('Missing files: $missingFiles');
    return null;
  }

214
  List<String> genSnapshotCmd = <String>[
215 216 217
    genSnapshot,
    '--vm_isolate_snapshot=$vmIsolateSnapshot',
    '--isolate_snapshot=$isolateSnapshot',
218
    '--packages=${packageMap.packagesPath}',
219 220
    '--url_mapping=dart:mojo.internal,$mojoInternalPath',
    '--url_mapping=dart:ui,$uiPath',
221
    '--url_mapping=dart:jni,$jniPath',
222 223 224
    '--url_mapping=dart:vmservice_sky,$vmServicePath',
  ];

225
  if (!interpreter) {
226 227
    genSnapshotCmd.add('--embedder_entry_points_manifest=$vmEntryPoints');
    genSnapshotCmd.add('--embedder_entry_points_manifest=$ioEntryPoints');
228 229
  }

230 231 232 233 234 235 236 237 238 239 240 241
  switch (platform) {
    case TargetPlatform.android_arm:
    case TargetPlatform.android_x64:
    case TargetPlatform.android_x86:
      genSnapshotCmd.addAll(<String>[
        '--rodata_blob=$rodataBlob',
        '--instructions_blob=$instructionsBlob',
        '--embedder_entry_points_manifest=$vmEntryPointsAndroid',
        '--no-sim-use-hardfp',
      ]);
      break;
    case TargetPlatform.ios:
242
      genSnapshotCmd.add(interpreter ? snapshotDartIOS : '--assembly=$assembly');
243 244 245 246 247 248
      break;
    case TargetPlatform.darwin_x64:
    case TargetPlatform.linux_x64:
      assert(false);
  }

249
  if (buildMode != BuildMode.release) {
250
    genSnapshotCmd.addAll(<String>[
251 252 253 254 255 256 257
      '--no-checked',
      '--conditional_directives',
    ]);
  }

  genSnapshotCmd.add(mainPath);

258 259
  RunResult results = await runAsync(genSnapshotCmd);
  if (results.exitCode != 0) {
260 261
    printError('Dart snapshot generator failed with exit code ${results.exitCode}');
    printError(results.toString());
262 263
    return null;
  }
264

265
  // On iOS, we use Xcode to compile the snapshot into a dynamic library that the
266 267
  // end-developer can link into their app.
  if (platform == TargetPlatform.ios) {
268
    printStatus('Building app.dylib...');
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290

    // These names are known to from the engine.
    const String kDartVmIsolateSnapshotBuffer = 'kDartVmIsolateSnapshotBuffer';
    const String kDartIsolateSnapshotBuffer = 'kDartIsolateSnapshotBuffer';

    runCheckedSync(<String>['mv', vmIsolateSnapshot, path.join(outputDir.path, kDartVmIsolateSnapshotBuffer)]);
    runCheckedSync(<String>['mv', isolateSnapshot, path.join(outputDir.path, kDartIsolateSnapshotBuffer)]);

    String kDartVmIsolateSnapshotBufferC = path.join(outputDir.path, '$kDartVmIsolateSnapshotBuffer.c');
    String kDartIsolateSnapshotBufferC = path.join(outputDir.path, '$kDartIsolateSnapshotBuffer.c');

    runCheckedSync(<String>[
      'xxd', '--include', kDartVmIsolateSnapshotBuffer, path.basename(kDartVmIsolateSnapshotBufferC)
    ], workingDirectory: outputDir.path);
    runCheckedSync(<String>[
      'xxd', '--include', kDartIsolateSnapshotBuffer, path.basename(kDartIsolateSnapshotBufferC)
    ], workingDirectory: outputDir.path);

    String assemblyO = path.join(outputDir.path, 'snapshot_assembly.o');
    String kDartVmIsolateSnapshotBufferO = path.join(outputDir.path, '$kDartVmIsolateSnapshotBuffer.o');
    String kDartIsolateSnapshotBufferO = path.join(outputDir.path, '$kDartIsolateSnapshotBuffer.o');

291
    List<String> commonBuildOptions = <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
292
    if (!interpreter)
293 294 295 296 297 298 299 300 301
      runCheckedSync(<String>['xcrun', 'cc']
        ..addAll(commonBuildOptions)
        ..addAll(<String>['-c', assembly, '-o', assemblyO]));
    runCheckedSync(<String>['xcrun', 'cc']
      ..addAll(commonBuildOptions)
      ..addAll(<String>['-c', kDartVmIsolateSnapshotBufferC, '-o', kDartVmIsolateSnapshotBufferO]));
    runCheckedSync(<String>['xcrun', 'cc']
      ..addAll(commonBuildOptions)
      ..addAll(<String>['-c', kDartIsolateSnapshotBufferC, '-o', kDartIsolateSnapshotBufferO]));
302

303
    String appSo = path.join(outputDir.path, 'app.dylib');
304

305 306 307 308 309 310
    List<String> linkCommand = <String>['xcrun', 'clang']
      ..addAll(commonBuildOptions)
      ..addAll(<String>[
        '-dynamiclib',
        '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
        '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
311
        '-install_name', '@rpath/app.dylib',
312 313 314 315
        '-o', appSo,
        kDartVmIsolateSnapshotBufferO,
        kDartIsolateSnapshotBufferO,
    ]);
316
    if (!interpreter)
317 318
      linkCommand.add(assemblyO);
    runCheckedSync(linkCommand);
319 320
  }

321 322
  return outputPath;
}