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.

  • Languages
  • $swift-package-manager
  • 190 lines
  • 2 files
  • ~2,050 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- swift-package-manager

Installs to ~/.agents/skills/swift-package-manager/ for every project on this machine.

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

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.

~/.agents/skills/swift-package-manager/SKILL.md

--- name: swift-package-manager description: Creates and maintains Swift packages with SwiftPM - Package.swift manifests, products and targets, dependency version requirements and… license: MIT ---

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 kindRequirementResolved file
Library others depend onfrom: "1.5.0" (up to next major)Do not rely on it; consumers resolve their own
App or toolfrom: plus committed Package.resolvedCommit; update deliberately with swift package update <name>
Need a bug fix not yet releasedrevision: "<sha>" temporarily, with a TODORemove 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 use Bundle.main inside a package; it points to the host app.
  • Localisation needs defaultLocalization: "en" in the Package initializer.
  • 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

KindWhen it runsExample
Build tool plugin (.buildTool())Every build, generates sources into a sandboxed work dirCode 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 with swift 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 on swift-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 with swift 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-version raises 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 Utilities target collides with yours. Use prefixed names.
  • Package.resolved merge conflicts: take either side, then run swift package resolve and commit the result.
  • Xcode caches resolution aggressively; after manifest changes that seem ignored, use File > Packages > Reset Package Caches or swift package reset.
  • unsafeFlags in 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.yamlgenerated from this page

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.

esc