Files
nucleic/scripts/lib/build-scratch.sh
T

91 lines
4.5 KiB
Bash

#!/usr/bin/env bash
#
# Single source of truth for the SwiftPM scratch (build) directory, keyed by where the
# build is running. Prints the flags to append to `swift build/run/test` — empty on the
# host, a `--scratch-path <dir>` redirect on the macOS VM.
#
# Why this exists
# ---------------
# SwiftPM and the SwiftBuild backend bake the repository's ABSOLUTE path into the build
# tree — build.db, the *.yaml manifests, manifest.pif, everything under
# .build/out/Intermediates.noindex/XCBuildData, and the per-file `.d` dependency lists all
# contain fully-qualified source paths. That is fine until the SAME checkout is reachable
# at more than one absolute path, which is exactly our situation:
#
# host / sandbox container : /Users/<user>/.nucleic/control/.../trunk
# per-session macOS VM : /Volumes/My Shared Files/workspace (virtio-fs share of the trunk)
#
# Both mount points see one physical .build/ (it lives inside the shared trunk). The moment
# a build runs from the second mount, the embedded paths stop matching the current location:
# incremental builds thrash or fail outright, and you end up with a .build/ that contains a
# mix of /Users/... and "/Volumes/My Shared Files/..." strings (grep it — that mixture is
# the smoking gun of a poisoned cache). On the VM there is a second cost: driving every
# object file through the virtio-fs share is far slower than the guest's own disk.
#
# The fix
# -------
# Give each mount point its own scratch tree:
# * Host / container -> the default in-repo .build/ (this script prints nothing, so the
# existing `swift build` behaviour is untouched — zero risk).
# * macOS VM (/Volumes) -> a scratch tree on the GUEST's local disk under $HOME, keyed by a
# hash of the mount path so distinct shares/worktrees never collide.
#
# This is the general recipe for "build artifact embeds a host path that differs across
# VM/sandbox mounts": don't try to rewrite the baked-in paths after the fact — instead put
# the artifact where its embedded path will be stable, one location per mount namespace.
#
# Escape hatch: export NUCLEIC_SCRATCH_PATH=/some/dir to force a specific scratch directory
# (useful for a throwaway build, or to pin the VM tree somewhere other than $HOME).
#
# Usage
# SCRATCH_ARGS="$(bash scripts/lib/build-scratch.sh)" # "" or "--scratch-path /…/<hash>"
# swift build $SCRATCH_ARGS ... # leave $SCRATCH_ARGS UNQUOTED
# swift build $SCRATCH_ARGS --show-bin-path # …and pass it to --show-bin-path too
#
# Takes the repo/working root as $1 (defaults to $PWD). Emits nothing but the flags on
# stdout, so it is safe to capture in `$(...)`.
set -euo pipefail
root="${1:-$PWD}"
# Explicit override always wins.
if [ -n "${NUCLEIC_SCRATCH_PATH:-}" ]; then
printf -- '--scratch-path %s' "$NUCLEIC_SCRATCH_PATH"
exit 0
fi
# Resolve symlinks before classifying. Inside the macOS guest the repo's ORIGINAL host
# path (/Users/…) is a symlink to the automounted virtiofs share (/Volumes/My Shared
# Files/…) — the engine re-creates it (MacVMEngine+HostPaths.swift, see MACOS_VM.md §6.1)
# so SwiftPM's baked-in /Users/… paths resolve in the guest. mac_vm_exec runs from that
# /Users path BY DEFAULT, so `$root` is that symlink and the bare "/Volumes/*" match below
# would miss it — leaving .build/ on the slow virtio-fs share, where SwiftPM's XCFramework
# extraction (Sparkle's binary target) fails outright because virtio-fs supports neither
# clonefile() nor the framework symlinks the unpack needs. Look THROUGH the link so a build
# launched from either path is recognized as being on the share.
resolved="$root"
if cd "$root" 2>/dev/null; then
resolved="$(pwd -P)"
fi
case "$resolved" in
/Volumes/*)
# On the VM share — reached directly via the /Volumes automount or through the /Users
# symlink. Redirect onto the guest's own fast, private disk. Hash the ORIGINAL,
# unresolved `$root` (not `$resolved`): a build from the /Users path bakes /Users/… into
# its tree while one from the /Volumes path bakes /Volumes/…, so they must NOT share a
# scratch tree or they poison each other's absolute paths — the very thing this guards.
if command -v shasum >/dev/null 2>&1; then
tag="$(printf '%s' "$root" | shasum | cut -c1-12)"
else
tag="$(printf '%s' "$root" | sha1sum | cut -c1-12)"
fi
printf -- '--scratch-path %s/.nucleic-scratch/%s' "${HOME:-/tmp}" "$tag"
;;
*)
# Host / sandbox container: keep the default in-repo .build/. Print nothing.
:
;;
esac