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

94 lines
4.0 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 : same /Users/... path (direct virtiofs mount)
#
# Both environments see one physical .build/ inside the shared trunk. The direct mount preserves the
# same absolute source path, but driving every object file
# through virtiofs is far slower than the guest's own disk, and SwiftPM binary-target extraction
# needs filesystem operations virtiofs does not provide.
#
# 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 (virtiofs) -> 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.
#
# Keep generated artifacts on the filesystem whose semantics and performance they require, while
# source remains shared at one stable absolute path.
#
# 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 for legacy guests, then classify by the mounted filesystem as well as path.
# Directly mounted macOS shares keep their /Users/... spelling under pwd -P, so pathname-only
# detection would incorrectly leave SwiftPM artifacts on virtiofs.
resolved="$root"
if cd "$root" 2>/dev/null; then
resolved="$(pwd -P)"
fi
on_vm_share=false
case "$resolved" in
/Volumes/*) on_vm_share=true ;;
esac
# A custom-tagged macOS share is mounted directly at the original workdir. Find any enclosing
# virtiofs mount point from mount(8)'s "<source> on <path> (virtiofs, ...)" output. Process
# substitution keeps the flag in this shell rather than a pipeline subshell.
while IFS= read -r line; do
case "$line" in
*" (virtiofs"*)
mountpoint="${line#* on }"
mountpoint="${mountpoint% (*}"
case "$resolved/" in
"$mountpoint/"*) on_vm_share=true ;;
esac
;;
esac
done < <(/sbin/mount 2>/dev/null || mount 2>/dev/null || true)
if [ "$on_vm_share" = true ]; then
# Keep build products on the guest's private disk: virtiofs is slower and cannot support all
# clonefile/framework-symlink operations SwiftPM's binary-target extraction requires. Hash the
# spelling used by the build so independently mounted worktrees never share a scratch tree.
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"
fi