Unverified Commit 3969e5b4 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Move iOS Flutter.framework thinning into copy assemble build target (#77007)

parent dac968a4
...@@ -204,19 +204,6 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr ...@@ -204,19 +204,6 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr
return 0 return 0
} }
# Destructively thins the Flutter and App frameworks to include only the specified
# architectures.
ThinAppFrameworks() {
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 # Adds the App.framework as an embedded binary and the flutter_assets as
# resources. # resources.
EmbedFlutterFrameworks() { EmbedFlutterFrameworks() {
...@@ -273,11 +260,6 @@ AddObservatoryBonjourService() { ...@@ -273,11 +260,6 @@ AddObservatoryBonjourService() {
fi fi
} }
EmbedAndThinFrameworks() {
EmbedFlutterFrameworks
ThinAppFrameworks
}
# Main entry point. # Main entry point.
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
# Named entry points were introduced in Flutter v0.0.7. # Named entry points were introduced in Flutter v0.0.7.
...@@ -288,11 +270,13 @@ else ...@@ -288,11 +270,13 @@ else
"build") "build")
BuildApp ;; BuildApp ;;
"thin") "thin")
ThinAppFrameworks ;; # No-op, thinning is handled during the bundle asset assemble build target.
;;
"embed") "embed")
EmbedFlutterFrameworks ;; EmbedFlutterFrameworks ;;
"embed_and_thin") "embed_and_thin")
EmbedAndThinFrameworks ;; # Thinning is handled during the bundle asset assemble build target, so just embed.
EmbedFlutterFrameworks ;;
"test_observatory_bonjour_service") "test_observatory_bonjour_service")
# Exposed for integration testing only. # Exposed for integration testing only.
AddObservatoryBonjourService ;; AddObservatoryBonjourService ;;
......
...@@ -277,6 +277,14 @@ abstract class UnpackIOS extends Target { ...@@ -277,6 +277,14 @@ abstract class UnpackIOS extends Target {
if (environment.defines[kSdkRoot] == null) { if (environment.defines[kSdkRoot] == null) {
throw MissingDefineException(kSdkRoot, name); throw MissingDefineException(kSdkRoot, name);
} }
if (environment.defines[kIosArchs] == null) {
throw MissingDefineException(kIosArchs, name);
}
await _copyFramework(environment);
await _thinFramework(environment);
}
Future<void> _copyFramework(Environment environment) async {
final Directory sdkRoot = environment.fileSystem.directory(environment.defines[kSdkRoot]); final Directory sdkRoot = environment.fileSystem.directory(environment.defines[kSdkRoot]);
final EnvironmentType environmentType = environmentTypeFromSdkroot(sdkRoot); final EnvironmentType environmentType = environmentTypeFromSdkroot(sdkRoot);
final String basePath = environment.artifacts.getArtifactPath( final String basePath = environment.artifacts.getArtifactPath(
...@@ -302,6 +310,59 @@ abstract class UnpackIOS extends Target { ...@@ -302,6 +310,59 @@ 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');
}
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 thinning 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');
}
}
} }
/// Unpack the release prebuilt engine framework. /// Unpack the release prebuilt engine framework.
...@@ -534,78 +595,3 @@ Future<RunResult> createStubAppFramework(File outputFile, String sdkRoot, ...@@ -534,78 +595,3 @@ Future<RunResult> createStubAppFramework(File outputFile, String sdkRoot,
} }
} }
} }
/// Destructively thins the Flutter.framework 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 flutterFramework = frameworkDirectory.childDirectory('Flutter.framework').childFile('Flutter');
final String binaryPath = flutterFramework.path;
if (!flutterFramework.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,7 +66,6 @@ const List<Target> _kDefaultTargets = <Target>[ ...@@ -66,7 +66,6 @@ const List<Target> _kDefaultTargets = <Target>[
DebugIosApplicationBundle(), DebugIosApplicationBundle(),
ProfileIosApplicationBundle(), ProfileIosApplicationBundle(),
ReleaseIosApplicationBundle(), ReleaseIosApplicationBundle(),
ThinIosApplicationFrameworks(),
// Windows targets // Windows targets
UnpackWindows(), UnpackWindows(),
DebugBundleWindowsAssets(), DebugBundleWindowsAssets(),
......
...@@ -239,10 +239,26 @@ void main() { ...@@ -239,10 +239,26 @@ void main() {
Platform: () => macPlatform, Platform: () => macPlatform,
}); });
group('copy engine Flutter.framework', () { group('copy and thin engine Flutter.framework', () {
testWithoutContext('iphonesimulator', () async { Directory outputDir;
FakeCommand copyPhysicalFrameworkCommand;
setUp(() {
final FileSystem fileSystem = MemoryFileSystem.test(); final FileSystem fileSystem = MemoryFileSystem.test();
final Directory outputDir = fileSystem.directory('output'); outputDir = fileSystem.directory('output');
copyPhysicalFrameworkCommand = FakeCommand(command: <String>[
'rsync',
'-av',
'--delete',
'--filter',
'- .DS_Store/',
'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.physical',
outputDir.path,
]);
});
testWithoutContext('iphonesimulator', () async {
final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter');
final Environment environment = Environment.test( final Environment environment = Environment.test(
fileSystem.currentDirectory, fileSystem.currentDirectory,
processManager: processManager, processManager: processManager,
...@@ -251,6 +267,7 @@ void main() { ...@@ -251,6 +267,7 @@ void main() {
fileSystem: fileSystem, fileSystem: fileSystem,
outputDir: outputDir, outputDir: outputDir,
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'x86_64',
kSdkRoot: 'path/to/iPhoneSimulator.sdk', kSdkRoot: 'path/to/iPhoneSimulator.sdk',
}, },
); );
...@@ -264,47 +281,34 @@ void main() { ...@@ -264,47 +281,34 @@ void main() {
'- .DS_Store/', '- .DS_Store/',
'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.simulator', 'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.simulator',
outputDir.path, outputDir.path,
]), ],
onRun: () => binary.createSync(recursive: true),
),
); );
await const DebugUnpackIOS().build(environment); processManager.addCommand(
}); FakeCommand(command: <String>[
'lipo',
testWithoutContext('iphoneos', () async { '-info',
final FileSystem fileSystem = MemoryFileSystem.test(); binary.path,
final Directory outputDir = fileSystem.directory('output'); ], stdout: 'Non-fat file:'),
final Environment environment = Environment.test(
fileSystem.currentDirectory,
processManager: processManager,
artifacts: artifacts,
logger: logger,
fileSystem: fileSystem,
outputDir: outputDir,
defines: <String, String>{
kSdkRoot: 'path/to/iPhoneOS.sdk',
},
); );
processManager.addCommand( processManager.addCommand(
FakeCommand(command: <String>[ FakeCommand(command: <String>[
'rsync', 'lipo',
'-av', binary.path,
'--delete', '-verify_arch',
'--filter', 'x86_64',
'- .DS_Store/',
'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.physical',
outputDir.path,
]), ]),
); );
await const DebugUnpackIOS().build(environment); await const DebugUnpackIOS().build(environment);
expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
expect(processManager.hasRemainingExpectations, isFalse);
}); });
});
group('thin frameworks', () { testWithoutContext('thinning fails when frameworks missing', () async {
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( final Environment environment = Environment.test(
fileSystem.currentDirectory, fileSystem.currentDirectory,
processManager: processManager, processManager: processManager,
...@@ -314,10 +318,12 @@ void main() { ...@@ -314,10 +318,12 @@ void main() {
outputDir: outputDir, outputDir: outputDir,
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64', kIosArchs: 'arm64',
kSdkRoot: 'path/to/iPhoneOS.sdk',
}, },
); );
processManager.addCommand(copyPhysicalFrameworkCommand);
expect( expect(
const ThinIosApplicationFrameworks().build(environment), const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having( throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
'description', 'description',
...@@ -325,9 +331,7 @@ void main() { ...@@ -325,9 +331,7 @@ void main() {
))); )));
}); });
testWithoutContext('fails when requested archs missing from framework', () async { testWithoutContext('thinning 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 binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true); final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test( final Environment environment = Environment.test(
...@@ -339,9 +343,11 @@ void main() { ...@@ -339,9 +343,11 @@ void main() {
outputDir: outputDir, outputDir: outputDir,
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64 armv7', kIosArchs: 'arm64 armv7',
kSdkRoot: 'path/to/iPhoneOS.sdk',
}, },
); );
processManager.addCommand(copyPhysicalFrameworkCommand);
processManager.addCommand( processManager.addCommand(
FakeCommand(command: <String>[ FakeCommand(command: <String>[
'lipo', 'lipo',
...@@ -361,7 +367,7 @@ void main() { ...@@ -361,7 +367,7 @@ void main() {
); );
expect( expect(
const ThinIosApplicationFrameworks().build(environment), const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having( throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
'description', 'description',
...@@ -369,9 +375,7 @@ void main() { ...@@ -369,9 +375,7 @@ void main() {
))); )));
}); });
testWithoutContext('fails when lipo extract fails', () async { testWithoutContext('thinning 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 binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true); final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test( final Environment environment = Environment.test(
...@@ -383,9 +387,11 @@ void main() { ...@@ -383,9 +387,11 @@ void main() {
outputDir: outputDir, outputDir: outputDir,
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64 armv7', kIosArchs: 'arm64 armv7',
kSdkRoot: 'path/to/iPhoneOS.sdk',
}, },
); );
processManager.addCommand(copyPhysicalFrameworkCommand);
processManager.addCommand( processManager.addCommand(
FakeCommand(command: <String>[ FakeCommand(command: <String>[
'lipo', 'lipo',
...@@ -419,17 +425,15 @@ void main() { ...@@ -419,17 +425,15 @@ void main() {
); );
expect( expect(
const ThinIosApplicationFrameworks().build(environment), const DebugUnpackIOS().build(environment),
throwsA(isA<Exception>().having( throwsA(isA<Exception>().having(
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
'description', 'description',
contains('Failed to extract arm64 armv7 for Runner.app/Frameworks/Flutter.framework/Flutter.\nlipo error\nRunning lipo -info:\nArchitectures in the fat file:'), contains('Failed to extract arm64 armv7 for output/Flutter.framework/Flutter.\nlipo error\nRunning lipo -info:\nArchitectures in the fat file:'),
))); )));
}); });
testWithoutContext('skips thin frameworks', () async { testWithoutContext('skips thin frameworks', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory outputDir = fileSystem.directory('Runner.app').childDirectory('Frameworks')..createSync(recursive: true);
final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true); final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test( final Environment environment = Environment.test(
...@@ -441,9 +445,11 @@ void main() { ...@@ -441,9 +445,11 @@ void main() {
outputDir: outputDir, outputDir: outputDir,
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64', kIosArchs: 'arm64',
kSdkRoot: 'path/to/iPhoneOS.sdk',
}, },
); );
processManager.addCommand(copyPhysicalFrameworkCommand);
processManager.addCommand( processManager.addCommand(
FakeCommand(command: <String>[ FakeCommand(command: <String>[
'lipo', 'lipo',
...@@ -460,16 +466,14 @@ void main() { ...@@ -460,16 +466,14 @@ void main() {
'arm64', 'arm64',
]), ]),
); );
await const ThinIosApplicationFrameworks().build(environment); await const DebugUnpackIOS().build(environment);
expect(logger.traceText, contains('Skipping lipo for non-fat file Runner.app/Frameworks/Flutter.framework/Flutter')); expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('thins fat frameworks', () async { testWithoutContext('thins fat frameworks', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Directory outputDir = fileSystem.directory('Runner.app').childDirectory('Frameworks')..createSync(recursive: true);
final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true); final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
final Environment environment = Environment.test( final Environment environment = Environment.test(
...@@ -481,9 +485,11 @@ void main() { ...@@ -481,9 +485,11 @@ void main() {
outputDir: outputDir, outputDir: outputDir,
defines: <String, String>{ defines: <String, String>{
kIosArchs: 'arm64 armv7', kIosArchs: 'arm64 armv7',
kSdkRoot: 'path/to/iPhoneOS.sdk',
}, },
); );
processManager.addCommand(copyPhysicalFrameworkCommand);
processManager.addCommand( processManager.addCommand(
FakeCommand(command: <String>[ FakeCommand(command: <String>[
'lipo', 'lipo',
...@@ -515,7 +521,7 @@ void main() { ...@@ -515,7 +521,7 @@ void main() {
]), ]),
); );
await const ThinIosApplicationFrameworks().build(environment); await const DebugUnpackIOS().build(environment);
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager.hasRemainingExpectations, isFalse);
}); });
}); });
......
...@@ -23,6 +23,7 @@ void main() { ...@@ -23,6 +23,7 @@ void main() {
Directory buildPath; Directory buildPath;
Directory outputApp; Directory outputApp;
Directory frameworkDirectory;
Directory outputFlutterFramework; Directory outputFlutterFramework;
File outputFlutterFrameworkBinary; File outputFlutterFrameworkBinary;
Directory outputAppFramework; Directory outputAppFramework;
...@@ -70,22 +71,11 @@ void main() { ...@@ -70,22 +71,11 @@ void main() {
outputApp = buildPath.childDirectory('Runner.app'); outputApp = buildPath.childDirectory('Runner.app');
outputFlutterFramework = fileSystem.directory( frameworkDirectory = outputApp.childDirectory('Frameworks');
fileSystem.path.join( outputFlutterFramework = frameworkDirectory.childDirectory('Flutter.framework');
outputApp.path,
'Frameworks',
'Flutter.framework',
),
);
outputFlutterFrameworkBinary = outputFlutterFramework.childFile('Flutter'); outputFlutterFrameworkBinary = outputFlutterFramework.childFile('Flutter');
outputAppFramework = fileSystem.directory(fileSystem.path.join( outputAppFramework = frameworkDirectory.childDirectory('App.framework');
outputApp.path,
'Frameworks',
'App.framework',
));
outputAppFrameworkBinary = outputAppFramework.childFile('App'); outputAppFrameworkBinary = outputAppFramework.childFile('App');
}); });
...@@ -94,6 +84,8 @@ void main() { ...@@ -94,6 +84,8 @@ void main() {
}); });
testWithoutContext('flutter build ios builds a valid app', () { testWithoutContext('flutter build ios builds a valid app', () {
// Should only contain Flutter.framework and App.framework.
expect(frameworkDirectory.listSync().length, 2);
expect(outputAppFramework.childFile('App'), exists); expect(outputAppFramework.childFile('App'), exists);
final File vmSnapshot = fileSystem.file(fileSystem.path.join( final File vmSnapshot = fileSystem.file(fileSystem.path.join(
...@@ -195,8 +187,6 @@ void main() { ...@@ -195,8 +187,6 @@ void main() {
'VERBOSE_SCRIPT_LOGGING': '1', 'VERBOSE_SCRIPT_LOGGING': '1',
'FLUTTER_BUILD_MODE': 'release', 'FLUTTER_BUILD_MODE': 'release',
'ACTION': 'install', 'ACTION': 'install',
'ARCHS': 'arm64 armv7',
'FLUTTER_ROOT': flutterRoot,
// Skip bitcode stripping since we just checked that above. // Skip bitcode stripping since we just checked that above.
}, },
); );
......
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