Commit 2a7d5779 authored by Jenn Magder's avatar Jenn Magder Committed by Flutter GitHub Bot

Generate bitcode for plugin frameworks for flutter build ios-framework (#49102)

parent 4c32ae8e
......@@ -42,6 +42,21 @@ Future<void> main() async {
);
});
// First, build the module in Debug to copy the debug version of Flutter.framework.
// This proves "flutter build ios-framework" re-copies the relevant Flutter.framework,
// otherwise building plugins with bitcode will fail linking because the debug version
// of Flutter.framework does not contain bitcode.
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>[
'ios',
'--debug',
'--no-codesign',
],
);
});
// This builds all build modes' frameworks by default
section('Build frameworks');
......@@ -123,6 +138,7 @@ Future<void> main() async {
);
await _checkFrameworkArchs(appFrameworkPath, mode);
await _checkBitcode(appFrameworkPath, mode);
final String aotSymbols = await dylibSymbols(appFrameworkPath);
......@@ -168,6 +184,7 @@ Future<void> main() async {
);
await _checkFrameworkArchs(engineFrameworkPath, mode);
await _checkBitcode(engineFrameworkPath, mode);
checkFileExists(path.join(
outputPath,
......@@ -211,6 +228,7 @@ Future<void> main() async {
'device_info',
);
await _checkFrameworkArchs(pluginFrameworkPath, mode);
await _checkBitcode(pluginFrameworkPath, mode);
checkFileExists(path.join(
outputPath,
......@@ -235,7 +253,7 @@ Future<void> main() async {
}
}
section("Check all modes' have generated plugin registrant");
section('Check all modes have generated plugin registrant');
for (final String mode in <String>['Debug', 'Profile', 'Release']) {
final String registrantFrameworkPath = path.join(
......@@ -246,6 +264,7 @@ Future<void> main() async {
);
await _checkFrameworkArchs(registrantFrameworkPath, mode);
await _checkBitcode(registrantFrameworkPath, mode);
checkFileExists(path.join(
outputPath,
......@@ -310,3 +329,12 @@ Future<void> _checkFrameworkArchs(String frameworkPath, String mode) async {
throw TaskResult.failure('$mode $frameworkPath x86_64 architecture ${isDebug ? 'missing' : 'present'}');
}
}
Future<void> _checkBitcode(String frameworkPath, String mode) async {
checkFileExists(frameworkPath);
// Bitcode only needed in Release mode for archiving.
if (mode == 'Release' && !await containsBitcode(frameworkPath)) {
throw TaskResult.failure('$frameworkPath does not contain bitcode');
}
}
......@@ -54,6 +54,48 @@ Future<String> dylibSymbols(String pathToDylib) {
return eval('nm', <String>['-g', pathToDylib]);
}
Future<String> fileType(String pathToDylib) {
return eval('file', <String>[pathToDylib]);
Future<String> fileType(String pathToBinary) {
return eval('file', <String>[pathToBinary]);
}
Future<bool> containsBitcode(String pathToBinary) async {
// See: https://stackoverflow.com/questions/32755775/how-to-check-a-static-library-is-built-contain-bitcode
final String loadCommands = await eval('otool', <String>[
'-l',
pathToBinary,
]);
if (!loadCommands.contains('__LLVM')) {
return false;
}
// Presence of the section may mean a bitcode marker was embedded (size=1), but there is no content.
if (!loadCommands.contains('size 0x0000000000000001')) {
return true;
}
// Check the false positives: size=1 wasn't referencing the __LLVM section.
bool emptyBitcodeMarkerFound = false;
// Section
// sectname __bundle
// segname __LLVM
// addr 0x003c4000
// size 0x0042b633
// offset 3932160
// ...
final List<String> lines = LineSplitter.split(loadCommands).toList();
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(
(String line) => line.contains(' size 0x0000000000000001'),
orElse: () => null,
);
if (emptyBitcodeMarker != null) {
emptyBitcodeMarkerFound = true;
return;
}
}
});
return !emptyBitcodeMarkerFound;
}
......@@ -159,7 +159,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
cache ??= globals.cache;
for (final BuildMode mode in buildModes) {
globals.printStatus('Building framework for $iosProject in ${getNameForBuildMode(mode)} mode...');
globals.printStatus('Building frameworks for $iosProject in ${getNameForBuildMode(mode)} mode...');
final String xcodeBuildConfiguration = toTitleCase(getNameForBuildMode(mode));
final Directory modeDirectory = outputDirectory.childDirectory(xcodeBuildConfiguration);
......@@ -175,7 +175,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
produceFlutterPodspec(mode, modeDirectory);
} else {
// Copy Flutter.framework.
await _produceFlutterFramework(outputDirectory, mode, iPhoneBuildOutput, simulatorBuildOutput, modeDirectory);
await _produceFlutterFramework(mode, modeDirectory);
}
// Build aot, create module.framework and copy.
......@@ -266,10 +266,7 @@ end
}
Future<void> _produceFlutterFramework(
Directory outputDirectory,
BuildMode mode,
Directory iPhoneBuildOutput,
Directory simulatorBuildOutput,
Directory modeDirectory,
) async {
final Status status = globals.logger.startProgress(
......@@ -446,6 +443,15 @@ end
final Status status = globals.logger.startProgress(
' ├─Building plugins...', timeout: timeoutConfiguration.slowOperation);
try {
// Regardless of the last "flutter build" build mode,
// copy the corresponding engine.
// A plugin framework built with bitcode must link against the bitcode version
// of Flutter.framework (Release).
_project.ios.copyEngineArtifactToProject(mode);
final String bitcodeGenerationMode = mode == BuildMode.release ?
'bitcode' : 'marker'; // In release, force bitcode embedding without archiving.
List<String> pluginsBuildCommand = <String>[
'xcrun',
'xcodebuild',
......@@ -455,6 +461,7 @@ end
'-configuration',
xcodeBuildConfiguration,
'SYMROOT=${iPhoneBuildOutput.path}',
'BITCODE_GENERATION_MODE=$bitcodeGenerationMode',
'ONLY_ACTIVE_ARCH=NO' // No device targeted, so build all valid architectures.
];
......@@ -592,7 +599,7 @@ end
final Status status = globals.logger.startProgress(
' ├─Creating $frameworkBinaryName.xcframework...',
timeout: timeoutConfiguration.fastOperation,
timeout: timeoutConfiguration.slowOperation,
);
try {
if (mode == BuildMode.debug) {
......
......@@ -490,10 +490,6 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
return;
}
final Directory engineDest = ephemeralDirectory
.childDirectory('Flutter')
.childDirectory('engine');
_deleteIfExistsSync(ephemeralDirectory);
_overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'library'),
......@@ -511,23 +507,32 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
ephemeralDirectory,
);
}
// Copy podspec and framework from engine cache. The actual build mode
// doesn't actually matter as it will be overwritten by xcode_backend.sh.
// However, cocoapods will run before that script and requires something
// to be in this location.
final Directory framework = globals.fs.directory(
globals.artifacts.getArtifactPath(Artifact.flutterFramework,
copyEngineArtifactToProject(BuildMode.debug);
}
}
void copyEngineArtifactToProject(BuildMode mode) {
// Copy podspec and framework from engine cache. The actual build mode
// doesn't actually matter as it will be overwritten by xcode_backend.sh.
// However, cocoapods will run before that script and requires something
// to be in this location.
final Directory framework = globals.fs.directory(
globals.artifacts.getArtifactPath(
Artifact.flutterFramework,
platform: TargetPlatform.ios,
mode: BuildMode.debug,
));
if (framework.existsSync()) {
final File podspec = framework.parent.childFile('Flutter.podspec');
fsUtils.copyDirectorySync(
framework,
engineDest.childDirectory('Flutter.framework'),
);
podspec.copySync(engineDest.childFile('Flutter.podspec').path);
}
mode: mode,
)
);
if (framework.existsSync()) {
final Directory engineDest = ephemeralDirectory
.childDirectory('Flutter')
.childDirectory('engine');
final File podspec = framework.parent.childFile('Flutter.podspec');
fsUtils.copyDirectorySync(
framework,
engineDest.childDirectory('Flutter.framework'),
);
podspec.copySync(engineDest.childFile('Flutter.podspec').path);
}
}
......
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