Skip to content
20 changes: 19 additions & 1 deletion cli/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] };
};
};
};
Expand Down
3 changes: 2 additions & 1 deletion cli/src/ios/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | null> {
Expand All @@ -38,6 +38,7 @@ export async function getCommonChecks(config: Config): Promise<CheckFunction[]>
if (swiftToolsVersion) {
checks.push(() => checkSwiftToolsVersion(config, swiftToolsVersion));
}
checks.push(() => checkPackageTraitsRequirements(config));
}
return checks;
}
Expand Down
43 changes: 42 additions & 1 deletion cli/src/util/spm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export async function removeCocoapodsFiles(config: Config): Promise<void> {
export async function generatePackageText(config: Config, plugins: Plugin[]): Promise<string> {
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}
Expand All @@ -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})`;
}
}

Expand Down Expand Up @@ -206,6 +216,37 @@ export async function checkSwiftToolsVersion(config: Config, version: string | u
return null;
}

export async function checkPackageTraitsRequirements(config: Config): Promise<string | null> {
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<Plugin[]> {
Expand Down
Loading