Swift Package Manager skill for Codex
Package.swift that builds clean on every platform. Codex writes manifests with clear products and targets, chooses version requirements by package kind, and keeps Package.resolved committed for apps only. It fixes resource loading with Bundle.module, splits Xcode apps into local package targets leaf first, sets up build tool and command plugins within the SwiftPM sandbox, and checks API breakage before any tag.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- swift-package-manager Installs to ~/.agents/skills/swift-package-manager/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install swift-package-manager The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project swift-package-manager Installs to .agents/skills/swift-package-manager/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/swift-package-manager.zipunzip -o swift-package-manager.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) swift-package-manager PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $swift-package-manager, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Creates and maintains Swift packages with SwiftPM - Package.swift manifests, products and targets, dependency version requirements and Package.resolved, resources with Bundle.module, Swift Testing, build tool and command plugins, binary XCFramework targets, traits, and Linux or static SDK builds - including splitting an Xcode app into local packages. Use when writing or fixing Package.swift, adding a dependency, modularising an iOS or macOS app, creating a plugin or macro package, or debugging resolution and resource errors.
Use it when
- Creating or editing Package.swift for a library, CLI, plugin or macro
- Dependency resolution fails or Package.resolved conflicts in a merge
- Modularising an Xcode project into Swift package targets
- Resources, plugins or binary XCFramework targets misbehave
Use something else when
- The errors are about actors, Sendable or Swift 6 isolation: use swift-concurrency
- You are preparing an App Store submission: use ios-app-store-release
How to check the result
swift build in debug and release and swift test pass from a clean clone, and swift package diagnose-api-breaking-changes against the last tag reports nothing unexpected.
The mistake it prevents
Loading package resources with Bundle.main, which works in the app by accident and returns nil in tests and other host apps.
Done well means: swift build and swift test pass from a clean clone, dependency
requirements are as loose as compatibility allows for libraries and pinned by the committed
Package.resolved for apps, each target has one clear responsibility, and resources load
through Bundle.module on every platform the package declares.
Inspect first
head -1 Package.swift # // swift-tools-version: x.y (minimum toolchain)
swift --version
swift package describe # targets, products, dependencies
swift package show-dependencies --format text
git ls-files Package.resolved
Read AGENTS.md for supported platforms and Xcode version. Resolving or updating
dependencies clones git repositories (network); with network off, builds work only from
an existing .build checkout or the shared cache.
Manifest shape
// swift-tools-version: 6.1
import PackageDescription
let package = Package(
name: "Weather",
platforms: [.iOS(.v17), .macOS(.v14)],
products: [
.library(name: "WeatherKit", targets: ["WeatherKit"]),
.executable(name: "weather", targets: ["WeatherCLI"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
.package(url: "https://github.com/apple/swift-collections", "1.1.0"..<"2.0.0"),
.package(path: "../SharedModels"), // local package
],
targets: [
.target(
name: "WeatherKit",
dependencies: [.product(name: "Collections", package: "swift-collections")],
resources: [.process("Resources")]
),
.executableTarget(
name: "WeatherCLI",
dependencies: ["WeatherKit", .product(name: "ArgumentParser", package: "swift-argument-parser")]
),
.testTarget(name: "WeatherKitTests", dependencies: ["WeatherKit"], resources: [.copy("Fixtures")]),
]
)
Conventions: sources in Sources/<Target>/, tests in Tests/<Target>/. A package’s
identity for .product(package:) comes from the last path component of its URL
(swift-collections), not its name:.
Editing helpers (Swift 6+) keep formatting intact: swift package add-dependency <url> --from 1.5.0,
swift package add-target, swift package add-target-dependency, swift package add-product.
Version requirements
| Package kind | Requirement | Resolved file |
|---|---|---|
| Library others depend on | from: "1.5.0" (up to next major) | Do not rely on it; consumers resolve their own |
| App or tool | from: plus committed Package.resolved | Commit; update deliberately with swift package update <name> |
| Need a bug fix not yet released | revision: "<sha>" temporarily, with a TODO | Remove before release; a library cannot publish branch/revision deps for SemVer consumers |
| Pre-1.0 dependency | .upToNextMinor(from: "0.4.2") | 0.x minors may break |
exact: in a library forces every consumer onto one version and causes unresolvable
graphs; avoid it.
Resources
.process("Resources")optimises (asset catalogs, xibs, localisation, image compression);.copy("Fixtures")keeps a directory tree byte for byte.- Load with
Bundle.module.url(forResource: "cities", withExtension: "json"). Never useBundle.maininside a package; it points to the host app. - Localisation needs
defaultLocalization: "en"in thePackageinitializer. - SwiftUI images and colours from an asset catalog:
Image("Logo", bundle: .module).
Testing
import Testing
@testable import WeatherKit
@Test("parses Celsius readings", arguments: ["12.5C", "-3C"])
func parsesCelsius(raw: String) throws {
let reading = try Reading(parsing: raw)
#expect(reading.unit == .celsius)
}
Swift Testing ships with Swift 6 toolchains; XCTest still works side by side.
swift test --filter WeatherKitTests, swift test --parallel. For coverage:
swift test --enable-code-coverage then swift test --show-codecov-path.
Modularising an Xcode app
- [ ] 1. Create a local package (Packages/AppFeatures) and add it to the project once
- [ ] 2. Move leaf code first: models, networking, utilities; then feature modules
- [ ] 3. Make cross-module API public deliberately; avoid @testable as a workaround in production
- [ ] 4. Move resources with their code and switch to Bundle.module
- [ ] 5. Each feature target depends only on lower layers; no cycles
- [ ] 6. Build the app scheme and run package tests in CI (swift test or xcodebuild -scheme <package scheme>)
One package with many targets is easier than many packages; split into separate packages only for independent versioning or reuse outside the app.
Plugins
| Kind | When it runs | Example |
|---|---|---|
Build tool plugin (.buildTool()) | Every build, generates sources into a sandboxed work dir | Code generation from OpenAPI, protobuf, SwiftGen |
Command plugin (.command(intent:permissions:)) | When a user runs swift package <verb> | Formatting, docs generation, license headers |
swift package plugin --list
swift package --allow-writing-to-package-directory format-source-code # example verb
swift package init --type build-tool-plugin # scaffold
Plugins run sandboxed on macOS with no network and write access only to their work directory unless the command plugin declares permissions; do not try to bypass this. Build tool plugins must declare output files so builds stay incremental. Packages that use third-party plugins require the user to trust them in Xcode the first time.
Binary targets and macros
.binaryTarget(name: "Vendor", url: "https://.../Vendor.xcframework.zip", checksum: "..."); compute the checksum withswift package compute-checksum Vendor.xcframework.zip. XCFramework binaries are Apple-only; Linux needs source or an artifact bundle.- Macro packages (
swift package init --type macro) depend onswift-syntax, which is heavy to build; keep macros in their own package so app builds do not recompile it.
Linux and command-line distribution
- Build release binaries:
swift build -c release --product weather; output under.build/release/. - Fully static Linux binaries: install the Static Linux SDK (
swift sdk install <url>, needs network) and build withswift build --swift-sdk <triple>-swift-linux-musl. - Guard Apple-only APIs with
#if canImport(UIKit)/#if os(Linux); Foundation on Linux is swift-corelibs-foundation (now swift-foundation based) and may lack some APIs.
Gotchas
- Raising
swift-tools-versionraises the minimum toolchain for every consumer; do it on purpose and say so in the changelog. - Target names must be unique across the whole dependency graph; a dependency with a
Utilitiestarget collides with yours. Use prefixed names. Package.resolvedmerge conflicts: take either side, then runswift package resolveand commit the result.- Xcode caches resolution aggressively; after manifest changes that seem ignored, use
File > Packages > Reset Package Caches or
swift package reset. unsafeFlagsin a dependency makes it unusable from other packages by version; never use them in a library.- Test targets cannot be depended on by other targets; put shared test helpers in a regular
target named
*TestSupport.
Verification
swift package resolve
swift build && swift build -c release
swift test
swift package diagnose-api-breaking-changes <last-release-tag> # libraries before tagging
Tagging and pushing a release publishes the version to every consumer; ask the user before creating or pushing tags.
Report template
**Package:** Weather (tools 6.1; iOS 17, macOS 14)
**Targets:** WeatherKit (lib), WeatherCLI (exe), WeatherKitTests
**Dependencies:** swift-argument-parser from 1.5.0; swift-collections 1.1..<2.0
**Resources:** Resources/ processed, loaded via Bundle.module (fixed Bundle.main bug)
**Checks:** swift build (debug, release) and swift test pass; API diff vs 2.3.0: no breaking changes What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Swift Package Manager"
short_description: "Package.swift that builds clean on every platform"
default_prompt: "$swift-package-manager Split our iOS app into a local Swift package with Models, Networking and Feature targets" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.