blob: 037c8f1dbc22229d527cbd9e57394f875e651591 [file] [log] [blame]
// Copyright 2014 The Flutter 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 'package:process/process.dart';
import '../artifacts.dart';
import '../build_info.dart';
import '../macos/xcode.dart';
import 'file_system.dart';
import 'logger.dart';
import 'process.dart';
/// A snapshot build configuration.
class SnapshotType {
SnapshotType(this.platform, this.mode)
: assert(mode != null);
final TargetPlatform? platform;
final BuildMode mode;
String toString() => '$platform $mode';
/// Interface to the gen_snapshot command-line tool.
class GenSnapshot {
required Artifacts artifacts,
required ProcessManager processManager,
required Logger logger,
}) : _artifacts = artifacts,
_processUtils = ProcessUtils(logger: logger, processManager: processManager);
final Artifacts _artifacts;
final ProcessUtils _processUtils;
String getSnapshotterPath(SnapshotType snapshotType) {
return _artifacts.getArtifactPath(
Artifact.genSnapshot, platform: snapshotType.platform, mode: snapshotType.mode);
/// Ignored warning messages from gen_snapshot.
static const Set<String> kIgnoredWarnings = <String>{
// --strip on elf snapshot.
'Warning: Generating ELF library without DWARF debugging information.',
// --strip on ios-assembly snapshot.
'Warning: Generating assembly code without DWARF debugging information.',
// A fun two-part message with spaces for obfuscation.
'Warning: This VM has been configured to obfuscate symbol information which violates the Dart standard.',
' See for more information.',
Future<int> run({
required SnapshotType snapshotType,
DarwinArch? darwinArch,
Iterable<String> additionalArgs = const <String>[],
}) {
assert(darwinArch != DarwinArch.armv7);
assert(snapshotType.platform != TargetPlatform.ios || darwinArch != null);
final List<String> args = <String>[
String snapshotterPath = getSnapshotterPath(snapshotType);
// iOS and macOS have separate gen_snapshot binaries for each target
// architecture (iOS: armv7, arm64; macOS: x86_64, arm64). Select the right
// one for the target architecture in question.
if (snapshotType.platform == TargetPlatform.ios ||
snapshotType.platform == TargetPlatform.darwin) {
snapshotterPath += '_${getDartNameForDarwinArch(darwinArch!)}';
<String>[snapshotterPath, ...args],
mapFunction: (String line) => kIgnoredWarnings.contains(line) ? null : line,
class AOTSnapshotter {
this.reportTimings = false,
required Logger logger,
required FileSystem fileSystem,
required Xcode xcode,
required ProcessManager processManager,
required Artifacts artifacts,
}) : _logger = logger,
_fileSystem = fileSystem,
_xcode = xcode,
_genSnapshot = GenSnapshot(
artifacts: artifacts,
processManager: processManager,
logger: logger,
final Logger _logger;
final FileSystem _fileSystem;
final Xcode _xcode;
final GenSnapshot _genSnapshot;
/// If true then AOTSnapshotter would report timings for individual building
/// steps (Dart front-end parsing and snapshot generation) in a stable
/// machine readable form. See [AOTSnapshotter._timedStep].
final bool reportTimings;
/// Builds an architecture-specific ahead-of-time compiled snapshot of the specified script.
Future<int> build({
required TargetPlatform platform,
required BuildMode buildMode,
required String mainPath,
required String outputPath,
DarwinArch? darwinArch,
String? sdkRoot,
List<String> extraGenSnapshotOptions = const <String>[],
required bool bitcode,
String? splitDebugInfo,
required bool dartObfuscation,
bool quiet = false,
}) async {
assert(platform != TargetPlatform.ios || darwinArch != null);
if (bitcode && platform != TargetPlatform.ios) {
_logger.printError('Bitcode is only supported for iOS.');
return 1;
if (!_isValidAotPlatform(platform, buildMode)) {
_logger.printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
return 1;
final Directory outputDir =;
outputDir.createSync(recursive: true);
final List<String> genSnapshotArgs = <String>[
final bool targetingApplePlatform =
platform == TargetPlatform.ios || platform == TargetPlatform.darwin;
_logger.printTrace('targetingApplePlatform = $targetingApplePlatform');
final bool extractAppleDebugSymbols =
buildMode == BuildMode.profile || buildMode == BuildMode.release;
_logger.printTrace('extractAppleDebugSymbols = $extractAppleDebugSymbols');
// We strip snapshot by default, but allow to suppress this behavior
// by supplying --no-strip in extraGenSnapshotOptions.
bool shouldStrip = true;
if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) {
_logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
for (final String option in extraGenSnapshotOptions) {
if (option == '--no-strip') {
shouldStrip = false;
final String assembly = _fileSystem.path.join(outputDir.path, 'snapshot_assembly.S');
if (targetingApplePlatform) {
} else {
final String aotSharedLibrary = _fileSystem.path.join(outputDir.path, '');
// When buiding for iOS and splitting out debug info, we want to strip
// manually after the dSYM export, instead of in the `gen_snapshot`.
final bool stripAfterBuild;
if (targetingApplePlatform) {
stripAfterBuild = shouldStrip;
if (stripAfterBuild) {
_logger.printTrace('Will strip AOT snapshot manually after build and dSYM generation.');
} else {
stripAfterBuild = false;
if (shouldStrip) {
_logger.printTrace('Will strip AOT snapshot during build.');
if (platform == TargetPlatform.android_arm) {
// Use softfp for Android armv7 devices.
// TODO(cbracken): eliminate this when we fix
// Not supported by the Pixel in 32-bit mode.
// The name of the debug file must contain additional information about
// the architecture, since a single build command may produce
// multiple debug files.
final String archName = getNameForTargetPlatform(platform, darwinArch: darwinArch);
final String debugFilename = 'app.$archName.symbols';
final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
if (shouldSplitDebugInfo) {
.createSync(recursive: true);
// Optimization arguments.
// Faster async/await
if (shouldSplitDebugInfo) ...<String>[
'--save-debugging-info=${_fileSystem.path.join(splitDebugInfo!, debugFilename)}',
if (dartObfuscation)
final SnapshotType snapshotType = SnapshotType(platform, buildMode);
final int genSnapshotExitCode = await
snapshotType: snapshotType,
additionalArgs: genSnapshotArgs,
darwinArch: darwinArch,
if (genSnapshotExitCode != 0) {
_logger.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode');
return genSnapshotExitCode;
// On iOS and macOS, we use Xcode to compile the snapshot into a dynamic library that the
// end-developer can link into their app.
if (targetingApplePlatform) {
return _buildFramework(
appleArch: darwinArch!,
isIOS: platform == TargetPlatform.ios,
sdkRoot: sdkRoot,
assemblyPath: assembly,
outputPath: outputDir.path,
bitcode: bitcode,
quiet: quiet,
stripAfterBuild: stripAfterBuild,
extractAppleDebugSymbols: extractAppleDebugSymbols
} else {
return 0;
/// Builds an iOS or macOS framework at [outputPath]/App.framework from the assembly
/// source at [assemblyPath].
Future<int> _buildFramework({
required DarwinArch appleArch,
required bool isIOS,
String? sdkRoot,
required String assemblyPath,
required String outputPath,
required bool bitcode,
required bool quiet,
required bool stripAfterBuild,
required bool extractAppleDebugSymbols
}) async {
final String targetArch = getNameForDarwinArch(appleArch);
if (!quiet) {
_logger.printStatus('Building App.framework for $targetArch...');
final List<String> commonBuildOptions = <String>[
if (isIOS)
// When the minimum version is updated, remember to update
// template MinimumOSVersion.
if (sdkRoot != null) ...<String>[
const String embedBitcodeArg = '-fembed-bitcode';
final String assemblyO = _fileSystem.path.join(outputPath, 'snapshot_assembly.o');
final RunResult compileResult = await<String>[
if (bitcode) embedBitcodeArg,
if (compileResult.exitCode != 0) {
_logger.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}');
return compileResult.exitCode;
final String frameworkDir = _fileSystem.path.join(outputPath, 'App.framework'); true);
final String appLib = _fileSystem.path.join(frameworkDir, 'App');
final List<String> linkArgs = <String>[
'-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
'-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
'-install_name', '@rpath/App.framework/App',
if (bitcode) embedBitcodeArg,
'-o', appLib,
final RunResult linkResult = await _xcode.clang(linkArgs);
if (linkResult.exitCode != 0) {
_logger.printError('Failed to link AOT snapshot. Linker terminated with exit code ${linkResult.exitCode}');
return linkResult.exitCode;
if (extractAppleDebugSymbols) {
final RunResult dsymResult = await _xcode.dsymutil(<String>['-o', '$frameworkDir.dSYM', appLib]);
if (dsymResult.exitCode != 0) {
_logger.printError('Failed to generate dSYM - dsymutil terminated with exit code ${dsymResult.exitCode}');
return dsymResult.exitCode;
if (stripAfterBuild) {
// See for arguments
final RunResult stripResult = await _xcode.strip(<String>['-x', appLib, '-o', appLib]);
if (stripResult.exitCode != 0) {
_logger.printError('Failed to strip debugging symbols from the generated AOT snapshot - strip terminated with exit code ${stripResult.exitCode}');
return stripResult.exitCode;
} else {
assert(stripAfterBuild == false);
return 0;
bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) {
if (buildMode == BuildMode.debug) {
return false;
return const <TargetPlatform>[