diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts index 9afc560897..6c8645406d 100644 --- a/cli/src/declarations.ts +++ b/cli/src/declarations.ts @@ -536,11 +536,29 @@ export interface CapacitorConfig { * * This setting may graduate to `ios.spm.swiftToolsVersion` in a future major release. * - * @since 8.2.0 + * @since 8.3.0 * @default '5.9' * @example '6.1' */ swiftToolsVersion?: string; + + /** + * Define package traits for SPM plugin dependencies. + * + * This requires explicitly setting experimental.ios.spm.swiftToolsVersion + * to '6.1' or higher. + * + * The key is the plugin ID (e.g. `@capacitor-firebase/analytics`) + * and the value is an array of trait names. + * + * Packages can have default traits. If you use this property, and + * want to preserve the defaults, include ".defaults" in the array. + * + * This setting may graduate to `ios.spm.packageTraits` in a future major release. + * + * @since 8.3.0 + */ + packageTraits?: { [pluginId: string]: string[] }; }; }; }; diff --git a/cli/src/ios/common.ts b/cli/src/ios/common.ts index 7ac2831e03..779dafd90f 100644 --- a/cli/src/ios/common.ts +++ b/cli/src/ios/common.ts @@ -11,7 +11,7 @@ import type { Config } from '../definitions'; import { logger } from '../log'; import { PluginType, getPluginPlatform } from '../plugin'; import type { Plugin } from '../plugin'; -import { checkSwiftToolsVersion } from '../util/spm'; +import { checkPackageTraitsRequirements, checkSwiftToolsVersion } from '../util/spm'; import { isInstalled, runCommand } from '../util/subprocess'; export async function checkIOSPackage(config: Config): Promise { @@ -38,6 +38,7 @@ export async function getCommonChecks(config: Config): Promise if (swiftToolsVersion) { checks.push(() => checkSwiftToolsVersion(config, swiftToolsVersion)); } + checks.push(() => checkPackageTraitsRequirements(config)); } return checks; } diff --git a/cli/src/util/spm.ts b/cli/src/util/spm.ts index 5deb4b3b87..3e2e515293 100644 --- a/cli/src/util/spm.ts +++ b/cli/src/util/spm.ts @@ -98,6 +98,7 @@ export async function removeCocoapodsFiles(config: Config): Promise { export async function generatePackageText(config: Config, plugins: Plugin[]): Promise { const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name); const iosVersion = getMajoriOSVersion(config); + const packageTraits = config.app.extConfig.experimental?.ios?.spm?.packageTraits ?? {}; const swiftToolsVersion = config.app.extConfig.experimental?.ios?.spm?.swiftToolsVersion ?? '5.9'; let packageSwiftText = `// swift-tools-version: ${swiftToolsVersion} @@ -120,7 +121,16 @@ let package = Package( packageSwiftText += `,\n .package(name: "${plugin.name}", path: "../../capacitor-cordova-ios-plugins/sources/${plugin.name}")`; } else { const relPath = relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath); - packageSwiftText += `,\n .package(name: "${plugin.ios?.name}", path: "${relPath}")`; + const traits = packageTraits[plugin.id]; + const traitsSuffix = traits?.length + ? `, traits: [${traits + .map((t) => { + // Any trait is written with quotes, with the exception of .defaults + return /^\.?defaults?$/i.test(t) ? '.defaults' : `"${t}"`; + }) + .join(', ')}]` + : ''; + packageSwiftText += `,\n .package(name: "${plugin.ios?.name}", path: "${relPath}"${traitsSuffix})`; } } @@ -206,6 +216,37 @@ export async function checkSwiftToolsVersion(config: Config, version: string | u return null; } +export async function checkPackageTraitsRequirements(config: Config): Promise { + const packageTraits = config.app.extConfig.experimental?.ios?.spm?.packageTraits; + const swiftToolsVersion = config.app.extConfig.experimental?.ios?.spm?.swiftToolsVersion; + + const hasPackageTraits = packageTraits && Object.keys(packageTraits).some((key) => packageTraits[key]?.length > 0); + + if (!hasPackageTraits) { + return null; + } + + if (!swiftToolsVersion) { + return ( + `Package traits require an explicit Swift tools version of 6.1 or higher.\n` + + `Set experimental.ios.spm.swiftToolsVersion to '6.1' or higher in your Capacitor configuration.` + ); + } + + const versionParts = swiftToolsVersion.split('.').map((part) => parseInt(part, 10)); + const major = versionParts[0] || 0; + const minor = versionParts[1] || 0; + + if (major < 6 || (major === 6 && minor < 1)) { + return ( + `Package traits require Swift tools version 6.1 or higher, but "${swiftToolsVersion}" was specified.\n` + + `Update experimental.ios.spm.swiftToolsVersion to '6.1' or higher in your Capacitor configuration.` + ); + } + + return null; +} + // Private Functions async function pluginsWithPackageSwift(plugins: Plugin[]): Promise {