Changelog
What's new in Once
Every user-facing change to Once, newest first. Subscribe to keep up.
-
Explicit modules for native Apple builds
Permalink →Once can now use the Swift compiler's dependency scan to build imported Swift and Clang modules as separate cacheable actions. Enable
explicit_moduleson an Apple target or a native Xcode or Swift package workspace. Native Xcode projects also honorSWIFT_ENABLE_EXPLICIT_MODULES = YES.Shared module work reuses the action cache, and independent declared actions can run concurrently within the build's memory budget. Module identity includes compiler and development-kit contents. Existing builds keep their implicit-module behavior unless explicit modules are selected.
An optional
dependency_check = "error"rejects source imports of workspace modules that are not declared dependencies. Errors include the target and a suggested repair. Xcode's inferred import edges do not satisfy this check.The module authoring contract now includes deferred action planning and host directory digests. These are ecosystem-neutral primitives that other target kinds can use. This release also fixes signing for standalone Apple executables. Parent-relative dependency references now resolve correctly in nested Apple packages.
See Apple builds for configuration and current limits.
-
Correct local and multi-platform Apple dependency graphs
Permalink →Once now follows transitive local Swift package dependencies without requiring a lockfile for an entirely local graph. Remote dependencies introduced by local packages still use the root package's dependency resolution.
Xcode projects with both macOS and iPhone targets now get distinct dependency graphs for each destination, including package targets and binary frameworks. Manifest parsing is shared across destinations and local product discovery.
Package product dependencies retain every module in the product, even when a module shares the product's name. Native application attributes and system library dependencies also remain consistent with their graph contracts.
These improvements came from exercising Vapor and NetNewsWire. Small, offline acceptance fixtures preserve their graph patterns and check dependency edges, source membership, settings, selective builds, cache restoration, and changes to transitive inputs without compiling the full applications.
Read about Swift packages and Xcode projects.
-
Run native Xcode script phases with reconciled build settings
Permalink →Once now retains Xcode script phases without source or archive outputs, including validation and product-processing scripts. Preparation runs before compilation, resource generators can run between linking and packaging, and product scripts run after assembly and before final signing. Later scripts consume the bundle produced by earlier scripts, including undeclared resources created by always-run phases. Deleted resources stay deleted, and restoring a captured bundle no longer overwrites newer outputs inside it.
Scripts receive native build settings mapped to Once's workspace, product, and intermediate directories, including input and output file-list variables. Fully declared scripts can reuse cached results. Always-run scripts and scripts with untracked inputs bypass the cache while allowing individual compilation actions to remain cached. Installation-only phases remain excluded from ordinary builds.
Native shell phases execute in the workspace so absolute build-setting paths and in-place edits address the same products. Caching relies on complete input and output declarations, not filesystem isolation.
-
Test results that follow the run
Permalink →Swift Testing bundles now report every test and what actually became of it. The testing library keeps a record of the run, and Once turns that record into its normalized results instead of listing what the sources declare and crediting each one with the run's exit status. A test that was filtered out, skipped, or never reached is reported as such rather than as a pass, and an issue a test marks as known no longer counts as a failure.
Where results still have to come from the XCTest host, that host reports the run's outcome rather than each test's. Once lists the cases it finds so a shard can address them, but lists them without a verdict, so a green run never carries verdicts nothing produced.
Test bundles that depend on a Swift macro can now import it. Code behind
canImportof a macro module used to compile away, which quietly emptied the test targets that exercise macro implementations. Such a bundle builds for the host along with its dependencies, matching how a macro itself is built. -
Preserve Swift dependency traits
Permalink →Native Swift package builds now preserve traits requested by dependencies. Once combines requests across the package graph and expands traits that enable other traits before compiling libraries and macros. This fixes missing types when a dependency exposes functionality behind an opt-in trait.
Explicit trait lists, disabled defaults, and conditional trait requests follow the package declarations. The same resolved traits control compiler settings and optional target dependencies.
-
Builds with a Swift toolchain outside Xcode
Permalink →A Swift toolchain installed beside Xcode contributes the compiler, but the linker and the software development kit still come from Xcode. Apple builds now resolve those separately and pass the linker's location to every compile and link, so a package that needs a newer compiler than the one Xcode ships builds without falling back to whatever the surrounding shell happened to expose.
Swift Testing follows the same rule. A macro plugin only matches the testing library from its own toolchain, and the two ship on different schedules, so test targets now compile and link against the copy the selected compiler provides when it has one, and against the version Xcode publishes otherwise. Test bundles that previously failed to expand
@Testand@Suitebuild again.Swift package libraries are also force-loaded into what depends on them, the way Swift Package Manager links every object file a target produced. A source file whose only contribution is a protocol conformance is no longer dropped, so conformances declared in an extension are found at runtime.
-
Explainable input fingerprints
Permalink →Evidence records now explain why a build's inputs changed, not only that they changed. Alongside the input digest it already tracked, Once records a versioned input fingerprint that breaks the digest down into labeled components for sources, generated inputs, dependencies, module code, toolchains, command shape, and environment configuration.
Each component carries a category, a stable label, and its own digest, so two runs can be compared to see exactly which part of the input moved. Targets that run several actions aggregate their per-action fingerprints into a single target-level view.
The fingerprint is available from both command and agent evidence queries, and older evidence stays readable because the field is optional. Raw command arguments, toolchain identities, and environment values are represented only by their digests and are never stored in plain text, so the fingerprint stays safe to share.
-
Faster native project discovery
Permalink →Once now discovers native Cargo, Mix, and future ecosystem projects with one shared filesystem pass. Workspace include and exclude patterns prune unrelated directories before Once enters them, reducing startup work in large repositories without changing the targets that discovery produces.
Discovery also keeps its own memory usage predictable. Project kinds that own nested packages retain only their shallowest matching roots, while kinds that recognize every nested project have a 16 mebibyte retained-result limit. Once stops with guidance for narrowing the workspace if that limit is reached, instead of allowing the number of matching directories to cause unbounded memory growth.
-
Near-instant unchanged builds
Permalink →Once now returns almost immediately when nothing relevant has changed. A build that already matches the cache no longer revalidates the whole graph on every invocation. Instead, Once records what changed since the last successful build and reuses the previous result when the sources, declared outputs, environment, and tools it observed are all unchanged.
Input checking is also cheaper. Once validates unchanged files by their metadata rather than rereading their contents, so a repeat build no longer pays to rehash the entire dependency tree. When a cached result is reused, only the outputs that actually differ are restored. Together these reduce both the time and the memory an unchanged or lightly changed build needs.
Correctness is preserved conservatively: any change to a watched source or output, a relevant environment variable, or an observed tool on disk falls back to a full validation before the fast path is trusted again.
-
Native Cargo project graphs
Permalink →Once can now read an existing
Cargo.tomland turn it into a typed, cacheable build graph. Rust developers can build, run, and test a workspace without first translating it intoonce.toml.Native discovery understands Cargo workspaces and the locked dependency closure from
Cargo.lock, including registry, Git, and path packages. Each workspace member and locked dependency compiles as its own action, so unchanged work can be restored independently from a local or remote cache. Procedural macros and build scripts are modeled as their own targets so they rebuild only when their own inputs change.Once keeps
Cargo.tomlandCargo.lockauthoritative and uses the Rust toolchain selected by the project's configuration. Test targets report their results through the same normalized shape as the other ecosystems, so scheduled runs, change-scoped selection, and summaries work the same way.Start by running
once query targets, then useonce build,once run, andonce testwith the target identifiers Once reports. -
Bounded memory scheduling for local actions
Permalink →Once now schedules concurrent local actions against a memory budget. The default is two thirds of the memory visible to the host or container, while the new global
--memory-limitoption lets constrained machines choose a smaller budget.Builds, lint targets, run targets, literal commands, scripts, and test workers share the same limit. Actions without a declared estimate receive a conservative default, and oversized actions run alone instead of waiting forever.
Large command streams, file outputs, directory outputs, and cache uploads or downloads now move through bounded buffers and temporary files instead of requiring complete artifacts in memory.
-
Native Mix project graphs
Permalink →Once can now read an existing
mix.exsand turn it into a typed, cacheable build graph. Elixir developers can build an application, run formatting and dependency checks, execute tests and Mix tasks, and assemble releases without first translating the project intoonce.toml.Native discovery understands locked Hex, Git, path, Mix, and Rebar 3 dependencies. Each package compiles as its own action, so unchanged dependency work can be restored independently from a local or remote cache. The graph also keeps development, test, and production environments separate.
Once uses the Elixir and Erlang versions selected by the project's toolchain configuration while keeping
mix.exsandmix.lockauthoritative. Umbrella applications and nested Mix projects receive package-qualified targets, and structured diagnostics explain unsupported dependency build managers or missing materialized sources.Start by running
once query targets, then useonce build,once run, andonce testwith the target identifiers Once reports. -
Device-specific Apple app size analysis
Permalink →Once can now produce ad-hoc signed app archives for one Apple device model at a time with
apple_thinned_package. Keeping each model in its own target makes the variants independently cacheable and ready for size-analysis services such as Sentry without the noise of a universal build.The target validates device builds, uses Xcode's thinning tool, re-signs embedded bundles, and writes a deterministic archive plus a stable manifest. Existing
apple_applicationbundles now include the supported-platform metadata that Xcode requires for thinning. -
"New blog post: Automation needs a git"
Permalink →The first post on the Once blog is live. "Automation needs a git" is Pedro's account of why Once exists: the search for a shared substrate for automation, the incremental shell-based API for decorating existing scripts, and native support for ecosystems starting with Rust. Read it under the blog.
-
A blog for ideas from the Once team
Permalink →The Once website now has a blog for longer-form notes about build graphs, reusable automation, and the ideas shaping the project.
Posts include their authors and Gravatar portraits. The list is paginated, each post has its own generated Open Graph image with author portraits, and readers can subscribe through Really Simple Syndication or Atom feeds. Markdown is compiled with MDEx, with highlighted code windows built from Noora components.
-
Cacheable linting with normalized findings
Permalink →Once can now run lint targets through the same graph, cache, evidence, and remote-execution model used by builds and tests. The first integrations cover Ruff, golangci-lint, ESLint, RuboCop, Credo, detekt, and SwiftLint.
once lintkeeps each analyzer's native configuration authoritative while presenting one finding model and failure policy to humans, automation, and coding agents. Project modules can add another analyzer through the documented lint provider and portable report contract.The bundled ESLint starter includes its required flat configuration, Credo limits analysis to declared sources, and RuboCop prepares Ruby for portable report conversion. Invalid custom lint providers now return structured repair diagnostics before their actions run.
-
Documentation now lives in the Once app
Permalink →The documentation moved from a separate static site into the Once web application, served under
/docs. It is rendered natively with the Noora design system, so the docs and the marketing site now share one look and feel.This unlocks upcoming interactive features like a package registry and live content, and lets the docs run on the same infrastructure as the rest of Once.
-
Refreshed marketing site and a changelog
Permalink →The Once home page was rebuilt around the idea of build once, reuse everywhere, with a new hero, tabbed build examples for iOS, Android, Rust, and Zig, and the list of natively supported languages and ecosystems.
This changelog is new too. Every user-facing change to Once will show up here, and you can subscribe over RSS or Atom.