Unverified Commit 7e02cc34 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Bitcode strip Flutter.framework in assemble build target (#77329)

parent 565e4877
...@@ -42,7 +42,7 @@ Future<bool> containsBitcode(String pathToBinary) async { ...@@ -42,7 +42,7 @@ Future<bool> containsBitcode(String pathToBinary) async {
if (line.contains('segname __LLVM') && lines.length - index - 1 > 3) { if (line.contains('segname __LLVM') && lines.length - index - 1 > 3) {
final String emptyBitcodeMarker = lines final String emptyBitcodeMarker = lines
.skip(index - 1) .skip(index - 1)
.take(3) .take(4)
.firstWhere( .firstWhere(
(String line) => line.contains(' size 0x0000000000000001'), (String line) => line.contains(' size 0x0000000000000001'),
orElse: () => null, orElse: () => null,
......
...@@ -137,9 +137,8 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr ...@@ -137,9 +137,8 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr
local_engine_flag="--local-engine=${LOCAL_ENGINE}" local_engine_flag="--local-engine=${LOCAL_ENGINE}"
flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.xcframework" flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.xcframework"
fi fi
local bitcode_flag="" local bitcode_flag=""
if [[ "$ENABLE_BITCODE" == "YES" ]]; then if [[ "$ENABLE_BITCODE" == "YES" && "$ACTION" == "install" ]]; then
bitcode_flag="true" bitcode_flag="true"
fi fi
...@@ -218,10 +217,6 @@ EmbedFlutterFrameworks() { ...@@ -218,10 +217,6 @@ EmbedFlutterFrameworks() {
# Copy Xcode behavior and don't copy over headers or modules. # Copy Xcode behavior and don't copy over headers or modules.
RunCommand rsync -av --delete --filter "- .DS_Store" --filter "- Headers" --filter "- Modules" "${BUILT_PRODUCTS_DIR}/Flutter.framework" "${xcode_frameworks_dir}/" RunCommand rsync -av --delete --filter "- .DS_Store" --filter "- Headers" --filter "- Modules" "${BUILT_PRODUCTS_DIR}/Flutter.framework" "${xcode_frameworks_dir}/"
if [[ "$ACTION" != "install" || "$ENABLE_BITCODE" == "NO" ]]; then
# Strip bitcode from the destination unless archiving, or if bitcode is disabled entirely.
RunCommand "${DT_TOOLCHAIN_DIR}"/usr/bin/bitcode_strip "${BUILT_PRODUCTS_DIR}/Flutter.framework/Flutter" -r -o "${xcode_frameworks_dir}/Flutter.framework/Flutter"
fi
# Sign the binaries we moved. # Sign the binaries we moved.
if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ]]; then if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ]]; then
......
...@@ -280,8 +280,18 @@ abstract class UnpackIOS extends Target { ...@@ -280,8 +280,18 @@ abstract class UnpackIOS extends Target {
if (environment.defines[kIosArchs] == null) { if (environment.defines[kIosArchs] == null) {
throw MissingDefineException(kIosArchs, name); throw MissingDefineException(kIosArchs, name);
} }
if (environment.defines[kBitcodeFlag] == null) {
throw MissingDefineException(kBitcodeFlag, name);
}
await _copyFramework(environment); await _copyFramework(environment);
await _thinFramework(environment);
final File frameworkBinary = environment.outputDir.childDirectory('Flutter.framework').childFile('Flutter');
final String frameworkBinaryPath = frameworkBinary.path;
if (!frameworkBinary.existsSync()) {
throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin');
}
await _thinFramework(environment, frameworkBinaryPath);
await _bitcodeStripFramework(environment, frameworkBinaryPath);
} }
Future<void> _copyFramework(Environment environment) async { Future<void> _copyFramework(Environment environment) async {
...@@ -312,37 +322,30 @@ abstract class UnpackIOS extends Target { ...@@ -312,37 +322,30 @@ abstract class UnpackIOS extends Target {
} }
/// Destructively thin Flutter.framework to include only the specified architectures. /// Destructively thin Flutter.framework to include only the specified architectures.
Future<void> _thinFramework(Environment environment) async { Future<void> _thinFramework(Environment environment, String frameworkBinaryPath) async {
final Directory frameworkDirectory = environment.outputDir;
final File flutterFramework = frameworkDirectory.childDirectory('Flutter.framework').childFile('Flutter');
final String binaryPath = flutterFramework.path;
if (!flutterFramework.existsSync()) {
throw Exception('Binary $binaryPath does not exist, cannot thin');
}
final String archs = environment.defines[kIosArchs]; final String archs = environment.defines[kIosArchs];
final List<String> archList = archs.split(' ').toList(); final List<String> archList = archs.split(' ').toList();
final ProcessResult infoResult = environment.processManager.runSync(<String>[ final ProcessResult infoResult = environment.processManager.runSync(<String>[
'lipo', 'lipo',
'-info', '-info',
binaryPath, frameworkBinaryPath,
]); ]);
final String lipoInfo = infoResult.stdout as String; final String lipoInfo = infoResult.stdout as String;
final ProcessResult verifyResult = environment.processManager.runSync(<String>[ final ProcessResult verifyResult = environment.processManager.runSync(<String>[
'lipo', 'lipo',
binaryPath, frameworkBinaryPath,
'-verify_arch', '-verify_arch',
...archList ...archList
]); ]);
if (verifyResult.exitCode != 0) { if (verifyResult.exitCode != 0) {
throw Exception('Binary $binaryPath does not contain $archs. Running lipo -info:\n$lipoInfo'); throw Exception('Binary $frameworkBinaryPath does not contain $archs. Running lipo -info:\n$lipoInfo');
} }
// Skip thinning for non-fat executables. // Skip thinning for non-fat executables.
if (lipoInfo.startsWith('Non-fat file:')) { if (lipoInfo.startsWith('Non-fat file:')) {
environment.logger.printTrace('Skipping lipo for non-fat file $binaryPath'); environment.logger.printTrace('Skipping lipo for non-fat file $frameworkBinaryPath');
return; return;
} }
...@@ -350,17 +353,36 @@ abstract class UnpackIOS extends Target { ...@@ -350,17 +353,36 @@ abstract class UnpackIOS extends Target {
final ProcessResult extractResult = environment.processManager.runSync(<String>[ final ProcessResult extractResult = environment.processManager.runSync(<String>[
'lipo', 'lipo',
'-output', '-output',
binaryPath, frameworkBinaryPath,
for (final String arch in archList) for (final String arch in archList)
...<String>[ ...<String>[
'-extract', '-extract',
arch, arch,
], ],
...<String>[binaryPath], ...<String>[frameworkBinaryPath],
]); ]);
if (extractResult.exitCode != 0) { if (extractResult.exitCode != 0) {
throw Exception('Failed to extract $archs for $binaryPath.\n${extractResult.stderr}\nRunning lipo -info:\n$lipoInfo'); throw Exception('Failed to extract $archs for $frameworkBinaryPath.\n${extractResult.stderr}\nRunning lipo -info:\n$lipoInfo');
}
}
/// Destructively strip bitcode from the framework, if needed.
Future<void> _bitcodeStripFramework(Environment environment, String frameworkBinaryPath) async {
if (environment.defines[kBitcodeFlag] == 'true') {
return;
}
final ProcessResult stripResult = environment.processManager.runSync(<String>[
'xcrun',
'bitcode_strip',
frameworkBinaryPath,
'-m', // leave the bitcode marker.
'-o',
frameworkBinaryPath,
]);
if (stripResult.exitCode != 0) {
throw Exception('Failed to strip bitcode for $frameworkBinaryPath.\n${stripResult.stderr}');
} }
} }
} }
......
...@@ -239,7 +239,7 @@ void main() { ...@@ -239,7 +239,7 @@ void main() {
Platform: () => macPlatform, Platform: () => macPlatform,
}); });
group('copy and thin engine Flutter.framework', () { group('copy, thin, and bitcode strip engine Flutter.framework', () {
Directory outputDir; Directory outputDir;
FakeCommand copyPhysicalFrameworkCommand; FakeCommand copyPhysicalFrameworkCommand;
...@@ -269,6 +269,7 @@ void main() { ...@@ -269,6 +269,7 @@ void main() {
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'x86_64', kIosArchs: 'x86_64',
kSdkRoot: 'path/to/iPhoneSimulator.sdk', kSdkRoot: 'path/to/iPhoneSimulator.sdk',
kBitcodeFlag: 'true',
}, },
); );
...@@ -308,7 +309,7 @@ void main() { ...@@ -308,7 +309,7 @@ void main() {
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('thinning fails when frameworks missing', () async { testWithoutContext('fails when frameworks missing', () async {
final Environment environment = Environment.test( final Environment environment = Environment.test(
fileSystem.currentDirectory, fileSystem.currentDirectory,
processManager: processManager, processManager: processManager,
...@@ -319,10 +320,11 @@ void main() { ...@@ -319,10 +320,11 @@ void main() {
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64', kIosArchs: 'arm64',
kSdkRoot: 'path/to/iPhoneOS.sdk', kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: '',
}, },
); );
processManager.addCommand(copyPhysicalFrameworkCommand); processManager.addCommand(copyPhysicalFrameworkCommand);
expect( await expectLater(
const DebugUnpackIOS().build(environment), const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having( throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
...@@ -331,7 +333,7 @@ void main() { ...@@ -331,7 +333,7 @@ void main() {
))); )));
}); });
testWithoutContext('thinning fails when requested archs missing from framework', () async { testWithoutContext('fails when requested archs missing from framework', () async {
final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true); final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test( final Environment environment = Environment.test(
...@@ -344,6 +346,7 @@ void main() { ...@@ -344,6 +346,7 @@ void main() {
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64 armv7', kIosArchs: 'arm64 armv7',
kSdkRoot: 'path/to/iPhoneOS.sdk', kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: '',
}, },
); );
...@@ -366,7 +369,7 @@ void main() { ...@@ -366,7 +369,7 @@ void main() {
], exitCode: 1), ], exitCode: 1),
); );
expect( await expectLater(
const DebugUnpackIOS().build(environment), const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having( throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
...@@ -375,7 +378,7 @@ void main() { ...@@ -375,7 +378,7 @@ void main() {
))); )));
}); });
testWithoutContext('thinning fails when lipo extract fails', () async { testWithoutContext('fails when lipo extract fails', () async {
final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true); final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test( final Environment environment = Environment.test(
...@@ -388,6 +391,7 @@ void main() { ...@@ -388,6 +391,7 @@ void main() {
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64 armv7', kIosArchs: 'arm64 armv7',
kSdkRoot: 'path/to/iPhoneOS.sdk', kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: '',
}, },
); );
...@@ -424,7 +428,7 @@ void main() { ...@@ -424,7 +428,7 @@ void main() {
stderr: 'lipo error'), stderr: 'lipo error'),
); );
expect( await expectLater(
const DebugUnpackIOS().build(environment), const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having( throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
...@@ -433,7 +437,7 @@ void main() { ...@@ -433,7 +437,7 @@ void main() {
))); )));
}); });
testWithoutContext('skips thin frameworks', () async { testWithoutContext('skips thin framework', () async {
final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true); final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test( final Environment environment = Environment.test(
...@@ -446,6 +450,7 @@ void main() { ...@@ -446,6 +450,7 @@ void main() {
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64', kIosArchs: 'arm64',
kSdkRoot: 'path/to/iPhoneOS.sdk', kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: 'true',
}, },
); );
...@@ -473,7 +478,7 @@ void main() { ...@@ -473,7 +478,7 @@ void main() {
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('thins fat frameworks', () async { testWithoutContext('thins fat framework', () async {
final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true); final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test( final Environment environment = Environment.test(
...@@ -486,6 +491,7 @@ void main() { ...@@ -486,6 +491,7 @@ void main() {
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64 armv7', kIosArchs: 'arm64 armv7',
kSdkRoot: 'path/to/iPhoneOS.sdk', kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: 'true',
}, },
); );
...@@ -524,5 +530,100 @@ void main() { ...@@ -524,5 +530,100 @@ void main() {
await const DebugUnpackIOS().build(environment); await const DebugUnpackIOS().build(environment);
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('fails when bitcode strip fails', () async {
final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test(
fileSystem.currentDirectory,
processManager: processManager,
artifacts: artifacts,
logger: logger,
fileSystem: fileSystem,
outputDir: outputDir,
defines: <String, String>{
kIosArchs: 'arm64',
kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: '',
},
);
processManager.addCommands(<FakeCommand>[
copyPhysicalFrameworkCommand,
FakeCommand(command: <String>[
'lipo',
'-info',
binary.path,
], stdout: 'Non-fat file:'),
FakeCommand(command: <String>[
'lipo',
binary.path,
'-verify_arch',
'arm64',
]),
FakeCommand(command: <String>[
'xcrun',
'bitcode_strip',
binary.path,
'-m',
'-o',
binary.path,
], exitCode: 1, stderr: 'bitcode_strip error'),
]);
await expectLater(
const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(),
'description',
contains('Failed to strip bitcode for output/Flutter.framework/Flutter.\nbitcode_strip error'),
)));
expect(processManager.hasRemainingExpectations, isFalse);
});
testWithoutContext('strips framework', () async {
final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test(
fileSystem.currentDirectory,
processManager: processManager,
artifacts: artifacts,
logger: logger,
fileSystem: fileSystem,
outputDir: outputDir,
defines: <String, String>{
kIosArchs: 'arm64',
kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: '',
},
);
processManager.addCommands(<FakeCommand>[
copyPhysicalFrameworkCommand,
FakeCommand(command: <String>[
'lipo',
'-info',
binary.path,
], stdout: 'Non-fat file:'),
FakeCommand(command: <String>[
'lipo',
binary.path,
'-verify_arch',
'arm64',
]),
FakeCommand(command: <String>[
'xcrun',
'bitcode_strip',
binary.path,
'-m',
'-o',
binary.path,
]),
]);
await const DebugUnpackIOS().build(environment);
expect(processManager.hasRemainingExpectations, isFalse);
});
}); });
} }
...@@ -40,7 +40,7 @@ bool containsBitcode(String pathToBinary, ProcessManager processManager) { ...@@ -40,7 +40,7 @@ bool containsBitcode(String pathToBinary, ProcessManager processManager) {
lines.asMap().forEach((int index, String line) { lines.asMap().forEach((int index, String line) {
if (line.contains('segname __LLVM') && lines.length - index - 1 > 3) { if (line.contains('segname __LLVM') && lines.length - index - 1 > 3) {
final String emptyBitcodeMarker = final String emptyBitcodeMarker =
lines.skip(index - 1).take(3).firstWhere( lines.skip(index - 1).take(4).firstWhere(
(String line) => line.contains(' size 0x0000000000000001'), (String line) => line.contains(' size 0x0000000000000001'),
orElse: () => null, orElse: () => null,
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment