Swift Packages
Once can import a locked Swift Package Manager graph and expose selected static products to Apple libraries, frameworks, applications, and tests. Swift Package Manager remains authoritative for package manifests, version selection, and Package.resolved; Once makes the locked result queryable and cacheable.
Prerequisites
Install Xcode and its command-line tools, then verify the selected Swift toolchain:
xcrun --find swift
xcrun swift --versionStart 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.
Declare the Locked Package Graph
Keep package declarations in Package.swift and exact remote selections in Package.resolved. A checked-in dependency graph avoids running package inspection while Once loads the graph.
[[target]]
name = "Packages"
kind = "swift_package_dependencies"
srcs = [
"Package.swift",
"Package.resolved",
"swiftpm-dependencies.json",
"Sources/**/*.swift",
"Vendor/Greeting/**/*.swift",
"Vendor/Greeting/Package.swift",
]
[target.attrs]
package_path = "."
resolved_file = "Package.resolved"
resolver_inputs = [
"Package.swift",
"Package.resolved",
"swiftpm-dependencies.json",
"Vendor/Greeting/Package.swift",
]
graph_file = "swiftpm-dependencies.json"
products = ["Root", "Greeting"]
platform = "macos"
minimum_os = "13.0"Every name in products must be a static library product. Include the root facade and every transitive archive needed by downstream links. Once does not guess archive names for automatic library products.
resolved_file and graph_file are relative to package_path. Their defaults therefore follow a package moved into a workspace subdirectory.
The bundled starter uses this layout:
Package.swift
Package.resolved
swiftpm-dependencies.json
Sources/Root/Root.swift
Vendor/Greeting/Package.swift
Vendor/Greeting/Sources/Greeting/Greeting.swiftRoot imports Greeting and exposes one function to first-party consumers. The local package keeps the starter network-independent while exercising the same package graph and product linking boundary as a vendored remote package.
Connect a First-Party Consumer
Add the dependency-set target to an ordinary Apple target:
[[target]]
name = "Consumer"
kind = "apple_application"
srcs = ["Consumer/**/*.swift"]
deps = ["./Packages"]
[target.attrs]
platform = "macos"
bundle_id = "dev.once.Consumer"
minimum_os = "13.0"The consumer can import the root product normally:
import Root
@main
struct Consumer {
static func main() {
print(greet())
}
}The deps edge carries the package module directory, selected archives, frameworks, dynamic libraries, and linker options into the Apple target.
Query and Build the Expanded Graph
Inspect the synthetic pins and the two buildable targets before execution:
once query targets --kind swift_package_pin
once query capabilities Packages
once query capabilities Consumer
once build Consumer
./.once/out/Consumer/Consumer.app/ConsumerThe starter creates a swiftpm-greeting-local pin, performs one locked package build for Root and Greeting, and links both products into Consumer. Running the built application prints Hello from a locked local package.
Synthetic swift_package_pin targets carry identity, revision, version, checksum, location, and package-to-package edges. They have no compile action. The swift_package_dependencies owner performs the locked native package build and emits the provider used by the local Apple consumer.
Update or Vendor Packages Explicitly
Update package state outside an ordinary Once build:
xcrun swift package resolve
xcrun swift package show-dependencies --format json > swiftpm-dependencies.raw.json
# Add the Once provenance fields, then save the result as swiftpm-dependencies.json.
once query targets --kind swift_package_pin
once build ConsumerThe Swift Package Manager command emits only its raw package graph. Review that graph and Package.resolved together, then add the required provenance fields before using it as swiftpm-dependencies.json. The JavaScript Object Notation graph snapshot must contain every locked pin, or graph loading rejects it as stale. Add once_manifest with the exact Package.swift text and once_resolved with the exact Package.resolved text. This makes manifest, lockfile, and local dependency changes reject a stale graph. Add once_inputs as a map containing every exact resolver input except the graph snapshot itself. Include each workspace-local package manifest in resolver_inputs so its dependency edges participate in that binding. Any graph node without a lock entry must resolve to a workspace-local path. For network-independent remote builds, set vendor_path to a complete Swift Package Manager scratch tree containing the matching checkouts, repositories, and workspace state. Once copies that tree into target-local output before building, so the checked-in vendor state is not mutated.
Without a graph snapshot, graph loading runs locked package inspection in a disposable .once scratch directory. Remote packages require an explicit allow_network = true for that live inspection. A configured vendor_path requires a checked-in graph snapshot, which prevents analysis from mutating the vendored tree. Automatic version selection remains disabled. Once does not independently sandbox Swift Package Manager network access during a build, so an incomplete vendored tree can still cause native package-manager access. By default, Once uses the Swift Package Manager executable paired with the Apple compiler selected through Xcode, so package modules and first-party consumers cannot silently compile with different Swift versions.
Locked package identities, versions, revisions, and checksums participate in the native build action identity even when Package.resolved is kept only in resolver_inputs. Updating a pin therefore invalidates the package build without requiring the lock file to be duplicated in srcs.
Current Boundary
The adapter currently exposes explicit static products for one Apple platform and architecture per target. Resource bundles, binary artifacts, and build-tool plugins need typed provider fields before Once can safely embed or execute them. Use a first-party static package facade or a script for those packages until their artifacts are represented explicitly.
See swift_package_dependencies for every attribute and provider field, and swift_package_pin for the synthetic locked identity shape.
