build_aot.dart 17.6 KB
Newer Older
1 2 3 4 5 6
// 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';

7
import '../android/android_sdk.dart';
8
import '../artifacts.dart';
9
import '../base/build.dart';
10
import '../base/common.dart';
11
import '../base/file_system.dart';
12
import '../base/logger.dart';
13
import '../base/process.dart';
14
import '../base/process_manager.dart';
15
import '../base/utils.dart';
16
import '../build_info.dart';
17
import '../compile.dart';
18
import '../dart/package_map.dart';
19
import '../globals.dart';
20
import '../resident_runner.dart';
21
import '../runner/flutter_command.dart';
22
import 'build.dart';
23 24 25

// Files generated by the ahead-of-time snapshot builder.
const List<String> kAotSnapshotFiles = const <String>[
26
  'vm_snapshot_data', 'vm_snapshot_instr', 'isolate_snapshot_data', 'isolate_snapshot_instr',
27 28
];

29
class BuildAotCommand extends BuildSubCommand {
30
  BuildAotCommand({bool verboseHelp: false}) {
31 32 33
    usesTargetOption();
    addBuildModeFlags();
    usesPubOption();
34
    argParser
35
      ..addOption('output-dir', defaultsTo: getAotBuildDirectory())
36 37
      ..addOption('target-platform',
        defaultsTo: 'android-arm',
38
        allowed: <String>['android-arm', 'android-arm64', 'ios']
39
      )
40
      ..addFlag('interpreter')
41
      ..addFlag('quiet', defaultsTo: false)
42 43 44 45 46
      ..addFlag('preview-dart-2',
        defaultsTo: true,
        hide: !verboseHelp,
        help: 'Preview Dart 2.0 functionality.',
      )
47
      ..addMultiOption(FlutterOptions.kExtraFrontEndOptions,
48 49 50
        splitCommas: true,
        hide: true,
      )
51
      ..addMultiOption(FlutterOptions.kExtraGenSnapshotOptions,
52 53
        splitCommas: true,
        hide: true,
54
      )
55 56 57
      ..addFlag('prefer-shared-library',
        negatable: false,
        help: 'Whether to prefer compiling to a *.so file (android only).');
58 59 60 61 62 63
  }

  @override
  final String name = 'aot';

  @override
64
  final String description = "Build an ahead-of-time compiled snapshot of your app's Dart code.";
65 66

  @override
67
  Future<Null> runCommand() async {
68
    await super.runCommand();
69 70
    final String targetPlatform = argResults['target-platform'];
    final TargetPlatform platform = getTargetPlatformForName(targetPlatform);
71 72
    if (platform == null)
      throwToolExit('Unknown platform: $targetPlatform');
73

74
    final String typeName = artifacts.getEngineType(platform, getBuildMode());
75 76 77 78 79
    Status status;
    if (!argResults['quiet']) {
      status = logger.startProgress('Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...',
          expectSlowOperation: true);
    }
80
    final String outputPath = await buildAotSnapshot(
81
      findMainDartFile(targetFile),
82
      platform,
83
      getBuildMode(),
84
      outputPath: argResults['output-dir'],
85 86
      interpreter: argResults['interpreter'],
      previewDart2: argResults['preview-dart-2'],
87 88
      extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
      extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
89
      preferSharedLibrary: argResults['prefer-shared-library'],
90
    );
91
    status?.stop();
92

93
    if (outputPath == null)
94
      throwToolExit(null);
95

96 97 98 99 100 101
    final String builtMessage = 'Built to $outputPath${fs.path.separator}.';
    if (argResults['quiet']) {
      printTrace(builtMessage);
    } else {
      printStatus(builtMessage);
    }
102 103 104
  }
}

Adam Barth's avatar
Adam Barth committed
105
String _getPackagePath(PackageMap packageMap, String package) {
106
  return fs.path.dirname(packageMap.map[package].toFilePath());
107 108
}

109
/// Build an AOT snapshot. Return null (and log to `printError`) if the method
110
/// fails.
111
Future<String> buildAotSnapshot(
112
  String mainPath,
113
  TargetPlatform platform,
114
  BuildMode buildMode, {
115
  String outputPath,
116 117
  bool interpreter: false,
  bool previewDart2: false,
118 119
  List<String> extraFrontEndOptions,
  List<String> extraGenSnapshotOptions,
120
  bool preferSharedLibrary: false,
121
}) async {
122
  outputPath ??= getAotBuildDirectory();
123 124 125 126 127 128
  try {
    return _buildAotSnapshot(
      mainPath,
      platform,
      buildMode,
      outputPath: outputPath,
129 130
      interpreter: interpreter,
      previewDart2: previewDart2,
131 132
      extraFrontEndOptions: extraFrontEndOptions,
      extraGenSnapshotOptions: extraGenSnapshotOptions,
133
      preferSharedLibrary: preferSharedLibrary,
134 135 136 137 138 139 140 141
    );
  } on String catch (error) {
    // Catch the String exceptions thrown from the `runCheckedSync` methods below.
    printError(error);
    return null;
  }
}

142
// TODO(cbracken): split AOT and Assembly AOT snapshotting logic and migrate to Snapshotter class.
143
Future<String> _buildAotSnapshot(
144 145 146
  String mainPath,
  TargetPlatform platform,
  BuildMode buildMode, {
147
  String outputPath,
148 149
  bool interpreter: false,
  bool previewDart2: false,
150 151
  List<String> extraFrontEndOptions,
  List<String> extraGenSnapshotOptions,
152
  bool preferSharedLibrary: false,
153
}) async {
154
  outputPath ??= getAotBuildDirectory();
155
  if (!isAotBuildMode(buildMode) && !interpreter) {
156
    printError('${toTitleCase(getModeName(buildMode))} mode does not support AOT compilation.');
157 158 159
    return null;
  }

160 161 162
  if (!(platform == TargetPlatform.android_arm ||
        platform == TargetPlatform.android_arm64 ||
        platform == TargetPlatform.ios)) {
163 164 165 166
    printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
    return null;
  }

167
  final String genSnapshot = artifacts.getArtifactPath(Artifact.genSnapshot, platform, buildMode);
168

169
  final Directory outputDir = fs.directory(outputPath);
170
  outputDir.createSync(recursive: true);
171 172 173 174
  final String vmSnapshotData = fs.path.join(outputDir.path, 'vm_snapshot_data');
  final String vmSnapshotInstructions = fs.path.join(outputDir.path, 'vm_snapshot_instr');
  final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data');
  final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr');
175
  final String dependencies = fs.path.join(outputDir.path, 'snapshot.d');
176 177 178 179 180 181 182 183 184 185
  final String assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
  final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
  final String assemblySo = fs.path.join(outputDir.path, 'app.so');
  final bool compileToSharedLibrary =
      preferSharedLibrary && androidSdk.ndkCompiler != null;

  if (preferSharedLibrary && !compileToSharedLibrary) {
    printStatus(
        'Could not find NDK compiler. Not building in shared library mode');
  }
186

187 188 189 190 191
  final String vmEntryPoints = artifacts.getArtifactPath(
    Artifact.dartVmEntryPointsTxt,
    platform,
    buildMode,
  );
192
  final String ioEntryPoints = artifacts.getArtifactPath(Artifact.dartIoEntriesTxt, platform, buildMode);
193

194 195 196 197 198 199 200 201
  final List<String> entryPointsJsonFiles = <String>[];
  if (previewDart2 && !interpreter) {
    entryPointsJsonFiles.addAll(<String>[
      artifacts.getArtifactPath(Artifact.entryPointsJson, platform, buildMode),
      artifacts.getArtifactPath(Artifact.entryPointsExtraJson, platform, buildMode),
    ]);
  }

202 203
  final PackageMap packageMap = new PackageMap(PackageMap.globalPackagesPath);
  final String packageMapError = packageMap.checkValid();
204 205
  if (packageMapError != null) {
    printError(packageMapError);
206 207 208
    return null;
  }

Adam Barth's avatar
Adam Barth committed
209 210
  final String skyEnginePkg = _getPackagePath(packageMap, 'sky_engine');
  final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart');
211
  final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart');
212

213
  final List<String> inputPaths = <String>[
214
    vmEntryPoints,
215
    ioEntryPoints,
216 217
    uiPath,
    vmServicePath,
218
    mainPath,
219
  ];
220

221 222
  inputPaths.addAll(entryPointsJsonFiles);

223 224
  final Set<String> outputPaths = new Set<String>();

225 226 227 228 229
  // These paths are used only on iOS.
  String snapshotDartIOS;

  switch (platform) {
    case TargetPlatform.android_arm:
230
    case TargetPlatform.android_arm64:
231 232
    case TargetPlatform.android_x64:
    case TargetPlatform.android_x86:
233 234 235 236 237 238 239 240
      if (compileToSharedLibrary) {
        outputPaths.add(assemblySo);
      } else {
        outputPaths.addAll(<String>[
          vmSnapshotData,
          isolateSnapshotData,
        ]);
      }
241 242
      break;
    case TargetPlatform.ios:
243
      snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode);
244
      inputPaths.add(snapshotDartIOS);
245 246 247
      break;
    case TargetPlatform.darwin_x64:
    case TargetPlatform.linux_x64:
248
    case TargetPlatform.windows_x64:
249
    case TargetPlatform.fuchsia:
250 251 252
      assert(false);
  }

253 254 255
  final Iterable<String> missingInputs = inputPaths.where((String p) => !fs.isFileSync(p));
  if (missingInputs.isNotEmpty) {
    printError('Missing input files: $missingInputs');
256 257
    return null;
  }
258 259 260 261
  if (!processManager.canRun(genSnapshot)) {
    printError('Cannot locate the genSnapshot executable');
    return null;
  }
262

263
  final List<String> genSnapshotCmd = <String>[
264
    genSnapshot,
265
    '--await_is_keyword',
266 267
    '--vm_snapshot_data=$vmSnapshotData',
    '--isolate_snapshot_data=$isolateSnapshotData',
268
    '--packages=${packageMap.packagesPath}',
269
    '--url_mapping=dart:ui,$uiPath',
270
    '--url_mapping=dart:vmservice_io,$vmServicePath',
271
    '--print_snapshot_sizes',
272
    '--dependencies=$dependencies',
273
    '--causal_async_stacks',
274 275
  ];

276
  if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty)
277
    printTrace('Extra front-end options: $extraFrontEndOptions');
278 279

  if ((extraGenSnapshotOptions != null) && extraGenSnapshotOptions.isNotEmpty) {
280
    printTrace('Extra gen-snapshot options: $extraGenSnapshotOptions');
281 282 283
    genSnapshotCmd.addAll(extraGenSnapshotOptions);
  }

284
  if (!interpreter) {
285 286
    genSnapshotCmd.add('--embedder_entry_points_manifest=$vmEntryPoints');
    genSnapshotCmd.add('--embedder_entry_points_manifest=$ioEntryPoints');
287 288
  }

289 290 291 292 293 294 295 296 297
  // iOS symbols used to load snapshot data in the engine.
  const String kVmSnapshotData = 'kDartVmSnapshotData';
  const String kIsolateSnapshotData = 'kDartIsolateSnapshotData';

  // iOS snapshot generated files, compiled object files.
  final String kVmSnapshotDataC = fs.path.join(outputDir.path, '$kVmSnapshotData.c');
  final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c');
  final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o');
  final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o');
298
  final String kApplicationKernelPath = fs.path.join(getBuildDirectory(), 'app.dill');
299

300 301
  switch (platform) {
    case TargetPlatform.android_arm:
302
    case TargetPlatform.android_arm64:
303 304
    case TargetPlatform.android_x64:
    case TargetPlatform.android_x86:
305 306 307 308 309 310 311 312 313 314 315
      if (compileToSharedLibrary) {
        genSnapshotCmd.add('--snapshot_kind=app-aot-assembly');
        genSnapshotCmd.add('--assembly=$assembly');
        outputPaths.add(assemblySo);
      } else {
        genSnapshotCmd.addAll(<String>[
          '--snapshot_kind=app-aot-blobs',
          '--vm_snapshot_instructions=$vmSnapshotInstructions',
          '--isolate_snapshot_instructions=$isolateSnapshotInstructions',
        ]);
      }
316 317
      if (platform == TargetPlatform.android_arm) {
        genSnapshotCmd.addAll(<String>[
318 319
          '--no-sim-use-hardfp', // Android uses the softfloat ABI.
          '--no-use-integer-division', // Not supported by the Pixel in 32-bit mode.
320 321
        ]);
      }
322 323
      break;
    case TargetPlatform.ios:
324 325 326
      if (interpreter) {
        genSnapshotCmd.add('--snapshot_kind=core');
        genSnapshotCmd.add(snapshotDartIOS);
327 328 329 330
        outputPaths.addAll(<String>[
          kVmSnapshotDataO,
          kIsolateSnapshotDataO,
        ]);
331 332 333
      } else {
        genSnapshotCmd.add('--snapshot_kind=app-aot-assembly');
        genSnapshotCmd.add('--assembly=$assembly');
334
        outputPaths.add(assemblyO);
335
      }
336 337 338
      break;
    case TargetPlatform.darwin_x64:
    case TargetPlatform.linux_x64:
339
    case TargetPlatform.windows_x64:
340
    case TargetPlatform.fuchsia:
341 342 343
      assert(false);
  }

344
  if (buildMode != BuildMode.release) {
345
    genSnapshotCmd.addAll(<String>[
346 347 348 349 350
      '--no-checked',
      '--conditional_directives',
    ]);
  }

351
  final String entryPoint = mainPath;
352
  final SnapshotType snapshotType = new SnapshotType(platform, buildMode);
353 354 355 356 357 358 359
  Future<Fingerprint> makeFingerprint() async {
    final Set<String> snapshotInputPaths = await readDepfile(dependencies)
      ..add(entryPoint)
      ..addAll(outputPaths);
    return Snapshotter.createFingerprint(snapshotType, entryPoint, snapshotInputPaths);
  }

360 361 362 363 364 365 366 367
  final File fingerprintFile = fs.file('$dependencies.fingerprint');
  final List<File> fingerprintFiles = <File>[fingerprintFile, fs.file(dependencies)]
    ..addAll(inputPaths.map(fs.file))
    ..addAll(outputPaths.map(fs.file));
  if (fingerprintFiles.every((File file) => file.existsSync())) {
    try {
      final String json = await fingerprintFile.readAsString();
      final Fingerprint oldFingerprint = new Fingerprint.fromJson(json);
368
      if (oldFingerprint == await makeFingerprint()) {
369 370 371 372 373 374 375 376 377
        printStatus('Skipping AOT snapshot build. Fingerprint match.');
        return outputPath;
      }
    } catch (e) {
      // Log exception and continue, this step is a performance improvement only.
      printTrace('Rebuilding snapshot due to fingerprint check error: $e');
    }
  }

378 379 380 381
  if (previewDart2) {
    mainPath = await compile(
      sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
      mainPath: mainPath,
382
      outputFilePath: kApplicationKernelPath,
383
      depFilePath: dependencies,
384
      extraFrontEndOptions: extraFrontEndOptions,
385
      linkPlatformKernelIn : true,
386 387
      aot : !interpreter,
      entryPointsJsonFiles: entryPointsJsonFiles,
388
      trackWidgetCreation: false,
389
    );
390 391 392 393
    if (mainPath == null) {
      printError('Compiler terminated unexpectedly.');
      return null;
    }
394 395 396 397
    // Write path to frontend_server, since things need to be re-generated when
    // that changes.
    await outputDir.childFile('frontend_server.d')
        .writeAsString('frontend_server.d: ${artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk)}\n');
398

399 400 401 402
    genSnapshotCmd.addAll(<String>[
      '--reify-generic-functions',
      '--strong',
    ]);
403 404
  }

405 406
  genSnapshotCmd.add(mainPath);

407
  final RunResult results = await runAsync(genSnapshotCmd);
408
  if (results.exitCode != 0) {
409 410
    printError('Dart snapshot generator failed with exit code ${results.exitCode}');
    printError(results.toString());
411 412
    return null;
  }
413

414 415 416 417
  // Write path to gen_snapshot, since snapshots have to be re-generated when we roll
  // the Dart SDK.
  await outputDir.childFile('gen_snapshot.d').writeAsString('snapshot.d: $genSnapshot\n');

418
  // On iOS, we use Xcode to compile the snapshot into a dynamic library that the
419 420
  // end-developer can link into their app.
  if (platform == TargetPlatform.ios) {
421
    printStatus('Building App.framework...');
422

423
    final List<String> commonBuildOptions = <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
424

Ryan Macnak's avatar
Ryan Macnak committed
425
    if (interpreter) {
426 427
      await runCheckedAsync(<String>['mv', vmSnapshotData, fs.path.join(outputDir.path, kVmSnapshotData)]);
      await runCheckedAsync(<String>['mv', isolateSnapshotData, fs.path.join(outputDir.path, kIsolateSnapshotData)]);
428

429
      await runCheckedAsync(<String>[
430
        'xxd', '--include', kVmSnapshotData, fs.path.basename(kVmSnapshotDataC)
Ryan Macnak's avatar
Ryan Macnak committed
431
      ], workingDirectory: outputDir.path);
432
      await runCheckedAsync(<String>[
433
        'xxd', '--include', kIsolateSnapshotData, fs.path.basename(kIsolateSnapshotDataC)
Ryan Macnak's avatar
Ryan Macnak committed
434
      ], workingDirectory: outputDir.path);
435

436
      await runCheckedAsync(<String>['xcrun', 'cc']
Ryan Macnak's avatar
Ryan Macnak committed
437
        ..addAll(commonBuildOptions)
438
        ..addAll(<String>['-c', kVmSnapshotDataC, '-o', kVmSnapshotDataO]));
439
      await runCheckedAsync(<String>['xcrun', 'cc']
Ryan Macnak's avatar
Ryan Macnak committed
440
        ..addAll(commonBuildOptions)
441
        ..addAll(<String>['-c', kIsolateSnapshotDataC, '-o', kIsolateSnapshotDataO]));
Ryan Macnak's avatar
Ryan Macnak committed
442
    } else {
443
      await runCheckedAsync(<String>['xcrun', 'cc']
444 445
        ..addAll(commonBuildOptions)
        ..addAll(<String>['-c', assembly, '-o', assemblyO]));
Ryan Macnak's avatar
Ryan Macnak committed
446
    }
447

448 449 450
    final String frameworkDir = fs.path.join(outputDir.path, 'App.framework');
    fs.directory(frameworkDir).createSync(recursive: true);
    final String appLib = fs.path.join(frameworkDir, 'App');
451
    final List<String> linkCommand = <String>['xcrun', 'clang']
452 453 454 455 456
      ..addAll(commonBuildOptions)
      ..addAll(<String>[
        '-dynamiclib',
        '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
        '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
457 458
        '-install_name', '@rpath/App.framework/App',
        '-o', appLib,
459
    ]);
Ryan Macnak's avatar
Ryan Macnak committed
460
    if (interpreter) {
461 462
      linkCommand.add(kVmSnapshotDataO);
      linkCommand.add(kIsolateSnapshotDataO);
Ryan Macnak's avatar
Ryan Macnak committed
463
    } else {
464
      linkCommand.add(assemblyO);
Ryan Macnak's avatar
Ryan Macnak committed
465
    }
466
    await runCheckedAsync(linkCommand);
467 468 469
  } else {
    if (compileToSharedLibrary) {
      // A word of warning: Instead of compiling via two steps, to a .o file and
470
      // then to a .so file we use only one command. When using two commands
471
      // gcc will end up putting a .eh_frame and a .debug_frame into the shared
472
      // library. Without stripping .debug_frame afterwards, unwinding tools
473 474 475 476 477 478 479
      // based upon libunwind use just one and ignore the contents of the other
      // (which causes it to not look into the other section and therefore not
      // find the correct unwinding information).
      await runCheckedAsync(<String>[androidSdk.ndkCompiler]
          ..addAll(androidSdk.ndkCompilerArgs)
          ..addAll(<String>[ '-shared', '-nostdlib', '-o', assemblySo, assembly ]));
    }
480 481
  }

482
  // Compute and record build fingerprint.
483
  try {
484
    final Fingerprint fingerprint = await makeFingerprint();
485
    await fingerprintFile.writeAsString(fingerprint.toJson());
486 487
  } catch (e, s) {
    // Log exception and continue, this step is a performance improvement only.
488
    printStatus('Error during AOT snapshot fingerprinting: $e\n$s');
489 490
  }

491 492
  return outputPath;
}