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
|
// This builds all build modes' frameworks by default
|
||||||
section('Build frameworks');
|
section('Build frameworks');
|
||||||
|
|
||||||
@ -123,6 +138,7 @@ Future<void> main() async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await _checkFrameworkArchs(appFrameworkPath, mode);
|
await _checkFrameworkArchs(appFrameworkPath, mode);
|
||||||
|
await _checkBitcode(appFrameworkPath, mode);
|
||||||
|
|
||||||
final String aotSymbols = await dylibSymbols(appFrameworkPath);
|
final String aotSymbols = await dylibSymbols(appFrameworkPath);
|
||||||
|
|
||||||
@ -168,6 +184,7 @@ Future<void> main() async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await _checkFrameworkArchs(engineFrameworkPath, mode);
|
await _checkFrameworkArchs(engineFrameworkPath, mode);
|
||||||
|
await _checkBitcode(engineFrameworkPath, mode);
|
||||||
|
|
||||||
checkFileExists(path.join(
|
checkFileExists(path.join(
|
||||||
outputPath,
|
outputPath,
|
||||||
@ -211,6 +228,7 @@ Future<void> main() async {
|
|||||||
'device_info',
|
'device_info',
|
||||||
);
|
);
|
||||||
await _checkFrameworkArchs(pluginFrameworkPath, mode);
|
await _checkFrameworkArchs(pluginFrameworkPath, mode);
|
||||||
|
await _checkBitcode(pluginFrameworkPath, mode);
|
||||||
|
|
||||||
checkFileExists(path.join(
|
checkFileExists(path.join(
|
||||||
outputPath,
|
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']) {
|
for (final String mode in <String>['Debug', 'Profile', 'Release']) {
|
||||||
final String registrantFrameworkPath = path.join(
|
final String registrantFrameworkPath = path.join(
|
||||||
@ -246,6 +264,7 @@ Future<void> main() async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await _checkFrameworkArchs(registrantFrameworkPath, mode);
|
await _checkFrameworkArchs(registrantFrameworkPath, mode);
|
||||||
|
await _checkBitcode(registrantFrameworkPath, mode);
|
||||||
|
|
||||||
checkFileExists(path.join(
|
checkFileExists(path.join(
|
||||||
outputPath,
|
outputPath,
|
||||||
@ -310,3 +329,12 @@ Future<void> _checkFrameworkArchs(String frameworkPath, String mode) async {
|
|||||||
throw TaskResult.failure('$mode $frameworkPath x86_64 architecture ${isDebug ? 'missing' : 'present'}');
|
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]);
|
return eval('nm', <String>['-g', pathToDylib]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> fileType(String pathToDylib) {
|
Future<String> fileType(String pathToBinary) {
|
||||||
return eval('file', <String>[pathToDylib]);
|
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;
|
cache ??= globals.cache;
|
||||||
|
|
||||||
for (final BuildMode mode in buildModes) {
|
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 String xcodeBuildConfiguration = toTitleCase(getNameForBuildMode(mode));
|
||||||
final Directory modeDirectory = outputDirectory.childDirectory(xcodeBuildConfiguration);
|
final Directory modeDirectory = outputDirectory.childDirectory(xcodeBuildConfiguration);
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
|
|||||||
produceFlutterPodspec(mode, modeDirectory);
|
produceFlutterPodspec(mode, modeDirectory);
|
||||||
} else {
|
} else {
|
||||||
// Copy Flutter.framework.
|
// Copy Flutter.framework.
|
||||||
await _produceFlutterFramework(outputDirectory, mode, iPhoneBuildOutput, simulatorBuildOutput, modeDirectory);
|
await _produceFlutterFramework(mode, modeDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build aot, create module.framework and copy.
|
// Build aot, create module.framework and copy.
|
||||||
@ -266,10 +266,7 @@ end
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _produceFlutterFramework(
|
Future<void> _produceFlutterFramework(
|
||||||
Directory outputDirectory,
|
|
||||||
BuildMode mode,
|
BuildMode mode,
|
||||||
Directory iPhoneBuildOutput,
|
|
||||||
Directory simulatorBuildOutput,
|
|
||||||
Directory modeDirectory,
|
Directory modeDirectory,
|
||||||
) async {
|
) async {
|
||||||
final Status status = globals.logger.startProgress(
|
final Status status = globals.logger.startProgress(
|
||||||
@ -446,6 +443,15 @@ end
|
|||||||
final Status status = globals.logger.startProgress(
|
final Status status = globals.logger.startProgress(
|
||||||
' ├─Building plugins...', timeout: timeoutConfiguration.slowOperation);
|
' ├─Building plugins...', timeout: timeoutConfiguration.slowOperation);
|
||||||
try {
|
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>[
|
List<String> pluginsBuildCommand = <String>[
|
||||||
'xcrun',
|
'xcrun',
|
||||||
'xcodebuild',
|
'xcodebuild',
|
||||||
@ -455,6 +461,7 @@ end
|
|||||||
'-configuration',
|
'-configuration',
|
||||||
xcodeBuildConfiguration,
|
xcodeBuildConfiguration,
|
||||||
'SYMROOT=${iPhoneBuildOutput.path}',
|
'SYMROOT=${iPhoneBuildOutput.path}',
|
||||||
|
'BITCODE_GENERATION_MODE=$bitcodeGenerationMode',
|
||||||
'ONLY_ACTIVE_ARCH=NO' // No device targeted, so build all valid architectures.
|
'ONLY_ACTIVE_ARCH=NO' // No device targeted, so build all valid architectures.
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -592,7 +599,7 @@ end
|
|||||||
|
|
||||||
final Status status = globals.logger.startProgress(
|
final Status status = globals.logger.startProgress(
|
||||||
' ├─Creating $frameworkBinaryName.xcframework...',
|
' ├─Creating $frameworkBinaryName.xcframework...',
|
||||||
timeout: timeoutConfiguration.fastOperation,
|
timeout: timeoutConfiguration.slowOperation,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
if (mode == BuildMode.debug) {
|
if (mode == BuildMode.debug) {
|
||||||
|
@ -490,10 +490,6 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Directory engineDest = ephemeralDirectory
|
|
||||||
.childDirectory('Flutter')
|
|
||||||
.childDirectory('engine');
|
|
||||||
|
|
||||||
_deleteIfExistsSync(ephemeralDirectory);
|
_deleteIfExistsSync(ephemeralDirectory);
|
||||||
_overwriteFromTemplate(
|
_overwriteFromTemplate(
|
||||||
globals.fs.path.join('module', 'ios', 'library'),
|
globals.fs.path.join('module', 'ios', 'library'),
|
||||||
@ -511,23 +507,32 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
|
|||||||
ephemeralDirectory,
|
ephemeralDirectory,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Copy podspec and framework from engine cache. The actual build mode
|
copyEngineArtifactToProject(BuildMode.debug);
|
||||||
// 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(
|
void copyEngineArtifactToProject(BuildMode mode) {
|
||||||
globals.artifacts.getArtifactPath(Artifact.flutterFramework,
|
// 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,
|
platform: TargetPlatform.ios,
|
||||||
mode: BuildMode.debug,
|
mode: mode,
|
||||||
));
|
)
|
||||||
if (framework.existsSync()) {
|
);
|
||||||
final File podspec = framework.parent.childFile('Flutter.podspec');
|
if (framework.existsSync()) {
|
||||||
fsUtils.copyDirectorySync(
|
final Directory engineDest = ephemeralDirectory
|
||||||
framework,
|
.childDirectory('Flutter')
|
||||||
engineDest.childDirectory('Flutter.framework'),
|
.childDirectory('engine');
|
||||||
);
|
final File podspec = framework.parent.childFile('Flutter.podspec');
|
||||||
podspec.copySync(engineDest.childFile('Flutter.podspec').path);
|
fsUtils.copyDirectorySync(
|
||||||
}
|
framework,
|
||||||
|
engineDest.childDirectory('Flutter.framework'),
|
||||||
|
);
|
||||||
|
podspec.copySync(engineDest.childFile('Flutter.podspec').path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user