From 37afbdeffc8f1a9fb8c100a921b104622059c09e Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Thu, 7 May 2026 12:08:33 +0200 Subject: [PATCH] feat(build): contributor-friendly local-build.sh + BUILDING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-Authored-By: Claude Opus 4.7 (1M context) --- BUILDING.md | 24 +++++++++ CONTRIBUTING.md | 2 + README.md | 2 + scripts/local-build.sh | 109 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+) create mode 100644 BUILDING.md create mode 100755 scripts/local-build.sh diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 0000000..6f5d067 --- /dev/null +++ b/BUILDING.md @@ -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. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d056d0..d51b4ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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/`: diff --git a/README.md b/README.md index bbb0df8..5384030 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/scripts/local-build.sh b/scripts/local-build.sh new file mode 100755 index 0000000..819cb9a --- /dev/null +++ b/scripts/local-build.sh @@ -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