build_aot.dart 13.5 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 '../artifacts.dart';
8
import '../base/build.dart';
9
import '../base/common.dart';
10
import '../base/file_system.dart';
11
import '../base/logger.dart';
12
import '../base/process.dart';
13
import '../base/process_manager.dart';
14
import '../base/utils.dart';
15
import '../build_info.dart';
16
import '../compile.dart';
17
import '../dart/package_map.dart';
18
import '../globals.dart';
19
import '../resident_runner.dart';
20
import 'build.dart';
21 22 23

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

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

  @override
  final String name = 'aot';

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

  @override
50
  Future<Null> runCommand() async {
51
    await super.runCommand();
52 53
    final String targetPlatform = argResults['target-platform'];
    final TargetPlatform platform = getTargetPlatformForName(targetPlatform);
54 55
    if (platform == null)
      throwToolExit('Unknown platform: $targetPlatform');
56

57
    final String typeName = artifacts.getEngineType(platform, getBuildMode());
58 59 60 61 62
    Status status;
    if (!argResults['quiet']) {
      status = logger.startProgress('Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...',
          expectSlowOperation: true);
    }
63
    final String outputPath = await buildAotSnapshot(
64
      findMainDartFile(targetFile),
65
      platform,
66
      getBuildMode(),
67
      outputPath: argResults['output-dir'],
68 69
      interpreter: argResults['interpreter'],
      previewDart2: argResults['preview-dart-2'],
70
    );
71
    status?.stop();
72

73
    if (outputPath == null)
74
      throwToolExit(null);
75

76 77 78 79 80 81
    final String builtMessage = 'Built to $outputPath${fs.path.separator}.';
    if (argResults['quiet']) {
      printTrace(builtMessage);
    } else {
      printStatus(builtMessage);
    }
82 83 84
  }
}

Adam Barth's avatar
Adam Barth committed
85
String _getPackagePath(PackageMap packageMap, String package) {
86
  return fs.path.dirname(packageMap.map[package].toFilePath());
87 88
}

89
/// Build an AOT snapshot. Return null (and log to `printError`) if the method
90
/// fails.
91
Future<String> buildAotSnapshot(
92
  String mainPath,
93
  TargetPlatform platform,
94
  BuildMode buildMode, {
95
  String outputPath,
96 97
  bool interpreter: false,
  bool previewDart2: false,
98
}) async {
99
  outputPath ??= getAotBuildDirectory();
100 101 102 103 104 105
  try {
    return _buildAotSnapshot(
      mainPath,
      platform,
      buildMode,
      outputPath: outputPath,
106 107
      interpreter: interpreter,
      previewDart2: previewDart2,
108 109 110 111 112 113 114 115
    );
  } on String catch (error) {
    // Catch the String exceptions thrown from the `runCheckedSync` methods below.
    printError(error);
    return null;
  }
}

116
// TODO(cbracken): split AOT and Assembly AOT snapshotting logic and migrate to Snapshotter class.
117
Future<String> _buildAotSnapshot(
118 119 120
  String mainPath,
  TargetPlatform platform,
  BuildMode buildMode, {
121
  String outputPath,
122 123
  bool interpreter: false,
  bool previewDart2: false,
124
}) async {
125
  outputPath ??= getAotBuildDirectory();
126
  if (!isAotBuildMode(buildMode) && !interpreter) {
127
    printError('${toTitleCase(getModeName(buildMode))} mode does not support AOT compilation.');
128 129 130
    return null;
  }

131 132 133 134 135
  if (platform != TargetPlatform.android_arm && platform != TargetPlatform.ios) {
    printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
    return null;
  }

136
  final String genSnapshot = artifacts.getArtifactPath(Artifact.genSnapshot, platform, buildMode);
137

138
  final Directory outputDir = fs.directory(outputPath);
139
  outputDir.createSync(recursive: true);
140 141 142 143
  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');
144
  final String dependencies = fs.path.join(outputDir.path, 'snapshot.d');
145

146 147 148 149 150
  final String vmEntryPoints = artifacts.getArtifactPath(
    Artifact.dartVmEntryPointsTxt,
    platform,
    buildMode,
  );
151
  final String ioEntryPoints = artifacts.getArtifactPath(Artifact.dartIoEntriesTxt, platform, buildMode);
152

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

Adam Barth's avatar
Adam Barth committed
160 161
  final String skyEnginePkg = _getPackagePath(packageMap, 'sky_engine');
  final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart');
162
  final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart');
163

164
  final List<String> inputPaths = <String>[
165
    vmEntryPoints,
166
    ioEntryPoints,
167 168
    uiPath,
    vmServicePath,
169
    mainPath,
170
  ];
171

172 173
  final Set<String> outputPaths = new Set<String>();

174 175 176 177 178 179 180 181
  // 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:
182 183 184 185
      outputPaths.addAll(<String>[
        vmSnapshotData,
        isolateSnapshotData,
      ]);
186 187
      break;
    case TargetPlatform.ios:
188
      snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode);
189
      assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
190
      inputPaths.add(snapshotDartIOS);
191 192 193
      break;
    case TargetPlatform.darwin_x64:
    case TargetPlatform.linux_x64:
194
    case TargetPlatform.windows_x64:
195
    case TargetPlatform.fuchsia:
196 197 198
      assert(false);
  }

199 200 201
  final Iterable<String> missingInputs = inputPaths.where((String p) => !fs.isFileSync(p));
  if (missingInputs.isNotEmpty) {
    printError('Missing input files: $missingInputs');
202 203
    return null;
  }
204 205 206 207
  if (!processManager.canRun(genSnapshot)) {
    printError('Cannot locate the genSnapshot executable');
    return null;
  }
208

209
  final List<String> genSnapshotCmd = <String>[
210
    genSnapshot,
211 212
    '--assert_initializer',
    '--await_is_keyword',
213 214
    '--vm_snapshot_data=$vmSnapshotData',
    '--isolate_snapshot_data=$isolateSnapshotData',
215
    '--packages=${packageMap.packagesPath}',
216 217
    '--url_mapping=dart:ui,$uiPath',
    '--url_mapping=dart:vmservice_sky,$vmServicePath',
218
    '--print_snapshot_sizes',
219
    '--dependencies=$dependencies',
220
    '--causal_async_stacks',
221 222
  ];

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

228 229 230 231 232 233 234 235 236 237 238
  // 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');
  final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');

239 240 241 242 243
  switch (platform) {
    case TargetPlatform.android_arm:
    case TargetPlatform.android_x64:
    case TargetPlatform.android_x86:
      genSnapshotCmd.addAll(<String>[
244
        '--snapshot_kind=app-aot-blobs',
245 246
        '--vm_snapshot_instructions=$vmSnapshotInstructions',
        '--isolate_snapshot_instructions=$isolateSnapshotInstructions',
247
        '--no-sim-use-hardfp',  // Android uses the softfloat ABI.
248
        '--no-use-integer-division',  // Not supported by the Pixel in 32-bit mode.
249 250 251
      ]);
      break;
    case TargetPlatform.ios:
252 253 254
      if (interpreter) {
        genSnapshotCmd.add('--snapshot_kind=core');
        genSnapshotCmd.add(snapshotDartIOS);
255 256 257 258
        outputPaths.addAll(<String>[
          kVmSnapshotDataO,
          kIsolateSnapshotDataO,
        ]);
259 260 261
      } else {
        genSnapshotCmd.add('--snapshot_kind=app-aot-assembly');
        genSnapshotCmd.add('--assembly=$assembly');
262
        outputPaths.add(assemblyO);
263
      }
264 265 266
      break;
    case TargetPlatform.darwin_x64:
    case TargetPlatform.linux_x64:
267
    case TargetPlatform.windows_x64:
268
    case TargetPlatform.fuchsia:
269 270 271
      assert(false);
  }

272
  if (buildMode != BuildMode.release) {
273
    genSnapshotCmd.addAll(<String>[
274 275 276 277 278
      '--no-checked',
      '--conditional_directives',
    ]);
  }

279 280 281 282 283 284 285
  if (previewDart2) {
    mainPath = await compile(
      sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
      mainPath: mainPath,
    );
  }

286 287
  genSnapshotCmd.add(mainPath);

288
  final SnapshotType snapshotType = new SnapshotType(platform, buildMode);
289 290
  final File fingerprintFile = fs.file('$dependencies.fingerprint');
  final List<File> fingerprintFiles = <File>[fingerprintFile, fs.file(dependencies)]
291 292
      ..addAll(inputPaths.map(fs.file))
      ..addAll(outputPaths.map(fs.file));
293
  if (fingerprintFiles.every((File file) => file.existsSync())) {
294
    try {
295 296
      final String json = await fingerprintFile.readAsString();
      final Fingerprint oldFingerprint = new Fingerprint.fromJson(json);
297 298 299
      final Set<String> snapshotInputPaths = await readDepfile(dependencies)
        ..add(mainPath)
        ..addAll(outputPaths);
300 301 302
      final Fingerprint newFingerprint = Snapshotter.createFingerprint(snapshotType, mainPath, snapshotInputPaths);
      if (oldFingerprint == newFingerprint) {
        printStatus('Skipping AOT snapshot build. Fingerprint match.');
303 304
        return outputPath;
      }
305
    } catch (e) {
306
      // Log exception and continue, this step is a performance improvement only.
307
      printTrace('Rebuilding snapshot due to fingerprint check error: $e');
308 309 310
    }
  }

311
  final RunResult results = await runAsync(genSnapshotCmd);
312
  if (results.exitCode != 0) {
313 314
    printError('Dart snapshot generator failed with exit code ${results.exitCode}');
    printError(results.toString());
315 316
    return null;
  }
317

318 319 320 321
  // 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');

322
  // On iOS, we use Xcode to compile the snapshot into a dynamic library that the
323 324
  // end-developer can link into their app.
  if (platform == TargetPlatform.ios) {
325
    printStatus('Building App.framework...');
326

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

Ryan Macnak's avatar
Ryan Macnak committed
329
    if (interpreter) {
330 331
      await runCheckedAsync(<String>['mv', vmSnapshotData, fs.path.join(outputDir.path, kVmSnapshotData)]);
      await runCheckedAsync(<String>['mv', isolateSnapshotData, fs.path.join(outputDir.path, kIsolateSnapshotData)]);
332

333
      await runCheckedAsync(<String>[
334
        'xxd', '--include', kVmSnapshotData, fs.path.basename(kVmSnapshotDataC)
Ryan Macnak's avatar
Ryan Macnak committed
335
      ], workingDirectory: outputDir.path);
336
      await runCheckedAsync(<String>[
337
        'xxd', '--include', kIsolateSnapshotData, fs.path.basename(kIsolateSnapshotDataC)
Ryan Macnak's avatar
Ryan Macnak committed
338
      ], workingDirectory: outputDir.path);
339

340
      await runCheckedAsync(<String>['xcrun', 'cc']
Ryan Macnak's avatar
Ryan Macnak committed
341
        ..addAll(commonBuildOptions)
342
        ..addAll(<String>['-c', kVmSnapshotDataC, '-o', kVmSnapshotDataO]));
343
      await runCheckedAsync(<String>['xcrun', 'cc']
Ryan Macnak's avatar
Ryan Macnak committed
344
        ..addAll(commonBuildOptions)
345
        ..addAll(<String>['-c', kIsolateSnapshotDataC, '-o', kIsolateSnapshotDataO]));
Ryan Macnak's avatar
Ryan Macnak committed
346
    } else {
347
      await runCheckedAsync(<String>['xcrun', 'cc']
348 349
        ..addAll(commonBuildOptions)
        ..addAll(<String>['-c', assembly, '-o', assemblyO]));
Ryan Macnak's avatar
Ryan Macnak committed
350
    }
351

352 353 354
    final String frameworkDir = fs.path.join(outputDir.path, 'App.framework');
    fs.directory(frameworkDir).createSync(recursive: true);
    final String appLib = fs.path.join(frameworkDir, 'App');
355
    final List<String> linkCommand = <String>['xcrun', 'clang']
356 357 358 359 360
      ..addAll(commonBuildOptions)
      ..addAll(<String>[
        '-dynamiclib',
        '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
        '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
361 362
        '-install_name', '@rpath/App.framework/App',
        '-o', appLib,
363
    ]);
Ryan Macnak's avatar
Ryan Macnak committed
364
    if (interpreter) {
365 366
      linkCommand.add(kVmSnapshotDataO);
      linkCommand.add(kIsolateSnapshotDataO);
Ryan Macnak's avatar
Ryan Macnak committed
367
    } else {
368
      linkCommand.add(assemblyO);
Ryan Macnak's avatar
Ryan Macnak committed
369
    }
370
    await runCheckedAsync(linkCommand);
371 372
  }

373
  // Compute and record build fingerprint.
374 375 376 377
  try {
    final Set<String> snapshotInputPaths = await readDepfile(dependencies)
      ..add(mainPath)
      ..addAll(outputPaths);
378 379
    final Fingerprint fingerprint = Snapshotter.createFingerprint(snapshotType, mainPath, snapshotInputPaths);
    await fingerprintFile.writeAsString(fingerprint.toJson());
380 381
  } catch (e, s) {
    // Log exception and continue, this step is a performance improvement only.
382
    printStatus('Error during AOT snapshot fingerprinting: $e\n$s');
383 384
  }

385 386
  return outputPath;
}