Swift Packages
Once reads Package.swift and derives first-party package targets as Once
targets. Swift Package Manager remains authoritative for manifests, version
selection, and Package.resolved; Once uses that information to build and
cache the targets it derives.
Start From a Native Package#
Once recognizes Package.swift automatically and lowers first-party package
targets into the existing Apple target kinds. This works without once.toml:
once query workspace
once query targets
once build
once testThe generated swift_package_workspace seed reads the package manifest and
derives first-party libraries, executables, macros, binary targets, and tests.
Discovery skips generated .build and .swiftpm directories and does not
write once.toml. once build selects the package workspace when it is the
only discovered build root. once test runs first-party test bundles and
excludes test bundles that belong only to resolved packages. Use once test --all when you intentionally want the complete resolved test graph.
Local path dependencies within the workspace are followed transitively,
including sibling packages and shared dependencies reached through several
products. An entirely local graph does not need Package.resolved and does not
run dependency resolution.
If a local dependency introduces a remote package, Swift Package Manager still
resolves that package through the root package's lockfile.
Keep Swift Package Manager Commands#
For explicit module builds, add explicit_modules = true to your
swift_package_workspace seed's attributes. Optional
dependency_check = "error" checks source imports against declared package
dependencies. Once propagates these settings through the resolved package
graph; Package.swift remains the source of target and dependency declarations.
See explicit Apple modules
for requirements and current limits.
Once can sit behind the swift command for one native Swift Package
Manager package. Add
this mise wrapper, then run mise reshim and activate mise in the shell that
starts the build:
[wrappers.swift]
command = "once"
args = ["swift", "--"]Once routes the default debug build and test forms into the native graph:
swift build
swift testswift build builds the package seed and its resolved products. swift test
runs each first-party test bundle once through Once, excluding test targets
that belong to resolved dependencies. -q or --quiet,
-c debug or --configuration debug, and --package-path . are also
supported.
For troubleshooting without mise, call the compatibility surface directly and put the separator before the Swift Package Manager arguments:
once swift -- testEvery other request, including release builds, filters, package subcommands,
plugins, and a package path outside the current directory, invokes the system
swift executable with its original arguments and exit status. This preserves
Swift Package Manager behavior until the request has an exact Once equivalent.
For locked source-control dependencies, native package lowering materializes
the pinned sources during graph loading, then compiles them directly through
Once's Apple target kinds. See
swift_package_workspace for
its complete contract.
When the Xcode workspace resolver lowers package sources into Apple targets, compiler language and access-control flags follow the manifest tools version. Once adds the semantic package name only for tools version 5.9 or newer, where package-scoped access is part of the manifest contract. Older manifests remain isolated by module.
Native package lowering preserves linkerSettings for linked libraries,
frameworks, and unsafe linker flags, together with platform, configuration, and
trait conditions. It also honors .treatAllWarnings(as:) independently for
Swift, C, and C++ settings.
Manifest evaluation observes a hash of the inherited environment, so toggling variables that select targets or plugins invalidates cached resolution. The hash does not store environment names or values. This is conservative: even unrelated environment changes invalidate resolution. Build receipt fast paths also require an unchanged inherited environment.
Xcode's discovery-only scan for local package products skips unreadable manifests with a warning, for example templates containing placeholders. Manifests referenced by the graph must still load successfully.
Build-tool plugins execute before native compilation. Swift Package Manager compiles the plugins and their host tools, evaluates the commands, and runs prebuild commands to discover their outputs. Once then schedules the declared build commands with their argument vectors, inputs, outputs, environment, and working directory. Generated sources, headers, and resources become inputs to the consuming target. Commands without outputs still run when the package uses Swift tools version 6 or newer, including validation tools that fail the build.
Plugin planning uses only the consuming package as a workspace root, with local
mirrors and authoritative pins for its source-control dependencies. Missing pins
fail planning instead of selecting new versions. It does not build the
application's ordinary package products. Planning and plugin commands inherit
the host environment, run locally without sandboxing, and bypass the action
cache. Native compilation and packaging retain their normal content-addressed
caching. SwiftPM may also evaluate plugins on other targets in the consuming
package while planning, including test targets. This integration is tested
against Apple Swift 6.3.3 and uses SwiftPM's unsupported multi-root and native
planning interfaces, whose formats may change between toolchains. See
swift_package_build_tools
for its contract and compatibility limits.
Prerequisites#
Install Xcode and its command-line tools, then verify the selected Swift toolchain:
xcrun --find swift
xcrun swift --versionOnce selects the Swift compiler through xcrun. A Swiftly .swift-version
file does not change that selection. If a package requires a separately
installed Swift toolchain, select its bundle identifier for the build:
TOOLCHAINS=your.toolchain.bundle.identifier once buildCheck the selection with TOOLCHAINS=your.toolchain.bundle.identifier xcrun swift --version. The selected compiler must support the package's
swift-tools-version.
A Swift toolchain installed beside Xcode contributes the compiler and the macros it loads, while Xcode still contributes the linker and the software development kit. Once resolves each of those separately, so builds that mix them link and expand macros against matching tools. Swift Testing follows the same rule: when the selected compiler ships its own copy, tests compile and link against that one rather than the version Xcode publishes.
Start with the Apple guide if a first-party Apple target does not build yet. Package integration is easier to diagnose after the local compiler, software development kit, linker, and code-signing path work.
Packages With Remote Dependencies#
Native package lowering uses Package.resolved to materialize each
source-control dependency at its pinned revision. When a package declares
dependencies but does not commit the lockfile, Once asks Swift Package Manager
to resolve them and uses the generated lockfile. It then derives and compiles
the dependency targets directly, so Swift Package Manager does not build a
separate dependency graph. Registry dependencies are not supported by this
native path yet.
The ordinary workflow stays the same:
once query targets
once build SwiftPackage_MyPackage_MyLibraryThere is no Once initialization step. A first build can access the network when
Swift Package Manager must create Package.resolved or Once must materialize a
pinned source dependency.
Once reuses a checkout that already contains the pinned commit. Otherwise it tries a shallow fetch and retries without a depth limit when the server does not support shallow clients.
Once preserves package traits requested by dependencies, including traits that enable other traits and conditional requests. Code guarded by a dependency's opt-in trait is compiled with that trait enabled. Explicit empty trait lists disable defaults for that dependency declaration; requests from other packages are still combined according to Swift Package Manager's rules.