Unverified Commit e21d8220 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Remove build ios-framework --universal flag (#73383)

parent 8f07fcce
...@@ -10,7 +10,7 @@ import 'package:flutter_devicelab/framework/task_result.dart'; ...@@ -10,7 +10,7 @@ import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart'; import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
/// Tests that iOS .frameworks can be built on module projects. /// Tests that iOS .xcframeworks can be built.
Future<void> main() async { Future<void> main() async {
await task(() async { await task(() async {
...@@ -76,10 +76,10 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -76,10 +76,10 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
); );
}); });
// First, build the module in Debug to copy the debug version of Flutter.framework. // First, build the module in Debug to copy the debug version of Flutter.xcframework.
// This proves "flutter build ios-framework" re-copies the relevant Flutter.framework, // This proves "flutter build ios-framework" re-copies the relevant Flutter.xcframework,
// otherwise building plugins with bitcode will fail linking because the debug version // otherwise building plugins with bitcode will fail linking because the debug version
// of Flutter.framework does not contain bitcode. // of Flutter.xcframework does not contain bitcode.
await inDirectory(projectDir, () async { await inDirectory(projectDir, () async {
await flutter( await flutter(
'build', 'build',
...@@ -101,7 +101,6 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -101,7 +101,6 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
'build', 'build',
options: <String>[ options: <String>[
'ios-framework', 'ios-framework',
'--universal',
'--verbose', '--verbose',
'--output=$outputDirectoryName' '--output=$outputDirectoryName'
], ],
...@@ -110,40 +109,6 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -110,40 +109,6 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
final String outputPath = path.join(projectDir.path, outputDirectoryName); final String outputPath = path.join(projectDir.path, outputDirectoryName);
section('Check debug build has Dart snapshot as asset');
checkFileExists(path.join(
outputPath,
'Debug',
'App.framework',
'flutter_assets',
'vm_snapshot_data',
));
section('Check debug build has no Dart AOT');
// There's still an App.framework with a dylib, but it's empty.
checkFileExists(path.join(
outputPath,
'Debug',
'App.framework',
'App',
));
final String debugAppFrameworkPath = path.join(
outputPath,
'Debug',
'App.framework',
'App',
);
final String aotSymbols = await dylibSymbols(debugAppFrameworkPath);
if (aotSymbols.contains('architecture') ||
aotSymbols.contains('_kDartVmSnapshot')) {
throw TaskResult.failure('Debug App.framework contains AOT');
}
await _checkFrameworkArchs(debugAppFrameworkPath, true);
// Xcode changed the name of this generated directory in Xcode 12. // Xcode changed the name of this generated directory in Xcode 12.
const String xcode11ArmDirectoryName = 'ios-armv7_arm64'; const String xcode11ArmDirectoryName = 'ios-armv7_arm64';
const String xcode12ArmDirectoryName = 'ios-arm64_armv7'; const String xcode12ArmDirectoryName = 'ios-arm64_armv7';
...@@ -202,26 +167,49 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -202,26 +167,49 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
throw const FileSystemException('Expected Flutter.framework binary to exist.'); throw const FileSystemException('Expected Flutter.framework binary to exist.');
} }
final String debugAppFrameworkPath = path.join(
outputPath,
'Debug',
'App.xcframework',
localXcodeArmDirectoryName,
'App.framework',
'App',
);
checkFileExists(debugAppFrameworkPath);
section('Check debug build has Dart snapshot as asset');
checkFileExists(path.join( checkFileExists(path.join(
outputPath, outputPath,
'Debug', 'Debug',
'App.xcframework', 'App.xcframework',
'ios-x86_64-simulator', 'ios-x86_64-simulator',
'App.framework', 'App.framework',
'App', 'flutter_assets',
'vm_snapshot_data',
)); ));
section('Check debug build has no Dart AOT');
final String aotSymbols = await dylibSymbols(debugAppFrameworkPath);
if (aotSymbols.contains('architecture') ||
aotSymbols.contains('_kDartVmSnapshot')) {
throw TaskResult.failure('Debug App.framework contains AOT');
}
section('Check profile, release builds has Dart AOT dylib'); section('Check profile, release builds has Dart AOT dylib');
for (final String mode in <String>['Profile', 'Release']) { for (final String mode in <String>['Profile', 'Release']) {
final String appFrameworkPath = path.join( final String appFrameworkPath = path.join(
outputPath, outputPath,
mode, mode,
'App.xcframework',
localXcodeArmDirectoryName,
'App.framework', 'App.framework',
'App', 'App',
); );
await _checkFrameworkArchs(appFrameworkPath, false);
await _checkBitcode(appFrameworkPath, mode); await _checkBitcode(appFrameworkPath, mode);
final String aotSymbols = await dylibSymbols(appFrameworkPath); final String aotSymbols = await dylibSymbols(appFrameworkPath);
...@@ -231,20 +219,13 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -231,20 +219,13 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
} }
checkFileNotExists(path.join( checkFileNotExists(path.join(
outputPath,
mode,
'App.framework',
'flutter_assets',
'vm_snapshot_data',
));
checkFileExists(path.join(
outputPath, outputPath,
mode, mode,
'App.xcframework', 'App.xcframework',
localXcodeArmDirectoryName, localXcodeArmDirectoryName,
'App.framework', 'App.framework',
'App', 'flutter_assets',
'vm_snapshot_data',
)); ));
checkFileNotExists(path.join( checkFileNotExists(path.join(
...@@ -263,57 +244,46 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -263,57 +244,46 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
final String engineFrameworkPath = path.join( final String engineFrameworkPath = path.join(
outputPath, outputPath,
mode, mode,
'Flutter.xcframework',
builderXcodeArmDirectoryName,
'Flutter.framework', 'Flutter.framework',
'Flutter', 'Flutter',
); );
await _checkFrameworkArchs(engineFrameworkPath, true);
await _checkBitcode(engineFrameworkPath, mode); await _checkBitcode(engineFrameworkPath, mode);
checkFileExists(path.join( checkFileExists(path.join(
outputPath, outputPath,
mode, mode,
'Flutter.xcframework', 'Flutter.xcframework',
builderXcodeArmDirectoryName, 'ios-x86_64-simulator',
'Flutter.framework', 'Flutter.framework',
'Flutter', 'Flutter',
)); ));
checkFileExists(path.join( checkFileExists(path.join(
outputPath, outputPath,
mode, mode,
'Flutter.xcframework', 'Flutter.xcframework',
'ios-x86_64-simulator', 'ios-x86_64-simulator',
'Flutter.framework', 'Flutter.framework',
'Flutter', 'Headers',
'Flutter.h',
)); ));
} }
section("Check all modes' engine header");
for (final String mode in <String>['Debug', 'Profile', 'Release']) {
checkFileExists(path.join(outputPath, mode, 'Flutter.framework', 'Headers', 'Flutter.h'));
}
section('Check all modes have plugins'); section('Check all modes have plugins');
for (final String mode in <String>['Debug', 'Profile', 'Release']) { for (final String mode in <String>['Debug', 'Profile', 'Release']) {
final String pluginFrameworkPath = path.join( final String pluginFrameworkPath = path.join(
outputPath,
mode,
'device_info.framework',
'device_info',
);
await _checkFrameworkArchs(pluginFrameworkPath, mode == 'Debug');
await _checkBitcode(pluginFrameworkPath, mode);
checkFileExists(path.join(
outputPath, outputPath,
mode, mode,
'device_info.xcframework', 'device_info.xcframework',
localXcodeArmDirectoryName, localXcodeArmDirectoryName,
'device_info.framework', 'device_info.framework',
'device_info', 'device_info',
)); );
await _checkBitcode(pluginFrameworkPath, mode);
checkFileExists(path.join( checkFileExists(path.join(
outputPath, outputPath,
...@@ -362,20 +332,13 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -362,20 +332,13 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
final String registrantFrameworkPath = path.join( final String registrantFrameworkPath = path.join(
outputPath, outputPath,
mode, mode,
'FlutterPluginRegistrant.xcframework',
localXcodeArmDirectoryName,
'FlutterPluginRegistrant.framework', 'FlutterPluginRegistrant.framework',
'FlutterPluginRegistrant' 'FlutterPluginRegistrant',
); );
await _checkFrameworkArchs(registrantFrameworkPath, mode == 'Debug');
await _checkBitcode(registrantFrameworkPath, mode); await _checkBitcode(registrantFrameworkPath, mode);
checkFileExists(path.join(
outputPath,
mode,
'FlutterPluginRegistrant.framework',
'Headers',
'GeneratedPluginRegistrant.h',
));
checkFileExists(path.join( checkFileExists(path.join(
outputPath, outputPath,
mode, mode,
...@@ -412,7 +375,6 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -412,7 +375,6 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
options: <String>[ options: <String>[
'ios-framework', 'ios-framework',
'--cocoapods', '--cocoapods',
'--universal',
'--force', // Allow podspec creation on master. '--force', // Allow podspec creation on master.
'--output=$cocoapodsOutputDirectoryName' '--output=$cocoapodsOutputDirectoryName'
], ],
...@@ -430,29 +392,29 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -430,29 +392,29 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
checkDirectoryExists(path.join( checkDirectoryExists(path.join(
cocoapodsOutputPath, cocoapodsOutputPath,
mode, mode,
'App.framework', 'App.xcframework',
)); ));
if (Directory(path.join( if (Directory(path.join(
cocoapodsOutputPath, cocoapodsOutputPath,
mode, mode,
'FlutterPluginRegistrant.framework', 'FlutterPluginRegistrant.xcframework',
)).existsSync() != )).existsSync() !=
isModule) { isModule) {
throw TaskResult.failure( throw TaskResult.failure(
'Unexpected FlutterPluginRegistrant.framework.'); 'Unexpected FlutterPluginRegistrant.xcframework.');
} }
checkDirectoryExists(path.join( checkDirectoryExists(path.join(
cocoapodsOutputPath, cocoapodsOutputPath,
mode, mode,
'device_info.framework', 'device_info.xcframework',
)); ));
checkDirectoryExists(path.join( checkDirectoryExists(path.join(
cocoapodsOutputPath, cocoapodsOutputPath,
mode, mode,
'package_info.framework', 'package_info.xcframework',
)); ));
} }
...@@ -473,27 +435,6 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals ...@@ -473,27 +435,6 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
} }
} }
Future<void> _checkFrameworkArchs(String frameworkPath, bool shouldContainSimulator) async {
checkFileExists(frameworkPath);
final String archs = await fileType(frameworkPath);
if (!archs.contains('armv7')) {
throw TaskResult.failure('$frameworkPath armv7 architecture missing');
}
if (!archs.contains('arm64')) {
throw TaskResult.failure('$frameworkPath arm64 architecture missing');
}
final bool containsSimulator = archs.contains('x86_64');
// Debug should contain the simulator archs.
// Release and Profile should not.
if (containsSimulator != shouldContainSimulator) {
throw TaskResult.failure(
'$frameworkPath x86_64 architecture ${shouldContainSimulator ? 'missing' : 'present'}');
}
}
Future<void> _checkBitcode(String frameworkPath, String mode) async { Future<void> _checkBitcode(String frameworkPath, String mode) async {
checkFileExists(frameworkPath); checkFileExists(frameworkPath);
......
...@@ -73,14 +73,15 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -73,14 +73,15 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
'By default, all build configurations are built.' 'By default, all build configurations are built.'
) )
..addFlag('universal', ..addFlag('universal',
help: '(Deprecated) Produce universal frameworks that include all valid architectures. ' help: '(Deprecated) Produce universal frameworks that include all valid architectures.',
'This option will be removed in a future version of Flutter.',
negatable: true, negatable: true,
hide: true, hide: true,
) )
..addFlag('xcframework', ..addFlag('xcframework',
help: 'Produce xcframeworks that include all valid architectures.', help: 'Produce xcframeworks that include all valid architectures.',
negatable: false,
defaultsTo: true, defaultsTo: true,
hide: true,
) )
..addFlag('cocoapods', ..addFlag('cocoapods',
help: 'Produce a Flutter.podspec instead of an engine Flutter.xcframework (recommended if host app uses CocoaPods).', help: 'Produce a Flutter.podspec instead of an engine Flutter.xcframework (recommended if host app uses CocoaPods).',
...@@ -115,7 +116,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -115,7 +116,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
final String name = 'ios-framework'; final String name = 'ios-framework';
@override @override
final String description = 'Produces .frameworks for a Flutter project ' final String description = 'Produces .xcframeworks for a Flutter project '
'and its plugins for integration into existing, plain Xcode projects.\n' 'and its plugins for integration into existing, plain Xcode projects.\n'
'This can only be run on macOS hosts.'; 'This can only be run on macOS hosts.';
...@@ -150,16 +151,8 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -150,16 +151,8 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
throwToolExit('Building frameworks for iOS is only supported on the Mac.'); throwToolExit('Building frameworks for iOS is only supported on the Mac.');
} }
if (!boolArg('universal') && !boolArg('xcframework')) {
throwToolExit('--xcframework or --universal is required.');
}
if (boolArg('xcframework') && globals.xcode.majorVersion < 11) {
throwToolExit('--xcframework requires Xcode 11.');
}
if (boolArg('universal')) { if (boolArg('universal')) {
globals.printError('--universal has been deprecated to support Apple ' throwToolExit('--universal has been deprecated, only XCFrameworks are supported.');
'Silicon ARM simulators and will be removed in a future version of '
'Flutter. Use --xcframework instead.');
} }
if ((await buildInfos).isEmpty) { if ((await buildInfos).isEmpty) {
throwToolExit('At least one of "--debug" or "--profile", or "--release" is required.'); throwToolExit('At least one of "--debug" or "--profile", or "--release" is required.');
...@@ -196,7 +189,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { ...@@ -196,7 +189,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand {
_flutterVersion ??= globals.flutterVersion; _flutterVersion ??= globals.flutterVersion;
produceFlutterPodspec(buildInfo.mode, modeDirectory, force: boolArg('force')); produceFlutterPodspec(buildInfo.mode, modeDirectory, force: boolArg('force'));
} else { } else {
// Copy Flutter.framework. // Copy Flutter.xcframework.
await _produceFlutterFramework(buildInfo, modeDirectory); await _produceFlutterFramework(buildInfo, modeDirectory);
} }
...@@ -311,7 +304,7 @@ end ...@@ -311,7 +304,7 @@ end
Directory modeDirectory, Directory modeDirectory,
) async { ) async {
final Status status = globals.logger.startProgress( final Status status = globals.logger.startProgress(
' ├─Populating Flutter.xcframework...', ' ├─Copying Flutter.xcframework...',
); );
final String engineCacheFlutterFrameworkDirectory = globals.artifacts.getArtifactPath( final String engineCacheFlutterFrameworkDirectory = globals.artifacts.getArtifactPath(
Artifact.flutterXcframework, Artifact.flutterXcframework,
...@@ -334,8 +327,6 @@ end ...@@ -334,8 +327,6 @@ end
} finally { } finally {
status.stop(); status.stop();
} }
await _produceUniversalFromXCFramework(buildInfo, flutterFrameworkCopy);
} }
Future<void> _produceAppFramework( Future<void> _produceAppFramework(
...@@ -347,7 +338,7 @@ end ...@@ -347,7 +338,7 @@ end
const String appFrameworkName = 'App.framework'; const String appFrameworkName = 'App.framework';
final Status status = globals.logger.startProgress( final Status status = globals.logger.startProgress(
' ├─Building App.framework...', ' ├─Building App.xcframework...',
); );
final List<EnvironmentType> environmentTypes = <EnvironmentType>[ final List<EnvironmentType> environmentTypes = <EnvironmentType>[
EnvironmentType.physical, EnvironmentType.physical,
...@@ -407,14 +398,13 @@ end ...@@ -407,14 +398,13 @@ end
in result.exceptions.values) { in result.exceptions.values) {
globals.printError(measurement.exception.toString()); globals.printError(measurement.exception.toString());
} }
throwToolExit('The App.framework build failed.'); throwToolExit('The App.xcframework build failed.');
} }
} }
} finally { } finally {
status.stop(); status.stop();
} }
await _produceUniversalFramework(frameworks, 'App', outputDirectory);
await _produceXCFramework(frameworks, 'App', outputDirectory); await _produceXCFramework(frameworks, 'App', outputDirectory);
} }
...@@ -520,7 +510,6 @@ end ...@@ -520,7 +510,6 @@ end
.childDirectory(podFrameworkName) .childDirectory(podFrameworkName)
]; ];
await _produceUniversalFramework(frameworks, binaryName, modeDirectory);
await _produceXCFramework(frameworks, binaryName, modeDirectory); await _produceXCFramework(frameworks, binaryName, modeDirectory);
} }
} }
...@@ -529,37 +518,6 @@ end ...@@ -529,37 +518,6 @@ end
} }
} }
Future<void> _produceUniversalFromXCFramework(BuildInfo buildInfo, Directory xcframework) async {
if (boolArg('universal')) {
final String frameworkBinaryName =
globals.fs.path.basenameWithoutExtension(xcframework.basename);
final Status status = globals.logger.startProgress(
' ├─Creating $frameworkBinaryName.framework...',
);
try {
final Iterable<Directory> frameworks = xcframework
.listSync()
.whereType<Directory>()
.map((Directory triple) => triple
.listSync()
.whereType<Directory>()
.firstWhere((Directory frameworkDirectory) =>
frameworkDirectory.basename ==
'$frameworkBinaryName.framework'));
await _produceUniversalFramework(
frameworks, frameworkBinaryName, xcframework.parent);
} finally {
status.stop();
}
}
if (!boolArg('xcframework')) {
xcframework.deleteSync(recursive: true);
}
}
Future<void> _produceXCFramework(Iterable<Directory> frameworks, Future<void> _produceXCFramework(Iterable<Directory> frameworks,
String frameworkBinaryName, Directory outputDirectory) async { String frameworkBinaryName, Directory outputDirectory) async {
if (!boolArg('xcframework')) { if (!boolArg('xcframework')) {
...@@ -587,42 +545,4 @@ end ...@@ -587,42 +545,4 @@ end
'Unable to create $frameworkBinaryName.xcframework: ${xcframeworkResult.stderr}'); 'Unable to create $frameworkBinaryName.xcframework: ${xcframeworkResult.stderr}');
} }
} }
Future<void> _produceUniversalFramework(Iterable<Directory> frameworks,
String frameworkBinaryName, Directory outputDirectory) async {
if (!boolArg('universal')) {
return;
}
final Directory outputFrameworkDirectory =
outputDirectory.childDirectory('$frameworkBinaryName.framework');
// Copy the first framework over completely to get headers, resources, etc.
globals.fsUtils.copyDirectorySync(
frameworks.first,
outputFrameworkDirectory,
);
// Recreate the framework binary by lipo'ing the framework binaries together.
final List<String> lipoCommand = <String>[
...globals.xcode.xcrunCommand(),
'lipo',
'-create',
for (Directory framework in frameworks) ...<String>[
framework.childFile(frameworkBinaryName).path
],
'-output',
outputFrameworkDirectory.childFile(frameworkBinaryName).path
];
final RunResult lipoResult = await globals.processUtils.run(
lipoCommand,
workingDirectory: outputDirectory.path,
allowReentrantFlutter: false,
);
if (lipoResult.exitCode != 0) {
throwToolExit(
'Unable to create $frameworkBinaryName.framework: ${lipoResult.stderr}');
}
}
} }
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