New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Swift Package Manager as new opt-in feature for iOS and macOS #145721
Conversation
Preliminary instructions on how to convert plugins to Swift Packages Converting an existing Objective-C Plugin to a Swift PackageConverting an existing Objective-C Plugin to a Swift PackageReplace
/plugin_name/plugin_name_ios/ios/plugin_name_ios
/plugin_name/plugin_name_ios/ios/plugin_name_ios/Package.swift /plugin_name/plugin_name_ios/ios/plugin_name_ios/Sources /plugin_name/plugin_name_ios/ios/plugin_name_ios/Sources/plugin_name_ios /plugin_name/plugin_name_ios/ios/plugin_name_ios/Sources/plugin_name_ios/Resources /plugin_name/plugin_name_ios/ios/plugin_name_ios/Sources/plugin_name_ios/include /plugin_name/plugin_name_ios/ios/plugin_name_ios/Sources/plugin_name_ios/include/plugin_name_ios
Package.swift// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let pluginMinimumIOSVersion = Version("12.0.0")
let pluginMinimumMacOSVersion = Version("10.14.0")
let package = Package(
name: "plugin_name_ios",
platforms: [
flutterMinimumIOSVersion(pluginTargetVersion: pluginMinimumIOSVersion),
flutterMinimumMacOSVersion(pluginTargetVersion: pluginMinimumMacOSVersion)
],
products: [
.library(name: "plugin_name_ios", targets: ["plugin_name_ios"])
],
dependencies: [
flutterFrameworkDependency()
],
targets: [
.target(
name: "plugin_name_ios",
dependencies: [
.product(name: "Flutter", package: "Flutter")
],
exclude: ["plugin_name_ios.modulemap", "plugin_name_ios-umbrella.h"],
resources: [
.process("Resources")
],
cSettings: [
.headerSearchPath("include/plugin_name_ios")
]
)
]
)
/// Returns the Package.Dependency for the Flutter framework.
///
/// Do not edit or remove. Used by the Flutter CLI to ensure the correct framework is used.
///
/// - Parameters:
/// - localFrameworkPath: The path to the Flutter framework Swift Package. Can be used when
/// locally developing the package. Will not be used when ran with the Flutter CLI.
/// - Returns: A Package.Dependency for the Flutter framework.
func flutterFrameworkDependency(localFrameworkPath: String? = nil) -> Package.Dependency {
let flutterFrameworkPackagePath = localFrameworkPath ?? "unknown"
return .package(name: "Flutter", path: flutterFrameworkPackagePath)
}
/// Returns the SupportedPlatform for iOS, ensuring the minimum deployment target version for the
/// iOS platform is always greater than or equal to that of the Flutter framework.
///
/// Do not edit or remove. Used by the Flutter CLI to ensure the correct minimum deployment target
/// version for iOS is used.
///
/// - Parameters:
/// - pluginTargetVersion: The minimum deployment target version for iOS.
/// - Returns: The SupportedPlatform for iOS.
func flutterMinimumIOSVersion(pluginTargetVersion: Version) -> SupportedPlatform {
let iosFlutterMinimumVersion = Version("12.0.0")
var versionString = pluginTargetVersion.description
if iosFlutterMinimumVersion > pluginTargetVersion {
versionString = iosFlutterMinimumVersion.description
}
return SupportedPlatform.iOS(versionString)
}
/// Returns the SupportedPlatform for macOS, ensuring the minimum deployment target version for the
/// macOS platform is always greater than or equal to that of the Flutter framework.
///
/// Do not edit or remove. Used by the Flutter CLI to ensure the correct minimum deployment target
/// version for macOS is used.
///
/// - Parameters:
/// - pluginTargetVersion: The minimum deployment target version for macOS.
/// - Returns: The SupportedPlatform for macOS.
func flutterMinimumMacOSVersion(pluginTargetVersion: Version) -> SupportedPlatform {
let macosFlutterMinimumVersion = Version("10.14.0")
var versionString = pluginTargetVersion.description
if macosFlutterMinimumVersion > pluginTargetVersion {
versionString = macosFlutterMinimumVersion.description
}
return SupportedPlatform.macOS(versionString)
}
- objcHeaderOut: 'ios/Classes/messages.g.h',
+ objcHeaderOut: 'ios/plugin_name_ios/Sources/plugin_name_ios/messages.g.h',
- objcSourceOut: 'ios/Classes/messages.g.m',
+ objcSourceOut: 'ios/plugin_name_ios/Sources/plugin_name_ios/messages.g.m',
- s.source_files = 'Classes/**/*.{h,m}'
+ s.source_files = 'plugin_name_ios/Sources/plugin_name_ios/**/*.{h,m}'
- s.public_header_files = 'Classes/**/*.h'
+ s.public_header_files = 'plugin_name_ios/Sources/plugin_name_ios/**/*.h'
- s.module_map = 'Classes/plugin_name_ios.modulemap'
+ s.module_map = 'plugin_name_ios/Sources/plugin_name_ios/plugin_name_ios.modulemap'
- s.resource_bundles = {'plugin_name_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
+ s.resource_bundles = {'plugin_name_ios_privacy' => ['plugin_name_ios/Sources/plugin_name_ios/Resources/PrivacyInfo.xcprivacy']}
Moving unit tests from example app to Swift Package (optional)Previously, unit tests were added directly to the example app. This may have forced you to make certain headers public or used a separate test module.
products: [
.library(name: "plugin_name_ios", targets: ["plugin_name_ios"]),
.library(name: "plugin_name_ios_test", targets: ["plugin_name_ios_test"])
],
...
targets: [
.target(
name: "plugin_name_ios",
...
),
.target(
name: "plugin_name_ios_test",
dependencies: [
"plugin_name_ios",
],
resources: [
.process("Resources"),
],
cSettings: [
.headerSearchPath("../plugin_name_ios"),
]
),
]
Package.swift// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "FlutterPluginTest",
platforms: [
.iOS("12.0"),
],
dependencies: [
.package(path: "../ephemeral/FlutterGeneratedPluginSwiftPackage"),
.package(name: "plugin_name_ios", path: "../ephemeral/.symlinks/plugins/plugin_name_ios/ios/plugin_name_ios")
],
targets: [
.testTarget(
name: "FlutterPluginTest",
dependencies: [
"FlutterGeneratedPluginSwiftPackage",
.product(name: "plugin_name_ios_test", package: "plugin_name_ios")
]
)
]
)
Ensure you don't add it to any Targets
Converting an existing Swift Plugin to a Swift PackageConverting an existing Swift Plugin to a Swift PackageReplace
/plugin_name/plugin_name_ios/ios/plugin_name_ios
/plugin_name/plugin_name_ios/ios/plugin_name_ios/Package.swift /plugin_name/plugin_name_ios/ios/plugin_name_ios/Sources /plugin_name/plugin_name_ios/ios/plugin_name_ios/Sources/plugin_name_ios /plugin_name/plugin_name_ios/ios/plugin_name_ios/Sources/plugin_name_ios/Resources
Package.swift// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let pluginMinimumIOSVersion = Version("12.0.0")
let pluginMinimumMacOSVersion = Version("10.14.0")
let package = Package(
name: "plugin_name_ios",
platforms: [
flutterMinimumIOSVersion(pluginTargetVersion: pluginMinimumIOSVersion),
flutterMinimumMacOSVersion(pluginTargetVersion: pluginMinimumMacOSVersion)
],
products: [
.library(name: "plugin_name_ios", targets: ["plugin_name_ios"])
],
dependencies: [
flutterFrameworkDependency()
],
targets: [
.target(
name: "plugin_name_ios",
dependencies: [
.product(name: "Flutter", package: "Flutter")
],
resources: [
.process("Resources")
]
)
]
)
/// Returns the Package.Dependency for the Flutter framework.
///
/// Do not edit or remove. Used by the Flutter CLI to ensure the correct framework is used.
///
/// - Parameters:
/// - localFrameworkPath: The path to the Flutter framework Swift Package. Can be used when
/// locally developing the package. Will not be used when ran with the Flutter CLI.
/// - Returns: A Package.Dependency for the Flutter framework.
func flutterFrameworkDependency(localFrameworkPath: String? = nil) -> Package.Dependency {
let flutterFrameworkPackagePath = localFrameworkPath ?? "unknown"
return .package(name: "Flutter", path: flutterFrameworkPackagePath)
}
/// Returns the SupportedPlatform for iOS, ensuring the minimum deployment target version for the
/// iOS platform is always greater than or equal to that of the Flutter framework.
///
/// Do not edit or remove. Used by the Flutter CLI to ensure the correct minimum deployment target
/// version for iOS is used.
///
/// - Parameters:
/// - pluginTargetVersion: The minimum deployment target version for iOS.
/// - Returns: The SupportedPlatform for iOS.
func flutterMinimumIOSVersion(pluginTargetVersion: Version) -> SupportedPlatform {
let iosFlutterMinimumVersion = Version("12.0.0")
var versionString = pluginTargetVersion.description
if iosFlutterMinimumVersion > pluginTargetVersion {
versionString = iosFlutterMinimumVersion.description
}
return SupportedPlatform.iOS(versionString)
}
/// Returns the SupportedPlatform for macOS, ensuring the minimum deployment target version for the
/// macOS platform is always greater than or equal to that of the Flutter framework.
///
/// Do not edit or remove. Used by the Flutter CLI to ensure the correct minimum deployment target
/// version for macOS is used.
///
/// - Parameters:
/// - pluginTargetVersion: The minimum deployment target version for macOS.
/// - Returns: The SupportedPlatform for macOS.
func flutterMinimumMacOSVersion(pluginTargetVersion: Version) -> SupportedPlatform {
let macosFlutterMinimumVersion = Version("10.14.0")
var versionString = pluginTargetVersion.description
if macosFlutterMinimumVersion > pluginTargetVersion {
versionString = macosFlutterMinimumVersion.description
}
return SupportedPlatform.macOS(versionString)
}
- swiftOut: 'ios/Classes/messages.g.swift',
+ swiftOut: 'ios/plugin_name_ios/Sources/plugin_name_ios/messages.g.swift',
- s.source_files = 'Classes/**/*.swift'
+ s.source_files = 'plugin_name_ios/Sources/plugin_name_ios/**/*.swift'
- s.resource_bundles = {'plugin_name_ios_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
+ s.resource_bundles = {'plugin_name_ios_privacy' => ['plugin_name_ios/Sources/plugin_name_ios/Resources/PrivacyInfo.xcprivacy']}
Moving unit tests from example app to Swift Package (optional)Previously, unit tests were added directly to the example app. This may have forced you to make certain headers public or used a separate test module.
products: [
.library(name: "plugin_name_ios", targets: ["plugin_name_ios"]),
.library(name: "plugin_name_ios_test", targets: ["plugin_name_ios_test"])
],
...
targets: [
.target(
name: "plugin_name_ios",
...
),
.target(
name: "plugin_name_ios_test",
dependencies: [
"plugin_name_ios",
],
resources: [
.process("Resources"),
]
),
]
Package.swift// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "FlutterPluginTest",
platforms: [
.iOS("12.0"),
],
dependencies: [
.package(path: "../ephemeral/FlutterGeneratedPluginSwiftPackage"),
.package(name: "plugin_name_ios", path: "../ephemeral/.symlinks/plugins/plugin_name_ios/ios/plugin_name_ios")
],
targets: [
.testTarget(
name: "FlutterPluginTest",
dependencies: [
"FlutterGeneratedPluginSwiftPackage",
.product(name: "plugin_name_ios_test", package: "plugin_name_ios")
]
)
]
)
Ensure you don't add it to any Targets
|
There was the alternative that we could add a pre-action script that would copy the Flutter framework to the BUILT_PRODUCTS_DIR so it would always be guaranteed to be there when the plugins compiled, but that wouldn't fix the minimum deployment target version, which is why I went with this approach. FYI, this differs from my original design and approach due to the limitations I had found |
@jmagman @stuartmorgan JK not ready for review yet, want to try one more thing |
Closing in favor of #146256. |
This PR adds initial support for Swift Package Manager (SPM). Users must opt in. Only compatible with Xcode 15+.
Included Features
This PR includes the following features:
flutter config --enable-swift-package-manager
flutter config --no-enable-swift-package-manager
FlutterGeneratedPluginSwiftPackage
) that handles Flutter SPM-compatible plugin dependencies. Generated package is added to the Xcode project.This PR also converts
integration_test
andintegration_test_macos
plugins to be both Swift Packages and CocoaPod Pods.How it Works
The Flutter CLI will generate a Swift Package called
FlutterGeneratedPluginSwiftPackage
and add it to the Xcode project via theproject.pbxproj
.The
FlutterGeneratedPluginSwiftPackage
package will have local dependencies on all Swift Package compatible Flutter plugins. These plugins will be symlinked into a directory in the project (e.g. app_name/ios/Flutter/Packages/ephemeral/.symlinks/plugins/integration_test) except for the Package.swift - the Package.swift will be copied. The Flutter CLI will alter the copied Package.swift to inject things like the path to the Flutter framework and minimum deployment targets.This is done because plugins must be linked to the Flutter framework to compile and although it automatically uses the
BUILT_PRODUCTS_DIR
as a framework search path, if the plugin doesn't define a dependency on the Flutter framework, there's no guarantee that the framework will be processed and put in theBUILT_PRODUCTS_DIR
before the plugin is compiled. We also enforce a minimum iOS/macOS deployment target so that plugins will always have a target higher than or equal to the Flutter framework - if it was lower, SPM will error.Flutter plugins with a Package.swift must follow a certain template to be usable by the Flutter CLI. The Flutter CLI will throw an error about the plugin being invalid if missing key parts. See comment (step 3) below for template.
CocoaPods will continue to run and be used to support non-Swift Package compatible Flutter plugins.
Not Included Features
It does not include the following (will be added in future PRs):
Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.