feat(build): contributor-friendly local-build.sh + BUILDING.md

Adds `scripts/local-build.sh` for unsigned command-line Debug builds
so contributors without an Apple Developer account can clone, build,
and run without provisioning gymnastics. The script:

- Detects arm64 / x86_64
- Verifies xcode-select, xcrun, xcodebuild are present
- Probes the Metal toolchain and offers an interactive install (gated
  on `[[ -t 0 && -z "${CI:-}" ]]` — CI never gets prompted)
- Resolves Swift packages, builds Debug with signing disabled
- Optionally `ditto`s the result to /Applications/scarf.app on
  explicit y/N

`BUILDING.md` documents prerequisites alongside the script. Existing
canonical Release universal CLI in README stays — `local-build.sh`
is an alternative for contributors, not a replacement for the
shipping build.

Cherry-picked from #76 with thanks to @unixwzrd. BUILDING.md's
prerequisites are corrected to match the actual deployment target
(macOS 26.2, Xcode 26.2+).

Co-Authored-By: M S <unixwzrd.register@mac.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-05-07 12:08:33 +02:00
parent bfd9bab9a0
commit 37afbdeffc
4 changed files with 137 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Building Scarf
Scarf is a native macOS app built with Xcode. For contributor builds, use the local script:
```bash
./scripts/local-build.sh
```
Requirements:
- macOS 26.2 or newer (the app's deployment target)
- Xcode 26.2 or newer, selected by `xcode-select`
- Metal toolchain installed
- Hermes installed at `~/.hermes/` (see the project README for setup)
If the Metal toolchain is missing, the script will offer to install it in interactive shells. You can also install it manually:
```bash
xcodebuild -downloadComponent MetalToolchain
```
`scripts/local-build.sh` resolves Swift package dependencies, detects `arm64` vs `x86_64`, and builds the Debug app unsigned. Signing is intentionally disabled for local Debug builds so contributors do not need the maintainer's Apple Developer account.
Release signing is separate from contributor builds. Maintainers should continue using the existing release process for signed distributable builds.
+2
View File
@@ -8,6 +8,8 @@ Thanks for your interest in contributing to Scarf.
2. Open `scarf/scarf.xcodeproj` in Xcode 26.3+
3. Build and run (requires macOS 26.2+ and Hermes installed at `~/.hermes/`)
For an unsigned command-line Debug build without an Apple Developer account, run [`./scripts/local-build.sh`](scripts/local-build.sh). See [BUILDING.md](BUILDING.md) for prerequisites.
## Architecture
Scarf uses the MVVM-Feature pattern. Each feature is a self-contained module under `Features/`:
+2
View File
@@ -238,6 +238,8 @@ Or from the command line:
xcodebuild -project scarf/scarf.xcodeproj -scheme scarf -configuration Release -arch arm64 -arch x86_64 ONLY_ACTIVE_ARCH=NO build
```
For an unsigned local Debug build without an Apple Developer account (handy for contributors), use [`./scripts/local-build.sh`](scripts/local-build.sh) — see [BUILDING.md](BUILDING.md) for prerequisites.
## Architecture
Scarf follows the **MVVM-Feature** pattern with zero external dependencies beyond SwiftTerm:
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
PROJECT="$REPO_ROOT/scarf/scarf.xcodeproj"
SCHEME="${SCHEME:-scarf}"
CONFIG="${CONFIG:-Debug}"
DERIVED_DATA="$REPO_ROOT/build/DerivedData"
PACKAGE_RESOLVED_REL="scarf/scarf.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved"
PACKAGE_RESOLVED="$REPO_ROOT/$PACKAGE_RESOLVED_REL"
log() { printf '==> %s\n' "$*"; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
cleanup_generated_files() {
if [[ "${REMOVE_GENERATED_PACKAGE_RESOLVED:-0}" == "1" && -f "$PACKAGE_RESOLVED" ]]; then
rm -f "$PACKAGE_RESOLVED"
rmdir "$REPO_ROOT/scarf/scarf.xcodeproj/project.xcworkspace/xcshareddata/swiftpm" 2>/dev/null || true
rmdir "$REPO_ROOT/scarf/scarf.xcodeproj/project.xcworkspace/xcshareddata" 2>/dev/null || true
fi
}
trap cleanup_generated_files EXIT
log "Detecting architecture"
case "$(uname -m)" in
arm64) BUILD_ARCH="arm64" ;;
x86_64) BUILD_ARCH="x86_64" ;;
*) die "unsupported architecture: $(uname -m)" ;;
esac
log "Using architecture: $BUILD_ARCH"
log "Checking Xcode command line tools"
command -v xcode-select >/dev/null 2>&1 || die "xcode-select not found; install Xcode or Xcode command line tools"
if ! xcode-select -p >/dev/null 2>&1; then
die "Xcode command line tools not selected. Run: xcode-select --install"
fi
command -v xcrun >/dev/null 2>&1 || die "xcrun not found; install Xcode or Xcode command line tools"
command -v xcodebuild >/dev/null 2>&1 || die "xcodebuild not found; install Xcode"
log "Checking Metal toolchain"
if ! xcrun metal --version >/dev/null 2>&1 && ! xcrun -f metal >/dev/null 2>&1; then
if [[ -t 0 && -z "${CI:-}" ]]; then
printf 'Metal toolchain is missing. Install it now with xcodebuild -downloadComponent MetalToolchain? [y/N] '
read -r reply
if [[ "$reply" =~ ^[Yy]$ ]]; then
xcodebuild -downloadComponent MetalToolchain
if xcrun metal --version >/dev/null 2>&1 || xcrun -f metal >/dev/null 2>&1; then
log "Metal toolchain installed"
else
die "Metal toolchain still not available after install"
fi
else
cat >&2 <<'EOF'
error: Metal toolchain missing.
Install it when you are ready with:
xcodebuild -downloadComponent MetalToolchain
EOF
exit 1
fi
else
cat >&2 <<'EOF'
error: Metal toolchain missing.
Install it with:
xcodebuild -downloadComponent MetalToolchain
EOF
exit 1
fi
fi
log "Resolving Swift packages"
if [[ ! -e "$PACKAGE_RESOLVED" ]] && ! git -C "$REPO_ROOT" ls-files --error-unmatch "$PACKAGE_RESOLVED_REL" >/dev/null 2>&1; then
REMOVE_GENERATED_PACKAGE_RESOLVED=1
fi
xcodebuild \
-resolvePackageDependencies \
-project "$PROJECT"
log "Building unsigned $CONFIG app"
xcodebuild \
-project "$PROJECT" \
-scheme "$SCHEME" \
-configuration "$CONFIG" \
-derivedDataPath "$DERIVED_DATA" \
-arch "$BUILD_ARCH" \
ONLY_ACTIVE_ARCH=YES \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY="" \
DEVELOPMENT_TEAM="" \
build
APP_PATH="$DERIVED_DATA/Build/Products/$CONFIG/scarf.app"
[[ -d "$APP_PATH" ]] || die "build completed, but app bundle was not found at $APP_PATH"
printf '\nBuild complete:\n %s\n\n' "$APP_PATH"
if [[ -t 0 && -z "${CI:-}" ]]; then
read -r -p "Copy to /Applications? [y/N] " reply
if [[ "$reply" =~ ^[Yy]$ ]]; then
rm -rf "/Applications/scarf.app"
ditto "$APP_PATH" "/Applications/scarf.app"
echo "Installed to /Applications/scarf.app"
fi
fi