mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Move iOS framework thinning into the tool (#76665)
This commit is contained in:
parent
9df362a73f
commit
5bc100da31
@ -204,73 +204,17 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr
|
||||
return 0
|
||||
}
|
||||
|
||||
# Returns the CFBundleExecutable for the specified framework directory.
|
||||
GetFrameworkExecutablePath() {
|
||||
local framework_dir="$1"
|
||||
|
||||
local plist_path="${framework_dir}/Info.plist"
|
||||
local executable="$(defaults read "${plist_path}" CFBundleExecutable)"
|
||||
echo "${framework_dir}/${executable}"
|
||||
}
|
||||
|
||||
# Destructively thins the specified executable file to include only the
|
||||
# specified architectures.
|
||||
LipoExecutable() {
|
||||
local executable="$1"
|
||||
shift
|
||||
# Split $@ into an array.
|
||||
read -r -a archs <<< "$@"
|
||||
|
||||
# Extract architecture-specific framework executables.
|
||||
local all_executables=()
|
||||
for arch in "${archs[@]}"; do
|
||||
local output="${executable}_${arch}"
|
||||
local lipo_info="$(lipo -info "${executable}")"
|
||||
if [[ "${lipo_info}" == "Non-fat file:"* ]]; then
|
||||
if [[ "${lipo_info}" != *"${arch}" ]]; then
|
||||
echo "Non-fat binary ${executable} is not ${arch}. Running lipo -info:"
|
||||
echo "${lipo_info}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if lipo -output "${output}" -extract "${arch}" "${executable}"; then
|
||||
all_executables+=("${output}")
|
||||
else
|
||||
echo "Failed to extract ${arch} for ${executable}. Running lipo -info:"
|
||||
RunCommand lipo -info "${executable}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate a merged binary from the architecture-specific executables.
|
||||
# Skip this step for non-fat executables.
|
||||
if [[ ${#all_executables[@]} > 0 ]]; then
|
||||
local merged="${executable}_merged"
|
||||
RunCommand lipo -output "${merged}" -create "${all_executables[@]}"
|
||||
|
||||
RunCommand cp -f -- "${merged}" "${executable}" > /dev/null
|
||||
RunCommand rm -f -- "${merged}" "${all_executables[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Destructively thins the specified framework to include only the specified
|
||||
# Destructively thins the Flutter and App frameworks to include only the specified
|
||||
# architectures.
|
||||
ThinFramework() {
|
||||
local framework_dir="$1"
|
||||
shift
|
||||
|
||||
local executable="$(GetFrameworkExecutablePath "${framework_dir}")"
|
||||
LipoExecutable "${executable}" "$@"
|
||||
}
|
||||
|
||||
ThinAppFrameworks() {
|
||||
local xcode_frameworks_dir="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
[[ -d "${xcode_frameworks_dir}" ]] || return 0
|
||||
find "${xcode_frameworks_dir}" -type d -name "*.framework" | while read framework_dir; do
|
||||
ThinFramework "$framework_dir" "$ARCHS"
|
||||
done
|
||||
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
|
||||
${verbose_flag} \
|
||||
assemble \
|
||||
--no-version-check \
|
||||
--output="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" \
|
||||
-dTargetPlatform=ios \
|
||||
-dIosArchs="${ARCHS}" \
|
||||
"thin_ios_application_frameworks"
|
||||
}
|
||||
|
||||
# Adds the App.framework as an embedded binary and the flutter_assets as
|
||||
|
||||
@ -417,7 +417,6 @@ abstract class IosAssetBundle extends Target {
|
||||
environment.buildDir.childFile('flutter_assets.d'),
|
||||
);
|
||||
|
||||
|
||||
// Copy the plist from either the project or module.
|
||||
// TODO(jonahwilliams): add plist to inputs
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
|
||||
@ -535,3 +534,84 @@ Future<RunResult> createStubAppFramework(File outputFile, String sdkRoot,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructively thins the specified executable file to include only the specified architectures.
|
||||
///
|
||||
/// This target is not fingerprinted and will always run.
|
||||
class ThinIosApplicationFrameworks extends Target {
|
||||
const ThinIosApplicationFrameworks();
|
||||
|
||||
@override
|
||||
String get name => 'thin_ios_application_frameworks';
|
||||
|
||||
@override
|
||||
List<Target> get dependencies => const <Target>[];
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[];
|
||||
|
||||
@override
|
||||
Future<void> build(Environment environment) async {
|
||||
if (environment.defines[kIosArchs] == null) {
|
||||
throw MissingDefineException(kIosArchs, 'thin_ios_application_frameworks');
|
||||
}
|
||||
final Directory frameworkDirectory = environment.outputDir;
|
||||
|
||||
final File appFramework = frameworkDirectory.childDirectory('App.framework').childFile('App');
|
||||
final File flutterFramework = frameworkDirectory.childDirectory('Flutter.framework').childFile('Flutter');
|
||||
await _thinBinary(appFramework, environment);
|
||||
await _thinBinary(flutterFramework, environment);
|
||||
}
|
||||
|
||||
Future<void> _thinBinary(File binary, Environment environment) async {
|
||||
final String binaryPath = binary.path;
|
||||
if (!binary.existsSync()) {
|
||||
throw Exception('Binary $binaryPath does not exist, cannot thin');
|
||||
}
|
||||
final String archs = environment.defines[kIosArchs];
|
||||
final List<String> archList = archs.split(' ').toList();
|
||||
final ProcessResult infoResult = environment.processManager.runSync(<String>[
|
||||
'lipo',
|
||||
'-info',
|
||||
binaryPath,
|
||||
]);
|
||||
final String lipoInfo = infoResult.stdout as String;
|
||||
|
||||
final ProcessResult verifyResult = environment.processManager.runSync(<String>[
|
||||
'lipo',
|
||||
binaryPath,
|
||||
'-verify_arch',
|
||||
...archList
|
||||
]);
|
||||
|
||||
if (verifyResult.exitCode != 0) {
|
||||
throw Exception('Binary $binaryPath does not contain $archs. Running lipo -info:\n$lipoInfo');
|
||||
}
|
||||
|
||||
// Skip this step for non-fat executables.
|
||||
if (lipoInfo.startsWith('Non-fat file:')) {
|
||||
environment.logger.printTrace('Skipping lipo for non-fat file $binaryPath');
|
||||
return;
|
||||
}
|
||||
|
||||
// Thin in-place.
|
||||
final ProcessResult extractResult = environment.processManager.runSync(<String>[
|
||||
'lipo',
|
||||
'-output',
|
||||
binaryPath,
|
||||
for (final String arch in archList)
|
||||
...<String>[
|
||||
'-extract',
|
||||
arch,
|
||||
],
|
||||
...<String>[binaryPath],
|
||||
]);
|
||||
|
||||
if (extractResult.exitCode != 0) {
|
||||
throw Exception('Failed to extract $archs for $binaryPath.\n${extractResult.stderr}\nRunning lipo -info:\n$lipoInfo');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +66,7 @@ const List<Target> _kDefaultTargets = <Target>[
|
||||
DebugIosApplicationBundle(),
|
||||
ProfileIosApplicationBundle(),
|
||||
ReleaseIosApplicationBundle(),
|
||||
ThinIosApplicationFrameworks(),
|
||||
// Windows targets
|
||||
UnpackWindows(),
|
||||
DebugBundleWindowsAssets(),
|
||||
|
||||
@ -45,7 +45,7 @@ void main() {
|
||||
FileSystem fileSystem;
|
||||
FakeProcessManager processManager;
|
||||
Artifacts artifacts;
|
||||
Logger logger;
|
||||
BufferLogger logger;
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
@ -300,4 +300,274 @@ void main() {
|
||||
await const DebugUnpackIOS().build(environment);
|
||||
});
|
||||
});
|
||||
|
||||
group('thin frameworks', () {
|
||||
testWithoutContext('fails when frameworks missing', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final Directory outputDir = fileSystem.directory('Runner.app').childDirectory('Frameworks');
|
||||
final Environment environment = Environment.test(
|
||||
fileSystem.currentDirectory,
|
||||
processManager: processManager,
|
||||
artifacts: artifacts,
|
||||
logger: logger,
|
||||
fileSystem: fileSystem,
|
||||
outputDir: outputDir,
|
||||
defines: <String, String>{
|
||||
kIosArchs: 'arm64',
|
||||
},
|
||||
);
|
||||
expect(
|
||||
const ThinIosApplicationFrameworks().build(environment),
|
||||
throwsA(isA<Exception>().having(
|
||||
(Exception exception) => exception.toString(),
|
||||
'description',
|
||||
contains('App.framework/App does not exist, cannot thin'),
|
||||
)));
|
||||
});
|
||||
|
||||
testWithoutContext('fails when requested archs missing from framework', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final Directory outputDir = fileSystem.directory('Runner.app').childDirectory('Frameworks')..createSync(recursive: true);
|
||||
final File appBinary = outputDir.childDirectory('App.framework').childFile('App')..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 armv7',
|
||||
},
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'-info',
|
||||
appBinary.path,
|
||||
], stdout: 'Architectures in the fat file:'),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
appBinary.path,
|
||||
'-verify_arch',
|
||||
'arm64',
|
||||
'armv7',
|
||||
], exitCode: 1),
|
||||
);
|
||||
|
||||
expect(
|
||||
const ThinIosApplicationFrameworks().build(environment),
|
||||
throwsA(isA<Exception>().having(
|
||||
(Exception exception) => exception.toString(),
|
||||
'description',
|
||||
contains('does not contain arm64 armv7. Running lipo -info:\nArchitectures in the fat file:'),
|
||||
)));
|
||||
});
|
||||
|
||||
testWithoutContext('fails when lipo extract fails', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final Directory outputDir = fileSystem.directory('Runner.app').childDirectory('Frameworks')..createSync(recursive: true);
|
||||
final File appBinary = outputDir.childDirectory('App.framework').childFile('App')..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 armv7',
|
||||
},
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'-info',
|
||||
appBinary.path,
|
||||
], stdout: 'Architectures in the fat file:'),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
appBinary.path,
|
||||
'-verify_arch',
|
||||
'arm64',
|
||||
'armv7',
|
||||
]),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'-output',
|
||||
appBinary.path,
|
||||
'-extract',
|
||||
'arm64',
|
||||
'-extract',
|
||||
'armv7',
|
||||
appBinary.path,
|
||||
], exitCode: 1,
|
||||
stderr: 'lipo error'),
|
||||
);
|
||||
|
||||
expect(
|
||||
const ThinIosApplicationFrameworks().build(environment),
|
||||
throwsA(isA<Exception>().having(
|
||||
(Exception exception) => exception.toString(),
|
||||
'description',
|
||||
contains('Failed to extract arm64 armv7 for Runner.app/Frameworks/App.framework/App.\nlipo error\nRunning lipo -info:\nArchitectures in the fat file:'),
|
||||
)));
|
||||
});
|
||||
|
||||
testWithoutContext('skips thin frameworks', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final Directory outputDir = fileSystem.directory('Runner.app').childDirectory('Frameworks')..createSync(recursive: true);
|
||||
final File appBinary = outputDir.childDirectory('App.framework').childFile('App')..createSync(recursive: true);
|
||||
final File flutterBinary = 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',
|
||||
},
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'-info',
|
||||
appBinary.path,
|
||||
], stdout: 'Non-fat file:'),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
appBinary.path,
|
||||
'-verify_arch',
|
||||
'arm64',
|
||||
]),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'-info',
|
||||
flutterBinary.path,
|
||||
], stdout: 'Non-fat file:'),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
flutterBinary.path,
|
||||
'-verify_arch',
|
||||
'arm64',
|
||||
]),
|
||||
);
|
||||
await const ThinIosApplicationFrameworks().build(environment);
|
||||
|
||||
expect(logger.traceText, contains('Skipping lipo for non-fat file Runner.app/Frameworks/App.framework/App'));
|
||||
expect(logger.traceText, contains('Skipping lipo for non-fat file Runner.app/Frameworks/Flutter.framework/Flutter'));
|
||||
|
||||
expect(processManager.hasRemainingExpectations, isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('thins fat frameworks', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final Directory outputDir = fileSystem.directory('Runner.app').childDirectory('Frameworks')..createSync(recursive: true);
|
||||
final File appBinary = outputDir.childDirectory('App.framework').childFile('App')..createSync(recursive: true);
|
||||
final File flutterBinary = 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 armv7',
|
||||
},
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'-info',
|
||||
appBinary.path,
|
||||
], stdout: 'Architectures in the fat file:'),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
appBinary.path,
|
||||
'-verify_arch',
|
||||
'arm64',
|
||||
'armv7',
|
||||
]),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'-output',
|
||||
appBinary.path,
|
||||
'-extract',
|
||||
'arm64',
|
||||
'-extract',
|
||||
'armv7',
|
||||
appBinary.path,
|
||||
]),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'-info',
|
||||
flutterBinary.path,
|
||||
], stdout: 'Architectures in the fat file:'),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
flutterBinary.path,
|
||||
'-verify_arch',
|
||||
'arm64',
|
||||
'armv7',
|
||||
]),
|
||||
);
|
||||
|
||||
processManager.addCommand(
|
||||
FakeCommand(command: <String>[
|
||||
'lipo',
|
||||
'-output',
|
||||
flutterBinary.path,
|
||||
'-extract',
|
||||
'arm64',
|
||||
'-extract',
|
||||
'armv7',
|
||||
flutterBinary.path,
|
||||
]),
|
||||
);
|
||||
|
||||
await const ThinIosApplicationFrameworks().build(environment);
|
||||
expect(processManager.hasRemainingExpectations, isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -195,6 +195,8 @@ void main() {
|
||||
'VERBOSE_SCRIPT_LOGGING': '1',
|
||||
'FLUTTER_BUILD_MODE': 'release',
|
||||
'ACTION': 'install',
|
||||
'ARCHS': 'arm64 armv7',
|
||||
'FLUTTER_ROOT': flutterRoot,
|
||||
// Skip bitcode stripping since we just checked that above.
|
||||
},
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user