Skip to content

Commit e133721

Browse files
authored
Check for watch companion in build settings (#113956)
1 parent ae143ad commit e133721

File tree

7 files changed

+291
-29
lines changed

7 files changed

+291
-29
lines changed

dev/integration_tests/ios_app_with_extensions/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@
727727
GCC_C_LANGUAGE_STANDARD = gnu11;
728728
IBSC_MODULE = watch_Extension;
729729
INFOPLIST_FILE = watch/Info.plist;
730+
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.flutter.extensionTest;
730731
MARKETING_VERSION = 1.0.0;
731732
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
732733
MTL_FAST_MATH = YES;
@@ -757,6 +758,7 @@
757758
GCC_C_LANGUAGE_STANDARD = gnu11;
758759
IBSC_MODULE = watch_Extension;
759760
INFOPLIST_FILE = watch/Info.plist;
761+
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.flutter.extensionTest;
760762
MARKETING_VERSION = 1.0.0;
761763
MTL_FAST_MATH = YES;
762764
OTHER_LDFLAGS = "";
@@ -785,6 +787,7 @@
785787
GCC_C_LANGUAGE_STANDARD = gnu11;
786788
IBSC_MODULE = watch_Extension;
787789
INFOPLIST_FILE = watch/Info.plist;
790+
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.flutter.extensionTest;
788791
MARKETING_VERSION = 1.0.0;
789792
MTL_FAST_MATH = YES;
790793
OTHER_LDFLAGS = "";

dev/integration_tests/ios_app_with_extensions/ios/watch/Info.plist

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
<string>UIInterfaceOrientationPortrait</string>
2626
<string>UIInterfaceOrientationPortraitUpsideDown</string>
2727
</array>
28-
<key>WKCompanionAppBundleIdentifier</key>
29-
<string>$(APP_BUNDLE_IDENTIFIER)</string>
3028
<key>WKWatchKitApp</key>
3129
<true/>
3230
</dict>

packages/flutter_tools/lib/src/ios/mac.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,10 @@ Future<XcodeBuildResult> buildXcodeProject({
265265

266266
// Check if the project contains a watchOS companion app.
267267
final bool hasWatchCompanion = await app.project.containsWatchCompanion(
268-
projectInfo.targets,
269-
buildInfo,
270-
deviceID,
268+
targets: projectInfo.targets,
269+
schemes: projectInfo.schemes,
270+
buildInfo: buildInfo,
271+
deviceId: deviceID,
271272
);
272273
if (hasWatchCompanion) {
273274
// The -sdk argument has to be omitted if a watchOS companion app exists.

packages/flutter_tools/lib/src/ios/xcodeproj.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,11 @@ class XcodeProjectInterpreter {
198198
if (buildContext.environmentType == EnvironmentType.simulator)
199199
...<String>['-sdk', 'iphonesimulator'],
200200
'-destination',
201-
if (deviceId != null)
201+
if (buildContext.isWatch == true && buildContext.environmentType == EnvironmentType.physical)
202+
'generic/platform=watchOS'
203+
else if (buildContext.isWatch == true)
204+
'generic/platform=watchOS Simulator'
205+
else if (deviceId != null)
202206
'id=$deviceId'
203207
else if (buildContext.environmentType == EnvironmentType.physical)
204208
'generic/platform=iOS'
@@ -376,12 +380,14 @@ class XcodeProjectBuildContext {
376380
this.configuration,
377381
this.environmentType = EnvironmentType.physical,
378382
this.deviceId,
383+
this.isWatch = false,
379384
});
380385

381386
final String? scheme;
382387
final String? configuration;
383388
final EnvironmentType environmentType;
384389
final String? deviceId;
390+
final bool isWatch;
385391

386392
@override
387393
int get hashCode => Object.hash(scheme, configuration, environmentType, deviceId);
@@ -395,7 +401,8 @@ class XcodeProjectBuildContext {
395401
other.scheme == scheme &&
396402
other.configuration == configuration &&
397403
other.deviceId == deviceId &&
398-
other.environmentType == environmentType;
404+
other.environmentType == environmentType &&
405+
other.isWatch == isWatch;
399406
}
400407
}
401408

packages/flutter_tools/lib/src/xcode_project.dart

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ class IosProject extends XcodeBasedProject {
256256
BuildInfo? buildInfo, {
257257
EnvironmentType environmentType = EnvironmentType.physical,
258258
String? deviceId,
259+
String? scheme,
260+
bool isWatch = false,
259261
}) async {
260262
if (!existsSync()) {
261263
return null;
@@ -265,9 +267,11 @@ class IosProject extends XcodeBasedProject {
265267
return null;
266268
}
267269

268-
final String? scheme = info.schemeFor(buildInfo);
269270
if (scheme == null) {
270-
info.reportFlavorNotFoundAndExit();
271+
scheme = info.schemeFor(buildInfo);
272+
if (scheme == null) {
273+
info.reportFlavorNotFoundAndExit();
274+
}
271275
}
272276

273277
final String? configuration = (await projectInfo())?.buildConfigurationFor(
@@ -279,6 +283,7 @@ class IosProject extends XcodeBasedProject {
279283
scheme: scheme,
280284
configuration: configuration,
281285
deviceId: deviceId,
286+
isWatch: isWatch,
282287
);
283288
final Map<String, String>? currentBuildSettings = _buildSettingsByBuildContext[buildContext];
284289
if (currentBuildSettings == null) {
@@ -327,7 +332,12 @@ class IosProject extends XcodeBasedProject {
327332
}
328333

329334
/// Check if one the [targets] of the project is a watchOS companion app target.
330-
Future<bool> containsWatchCompanion(List<String> targets, BuildInfo buildInfo, String? deviceId) async {
335+
Future<bool> containsWatchCompanion({
336+
required List<String> targets,
337+
required List<String> schemes,
338+
required BuildInfo buildInfo,
339+
String? deviceId,
340+
}) async {
331341
final String? bundleIdentifier = await productBundleIdentifier(buildInfo);
332342
// A bundle identifier is required for a companion app.
333343
if (bundleIdentifier == null) {
@@ -336,8 +346,8 @@ class IosProject extends XcodeBasedProject {
336346
for (final String target in targets) {
337347
// Create Info.plist file of the target.
338348
final File infoFile = hostAppRoot.childDirectory(target).childFile('Info.plist');
339-
// The Info.plist file of a target contains the key WKCompanionAppBundleIdentifier,
340-
// if it is a watchOS companion app.
349+
// In older versions of Xcode, if the target was a watchOS companion app,
350+
// the Info.plist file of the target contained the key WKCompanionAppBundleIdentifier.
341351
if (infoFile.existsSync()) {
342352
final String? fromPlist = globals.plistParser.getStringValueFromFile(infoFile.path, 'WKCompanionAppBundleIdentifier');
343353
if (bundleIdentifier == fromPlist) {
@@ -357,6 +367,34 @@ class IosProject extends XcodeBasedProject {
357367
}
358368
}
359369
}
370+
371+
// If key not found in Info.plist above, do more expensive check of build settings.
372+
// In newer versions of Xcode, the build settings of the watchOS companion
373+
// app's scheme should contain the key INFOPLIST_KEY_WKCompanionAppBundleIdentifier.
374+
final bool watchIdentifierFound = xcodeProjectInfoFile.readAsStringSync().contains('WKCompanionAppBundleIdentifier');
375+
if (watchIdentifierFound == false) {
376+
return false;
377+
}
378+
for (final String scheme in schemes) {
379+
final Map<String, String>? allBuildSettings = await buildSettingsForBuildInfo(
380+
buildInfo,
381+
deviceId: deviceId,
382+
scheme: scheme,
383+
isWatch: true,
384+
);
385+
if (allBuildSettings != null) {
386+
final String? fromBuild = allBuildSettings['INFOPLIST_KEY_WKCompanionAppBundleIdentifier'];
387+
if (bundleIdentifier == fromBuild) {
388+
return true;
389+
}
390+
if (fromBuild != null && fromBuild.contains(r'$')) {
391+
final String substitutedVariable = substituteXcodeVariables(fromBuild, allBuildSettings);
392+
if (substitutedVariable == bundleIdentifier) {
393+
return true;
394+
}
395+
}
396+
}
397+
}
360398
return false;
361399
}
362400

packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,76 @@ void main() {
395395
ProcessManager: () => FakeProcessManager.any(),
396396
});
397397

398+
testUsingContext('build settings uses watch destination if isWatch is true', () async {
399+
platform.environment = const <String, String>{};
400+
401+
fakeProcessManager.addCommands(<FakeCommand>[
402+
kWhichSysctlCommand,
403+
kx64CheckCommand,
404+
FakeCommand(
405+
command: <String>[
406+
'xcrun',
407+
'xcodebuild',
408+
'-project',
409+
'/',
410+
'-destination',
411+
'generic/platform=watchOS',
412+
'-showBuildSettings',
413+
'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}',
414+
],
415+
exitCode: 1,
416+
),
417+
]);
418+
419+
expect(
420+
await xcodeProjectInterpreter.getBuildSettings(
421+
'',
422+
buildContext: const XcodeProjectBuildContext(isWatch: true),
423+
),
424+
const <String, String>{},
425+
);
426+
expect(fakeProcessManager, hasNoRemainingExpectations);
427+
}, overrides: <Type, Generator>{
428+
FileSystem: () => fileSystem,
429+
ProcessManager: () => FakeProcessManager.any(),
430+
});
431+
432+
testUsingContext('build settings uses watch simulator destination if isWatch is true and environment type is simulator', () async {
433+
platform.environment = const <String, String>{};
434+
435+
fakeProcessManager.addCommands(<FakeCommand>[
436+
kWhichSysctlCommand,
437+
kx64CheckCommand,
438+
FakeCommand(
439+
command: <String>[
440+
'xcrun',
441+
'xcodebuild',
442+
'-project',
443+
'/',
444+
'-sdk',
445+
'iphonesimulator',
446+
'-destination',
447+
'generic/platform=watchOS Simulator',
448+
'-showBuildSettings',
449+
'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}',
450+
],
451+
exitCode: 1,
452+
),
453+
]);
454+
455+
expect(
456+
await xcodeProjectInterpreter.getBuildSettings(
457+
'',
458+
buildContext: const XcodeProjectBuildContext(environmentType: EnvironmentType.simulator, isWatch: true),
459+
),
460+
const <String, String>{},
461+
);
462+
expect(fakeProcessManager, hasNoRemainingExpectations);
463+
}, overrides: <Type, Generator>{
464+
FileSystem: () => fileSystem,
465+
ProcessManager: () => FakeProcessManager.any(),
466+
});
467+
398468
testWithoutContext('xcodebuild clean contains Flutter Xcode environment variables', () async {
399469
platform.environment = const <String, String>{
400470
'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual',

0 commit comments

Comments
 (0)