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 https://github.com/flutter/flutter/issues/146204
This commit is contained in:
Loïc Sharma 2024-07-26 15:02:08 -07:00 committed by GitHub
parent 0632b904d9
commit 40843e3e61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 349 additions and 128 deletions

View File

@ -195,6 +195,11 @@ class XcodeProjectInterpreter {
final String? configuration = buildContext.configuration; final String? configuration = buildContext.configuration;
final String? target = buildContext.target; final String? target = buildContext.target;
final String? deviceId = buildContext.deviceId; final String? deviceId = buildContext.deviceId;
final String buildDir = switch (buildContext.sdk) {
XcodeSdk.MacOSX => getMacOSBuildDirectory(),
XcodeSdk.IPhoneOS || XcodeSdk.IPhoneSimulator => getIosBuildDirectory(),
XcodeSdk.WatchOS || XcodeSdk.WatchSimulator => getIosBuildDirectory(),
};
final List<String> showBuildSettingsCommand = <String>[ final List<String> showBuildSettingsCommand = <String>[
...xcrunCommand(), ...xcrunCommand(),
'xcodebuild', 'xcodebuild',
@ -206,21 +211,20 @@ class XcodeProjectInterpreter {
...<String>['-configuration', configuration], ...<String>['-configuration', configuration],
if (target != null) if (target != null)
...<String>['-target', target], ...<String>['-target', target],
if (buildContext.environmentType == EnvironmentType.simulator) if (buildContext.sdk == XcodeSdk.IPhoneSimulator || buildContext.sdk == XcodeSdk.WatchSimulator)
...<String>['-sdk', 'iphonesimulator'], ...<String>['-sdk', 'iphonesimulator'],
'-destination', '-destination',
if (buildContext.isWatch && buildContext.environmentType == EnvironmentType.physical) if (deviceId != null)
'generic/platform=watchOS'
else if (buildContext.isWatch)
'generic/platform=watchOS Simulator'
else if (deviceId != null)
'id=$deviceId' 'id=$deviceId'
else if (buildContext.environmentType == EnvironmentType.physical) else switch (buildContext.sdk) {
'generic/platform=iOS' XcodeSdk.IPhoneOS => 'generic/platform=iOS',
else XcodeSdk.IPhoneSimulator => 'generic/platform=iOS Simulator',
'generic/platform=iOS Simulator', XcodeSdk.MacOSX => 'generic/platform=macOS',
XcodeSdk.WatchOS => 'generic/platform=watchOS',
XcodeSdk.WatchSimulator => 'generic/platform=watchOS Simulator',
},
'-showBuildSettings', '-showBuildSettings',
'BUILD_DIR=${_fileSystem.path.absolute(getIosBuildDirectory())}', 'BUILD_DIR=${_fileSystem.path.absolute(buildDir)}',
...environmentVariablesAsXcodeBuildSettings(_platform), ...environmentVariablesAsXcodeBuildSettings(_platform),
]; ];
try { try {
@ -238,14 +242,19 @@ class XcodeProjectInterpreter {
return parseXcodeBuildSettings(out); return parseXcodeBuildSettings(out);
} on Exception catch (error) { } on Exception catch (error) {
if (error is ProcessException && error.toString().contains('timed out')) { if (error is ProcessException && error.toString().contains('timed out')) {
final String eventType = switch (buildContext.sdk) {
XcodeSdk.MacOSX => 'macos',
XcodeSdk.IPhoneOS || XcodeSdk.IPhoneSimulator => 'ios',
XcodeSdk.WatchOS || XcodeSdk.WatchSimulator => 'watchos',
};
BuildEvent('xcode-show-build-settings-timeout', BuildEvent('xcode-show-build-settings-timeout',
type: 'ios', type: eventType,
command: showBuildSettingsCommand.join(' '), command: showBuildSettingsCommand.join(' '),
flutterUsage: _usage, flutterUsage: _usage,
).send(); ).send();
_analytics.send(Event.flutterBuildInfo( _analytics.send(Event.flutterBuildInfo(
label: 'xcode-show-build-settings-timeout', label: 'xcode-show-build-settings-timeout',
buildType: 'ios', buildType: eventType,
command: showBuildSettingsCommand.join(' '), command: showBuildSettingsCommand.join(' '),
)); ));
} }
@ -394,26 +403,40 @@ String substituteXcodeVariables(String str, Map<String, String> xcodeBuildSettin
return str.replaceAllMapped(_varExpr, (Match m) => xcodeBuildSettings[m[1]!] ?? m[0]!); return str.replaceAllMapped(_varExpr, (Match m) => xcodeBuildSettings[m[1]!] ?? m[0]!);
} }
/// Xcode SDKs. Corresponds to undocumented Xcode SUPPORTED_PLATFORMS values.
/// Use `xcodebuild -showsdks` to get a list of SDKs installed on your machine.
enum XcodeSdk {
IPhoneOS,
IPhoneSimulator,
MacOSX,
WatchOS,
WatchSimulator,
}
@immutable @immutable
class XcodeProjectBuildContext { class XcodeProjectBuildContext {
const XcodeProjectBuildContext({ const XcodeProjectBuildContext({
this.scheme, this.scheme,
this.configuration, this.configuration,
this.environmentType = EnvironmentType.physical, this.sdk = XcodeSdk.IPhoneOS,
this.deviceId, this.deviceId,
this.target, this.target,
this.isWatch = false,
}); });
final String? scheme; final String? scheme;
final String? configuration; final String? configuration;
final EnvironmentType environmentType; final XcodeSdk sdk;
final String? deviceId; final String? deviceId;
final String? target; final String? target;
final bool isWatch;
@override @override
int get hashCode => Object.hash(scheme, configuration, environmentType, deviceId, target); int get hashCode => Object.hash(
scheme,
configuration,
sdk,
deviceId,
target,
);
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
@ -421,12 +444,11 @@ class XcodeProjectBuildContext {
return true; return true;
} }
return other is XcodeProjectBuildContext && return other is XcodeProjectBuildContext &&
other.scheme == scheme && other.scheme == scheme &&
other.configuration == configuration && other.configuration == configuration &&
other.deviceId == deviceId && other.deviceId == deviceId &&
other.environmentType == environmentType && other.sdk == sdk &&
other.isWatch == isWatch && other.target == target;
other.target == target;
} }
} }

View File

@ -28,6 +28,7 @@ import 'migrations/macos_deployment_target_migration.dart';
import 'migrations/nsapplicationmain_deprecation_migration.dart'; import 'migrations/nsapplicationmain_deprecation_migration.dart';
import 'migrations/remove_macos_framework_link_and_embedding_migration.dart'; import 'migrations/remove_macos_framework_link_and_embedding_migration.dart';
import 'migrations/secure_restorable_state_migration.dart'; import 'migrations/secure_restorable_state_migration.dart';
import 'swift_package_manager.dart';
/// When run in -quiet mode, Xcode should only print from the underlying tasks to stdout. /// When run in -quiet mode, Xcode should only print from the underlying tasks to stdout.
/// Passing this regexp to trace moves the stdout output to stderr. /// Passing this regexp to trace moves the stdout output to stderr.
@ -108,29 +109,6 @@ Future<void> buildMacOS({
if (!flutterBuildDir.existsSync()) { if (!flutterBuildDir.existsSync()) {
flutterBuildDir.createSync(recursive: true); flutterBuildDir.createSync(recursive: true);
} }
// Write configuration to an xconfig file in a standard location.
await updateGeneratedXcodeProperties(
project: flutterProject,
buildInfo: buildInfo,
targetOverride: targetOverride,
useMacOSConfig: true,
);
// TODO(vashworth): Call `SwiftPackageManager.updateMinimumDeployment`
// using MACOSX_DEPLOYMENT_TARGET once https://github.com/flutter/flutter/issues/146204
// is fixed.
await processPodsIfNeeded(flutterProject.macos, getMacOSBuildDirectory(), buildInfo.mode);
// If the xcfilelists do not exist, create empty version.
if (!flutterProject.macos.inputFileList.existsSync()) {
flutterProject.macos.inputFileList.createSync(recursive: true);
}
if (!flutterProject.macos.outputFileList.existsSync()) {
flutterProject.macos.outputFileList.createSync(recursive: true);
}
if (configOnly) {
return;
}
final Directory xcodeProject = flutterProject.macos.xcodeProject; final Directory xcodeProject = flutterProject.macos.xcodeProject;
@ -150,6 +128,44 @@ Future<void> buildMacOS({
if (configuration == null) { if (configuration == null) {
throwToolExit('Unable to find expected configuration in Xcode project.'); throwToolExit('Unable to find expected configuration in Xcode project.');
} }
final Map<String, String> buildSettings = await flutterProject.macos.buildSettingsForBuildInfo(
buildInfo,
scheme: scheme,
configuration: configuration,
) ?? <String, String>{};
// Write configuration to an xconfig file in a standard location.
await updateGeneratedXcodeProperties(
project: flutterProject,
buildInfo: buildInfo,
targetOverride: targetOverride,
useMacOSConfig: true,
);
if (flutterProject.usesSwiftPackageManager) {
final String? macOSDeploymentTarget = buildSettings['MACOSX_DEPLOYMENT_TARGET'];
if (macOSDeploymentTarget != null) {
SwiftPackageManager.updateMinimumDeployment(
platform: SupportedPlatform.macos,
project: flutterProject.macos,
deploymentTarget: macOSDeploymentTarget,
);
}
}
await processPodsIfNeeded(flutterProject.macos, getMacOSBuildDirectory(), buildInfo.mode);
// If the xcfilelists do not exist, create empty version.
if (!flutterProject.macos.inputFileList.existsSync()) {
flutterProject.macos.inputFileList.createSync(recursive: true);
}
if (!flutterProject.macos.outputFileList.existsSync()) {
flutterProject.macos.outputFileList.createSync(recursive: true);
}
if (configOnly) {
return;
}
// Run the Xcode build. // Run the Xcode build.
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
final Status status = globals.logger.startProgress( final Status status = globals.logger.startProgress(

View File

@ -155,6 +155,89 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform {
return _projectInfo ??= await xcodeProjectInterpreter.getInfo(hostAppRoot.path); return _projectInfo ??= await xcodeProjectInterpreter.getInfo(hostAppRoot.path);
} }
XcodeProjectInfo? _projectInfo; XcodeProjectInfo? _projectInfo;
/// The build settings for the host app of this project, as a detached map.
///
/// Returns null, if Xcode tooling is unavailable.
Future<Map<String, String>?> buildSettingsForBuildInfo(
BuildInfo? buildInfo, {
String? scheme,
String? configuration,
String? target,
EnvironmentType environmentType = EnvironmentType.physical,
String? deviceId,
bool isWatch = false,
}) async {
if (!existsSync()) {
return null;
}
final XcodeProjectInfo? info = await projectInfo();
if (info == null) {
return null;
}
scheme ??= info.schemeFor(buildInfo);
if (scheme == null) {
info.reportFlavorNotFoundAndExit();
}
configuration ??= (await projectInfo())?.buildConfigurationFor(
buildInfo,
scheme,
);
final XcodeSdk sdk = switch ((environmentType, this)) {
(EnvironmentType.physical, _) when isWatch => XcodeSdk.WatchOS,
(EnvironmentType.simulator, _) when isWatch => XcodeSdk.WatchSimulator,
(EnvironmentType.physical, IosProject _) => XcodeSdk.IPhoneOS,
(EnvironmentType.simulator, IosProject _) => XcodeSdk.WatchSimulator,
(EnvironmentType.physical, MacOSProject _) => XcodeSdk.MacOSX,
(_, _) => throw ArgumentError('Unsupported SDK')
};
return _buildSettingsForXcodeProjectBuildContext(
XcodeProjectBuildContext(
scheme: scheme,
configuration: configuration,
sdk: sdk,
target: target,
deviceId: deviceId,
),
);
}
Future<Map<String, String>?> _buildSettingsForXcodeProjectBuildContext(XcodeProjectBuildContext buildContext) async {
if (!existsSync()) {
return null;
}
final Map<String, String>? currentBuildSettings = _buildSettingsByBuildContext[buildContext];
if (currentBuildSettings == null) {
final Map<String, String>? calculatedBuildSettings = await _xcodeProjectBuildSettings(buildContext);
if (calculatedBuildSettings != null) {
_buildSettingsByBuildContext[buildContext] = calculatedBuildSettings;
}
}
return _buildSettingsByBuildContext[buildContext];
}
final Map<XcodeProjectBuildContext, Map<String, String>> _buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{};
Future<Map<String, String>?> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async {
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
if (xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
return null;
}
final Map<String, String> buildSettings = await xcodeProjectInterpreter.getBuildSettings(
xcodeProject.path,
buildContext: buildContext,
);
if (buildSettings.isNotEmpty) {
// No timeouts, flakes, or errors.
return buildSettings;
}
return null;
}
} }
/// Represents the iOS sub-project of a Flutter project. /// Represents the iOS sub-project of a Flutter project.
@ -424,80 +507,6 @@ class IosProject extends XcodeBasedProject {
return productName ?? XcodeBasedProject._defaultHostAppName; return productName ?? XcodeBasedProject._defaultHostAppName;
} }
/// The build settings for the host app of this project, as a detached map.
///
/// Returns null, if iOS tooling is unavailable.
Future<Map<String, String>?> buildSettingsForBuildInfo(
BuildInfo? buildInfo, {
String? scheme,
String? configuration,
String? target,
EnvironmentType environmentType = EnvironmentType.physical,
String? deviceId,
bool isWatch = false,
}) async {
if (!existsSync()) {
return null;
}
final XcodeProjectInfo? info = await projectInfo();
if (info == null) {
return null;
}
scheme ??= info.schemeFor(buildInfo);
if (scheme == null) {
info.reportFlavorNotFoundAndExit();
}
configuration ??= (await projectInfo())?.buildConfigurationFor(
buildInfo,
scheme,
);
return _buildSettingsForXcodeProjectBuildContext(
XcodeProjectBuildContext(
environmentType: environmentType,
scheme: scheme,
configuration: configuration,
target: target,
deviceId: deviceId,
isWatch: isWatch,
),
);
}
Future<Map<String, String>?> _buildSettingsForXcodeProjectBuildContext(XcodeProjectBuildContext buildContext) async {
if (!existsSync()) {
return null;
}
final Map<String, String>? currentBuildSettings = _buildSettingsByBuildContext[buildContext];
if (currentBuildSettings == null) {
final Map<String, String>? calculatedBuildSettings = await _xcodeProjectBuildSettings(buildContext);
if (calculatedBuildSettings != null) {
_buildSettingsByBuildContext[buildContext] = calculatedBuildSettings;
}
}
return _buildSettingsByBuildContext[buildContext];
}
final Map<XcodeProjectBuildContext, Map<String, String>> _buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{};
Future<Map<String, String>?> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async {
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
if (xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
return null;
}
final Map<String, String> buildSettings = await xcodeProjectInterpreter.getBuildSettings(
xcodeProject.path,
buildContext: buildContext,
);
if (buildSettings.isNotEmpty) {
// No timeouts, flakes, or errors.
return buildSettings;
}
return null;
}
Future<void> ensureReadyForPlatformSpecificTooling() async { Future<void> ensureReadyForPlatformSpecificTooling() async {
await _regenerateFromTemplateIfNeeded(); await _regenerateFromTemplateIfNeeded();
if (!_flutterLibRoot.existsSync()) { if (!_flutterLibRoot.existsSync()) {

View File

@ -325,7 +325,9 @@ void main() {
expect( expect(
await xcodeProjectInterpreter.getBuildSettings( await xcodeProjectInterpreter.getBuildSettings(
'', '',
buildContext: const XcodeProjectBuildContext(environmentType: EnvironmentType.simulator), buildContext: const XcodeProjectBuildContext(
sdk: XcodeSdk.IPhoneSimulator,
),
), ),
const <String, String>{}, const <String, String>{},
); );
@ -398,7 +400,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('build settings uses watch destination if isWatch is true', () async { testUsingContext('build settings uses watch destination', () async {
platform.environment = const <String, String>{}; platform.environment = const <String, String>{};
fakeProcessManager.addCommands(<FakeCommand>[ fakeProcessManager.addCommands(<FakeCommand>[
@ -422,7 +424,9 @@ void main() {
expect( expect(
await xcodeProjectInterpreter.getBuildSettings( await xcodeProjectInterpreter.getBuildSettings(
'', '',
buildContext: const XcodeProjectBuildContext(isWatch: true), buildContext: const XcodeProjectBuildContext(
sdk: XcodeSdk.WatchOS,
),
), ),
const <String, String>{}, const <String, String>{},
); );
@ -432,7 +436,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('build settings uses watch simulator destination if isWatch is true and environment type is simulator', () async { testUsingContext('build settings uses watch simulator destination', () async {
platform.environment = const <String, String>{}; platform.environment = const <String, String>{};
fakeProcessManager.addCommands(<FakeCommand>[ fakeProcessManager.addCommands(<FakeCommand>[
@ -458,7 +462,45 @@ void main() {
expect( expect(
await xcodeProjectInterpreter.getBuildSettings( await xcodeProjectInterpreter.getBuildSettings(
'', '',
buildContext: const XcodeProjectBuildContext(environmentType: EnvironmentType.simulator, isWatch: true), buildContext: const XcodeProjectBuildContext(
sdk: XcodeSdk.WatchSimulator,
),
),
const <String, String>{},
);
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('build settings uses macosx destination', () async {
platform.environment = const <String, String>{};
fakeProcessManager.addCommands(<FakeCommand>[
kWhichSysctlCommand,
kx64CheckCommand,
FakeCommand(
command: <String>[
'xcrun',
'xcodebuild',
'-project',
'/',
'-destination',
'generic/platform=macOS',
'-showBuildSettings',
'BUILD_DIR=${fileSystem.path.absolute('build', 'macos')}',
],
exitCode: 1,
),
]);
expect(
await xcodeProjectInterpreter.getBuildSettings(
'',
buildContext: const XcodeProjectBuildContext(
sdk: XcodeSdk.MacOSX,
),
), ),
const <String, String>{}, const <String, String>{},
); );

View File

@ -1458,7 +1458,7 @@ plugins {
const XcodeProjectBuildContext watchBuildContext = XcodeProjectBuildContext( const XcodeProjectBuildContext watchBuildContext = XcodeProjectBuildContext(
scheme: 'WatchScheme', scheme: 'WatchScheme',
deviceId: '123', deviceId: '123',
isWatch: true, sdk: XcodeSdk.WatchOS,
); );
mockXcodeProjectInterpreter.buildSettingsByBuildContext[watchBuildContext] = <String, String>{ mockXcodeProjectInterpreter.buildSettingsByBuildContext[watchBuildContext] = <String, String>{
'INFOPLIST_KEY_WKCompanionAppBundleIdentifier': 'io.flutter.someProject', 'INFOPLIST_KEY_WKCompanionAppBundleIdentifier': 'io.flutter.someProject',
@ -1498,7 +1498,7 @@ plugins {
const XcodeProjectBuildContext watchBuildContext = XcodeProjectBuildContext( const XcodeProjectBuildContext watchBuildContext = XcodeProjectBuildContext(
scheme: 'WatchScheme', scheme: 'WatchScheme',
deviceId: '123', deviceId: '123',
isWatch: true, sdk: XcodeSdk.WatchOS,
); );
mockXcodeProjectInterpreter.buildSettingsByBuildContext[watchBuildContext] = <String, String>{ mockXcodeProjectInterpreter.buildSettingsByBuildContext[watchBuildContext] = <String, String>{
IosProject.kProductBundleIdKey: 'io.flutter.someProject', IosProject.kProductBundleIdKey: 'io.flutter.someProject',

View File

@ -475,4 +475,136 @@ void main() {
); );
} }
}, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos. }, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos.
test("Generated Swift package uses iOS's project minimum deployment", () async {
final Directory workingDirectory = fileSystem.systemTempDirectory
.createTempSync('swift_package_manager_minimum_deployment_ios.');
final String workingDirectoryPath = workingDirectory.path;
try {
await SwiftPackageManagerUtils.enableSwiftPackageManager(flutterBin, workingDirectoryPath);
final String appDirectoryPath = await SwiftPackageManagerUtils.createApp(
flutterBin,
workingDirectoryPath,
iosLanguage: 'swift',
platform: 'ios',
usesSwiftPackageManager: true,
options: <String>['--platforms=ios'],
);
// Modify the project to raise the deployment version.
final File projectFile = fileSystem
.directory(appDirectoryPath)
.childDirectory('ios')
.childDirectory('Runner.xcodeproj')
.childFile('project.pbxproj');
final String oldProject = projectFile.readAsStringSync();
final String newProject = oldProject.replaceAll(
RegExp(r'IPHONEOS_DEPLOYMENT_TARGET = \d+\.\d+;'),
'IPHONEOS_DEPLOYMENT_TARGET = 15.1;',
);
projectFile.writeAsStringSync(newProject);
// Build the app. This generates Flutter's Swift package.
await SwiftPackageManagerUtils.buildApp(
flutterBin,
appDirectoryPath,
options: <String>['ios', '--debug', '-v'],
);
// Verify the generated Swift package uses the project's minimum deployment.
final File generatedManifestFile = fileSystem
.directory(appDirectoryPath)
.childDirectory('ios')
.childDirectory('Flutter')
.childDirectory('ephemeral')
.childDirectory('Packages')
.childDirectory('FlutterGeneratedPluginSwiftPackage')
.childFile('Package.swift');
expect(generatedManifestFile.existsSync(), isTrue);
final String generatedManifest = generatedManifestFile.readAsStringSync();
const String expected = '''
platforms: [
.iOS("15.1")
],
''';
expect(generatedManifest.contains(expected), isTrue);
} finally {
await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath);
ErrorHandlingFileSystem.deleteIfExists(
workingDirectory,
recursive: true,
);
}
}, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos.
test("Generated Swift package uses macOS's project minimum deployment", () async {
final Directory workingDirectory = fileSystem.systemTempDirectory
.createTempSync('swift_package_manager_minimum_deployment_macos.');
final String workingDirectoryPath = workingDirectory.path;
try {
await SwiftPackageManagerUtils.enableSwiftPackageManager(flutterBin, workingDirectoryPath);
final String appDirectoryPath = await SwiftPackageManagerUtils.createApp(
flutterBin,
workingDirectoryPath,
iosLanguage: 'swift',
platform: 'macos',
usesSwiftPackageManager: true,
options: <String>['--platforms=macos'],
);
// Modify the project to raise the deployment version.
final File projectFile = fileSystem
.directory(appDirectoryPath)
.childDirectory('macos')
.childDirectory('Runner.xcodeproj')
.childFile('project.pbxproj');
final String oldProject = projectFile.readAsStringSync();
final String newProject = oldProject.replaceAll(
RegExp(r'MACOSX_DEPLOYMENT_TARGET = \d+\.\d+;'),
'MACOSX_DEPLOYMENT_TARGET = 15.1;',
);
projectFile.writeAsStringSync(newProject);
// Build the app. This generates Flutter's Swift package.
await SwiftPackageManagerUtils.buildApp(
flutterBin,
appDirectoryPath,
options: <String>['macos', '--debug', '-v'],
);
// Verify the generated Swift package uses the project's minimum deployment.
final File generatedManifestFile = fileSystem
.directory(appDirectoryPath)
.childDirectory('macos')
.childDirectory('Flutter')
.childDirectory('ephemeral')
.childDirectory('Packages')
.childDirectory('FlutterGeneratedPluginSwiftPackage')
.childFile('Package.swift');
expect(generatedManifestFile.existsSync(), isTrue);
final String generatedManifest = generatedManifestFile.readAsStringSync();
const String expected = '''
platforms: [
.macOS("15.1")
],
''';
expect(generatedManifest.contains(expected), isTrue);
} finally {
await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath);
ErrorHandlingFileSystem.deleteIfExists(
workingDirectory,
recursive: true,
);
}
}, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos.
} }