Generate bitcode for plugin frameworks for flutter build ios-framework (#49102)
This commit is contained in:
parent
4c32ae8e91
commit
2a7d57791d
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user