Directly generate a Mach-O dynamic library using gen_snapshot. [reland] (#181539)

Instead of generating assembly code that is then compiled to a Mach-O
dynamic library, use the new app-aot-macho-dylib output option for
gen_snapshot to generate the Mach-O dynamic library without the assembly
step.

This is a reland of https://github.com/flutter/flutter/pull/174870. No
changes from the previously landed PR are needed, as the fixes for the
App Store issue described in
https://github.com/flutter/flutter/issues/178602 are all on the Dart
side: https://github.com/dart-lang/sdk/commit/8cbf864.

Related issues:

* https://github.com/dart-lang/sdk/issues/43299
* https://github.com/dart-lang/sdk/issues/60307
* https://github.com/flutter/flutter/issues/178602
* https://github.com/dart-lang/sdk/issues/62414

## Pre-launch Checklist

- [X] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [X] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [X] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [X] I signed the [CLA].
- [X] I listed at least one issue that this PR fixes in the description
above.
- [X] I updated/added relevant documentation (doc comments with `///`).
- [X] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [X] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [X] All existing and new tests are passing.
This commit is contained in:
Tess Strickland 2026-02-06 00:12:11 +01:00 committed by GitHub
parent 692dbb71e7
commit 93756969bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 111 additions and 328 deletions

View File

@ -161,11 +161,37 @@ class AOTSnapshotter {
}
}
final String assembly = _fileSystem.path.join(outputDir.path, 'snapshot_assembly.S');
String aotSharedLibrary = _fileSystem.path.join(outputDir.path, 'app.so');
String? frameworkPath;
if (targetingApplePlatform) {
genSnapshotArgs.addAll(<String>['--snapshot_kind=app-aot-assembly', '--assembly=$assembly']);
// On iOS and macOS, we use Xcode to compile the snapshot into a dynamic
// library that the end-developer can link into their app.
const frameworkName = 'App.framework';
if (!quiet) {
final String targetArch = darwinArch!.name;
_logger.printStatus('Building $frameworkName for $targetArch...');
}
frameworkPath = _fileSystem.path.join(outputPath, frameworkName);
_fileSystem.directory(frameworkPath).createSync(recursive: true);
const frameworkSnapshotName = 'App';
aotSharedLibrary = _fileSystem.path.join(frameworkPath, frameworkSnapshotName);
final String relocatableObject = _fileSystem.path.join(outputPath, 'app.o');
// When the minimum version is updated, remember to update
// template MinimumOSVersion.
// https://github.com/flutter/flutter/pull/62902
final minOSVersion = platform == TargetPlatform.ios
? FlutterDarwinPlatform.ios.deploymentTarget().toString()
: FlutterDarwinPlatform.macos.deploymentTarget().toString();
genSnapshotArgs.addAll(<String>[
'--snapshot_kind=app-aot-macho-dylib',
'--macho=$aotSharedLibrary',
'--macho-object=$relocatableObject',
'--macho-min-os-version=$minOSVersion',
'--macho-rpath=@executable_path/Frameworks,@loader_path/Frameworks',
'--macho-install-name=@rpath/$frameworkName/$frameworkSnapshotName',
]);
} else {
final String aotSharedLibrary = _fileSystem.path.join(outputDir.path, 'app.so');
genSnapshotArgs.addAll(<String>['--snapshot_kind=app-aot-elf', '--elf=$aotSharedLibrary']);
}
@ -230,123 +256,38 @@ class AOTSnapshotter {
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,
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 quiet,
required bool stripAfterBuild,
required bool extractAppleDebugSymbols,
}) async {
final String targetArch = appleArch.name;
if (!quiet) {
_logger.printStatus('Building App.framework for $targetArch...');
}
final commonBuildOptions = <String>[
'-arch',
targetArch,
if (isIOS)
// When the minimum version is updated, remember to update
// template MinimumOSVersion.
// https://github.com/flutter/flutter/pull/62902
'-miphoneos-version-min=${FlutterDarwinPlatform.ios.deploymentTarget()}',
if (sdkRoot != null) ...<String>['-isysroot', sdkRoot],
];
final String assemblyO = _fileSystem.path.join(outputPath, 'snapshot_assembly.o');
final RunResult compileResult = await _xcode.cc(<String>[
...commonBuildOptions,
'-c',
assemblyPath,
'-o',
assemblyO,
]);
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');
_fileSystem.directory(frameworkDir).createSync(recursive: true);
final String appLib = _fileSystem.path.join(frameworkDir, 'App');
final linkArgs = <String>[
...commonBuildOptions,
'-dynamiclib',
'-Xlinker',
'-rpath',
'-Xlinker',
'@executable_path/Frameworks',
'-Xlinker',
'-rpath',
'-Xlinker',
'@loader_path/Frameworks',
'-fapplication-extension',
'-install_name',
'@rpath/App.framework/App',
'-o',
appLib,
assemblyO,
];
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 https://www.unix.com/man-page/osx/1/strip/ for arguments
final RunResult stripResult = await _xcode.strip(<String>['-x', appLib, '-o', appLib]);
if (stripResult.exitCode != 0) {
if (extractAppleDebugSymbols) {
final RunResult dsymResult = await _xcode.dsymutil(<String>[
'-o',
'$frameworkPath.dSYM',
aotSharedLibrary,
]);
if (dsymResult.exitCode != 0) {
_logger.printError(
'Failed to strip debugging symbols from the generated AOT snapshot - strip terminated with exit code ${stripResult.exitCode}',
'Failed to generate dSYM - dsymutil terminated with exit code ${dsymResult.exitCode}',
);
return stripResult.exitCode;
return dsymResult.exitCode;
}
if (stripAfterBuild) {
// See https://www.unix.com/man-page/osx/1/strip/ for arguments
final RunResult stripResult = await _xcode.strip(<String>[
'-x',
aotSharedLibrary,
'-o',
aotSharedLibrary,
]);
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);
}
} else {
assert(!stripAfterBuild);
}
return 0;

View File

@ -16,27 +16,6 @@ const kWhichSysctlCommand = FakeCommand(command: <String>['which', 'sysctl']);
const kARMCheckCommand = FakeCommand(command: <String>['sysctl', 'hw.optional.arm64'], exitCode: 1);
const kDefaultClang = <String>[
'-miphoneos-version-min=13.0',
'-isysroot',
'path/to/sdk',
'-dynamiclib',
'-Xlinker',
'-rpath',
'-Xlinker',
'@executable_path/Frameworks',
'-Xlinker',
'-rpath',
'-Xlinker',
'@loader_path/Frameworks',
'-fapplication-extension',
'-install_name',
'@rpath/App.framework/App',
'-o',
'build/foo/App.framework/App',
'build/foo/snapshot_assembly.o',
];
void main() {
group('GenSnapshot', () {
late GenSnapshot genSnapshot;
@ -191,7 +170,6 @@ void main() {
testWithoutContext('builds iOS snapshot with dwarfStackTraces', () async {
final String outputPath = fileSystem.path.join('build', 'foo');
final String assembly = fileSystem.path.join(outputPath, 'snapshot_assembly.S');
final String debugPath = fileSystem.path.join('foo', 'app.ios-arm64.symbols');
final String genSnapshotPath = artifacts.getArtifactPath(
Artifact.genSnapshotArm64,
@ -203,8 +181,12 @@ void main() {
command: <String>[
genSnapshotPath,
'--deterministic',
'--snapshot_kind=app-aot-assembly',
'--assembly=$assembly',
'--snapshot_kind=app-aot-macho-dylib',
'--macho=$outputPath/App.framework/App',
'--macho-object=$outputPath/app.o',
'--macho-min-os-version=13.0',
'--macho-rpath=@executable_path/Frameworks,@loader_path/Frameworks',
'--macho-install-name=@rpath/App.framework/App',
'--dwarf-stack-traces',
'--resolve-dwarf-paths',
'--save-debugging-info=$debugPath',
@ -213,39 +195,23 @@ void main() {
),
kWhichSysctlCommand,
kARMCheckCommand,
const FakeCommand(
command: <String>[
'xcrun',
'cc',
'-arch',
'arm64',
'-miphoneos-version-min=13.0',
'-isysroot',
'path/to/sdk',
'-c',
'build/foo/snapshot_assembly.S',
'-o',
'build/foo/snapshot_assembly.o',
],
),
const FakeCommand(command: <String>['xcrun', 'clang', '-arch', 'arm64', ...kDefaultClang]),
const FakeCommand(
FakeCommand(
command: <String>[
'xcrun',
'dsymutil',
'-o',
'build/foo/App.framework.dSYM',
'build/foo/App.framework/App',
'$outputPath/App.framework.dSYM',
'$outputPath/App.framework/App',
],
),
const FakeCommand(
FakeCommand(
command: <String>[
'xcrun',
'strip',
'-x',
'build/foo/App.framework/App',
'$outputPath/App.framework/App',
'-o',
'build/foo/App.framework/App',
'$outputPath/App.framework/App',
],
),
]);
@ -267,7 +233,6 @@ void main() {
testWithoutContext('builds iOS snapshot with obfuscate', () async {
final String outputPath = fileSystem.path.join('build', 'foo');
final String assembly = fileSystem.path.join(outputPath, 'snapshot_assembly.S');
final String genSnapshotPath = artifacts.getArtifactPath(
Artifact.genSnapshotArm64,
platform: TargetPlatform.ios,
@ -278,47 +243,35 @@ void main() {
command: <String>[
genSnapshotPath,
'--deterministic',
'--snapshot_kind=app-aot-assembly',
'--assembly=$assembly',
'--snapshot_kind=app-aot-macho-dylib',
'--macho=$outputPath/App.framework/App',
'--macho-object=$outputPath/app.o',
'--macho-min-os-version=13.0',
'--macho-rpath=@executable_path/Frameworks,@loader_path/Frameworks',
'--macho-install-name=@rpath/App.framework/App',
'--obfuscate',
'main.dill',
],
),
kWhichSysctlCommand,
kARMCheckCommand,
const FakeCommand(
command: <String>[
'xcrun',
'cc',
'-arch',
'arm64',
'-miphoneos-version-min=13.0',
'-isysroot',
'path/to/sdk',
'-c',
'build/foo/snapshot_assembly.S',
'-o',
'build/foo/snapshot_assembly.o',
],
),
const FakeCommand(command: <String>['xcrun', 'clang', '-arch', 'arm64', ...kDefaultClang]),
const FakeCommand(
FakeCommand(
command: <String>[
'xcrun',
'dsymutil',
'-o',
'build/foo/App.framework.dSYM',
'build/foo/App.framework/App',
'$outputPath/App.framework.dSYM',
'$outputPath/App.framework/App',
],
),
const FakeCommand(
FakeCommand(
command: <String>[
'xcrun',
'strip',
'-x',
'build/foo/App.framework/App',
'$outputPath/App.framework/App',
'-o',
'build/foo/App.framework/App',
'$outputPath/App.framework/App',
],
),
]);
@ -349,46 +302,34 @@ void main() {
command: <String>[
genSnapshotPath,
'--deterministic',
'--snapshot_kind=app-aot-assembly',
'--assembly=${fileSystem.path.join(outputPath, 'snapshot_assembly.S')}',
'--snapshot_kind=app-aot-macho-dylib',
'--macho=$outputPath/App.framework/App',
'--macho-object=$outputPath/app.o',
'--macho-min-os-version=13.0',
'--macho-rpath=@executable_path/Frameworks,@loader_path/Frameworks',
'--macho-install-name=@rpath/App.framework/App',
'main.dill',
],
),
kWhichSysctlCommand,
kARMCheckCommand,
const FakeCommand(
command: <String>[
'xcrun',
'cc',
'-arch',
'arm64',
'-miphoneos-version-min=13.0',
'-isysroot',
'path/to/sdk',
'-c',
'build/foo/snapshot_assembly.S',
'-o',
'build/foo/snapshot_assembly.o',
],
),
const FakeCommand(command: <String>['xcrun', 'clang', '-arch', 'arm64', ...kDefaultClang]),
const FakeCommand(
FakeCommand(
command: <String>[
'xcrun',
'dsymutil',
'-o',
'build/foo/App.framework.dSYM',
'build/foo/App.framework/App',
'$outputPath/App.framework.dSYM',
'$outputPath/App.framework/App',
],
),
const FakeCommand(
FakeCommand(
command: <String>[
'xcrun',
'strip',
'-x',
'build/foo/App.framework/App',
'$outputPath/App.framework/App',
'-o',
'build/foo/App.framework/App',
'$outputPath/App.framework/App',
],
),
]);

View File

@ -23,7 +23,7 @@ import '../../../src/fake_process_manager.dart';
const kBoundaryKey = '4d2d9609-c662-4571-afde-31410f96caa6';
const kElfAot = '--snapshot_kind=app-aot-elf';
const kAssemblyAot = '--snapshot_kind=app-aot-assembly';
const kMachoDylibAot = '--snapshot_kind=app-aot-macho-dylib';
final Platform macPlatform = FakePlatform(
operatingSystem: 'macos',
@ -803,52 +803,15 @@ void main() {
'--deterministic',
'--write-v8-snapshot-profile-to=code_size_1/snapshot.arm64.json',
'--trace-precompiler-to=code_size_1/trace.arm64.json',
kAssemblyAot,
'--assembly=$build/arm64/snapshot_assembly.S',
kMachoDylibAot,
'--macho=$build/arm64/App.framework/App',
'--macho-object=$build/arm64/app.o',
'--macho-min-os-version=13.0',
'--macho-rpath=@executable_path/Frameworks,@loader_path/Frameworks',
'--macho-install-name=@rpath/App.framework/App',
'$build/app.dill',
],
),
FakeCommand(
command: <String>[
'xcrun',
'cc',
'-arch',
'arm64',
'-miphoneos-version-min=13.0',
'-isysroot',
'path/to/iPhoneOS.sdk',
'-c',
'$build/arm64/snapshot_assembly.S',
'-o',
'$build/arm64/snapshot_assembly.o',
],
),
FakeCommand(
command: <String>[
'xcrun',
'clang',
'-arch',
'arm64',
'-miphoneos-version-min=13.0',
'-isysroot',
'path/to/iPhoneOS.sdk',
'-dynamiclib',
'-Xlinker',
'-rpath',
'-Xlinker',
'@executable_path/Frameworks',
'-Xlinker',
'-rpath',
'-Xlinker',
'@loader_path/Frameworks',
'-fapplication-extension',
'-install_name',
'@rpath/App.framework/App',
'-o',
'$build/arm64/App.framework/App',
'$build/arm64/snapshot_assembly.o',
],
),
FakeCommand(
command: <String>[
'xcrun',

View File

@ -817,8 +817,12 @@ void main() {
command: <String>[
'Artifact.genSnapshotArm64.TargetPlatform.darwin.release',
'--deterministic',
'--snapshot_kind=app-aot-assembly',
'--assembly=${environment.buildDir.childFile('arm64/snapshot_assembly.S').path}',
'--snapshot_kind=app-aot-macho-dylib',
'--macho=${environment.buildDir.childFile('arm64/App.framework/App').path}',
'--macho-object=${environment.buildDir.childFile('arm64/app.o').path}',
'--macho-min-os-version=10.15',
'--macho-rpath=@executable_path/Frameworks,@loader_path/Frameworks',
'--macho-install-name=@rpath/App.framework/App',
environment.buildDir.childFile('app.dill').path,
],
),
@ -826,81 +830,15 @@ void main() {
command: <String>[
'Artifact.genSnapshotX64.TargetPlatform.darwin.release',
'--deterministic',
'--snapshot_kind=app-aot-assembly',
'--assembly=${environment.buildDir.childFile('x86_64/snapshot_assembly.S').path}',
'--snapshot_kind=app-aot-macho-dylib',
'--macho=${environment.buildDir.childFile('x86_64/App.framework/App').path}',
'--macho-object=${environment.buildDir.childFile('x86_64/app.o').path}',
'--macho-min-os-version=10.15',
'--macho-rpath=@executable_path/Frameworks,@loader_path/Frameworks',
'--macho-install-name=@rpath/App.framework/App',
environment.buildDir.childFile('app.dill').path,
],
),
FakeCommand(
command: <String>[
'xcrun',
'cc',
'-arch',
'arm64',
'-c',
environment.buildDir.childFile('arm64/snapshot_assembly.S').path,
'-o',
environment.buildDir.childFile('arm64/snapshot_assembly.o').path,
],
),
FakeCommand(
command: <String>[
'xcrun',
'cc',
'-arch',
'x86_64',
'-c',
environment.buildDir.childFile('x86_64/snapshot_assembly.S').path,
'-o',
environment.buildDir.childFile('x86_64/snapshot_assembly.o').path,
],
),
FakeCommand(
command: <String>[
'xcrun',
'clang',
'-arch',
'arm64',
'-dynamiclib',
'-Xlinker',
'-rpath',
'-Xlinker',
'@executable_path/Frameworks',
'-Xlinker',
'-rpath',
'-Xlinker',
'@loader_path/Frameworks',
'-fapplication-extension',
'-install_name',
'@rpath/App.framework/App',
'-o',
environment.buildDir.childFile('arm64/App.framework/App').path,
environment.buildDir.childFile('arm64/snapshot_assembly.o').path,
],
),
FakeCommand(
command: <String>[
'xcrun',
'clang',
'-arch',
'x86_64',
'-dynamiclib',
'-Xlinker',
'-rpath',
'-Xlinker',
'@executable_path/Frameworks',
'-Xlinker',
'-rpath',
'-Xlinker',
'@loader_path/Frameworks',
'-fapplication-extension',
'-install_name',
'@rpath/App.framework/App',
'-o',
environment.buildDir.childFile('x86_64/App.framework/App').path,
environment.buildDir.childFile('x86_64/snapshot_assembly.o').path,
],
),
FakeCommand(
command: <String>[
'xcrun',