[flutter_tools] fix build for projects with watchOS companion app (#51126)
Co-authored-by: Georg Wechslberger <wechslbe@ma.tum.de>
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 5a34d94..4a35480 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -239,6 +239,7 @@
targetOverride: mainPath,
buildForDevice: true,
activeArch: cpuArchitecture,
+ deviceID: id,
);
if (!buildResult.success) {
_logger.printError('Could not build the precompiled application for the device.');
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index f52283b..5531a5e 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -87,6 +87,7 @@
bool buildForDevice,
DarwinArch activeArch,
bool codesign = true,
+ String deviceID,
}) async {
if (!upgradePbxProjWithFlutterAssets(app.project, globals.logger)) {
return XcodeBuildResult(success: false);
@@ -227,10 +228,28 @@
}
}
- if (buildForDevice) {
- buildCommands.addAll(<String>['-sdk', 'iphoneos']);
+ // Check if the project contains a watchOS companion app.
+ final bool hasWatchCompanion = await app.project.containsWatchCompanion(projectInfo.targets);
+ if (hasWatchCompanion) {
+ // The -sdk argument has to be omitted if a watchOS companion app exists.
+ // Otherwise the build will fail as WatchKit dependencies cannot be build using the iOS SDK.
+ globals.printStatus('Watch companion app found. Adjusting build settings.');
+ if (!buildForDevice && (deviceID == null || deviceID == '')) {
+ globals.printError('No simulator device ID has been set.');
+ globals.printError('A device ID is required to build an app with a watchOS companion app.');
+ globals.printError('Please run "flutter devices" to get a list of available device IDs');
+ globals.printError('and specify one using the -d, --device-id flag.');
+ return XcodeBuildResult(success: false);
+ }
+ if (!buildForDevice) {
+ buildCommands.addAll(<String>['-destination', 'id=$deviceID']);
+ }
} else {
- buildCommands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']);
+ if (buildForDevice) {
+ buildCommands.addAll(<String>['-sdk', 'iphoneos']);
+ } else {
+ buildCommands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']);
+ }
}
if (activeArch != null) {
@@ -374,8 +393,17 @@
),
);
} else {
+ // If the app contains a watch companion target, the sdk argument of xcodebuild has to be omitted.
+ // For some reason this leads to TARGET_BUILD_DIR always ending in 'iphoneos' even though the
+ // actual directory will end with 'iphonesimulator' for simulator builds.
+ // The value of TARGET_BUILD_DIR is adjusted to accommodate for this effect.
+ String targetBuildDir = buildSettings['TARGET_BUILD_DIR'];
+ if (hasWatchCompanion && !buildForDevice) {
+ globals.printTrace('Replacing iphoneos with iphonesimulator in TARGET_BUILD_DIR.');
+ targetBuildDir = targetBuildDir.replaceFirst('iphoneos', 'iphonesimulator');
+ }
final String expectedOutputDirectory = globals.fs.path.join(
- buildSettings['TARGET_BUILD_DIR'],
+ targetBuildDir,
buildSettings['WRAPPER_NAME'],
);
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index f28bde5..876823f 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -471,6 +471,7 @@
buildInfo: buildInfo,
targetOverride: mainPath,
buildForDevice: false,
+ deviceID: id,
);
if (!buildResult.success) {
throwToolExit('Could not build the application for the simulator.');
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 9928c96..fd7fa2e 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -486,6 +486,25 @@
await _updateGeneratedXcodeConfigIfNeeded();
}
+ /// Check if one the [targets] of the project is a watchOS companion app target.
+ Future<bool> containsWatchCompanion(List<String> targets) async {
+ final String bundleIdentifier = await productBundleIdentifier;
+ // A bundle identifier is required for a companion app.
+ if (bundleIdentifier == null) {
+ return false;
+ }
+ for (final String target in targets) {
+ // Create Info.plist file of the target.
+ final File infoFile = hostAppRoot.childDirectory(target).childFile('Info.plist');
+ // The Info.plist file of a target contains the key WKCompanionAppBundleIdentifier,
+ // if it is a watchOS companion app.
+ if (infoFile.existsSync() && globals.plistParser.getValueFromFile(infoFile.path, 'WKCompanionAppBundleIdentifier') == bundleIdentifier) {
+ return true;
+ }
+ }
+ return false;
+ }
+
Future<void> _updateGeneratedXcodeConfigIfNeeded() async {
if (globals.cache.isOlderThanToolsStamp(generatedXcodePropertiesFile)) {
await xcode.updateGeneratedXcodeProperties(