Skip to content

Commit 40843e3

Browse files
authored
Update minimum macOS version as needed in Swift package (#152347)
If Swift Package Manager is enabled, the tool generates a Swift package at `<ios/macos>/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/`. This Swift package is how the tool adds plugins to the Flutter project. SwiftPM is strictly enforces platform versions: you cannot depend on a Swift package if its supported version is higher than your own. On iOS, we use the project's minimum deployment version for the generated Swift package. If a plugin has a higher requirement, you'll need to update your project's minimum deployment version. The generated Swift package is automatically updated the next time you run the tool. This updates macOS to do the same thing. Fixes flutter/flutter#146204
1 parent 0632b90 commit 40843e3

File tree

6 files changed

+347
-126
lines changed

6 files changed

+347
-126
lines changed

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

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ class XcodeProjectInterpreter {
195195
final String? configuration = buildContext.configuration;
196196
final String? target = buildContext.target;
197197
final String? deviceId = buildContext.deviceId;
198+
final String buildDir = switch (buildContext.sdk) {
199+
XcodeSdk.MacOSX => getMacOSBuildDirectory(),
200+
XcodeSdk.IPhoneOS || XcodeSdk.IPhoneSimulator => getIosBuildDirectory(),
201+
XcodeSdk.WatchOS || XcodeSdk.WatchSimulator => getIosBuildDirectory(),
202+
};
198203
final List<String> showBuildSettingsCommand = <String>[
199204
...xcrunCommand(),
200205
'xcodebuild',
@@ -206,21 +211,20 @@ class XcodeProjectInterpreter {
206211
...<String>['-configuration', configuration],
207212
if (target != null)
208213
...<String>['-target', target],
209-
if (buildContext.environmentType == EnvironmentType.simulator)
214+
if (buildContext.sdk == XcodeSdk.IPhoneSimulator || buildContext.sdk == XcodeSdk.WatchSimulator)
210215
...<String>['-sdk', 'iphonesimulator'],
211216
'-destination',
212-
if (buildContext.isWatch && buildContext.environmentType == EnvironmentType.physical)
213-
'generic/platform=watchOS'
214-
else if (buildContext.isWatch)
215-
'generic/platform=watchOS Simulator'
216-
else if (deviceId != null)
217+
if (deviceId != null)
217218
'id=$deviceId'
218-
else if (buildContext.environmentType == EnvironmentType.physical)
219-
'generic/platform=iOS'
220-
else
221-
'generic/platform=iOS Simulator',
219+
else switch (buildContext.sdk) {
220+
XcodeSdk.IPhoneOS => 'generic/platform=iOS',
221+
XcodeSdk.IPhoneSimulator => 'generic/platform=iOS Simulator',
222+
XcodeSdk.MacOSX => 'generic/platform=macOS',
223+
XcodeSdk.WatchOS => 'generic/platform=watchOS',
224+
XcodeSdk.WatchSimulator => 'generic/platform=watchOS Simulator',
225+
},
222226
'-showBuildSettings',
223-
'BUILD_DIR=${_fileSystem.path.absolute(getIosBuildDirectory())}',
227+
'BUILD_DIR=${_fileSystem.path.absolute(buildDir)}',
224228
...environmentVariablesAsXcodeBuildSettings(_platform),
225229
];
226230
try {
@@ -238,14 +242,19 @@ class XcodeProjectInterpreter {
238242
return parseXcodeBuildSettings(out);
239243
} on Exception catch (error) {
240244
if (error is ProcessException && error.toString().contains('timed out')) {
245+
final String eventType = switch (buildContext.sdk) {
246+
XcodeSdk.MacOSX => 'macos',
247+
XcodeSdk.IPhoneOS || XcodeSdk.IPhoneSimulator => 'ios',
248+
XcodeSdk.WatchOS || XcodeSdk.WatchSimulator => 'watchos',
249+
};
241250
BuildEvent('xcode-show-build-settings-timeout',
242-
type: 'ios',
251+
type: eventType,
243252
command: showBuildSettingsCommand.join(' '),
244253
flutterUsage: _usage,
245254
).send();
246255
_analytics.send(Event.flutterBuildInfo(
247256
label: 'xcode-show-build-settings-timeout',
248-
buildType: 'ios',
257+
buildType: eventType,
249258
command: showBuildSettingsCommand.join(' '),
250259
));
251260
}
@@ -394,39 +403,52 @@ String substituteXcodeVariables(String str, Map<String, String> xcodeBuildSettin
394403
return str.replaceAllMapped(_varExpr, (Match m) => xcodeBuildSettings[m[1]!] ?? m[0]!);
395404
}
396405

406+
/// Xcode SDKs. Corresponds to undocumented Xcode SUPPORTED_PLATFORMS values.
407+
/// Use `xcodebuild -showsdks` to get a list of SDKs installed on your machine.
408+
enum XcodeSdk {
409+
IPhoneOS,
410+
IPhoneSimulator,
411+
MacOSX,
412+
WatchOS,
413+
WatchSimulator,
414+
}
415+
397416
@immutable
398417
class XcodeProjectBuildContext {
399418
const XcodeProjectBuildContext({
400419
this.scheme,
401420
this.configuration,
402-
this.environmentType = EnvironmentType.physical,
421+
this.sdk = XcodeSdk.IPhoneOS,
403422
this.deviceId,
404423
this.target,
405-
this.isWatch = false,
406424
});
407425

408426
final String? scheme;
409427
final String? configuration;
410-
final EnvironmentType environmentType;
428+
final XcodeSdk sdk;
411429
final String? deviceId;
412430
final String? target;
413-
final bool isWatch;
414431

415432
@override
416-
int get hashCode => Object.hash(scheme, configuration, environmentType, deviceId, target);
433+
int get hashCode => Object.hash(
434+
scheme,
435+
configuration,
436+
sdk,
437+
deviceId,
438+
target,
439+
);
417440

418441
@override
419442
bool operator ==(Object other) {
420443
if (identical(other, this)) {
421444
return true;
422445
}
423446
return other is XcodeProjectBuildContext &&
424-
other.scheme == scheme &&
425-
other.configuration == configuration &&
426-
other.deviceId == deviceId &&
427-
other.environmentType == environmentType &&
428-
other.isWatch == isWatch &&
429-
other.target == target;
447+
other.scheme == scheme &&
448+
other.configuration == configuration &&
449+
other.deviceId == deviceId &&
450+
other.sdk == sdk &&
451+
other.target == target;
430452
}
431453
}
432454

packages/flutter_tools/lib/src/macos/build_macos.dart

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import 'migrations/macos_deployment_target_migration.dart';
2828
import 'migrations/nsapplicationmain_deprecation_migration.dart';
2929
import 'migrations/remove_macos_framework_link_and_embedding_migration.dart';
3030
import 'migrations/secure_restorable_state_migration.dart';
31+
import 'swift_package_manager.dart';
3132

3233
/// When run in -quiet mode, Xcode should only print from the underlying tasks to stdout.
3334
/// Passing this regexp to trace moves the stdout output to stderr.
@@ -108,6 +109,32 @@ Future<void> buildMacOS({
108109
if (!flutterBuildDir.existsSync()) {
109110
flutterBuildDir.createSync(recursive: true);
110111
}
112+
113+
final Directory xcodeProject = flutterProject.macos.xcodeProject;
114+
115+
// If the standard project exists, specify it to getInfo to handle the case where there are
116+
// other Xcode projects in the macos/ directory. Otherwise pass no name, which will work
117+
// regardless of the project name so long as there is exactly one project.
118+
final String? xcodeProjectName = xcodeProject.existsSync() ? xcodeProject.basename : null;
119+
final XcodeProjectInfo? projectInfo = await globals.xcodeProjectInterpreter?.getInfo(
120+
xcodeProject.parent.path,
121+
projectFilename: xcodeProjectName,
122+
);
123+
final String? scheme = projectInfo?.schemeFor(buildInfo);
124+
if (scheme == null) {
125+
projectInfo!.reportFlavorNotFoundAndExit();
126+
}
127+
final String? configuration = projectInfo?.buildConfigurationFor(buildInfo, scheme);
128+
if (configuration == null) {
129+
throwToolExit('Unable to find expected configuration in Xcode project.');
130+
}
131+
132+
final Map<String, String> buildSettings = await flutterProject.macos.buildSettingsForBuildInfo(
133+
buildInfo,
134+
scheme: scheme,
135+
configuration: configuration,
136+
) ?? <String, String>{};
137+
111138
// Write configuration to an xconfig file in a standard location.
112139
await updateGeneratedXcodeProperties(
113140
project: flutterProject,
@@ -116,9 +143,16 @@ Future<void> buildMacOS({
116143
useMacOSConfig: true,
117144
);
118145

119-
// TODO(vashworth): Call `SwiftPackageManager.updateMinimumDeployment`
120-
// using MACOSX_DEPLOYMENT_TARGET once https://github.com/flutter/flutter/issues/146204
121-
// is fixed.
146+
if (flutterProject.usesSwiftPackageManager) {
147+
final String? macOSDeploymentTarget = buildSettings['MACOSX_DEPLOYMENT_TARGET'];
148+
if (macOSDeploymentTarget != null) {
149+
SwiftPackageManager.updateMinimumDeployment(
150+
platform: SupportedPlatform.macos,
151+
project: flutterProject.macos,
152+
deploymentTarget: macOSDeploymentTarget,
153+
);
154+
}
155+
}
122156

123157
await processPodsIfNeeded(flutterProject.macos, getMacOSBuildDirectory(), buildInfo.mode);
124158
// If the xcfilelists do not exist, create empty version.
@@ -132,24 +166,6 @@ Future<void> buildMacOS({
132166
return;
133167
}
134168

135-
final Directory xcodeProject = flutterProject.macos.xcodeProject;
136-
137-
// If the standard project exists, specify it to getInfo to handle the case where there are
138-
// other Xcode projects in the macos/ directory. Otherwise pass no name, which will work
139-
// regardless of the project name so long as there is exactly one project.
140-
final String? xcodeProjectName = xcodeProject.existsSync() ? xcodeProject.basename : null;
141-
final XcodeProjectInfo? projectInfo = await globals.xcodeProjectInterpreter?.getInfo(
142-
xcodeProject.parent.path,
143-
projectFilename: xcodeProjectName,
144-
);
145-
final String? scheme = projectInfo?.schemeFor(buildInfo);
146-
if (scheme == null) {
147-
projectInfo!.reportFlavorNotFoundAndExit();
148-
}
149-
final String? configuration = projectInfo?.buildConfigurationFor(buildInfo, scheme);
150-
if (configuration == null) {
151-
throwToolExit('Unable to find expected configuration in Xcode project.');
152-
}
153169
// Run the Xcode build.
154170
final Stopwatch sw = Stopwatch()..start();
155171
final Status status = globals.logger.startProgress(

packages/flutter_tools/lib/src/xcode_project.dart

Lines changed: 83 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,89 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform {
155155
return _projectInfo ??= await xcodeProjectInterpreter.getInfo(hostAppRoot.path);
156156
}
157157
XcodeProjectInfo? _projectInfo;
158+
159+
/// The build settings for the host app of this project, as a detached map.
160+
///
161+
/// Returns null, if Xcode tooling is unavailable.
162+
Future<Map<String, String>?> buildSettingsForBuildInfo(
163+
BuildInfo? buildInfo, {
164+
String? scheme,
165+
String? configuration,
166+
String? target,
167+
EnvironmentType environmentType = EnvironmentType.physical,
168+
String? deviceId,
169+
bool isWatch = false,
170+
}) async {
171+
if (!existsSync()) {
172+
return null;
173+
}
174+
final XcodeProjectInfo? info = await projectInfo();
175+
if (info == null) {
176+
return null;
177+
}
178+
179+
scheme ??= info.schemeFor(buildInfo);
180+
if (scheme == null) {
181+
info.reportFlavorNotFoundAndExit();
182+
}
183+
184+
configuration ??= (await projectInfo())?.buildConfigurationFor(
185+
buildInfo,
186+
scheme,
187+
);
188+
189+
final XcodeSdk sdk = switch ((environmentType, this)) {
190+
(EnvironmentType.physical, _) when isWatch => XcodeSdk.WatchOS,
191+
(EnvironmentType.simulator, _) when isWatch => XcodeSdk.WatchSimulator,
192+
(EnvironmentType.physical, IosProject _) => XcodeSdk.IPhoneOS,
193+
(EnvironmentType.simulator, IosProject _) => XcodeSdk.WatchSimulator,
194+
(EnvironmentType.physical, MacOSProject _) => XcodeSdk.MacOSX,
195+
(_, _) => throw ArgumentError('Unsupported SDK')
196+
};
197+
198+
return _buildSettingsForXcodeProjectBuildContext(
199+
XcodeProjectBuildContext(
200+
scheme: scheme,
201+
configuration: configuration,
202+
sdk: sdk,
203+
target: target,
204+
deviceId: deviceId,
205+
),
206+
);
207+
}
208+
209+
Future<Map<String, String>?> _buildSettingsForXcodeProjectBuildContext(XcodeProjectBuildContext buildContext) async {
210+
if (!existsSync()) {
211+
return null;
212+
}
213+
final Map<String, String>? currentBuildSettings = _buildSettingsByBuildContext[buildContext];
214+
if (currentBuildSettings == null) {
215+
final Map<String, String>? calculatedBuildSettings = await _xcodeProjectBuildSettings(buildContext);
216+
if (calculatedBuildSettings != null) {
217+
_buildSettingsByBuildContext[buildContext] = calculatedBuildSettings;
218+
}
219+
}
220+
return _buildSettingsByBuildContext[buildContext];
221+
}
222+
223+
final Map<XcodeProjectBuildContext, Map<String, String>> _buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{};
224+
225+
Future<Map<String, String>?> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async {
226+
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
227+
if (xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
228+
return null;
229+
}
230+
231+
final Map<String, String> buildSettings = await xcodeProjectInterpreter.getBuildSettings(
232+
xcodeProject.path,
233+
buildContext: buildContext,
234+
);
235+
if (buildSettings.isNotEmpty) {
236+
// No timeouts, flakes, or errors.
237+
return buildSettings;
238+
}
239+
return null;
240+
}
158241
}
159242

160243
/// Represents the iOS sub-project of a Flutter project.
@@ -424,80 +507,6 @@ class IosProject extends XcodeBasedProject {
424507
return productName ?? XcodeBasedProject._defaultHostAppName;
425508
}
426509

427-
/// The build settings for the host app of this project, as a detached map.
428-
///
429-
/// Returns null, if iOS tooling is unavailable.
430-
Future<Map<String, String>?> buildSettingsForBuildInfo(
431-
BuildInfo? buildInfo, {
432-
String? scheme,
433-
String? configuration,
434-
String? target,
435-
EnvironmentType environmentType = EnvironmentType.physical,
436-
String? deviceId,
437-
bool isWatch = false,
438-
}) async {
439-
if (!existsSync()) {
440-
return null;
441-
}
442-
final XcodeProjectInfo? info = await projectInfo();
443-
if (info == null) {
444-
return null;
445-
}
446-
447-
scheme ??= info.schemeFor(buildInfo);
448-
if (scheme == null) {
449-
info.reportFlavorNotFoundAndExit();
450-
}
451-
452-
configuration ??= (await projectInfo())?.buildConfigurationFor(
453-
buildInfo,
454-
scheme,
455-
);
456-
return _buildSettingsForXcodeProjectBuildContext(
457-
XcodeProjectBuildContext(
458-
environmentType: environmentType,
459-
scheme: scheme,
460-
configuration: configuration,
461-
target: target,
462-
deviceId: deviceId,
463-
isWatch: isWatch,
464-
),
465-
);
466-
}
467-
468-
Future<Map<String, String>?> _buildSettingsForXcodeProjectBuildContext(XcodeProjectBuildContext buildContext) async {
469-
if (!existsSync()) {
470-
return null;
471-
}
472-
final Map<String, String>? currentBuildSettings = _buildSettingsByBuildContext[buildContext];
473-
if (currentBuildSettings == null) {
474-
final Map<String, String>? calculatedBuildSettings = await _xcodeProjectBuildSettings(buildContext);
475-
if (calculatedBuildSettings != null) {
476-
_buildSettingsByBuildContext[buildContext] = calculatedBuildSettings;
477-
}
478-
}
479-
return _buildSettingsByBuildContext[buildContext];
480-
}
481-
482-
final Map<XcodeProjectBuildContext, Map<String, String>> _buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{};
483-
484-
Future<Map<String, String>?> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async {
485-
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
486-
if (xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
487-
return null;
488-
}
489-
490-
final Map<String, String> buildSettings = await xcodeProjectInterpreter.getBuildSettings(
491-
xcodeProject.path,
492-
buildContext: buildContext,
493-
);
494-
if (buildSettings.isNotEmpty) {
495-
// No timeouts, flakes, or errors.
496-
return buildSettings;
497-
}
498-
return null;
499-
}
500-
501510
Future<void> ensureReadyForPlatformSpecificTooling() async {
502511
await _regenerateFromTemplateIfNeeded();
503512
if (!_flutterLibRoot.existsSync()) {

0 commit comments

Comments
 (0)