build_aot.dart 11.1 KB
Newer Older
1 2 3 4 5 6 7 8
// 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 'package:path/path.dart' as path;

9
import '../base/common.dart';
10
import '../base/file_system.dart';
11
import '../base/logger.dart';
12
import '../base/os.dart';
13
import '../base/process.dart';
14
import '../base/utils.dart';
15
import '../build_info.dart';
16
import '../dart/package_map.dart';
17
import '../globals.dart';
18
import '../resident_runner.dart';
19
import 'build.dart';
20 21 22

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

26
class BuildAotCommand extends BuildSubCommand {
27 28 29 30
  BuildAotCommand() {
    usesTargetOption();
    addBuildModeFlags();
    usesPubOption();
31
    argParser
32
      ..addOption('output-dir', defaultsTo: getAotBuildDirectory())
33 34 35
      ..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

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

    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(
57
      findMainDartFile(targetFile),
58
      platform,
59
      getBuildMode(),
60 61
      outputPath: argResults['output-dir'],
      interpreter: argResults['interpreter']
62
    );
Devon Carew's avatar
Devon Carew committed
63
    status.stop();
64

65
    if (outputPath == null)
66
      throwToolExit(null);
67

68
    printStatus('Built to $outputPath${fs.pathSeparator}.');
69 70 71
  }
}

72 73
String _getSdkExtensionPath(PackageMap packageMap, String package) {
  return path.dirname(packageMap.map[package].toFilePath());
74 75
}

76 77
/// Build an AOT snapshot. Return `null` (and log to `printError`) if the method
/// fails.
78
Future<String> buildAotSnapshot(
79
  String mainPath,
80
  TargetPlatform platform,
81
  BuildMode buildMode, {
82
  String outputPath,
83
  bool interpreter: false
84
}) async {
85
  outputPath ??= getAotBuildDirectory();
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  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;
  }
}

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

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

119
  String entryPointsDir, dartEntryPointsDir, snapshotterDir, genSnapshot;
120 121

  String engineSrc = tools.engineSrcPath;
122
  String genSnapshotExecutable = os.getExecutableName('gen_snapshot');
123
  if (engineSrc != null) {
124
    entryPointsDir  = path.join(engineSrc, 'flutter', 'runtime');
125
    dartEntryPointsDir = path.join(engineSrc, 'dart', 'runtime', 'bin');
126
    snapshotterDir = path.join(engineSrc, 'flutter', 'lib', 'snapshot');
127 128
    String engineOut = tools.getEngineArtifactsDirectory(platform, buildMode).path;
    if (platform == TargetPlatform.ios) {
129
      genSnapshot = path.join(engineOut, 'clang_x64', genSnapshotExecutable);
130 131
    } else {
      String host32BitToolchain = getCurrentHostPlatform() == HostPlatform.darwin_x64 ? 'clang_i386' : 'clang_x86';
132
      genSnapshot = path.join(engineOut, host32BitToolchain, genSnapshotExecutable);
133
    }
134
  } else {
135
    String artifactsDir = tools.getEngineArtifactsDirectory(platform, buildMode).path;
136
    entryPointsDir = artifactsDir;
137
    dartEntryPointsDir = entryPointsDir;
138
    snapshotterDir = entryPointsDir;
139
    if (platform == TargetPlatform.ios) {
140
      genSnapshot = path.join(artifactsDir, genSnapshotExecutable);
141 142
    } else {
      String hostToolsDir = path.join(artifactsDir, getNameForHostPlatform(getCurrentHostPlatform()));
143
      genSnapshot = path.join(hostToolsDir, genSnapshotExecutable);
144
    }
145
  }
146

147
  Directory outputDir = fs.directory(outputPath);
148
  outputDir.createSync(recursive: true);
149 150 151 152
  String vmSnapshotData = path.join(outputDir.path, 'vm_snapshot_data');
  String vmSnapshotInstructions = path.join(outputDir.path, 'vm_snapshot_instr');
  String isolateSnapshotData = path.join(outputDir.path, 'isolate_snapshot_data');
  String isolateSnapshotInstructions = path.join(outputDir.path, 'isolate_snapshot_instr');
153

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

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

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

169
  List<String> filePaths = <String>[
170 171
    genSnapshot,
    vmEntryPoints,
172
    ioEntryPoints,
173
    uiPath,
174
    jniPath,
175
    vmServicePath,
176
  ];
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194

  // 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:
195
      snapshotDartIOS = path.join(snapshotterDir, 'snapshot.dart');
196 197 198 199 200 201 202 203 204 205
      assembly = path.join(outputDir.path, 'snapshot_assembly.S');
      filePaths.addAll(<String>[
        snapshotDartIOS,
      ]);
      break;
    case TargetPlatform.darwin_x64:
    case TargetPlatform.linux_x64:
      assert(false);
  }

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

212
  List<String> genSnapshotCmd = <String>[
213
    genSnapshot,
214 215
    '--vm_snapshot_data=$vmSnapshotData',
    '--isolate_snapshot_data=$isolateSnapshotData',
216
    '--packages=${packageMap.packagesPath}',
217
    '--url_mapping=dart:ui,$uiPath',
218
    '--url_mapping=dart:jni,$jniPath',
219
    '--url_mapping=dart:vmservice_sky,$vmServicePath',
220
    '--print_snapshot_sizes',
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
  switch (platform) {
    case TargetPlatform.android_arm:
    case TargetPlatform.android_x64:
    case TargetPlatform.android_x86:
      genSnapshotCmd.addAll(<String>[
233 234
        '--vm_snapshot_instructions=$vmSnapshotInstructions',
        '--isolate_snapshot_instructions=$isolateSnapshotInstructions',
235 236
        '--embedder_entry_points_manifest=$vmEntryPointsAndroid',
        '--no-sim-use-hardfp',
237
        '--no-use-integer-division',  // Not supported by the Pixel in 32-bit mode.
238 239 240
      ]);
      break;
    case TargetPlatform.ios:
241
      genSnapshotCmd.add(interpreter ? snapshotDartIOS : '--assembly=$assembly');
242 243 244 245 246 247
      break;
    case TargetPlatform.darwin_x64:
    case TargetPlatform.linux_x64:
      assert(false);
  }

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

  genSnapshotCmd.add(mainPath);

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

264
  // On iOS, we use Xcode to compile the snapshot into a dynamic library that the
265 266
  // end-developer can link into their app.
  if (platform == TargetPlatform.ios) {
267
    printStatus('Building app.dylib...');
268

269
    // These names are known to from the engine.
270 271
    String kVmSnapshotData = 'kDartVmSnapshotData';
    String kIsolateSnapshotData = 'kDartIsolateSnapshotData';
272

273 274 275 276
    String kVmSnapshotDataC = path.join(outputDir.path, '$kVmSnapshotData.c');
    String kIsolateSnapshotDataC = path.join(outputDir.path, '$kIsolateSnapshotData.c');
    String kVmSnapshotDataO = path.join(outputDir.path, '$kVmSnapshotData.o');
    String kIsolateSnapshotDataO = path.join(outputDir.path, '$kIsolateSnapshotData.o');
Ryan Macnak's avatar
Ryan Macnak committed
277
    String assemblyO = path.join(outputDir.path, 'snapshot_assembly.o');
278

Ryan Macnak's avatar
Ryan Macnak committed
279
    List<String> commonBuildOptions = <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
280

Ryan Macnak's avatar
Ryan Macnak committed
281
    if (interpreter) {
282 283
      runCheckedSync(<String>['mv', vmSnapshotData, path.join(outputDir.path, kVmSnapshotData)]);
      runCheckedSync(<String>['mv', isolateSnapshotData, path.join(outputDir.path, kIsolateSnapshotData)]);
284

Ryan Macnak's avatar
Ryan Macnak committed
285
      runCheckedSync(<String>[
286
        'xxd', '--include', kVmSnapshotData, path.basename(kVmSnapshotDataC)
Ryan Macnak's avatar
Ryan Macnak committed
287 288
      ], workingDirectory: outputDir.path);
      runCheckedSync(<String>[
289
        'xxd', '--include', kIsolateSnapshotData, path.basename(kIsolateSnapshotDataC)
Ryan Macnak's avatar
Ryan Macnak committed
290
      ], workingDirectory: outputDir.path);
291

Ryan Macnak's avatar
Ryan Macnak committed
292 293
      runCheckedSync(<String>['xcrun', 'cc']
        ..addAll(commonBuildOptions)
294
        ..addAll(<String>['-c', kVmSnapshotDataC, '-o', kVmSnapshotDataO]));
Ryan Macnak's avatar
Ryan Macnak committed
295 296
      runCheckedSync(<String>['xcrun', 'cc']
        ..addAll(commonBuildOptions)
297
        ..addAll(<String>['-c', kIsolateSnapshotDataC, '-o', kIsolateSnapshotDataO]));
Ryan Macnak's avatar
Ryan Macnak committed
298
    } else {
299 300 301
      runCheckedSync(<String>['xcrun', 'cc']
        ..addAll(commonBuildOptions)
        ..addAll(<String>['-c', assembly, '-o', assemblyO]));
Ryan Macnak's avatar
Ryan Macnak committed
302
    }
303

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

306 307 308 309 310 311
    List<String> linkCommand = <String>['xcrun', 'clang']
      ..addAll(commonBuildOptions)
      ..addAll(<String>[
        '-dynamiclib',
        '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
        '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
312
        '-install_name', '@rpath/app.dylib',
313 314
        '-o', appSo,
    ]);
Ryan Macnak's avatar
Ryan Macnak committed
315
    if (interpreter) {
316 317
      linkCommand.add(kVmSnapshotDataO);
      linkCommand.add(kIsolateSnapshotDataO);
Ryan Macnak's avatar
Ryan Macnak committed
318
    } else {
319
      linkCommand.add(assemblyO);
Ryan Macnak's avatar
Ryan Macnak committed
320
    }
321
    runCheckedSync(linkCommand);
322 323
  }

324 325
  return outputPath;
}