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 {
if (line.contains('segname __LLVM') && lines.length - index - 1 > 3) {
final String emptyBitcodeMarker = lines
.skip(index - 1)
.take(3)
.take(4)
.firstWhere(
(String line) => line.contains(' size 0x0000000000000001'),
orElse: () => null,
......
......@@ -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}"
flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.xcframework"
fi
local bitcode_flag=""
if [[ "$ENABLE_BITCODE" == "YES" ]]; then
if [[ "$ENABLE_BITCODE" == "YES" && "$ACTION" == "install" ]]; then
bitcode_flag="true"
fi
......@@ -218,10 +217,6 @@ EmbedFlutterFrameworks() {
# 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}/"
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.
if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ]]; then
......
......@@ -280,8 +280,18 @@ abstract class UnpackIOS extends Target {
if (environment.defines[kIosArchs] == null) {
throw MissingDefineException(kIosArchs, name);
}
if (environment.defines[kBitcodeFlag] == null) {
throw MissingDefineException(kBitcodeFlag, name);
}
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 {
......@@ -312,37 +322,30 @@ abstract class UnpackIOS extends Target {
}
/// Destructively thin Flutter.framework to include only the specified architectures.
Future<void> _thinFramework(Environment environment) 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');
}
Future<void> _thinFramework(Environment environment, String frameworkBinaryPath) async {
final String archs = environment.defines[kIosArchs];
final List<String> archList = archs.split(' ').toList();
final ProcessResult infoResult = environment.processManager.runSync(<String>[
'lipo',
'-info',
binaryPath,
frameworkBinaryPath,
]);
final String lipoInfo = infoResult.stdout as String;
final ProcessResult verifyResult = environment.processManager.runSync(<String>[
'lipo',
binaryPath,
frameworkBinaryPath,
'-verify_arch',
...archList
]);
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.
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;
}
......@@ -350,17 +353,36 @@ abstract class UnpackIOS extends Target {
final ProcessResult extractResult = environment.processManager.runSync(<String>[
'lipo',
'-output',
binaryPath,
frameworkBinaryPath,
for (final String arch in archList)
...<String>[
'-extract',
arch,
],
...<String>[binaryPath],
...<String>[frameworkBinaryPath],
]);
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() {
Platform: () => macPlatform,
});
group('copy and thin engine Flutter.framework', () {
group('copy, thin, and bitcode strip engine Flutter.framework', () {
Directory outputDir;
FakeCommand copyPhysicalFrameworkCommand;
......@@ -269,6 +269,7 @@ void main() {
defines: <String, String>{
kIosArchs: 'x86_64',
kSdkRoot: 'path/to/iPhoneSimulator.sdk',
kBitcodeFlag: 'true',
},
);
......@@ -308,7 +309,7 @@ void main() {
expect(processManager.hasRemainingExpectations, isFalse);
});
testWithoutContext('thinning fails when frameworks missing', () async {
testWithoutContext('fails when frameworks missing', () async {
final Environment environment = Environment.test(
fileSystem.currentDirectory,
processManager: processManager,
......@@ -319,10 +320,11 @@ void main() {
defines: <String, String>{
kIosArchs: 'arm64',
kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: '',
},
);
processManager.addCommand(copyPhysicalFrameworkCommand);
expect(
await expectLater(
const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(),
......@@ -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 Environment environment = Environment.test(
......@@ -344,6 +346,7 @@ void main() {
defines: <String, String>{
kIosArchs: 'arm64 armv7',
kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: '',
},
);
......@@ -366,7 +369,7 @@ void main() {
], exitCode: 1),
);
expect(
await expectLater(
const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(),
......@@ -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 Environment environment = Environment.test(
......@@ -388,6 +391,7 @@ void main() {
defines: <String, String>{
kIosArchs: 'arm64 armv7',
kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: '',
},
);
......@@ -424,7 +428,7 @@ void main() {
stderr: 'lipo error'),
);
expect(
await expectLater(
const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(),
......@@ -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 Environment environment = Environment.test(
......@@ -446,6 +450,7 @@ void main() {
defines: <String, String>{
kIosArchs: 'arm64',
kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: 'true',
},
);
......@@ -473,7 +478,7 @@ void main() {
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 Environment environment = Environment.test(
......@@ -486,6 +491,7 @@ void main() {
defines: <String, String>{
kIosArchs: 'arm64 armv7',
kSdkRoot: 'path/to/iPhoneOS.sdk',
kBitcodeFlag: 'true',
},
);
......@@ -524,5 +530,100 @@ void main() {
await const DebugUnpackIOS().build(environment);
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) {
lines.asMap().forEach((int index, String line) {
if (line.contains('segname __LLVM') && lines.length - index - 1 > 3) {
final String emptyBitcodeMarker =
lines.skip(index - 1).take(3).firstWhere(
lines.skip(index - 1).take(4).firstWhere(
(String line) => line.contains(' size 0x0000000000000001'),
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