SwiftPM 6.4 build artifact paths are no longer safe to infer from a hard-coded .build hierarchy: run swift build --show-bin-path with the same build parameters, then update cache and collection logic around that returned path. Use the native build system only as a temporary diagnostic fallback, and keep production on the new path only after a dual-track validation.
This is for you if you maintain Shell, Makefile, Fastlane, or CI scripts and now see “build succeeded, artifact not found.” It also applies to Swift Package authors, plugin maintainers, test engineers, and DevOps teams managing remote Mac nodes, caches, and rollback rules.
First, classify the failure before changing the toolchain
The most useful symptom is not the missing file. It is the sequence of events around it:
- The build command exits successfully.
- A later step tries to locate a binary, test result, coverage file, or archive.
- The upload or packaging step reports that the file does not exist.
SwiftPM 6.4 documentation identifies Swift Build as the default build system and documents output-location changes. The official Swift Build migration documentation also points users toward querying the output directory instead of treating internal directory names as a permanent interface.
Record these values from the failed job before rerunning it:
- The complete build command.
- The complete artifact-discovery command.
- The working directory printed by the runner.
- The selected toolchain and
xcode-selectstate. - The build configuration, architecture, and destination.
- The exact exit status of every command.
- The path returned by
swift build --show-bin-path. - The path that the upload step actually searched.
- The cache key and whether the workspace was clean.
Check the path contract used by each responsibility
The same output path can be consumed differently by different parts of a Swift repository. Treat each consumer as a separate contract instead of applying one global replacement.
Script maintainers: remove assumptions from collection commands
Search Shell scripts, Makefiles, Fastlane lanes, and custom release helpers for patterns such as:
.build/debug/MyBinary
.build/release/MyBinary
.build/arm64-apple-macosx/release/MyBinary
The problem is not that every reference is guaranteed to fail. The problem is that these paths are inferred from an internal layout rather than obtained from the build command that created the product.
A safer pattern is:
set -eu
ROOT_DIR="${CI_PROJECT_DIR:-$PWD}"
CONFIGURATION="release"
ARCHITECTURE="arm64"
PACKAGE_PATH="$ROOT_DIR/<package-path>"
PRODUCT_NAME="<product-name>"
cd "$PACKAGE_PATH"
swift build \
-c "$CONFIGURATION" \
--arch "$ARCHITECTURE"
BIN_PATH="$(
swift build \
-c "$CONFIGURATION" \
--arch "$ARCHITECTURE" \
--show-bin-path
)"
BIN_PATH="$(printf '%s' "$BIN_PATH" | tr -d '\n')"
PRODUCT_PATH="$BIN_PATH/$PRODUCT_NAME"
test -d "$BIN_PATH"
test -f "$PRODUCT_PATH"
mkdir -p "$ROOT_DIR/<artifact-directory>"
cp "$PRODUCT_PATH" "$ROOT_DIR/<artifact-directory>/"
The important rule is parameter symmetry. The build and the path query must use the same package location, configuration, architecture, and destination. If your actual job uses a destination or another supported build option, repeat it in the query rather than asking for a default path.
The official package manager repository is the appropriate source for supported command behavior. Do not copy path logic from an old log, a local machine, or an internal .build directory that happened to exist before the migration.
Package authors: inspect plugins and resource-producing targets
A package plugin can create a second path problem even after the main binary lookup is fixed. Review build tool plugins, command plugins, binary targets, resource processing, and script plugins for code that concatenates .build subdirectories manually.
For each plugin, document:
- Which input files it consumes.
- Which output files it creates.
- Whether the output is declared to the build system.
- Whether the output is copied into a stable release directory.
- Which command produces the log used for diagnosis.
- Whether the plugin assumes one configuration or one architecture.
The official helper implementation used by the Swift project is useful for studying how a larger project separates build arguments from output handling. Use it as an implementation reference, not as proof that your package has the same directory structure.
Compare the two build systems without treating either layout as permanent
Swift Build and the native build system should be compared by observable deliverables, not by whether their .build or derived-data directories look alike. The official Swift Build repository describes the separate build-system work, while the default-build-system change discussion explains why an upgrade can expose old assumptions in CI.
| Validation area | Swift Build path strategy | Native build path strategy | Pass condition |
|---|---|---|---|
| Binary discovery | Query swift build --show-bin-path with matching parameters | Use the native system’s supported result or archive interface | The collector reads the producer’s reported location |
| Test results | Read the configured test report or log source | Read the native test result bundle or configured report | Failed tests remain identifiable |
| Cache identity | Include toolchain, build system, configuration, architecture, and lockfile state | Keep a separate cache namespace | No cross-system artifact reuse |
| Plugin outputs | Validate declared inputs and outputs | Validate the native phase or script output | Clean builds reproduce required files |
| Release packaging | Copy from the discovered path into a stable staging directory | Package from the native archive or export result | The final package is verified independently |
If Swift Build fails while the native system succeeds, keep the smallest reproducible package, the full command, the toolchain selection, and the output logs. Do not silently mask the failure by switching production back to native. A fallback can help isolate the boundary, but it cannot replace release acceptance.
Rebuild test collection around evidence, not one guessed directory
Test jobs often fail after the binary path is repaired because test launchers and report collectors have their own assumptions.
Separate these checks:
swift testexit status.- Test execution logs.
- Test result or report location.
- Coverage output.
- Failed-test identification.
- Artifact upload status.
For a test job, make the producer and consumer visible:
set -eu
PACKAGE_PATH="<package-path>"
TEST_CONFIGURATION="debug"
TEST_ARCHITECTURE="arm64"
cd "$PACKAGE_PATH"
swift test \
-c "$TEST_CONFIGURATION" \
--arch "$TEST_ARCHITECTURE" \
2>&1 | tee "<test-log-path>"
TEST_STATUS="${PIPESTATUS[0]}"
test "$TEST_STATUS" -eq 0
If the test command has a project-specific report option or a separate coverage export step, record its documented output rather than inventing a path from .build. Keep a native comparison job during migration, using the same commit and equivalent test scope. This lets you distinguish a changed report location from a changed test runner or a real test failure.
The Swift Build issue tracker entry for output differences should be attached to your internal incident record when the observed behavior matches it. Mark it as an external known issue, not as a substitute for your own command and environment evidence.
Rebuild cache identity on the remote Mac
A cache that predates the build-system change can make a corrected script look unreliable. The cache key must describe every input that can alter either the product or its location.
Include at least these dimensions in the cache identity:
- SwiftPM and Swift toolchain version.
- Selected build system.
- Build configuration.
- Target architecture.
- Package dependency lockfile.
- Relevant package manifest state.
- Plugin source and generated-input state.
- Runner image or macOS environment identifier.
- Workspace layout, when the path is embedded in generated files.
On a remote Mac, also inspect:
- The account running SSH, VNC, or the CI agent.
- The absolute workspace path.
- File ownership and writable directories.
- The active developer directory from
xcode-select. - Cleanup behavior between jobs.
- Whether a node restart removes temporary state.
- Whether the agent starts before the expected login environment is available.
If your team needs a disposable environment for this validation, you can review MACGPU’s remote Mac environment as one option for isolating cold-cache, restart, and toolchain checks without changing your primary workstation.
Use this acceptance checklist before production cutover
Run the checklist against the same commit and record the command output as build evidence.
- [ ] Start from a clean workspace with no restored build directory.
- [ ] Print the working directory before package commands run.
- [ ] Print the selected developer directory and toolchain identity.
- [ ] Record the exact SwiftPM 6.4 build command.
- [ ] Run
swift build --show-bin-pathwith identical configuration, architecture, and destination arguments. - [ ] Verify that the returned directory exists before appending a product name.
- [ ] Confirm that the expected binary is produced by the current job, not copied from a previous workspace.
- [ ] Run tests and preserve the test command’s exit status separately from report collection.
- [ ] Validate coverage, logs, and test-result sources independently.
- [ ] Run package plugins from a clean workspace and inspect declared outputs.
- [ ] Use separate cache namespaces for Swift Build and native builds.
- [ ] Repeat the job with a cold cache.
- [ ] Repeat the job after restarting the remote Mac node.
- [ ] Compare Swift Build and native results on the same commit.
- [ ] Verify signing, packaging, checksum, and upload from the final staging directory.
- [ ] Define the exact condition that permits production cutover or triggers rollback.
- Green: path discovery, tests, cache isolation, restart recovery, and release verification all pass on both tracks.
- Amber: the build and upload pass, but restart, cold-cache, plugin, or native comparison evidence is missing. Keep the change in a trial lane.
- Red: the build exits successfully but the path query disagrees with the collector, or the same commit produces unexplained differences between nodes. Stop the rollout.
Decide whether to stay, roll back, or isolate
The official Swift Evolution status and the Apple Developer Swift 6.4 materials should be checked before you label a toolchain state stable. As of August 27, 2026, SwiftPM 6.4 documentation confirms the default Swift Build transition and the output-location guidance, while Swift 6.4 is not marked there as an independently stable release. Xcode 27 status must be verified against its current release notes rather than inferred from media reports.
Choose one of these paths:
- Continue with Swift Build when discovery uses the supported query, clean and warm caches agree, plugins declare outputs correctly, and release artifacts pass verification.
- Temporarily use native when you need a controlled comparison to isolate a Swift Build behavior difference, but keep the failing Swift Build reproduction attached to the change.
- Isolate the migration when production nodes cannot be restarted safely, cache namespaces cannot be separated, or the release process cannot verify test and package outputs independently.
FAQ
Why can’t my SwiftPM 6.4 job find a binary inside .build?
SwiftPM 6.4 uses Swift Build as the default build system, and its artifact layout does not preserve every old .build directory assumption. A successful compiler command only proves that the build completed. It does not prove that a later upload step queried the correct directory. Replace hard-coded paths with swift build --show-bin-path and use the same configuration, architecture, and destination arguments for both commands.
How should CI discover the output directory with show-bin-path?
Run swift build with --show-bin-path in the same workspace and with the same build parameters used by the real build. Capture the output, remove the trailing newline, verify that the returned directory exists, and then copy or archive the expected file from that location. Do not query a default configuration while building another configuration, because the result can describe a different output tree.
How do Swift Build and the native build system differ for artifact collection?
Swift Build and the native build system can expose different output locations and different assumptions about test or package products. Treat their directory layouts as implementation details rather than a stable CI API. During migration, run both systems against the same commit, record commands and outputs, and compare the actual deliverables. Use native only as a diagnostic fallback unless the release process has completed full dual-track validation.
What should I do when local Swift builds pass but remote Mac CI cannot upload artifacts?
First separate compilation from collection: inspect the build exit status, working directory, toolchain selection, and the path returned on the remote node. Then check cache identity, execution user, workspace cleanup, and package-plugin output declarations. A remote Mac may expose environment drift that a local machine hides. Reproduce with a cold cache and after a node restart before changing permissions or reverting the toolchain.
SwiftPM 6.4 build artifact paths should be treated as queried build outputs, not as a directory tree that your CI is entitled to predict. After replacing hard-coded .build references, the remaining risk usually sits in cache identity, plugin declarations, test-report collection, or node drift.
If your existing Mac is unsuitable for a disruptive upgrade, a remote Mac can provide an isolated place to run cold-cache, restart, and dual-toolchain acceptance jobs. That approach avoids three common weaknesses of a fixed local machine: it may be occupied during long CI checks, its cache state may be difficult to reproduce, and a failed toolchain change can interfere with daily development. For a disposable or temporary validation node, you can compare available remote Mac CI options from MACGPU against buying and maintaining a dedicated Mac mini. Renting is less suitable for a permanent, heavy workload or a workflow that requires direct physical hardware interfaces, but it is often the cleaner choice for migration testing and short-lived CI capacity.