build(vcv): macOS x64+arm64 cross-build here, resource-bounded (osxcross + system clang)
- vcv/build-mac.sh: bounded Docker osxcross build (system clang, NOT LLVM-from- source; cached toolchain image for fast reruns) → ad-hoc-signed mac-x64 + mac-arm64 .vcvplugin. MacOSX12.3 SDK from joseluisq/macosx-sdks. - DISTRIBUTION.md: macOS is now a host bounded cross-build, not CI-only. All four platforms (lin/win/mac-x64/mac-arm64) now published at /next/vcv.
This commit is contained in:
parent
c86c7da5df
commit
6584423ccd
2 changed files with 248 additions and 11 deletions
|
|
@ -48,20 +48,33 @@ CPUS=4 MEM=6g vcv/build-win.sh # tighter caps
|
||||||
Note: the OSC bridge server needs Winsock on Windows; `vcv/Makefile` links
|
Note: the OSC bridge server needs Winsock on Windows; `vcv/Makefile` links
|
||||||
`-lws2_32` when `ARCH_WIN` is set (mingw ignores the MSVC `#pragma comment(lib,…)`).
|
`-lws2_32` when `ARCH_WIN` is set (mingw ignores the MSVC `#pragma comment(lib,…)`).
|
||||||
|
|
||||||
### macOS (x64 + arm64) — Apple SDK required
|
### macOS (x64 + arm64) — locally, resource-bounded (works today)
|
||||||
|
|
||||||
macOS cross-builds need Apple's **licensed macOS SDK**, which we never download or
|
`vcv/build-mac.sh` cross-builds both macOS arches in a **hard-capped Docker container**
|
||||||
redistribute on this host. Two routes:
|
(`--cpus=8 --memory=20g --memory-swap=20g`, no host swap), ad-hoc signs the dylibs with
|
||||||
|
`rcodesign`, and writes `vcv/dist/MEMLNaut-<version>-mac-{x64,arm64}.vcvplugin`.
|
||||||
|
|
||||||
1. **CI** (`.github/workflows/vcv-plugin.yml`) — the
|
```bash
|
||||||
[VCVRack/rack-plugin-toolchain](https://github.com/VCVRack/rack-plugin-toolchain)
|
vcv/build-mac.sh # first run ~18 min (builds the osxcross toolchain image)
|
||||||
builds mac-x64 + mac-arm64 on tagged releases (the toolchain fetches the SDK only
|
vcv/build-mac.sh # reruns ~2.5 min (cached image)
|
||||||
inside its own image build).
|
REBUILD_TOOLCHAIN=1 vcv/build-mac.sh # force-rebuild the toolchain image
|
||||||
2. **A Mac** — build with the native SDK + `RACK_DIR` set to the macOS Rack SDK.
|
```
|
||||||
|
|
||||||
The full rack-plugin-toolchain image build is **not** run on this host: its Dockerfile
|
Key choices that keep it bounded + legal-ish:
|
||||||
`COPY`s the Apple SDK and compiles osxcross, so it both needs the SDK and is multi-GB
|
- **osxcross is built with the container's SYSTEM clang** — we do NOT compile
|
||||||
/ multi-hour. That belongs on CI. (Linux + Windows above need none of it.)
|
Clang/LLVM from source (the official toolchain's `build_clang.sh` is the multi-hour,
|
||||||
|
RAM-hungry trap). osxcross only builds cctools-port + ld64 + wrappers (~16-18 min,
|
||||||
|
peak < 1 GB RAM). The result is cached as a local Docker image
|
||||||
|
`nisps-osxcross:darwin21.4-12.3` (~3 GB, contains the SDK) so reruns are fast.
|
||||||
|
- **Apple macOS SDK**: `MacOSX12.3.sdk.tar.xz` from
|
||||||
|
[joseluisq/macosx-sdks](https://github.com/joseluisq/macosx-sdks). Apple's licence
|
||||||
|
restricts SDK use to Apple hardware — this is a grey area; it never leaves the local
|
||||||
|
toolchain image and is never redistributed.
|
||||||
|
- The dylibs are **ad-hoc signed, not notarised**, so macOS Gatekeeper may quarantine a
|
||||||
|
download. Users clear it once: `xattr -dr com.apple.quarantine ~/Documents/Rack2/plugins-mac-*/MEMLNaut`.
|
||||||
|
|
||||||
|
CI (below) remains an alternative for macOS — and the only option if you don't want the
|
||||||
|
SDK on this host — but the local bounded build is the default now (no dedicated box).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
224
vcv/build-mac.sh
Executable file
224
vcv/build-mac.sh
Executable file
|
|
@ -0,0 +1,224 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# build-mac.sh — cross-build the MEMLNaut VCV plugin for macOS (x64 + arm64),
|
||||||
|
# here, in a RESOURCE-BOUNDED Docker container.
|
||||||
|
#
|
||||||
|
# Why Docker + caps: this host also runs live production services. The container
|
||||||
|
# is hard-capped (CPU + memory + no extra swap) so the build physically cannot
|
||||||
|
# starve or crash the host — the cgroup OOM-kills the container, not the system,
|
||||||
|
# if it ever exceeded the cap. Tune CPUS / MEM below for your machine.
|
||||||
|
#
|
||||||
|
# How (crucially): we do NOT compile Clang/LLVM from source. The official
|
||||||
|
# rack-plugin-toolchain runs build_clang.sh (LLVM from source = multi-hour,
|
||||||
|
# RAM-hungry — FORBIDDEN here). Instead we build osxcross with the container's
|
||||||
|
# SYSTEM clang (apt install clang lld); osxcross then only needs to build
|
||||||
|
# cctools-port + ld64 + wrappers, which is moderate and bounded (~15-20 min).
|
||||||
|
#
|
||||||
|
# That osxcross toolchain is cached as a local Docker image
|
||||||
|
# (nisps-osxcross:<darwin>-<sdkver>) on first build, so subsequent runs skip
|
||||||
|
# straight to compiling the plugin (~2 min). Set REBUILD_TOOLCHAIN=1 to force a
|
||||||
|
# fresh osxcross build.
|
||||||
|
#
|
||||||
|
# Apple's macOS SDK is fetched from joseluisq/macosx-sdks (MacOSX12.3.sdk) only
|
||||||
|
# inside the container, for the duration of the toolchain build. We never
|
||||||
|
# redistribute it; it lives only inside the local cache image.
|
||||||
|
#
|
||||||
|
# Output: vcv/dist/MEMLNaut-<version>-mac-x64.vcvplugin
|
||||||
|
# vcv/dist/MEMLNaut-<version>-mac-arm64.vcvplugin (both ad-hoc signed)
|
||||||
|
# Requires: docker. Run from anywhere; paths are resolved from this script.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CPUS="${CPUS:-8}" # cores the container may use
|
||||||
|
MEM="${MEM:-20g}" # hard memory cap (== memory-swap, so no host swap)
|
||||||
|
RACK_SDK_VERSION="${RACK_SDK_VERSION:-2.6.4}"
|
||||||
|
OSXCROSS_COMMIT="${OSXCROSS_COMMIT:-4372d5560307c649af5dbbfa20b39199c9ef48be}" # same pin as rack-plugin-toolchain
|
||||||
|
MACOS_SDK_VERSION="${MACOS_SDK_VERSION:-12.3}"
|
||||||
|
DARWIN_TARGET="darwin21.4" # macOS 12.x = Darwin 21
|
||||||
|
TOOLCHAIN_IMAGE="${TOOLCHAIN_IMAGE:-nisps-osxcross:${DARWIN_TARGET}-${MACOS_SDK_VERSION}}"
|
||||||
|
REBUILD_TOOLCHAIN="${REBUILD_TOOLCHAIN:-0}"
|
||||||
|
|
||||||
|
CAPS=(--cpus="$CPUS" --memory="$MEM" --memory-swap="$MEM") # the mandatory hard caps
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # vcv/
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 1: osxcross toolchain image (built once, cached). Uses the container's
|
||||||
|
# SYSTEM clang — never build_clang.sh / LLVM-from-source.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
build_toolchain_image () {
|
||||||
|
echo "==> building osxcross toolchain image $TOOLCHAIN_IMAGE (cpus=$CPUS, mem=$MEM, no extra swap)"
|
||||||
|
echo " cctools-port + ld64 + wrappers with SYSTEM clang. May take 15-20 min."
|
||||||
|
local TC="$(mktemp /tmp/osxcross-stage.XXXXXX.sh)"
|
||||||
|
cat > "$TC" <<EOF
|
||||||
|
set -euo pipefail
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
echo "==> [tc] installing build deps (system clang + lld; NO LLVM-from-source)"
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq \
|
||||||
|
clang lld llvm-dev libxml2-dev uuid-dev libssl-dev zlib1g-dev libbz2-dev \
|
||||||
|
cmake make python3 git patch xz-utils bzip2 curl unzip zstd jq tar wget rsync \
|
||||||
|
>/dev/null
|
||||||
|
cd /tmp
|
||||||
|
echo "==> [tc] cloning osxcross @ ${OSXCROSS_COMMIT}"
|
||||||
|
git clone -q https://github.com/tpoechtrager/osxcross.git
|
||||||
|
cd osxcross
|
||||||
|
git checkout -q ${OSXCROSS_COMMIT}
|
||||||
|
echo "==> [tc] fetching macOS ${MACOS_SDK_VERSION} SDK"
|
||||||
|
wget -q -O tarballs/MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz \
|
||||||
|
"https://github.com/joseluisq/macosx-sdks/releases/download/${MACOS_SDK_VERSION}/MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz"
|
||||||
|
echo "==> [tc] building osxcross (UNATTENDED, system clang, no build_clang.sh)"
|
||||||
|
UNATTENDED=1 TARGET_DIR=/opt/osxcross JOBS=${CPUS} ./build.sh
|
||||||
|
echo "==> [tc] building compiler-rt (best-effort)"
|
||||||
|
UNATTENDED=1 TARGET_DIR=/opt/osxcross JOBS=${CPUS} ENABLE_COMPILER_RT_INSTALL=1 \
|
||||||
|
./build_compiler_rt.sh || echo " (compiler-rt skipped; continuing)"
|
||||||
|
echo "==> [tc] osxcross wrappers:"
|
||||||
|
ls /opt/osxcross/bin/ | grep -E "apple-${DARWIN_TARGET}-(clang|strip)" || true
|
||||||
|
echo "OSXCROSS_TOOLCHAIN_READY"
|
||||||
|
EOF
|
||||||
|
trap 'rm -f "$TC"' RETURN
|
||||||
|
docker rm -f nisps-osxcross-stage >/dev/null 2>&1 || true
|
||||||
|
docker run --name nisps-osxcross-stage "${CAPS[@]}" \
|
||||||
|
-v "$TC":/stage.sh:ro ubuntu:24.04 bash /stage.sh
|
||||||
|
echo "==> committing toolchain image $TOOLCHAIN_IMAGE"
|
||||||
|
docker commit nisps-osxcross-stage "$TOOLCHAIN_IMAGE" >/dev/null
|
||||||
|
docker rm -f nisps-osxcross-stage >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$REBUILD_TOOLCHAIN" = "1" ] || ! docker image inspect "$TOOLCHAIN_IMAGE" >/dev/null 2>&1; then
|
||||||
|
build_toolchain_image
|
||||||
|
else
|
||||||
|
echo "==> reusing cached osxcross toolchain image $TOOLCHAIN_IMAGE (set REBUILD_TOOLCHAIN=1 to rebuild)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 2: build (and ad-hoc sign) the plugin for both mac arches, against the
|
||||||
|
# cached toolchain image. Staged in a clean copy of vcv/ so artefacts never
|
||||||
|
# pollute the repo tree.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
WORK="$(mktemp -d /tmp/vcv-mac-build.XXXXXX)"
|
||||||
|
trap 'rm -rf "$WORK"' EXIT
|
||||||
|
|
||||||
|
echo "==> staging a clean copy of the plugin source"
|
||||||
|
cp -r "$SCRIPT_DIR/." "$WORK/"
|
||||||
|
rm -rf "$WORK/build" "$WORK/dist" "$WORK/out" "$WORK/plugin.so" "$WORK"/*.vcvplugin 2>/dev/null || true
|
||||||
|
|
||||||
|
cat > "$WORK/_in-container.sh" <<EOF
|
||||||
|
set -euo pipefail
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
export PATH=/opt/osxcross/bin:\$PATH
|
||||||
|
|
||||||
|
# ---- apple-codesign (rcodesign) for ad-hoc signing -------------------------
|
||||||
|
RCODESIGN=""
|
||||||
|
echo "==> [container] fetching apple-codesign (rcodesign) for ad-hoc signing"
|
||||||
|
RC_VER="0.27.0"
|
||||||
|
RC_URL="https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F\${RC_VER}/apple-codesign-\${RC_VER}-x86_64-unknown-linux-musl.tar.gz"
|
||||||
|
if wget -q -O /tmp/rcodesign.tar.gz "\$RC_URL"; then
|
||||||
|
mkdir -p /tmp/rc && tar -xzf /tmp/rcodesign.tar.gz -C /tmp/rc --strip-components=1 2>/dev/null || \
|
||||||
|
tar -xzf /tmp/rcodesign.tar.gz -C /tmp/rc 2>/dev/null || true
|
||||||
|
RCODESIGN="\$(find /tmp/rc -name rcodesign -type f | head -1)"
|
||||||
|
if [ -n "\$RCODESIGN" ]; then chmod +x "\$RCODESIGN"; echo " rcodesign: \$RCODESIGN"; fi
|
||||||
|
fi
|
||||||
|
[ -z "\$RCODESIGN" ] && echo " (rcodesign unavailable; will skip ad-hoc signing)"
|
||||||
|
|
||||||
|
build_arch () {
|
||||||
|
local PLAT="\$1" # mac-x64 | mac-arm64
|
||||||
|
local SDK_ARCH="\$2" # x64 | arm64
|
||||||
|
local TRIPLE="\$3" # x86_64-apple-${DARWIN_TARGET} | arm64-apple-${DARWIN_TARGET}
|
||||||
|
local ARCH_FLAG="\$4" # x86_64 | arm64 (passed to clang -arch)
|
||||||
|
|
||||||
|
echo "==> [container] === building \$PLAT ==="
|
||||||
|
cd /tmp
|
||||||
|
rm -rf "Rack-SDK"
|
||||||
|
wget -q "https://vcvrack.com/downloads/Rack-SDK-${RACK_SDK_VERSION}-mac-\${SDK_ARCH}.zip"
|
||||||
|
unzip -q "Rack-SDK-${RACK_SDK_VERSION}-mac-\${SDK_ARCH}.zip"
|
||||||
|
rm -f "Rack-SDK-${RACK_SDK_VERSION}-mac-\${SDK_ARCH}.zip"
|
||||||
|
|
||||||
|
cd /src
|
||||||
|
# Wipe all build products between arches explicitly. 'make clean' is unreliable
|
||||||
|
# here because the Makefile's 'include \$(RACK_DIR)/arch.mk' needs RACK_DIR set
|
||||||
|
# just to parse — without it clean silently no-ops, leaving the previous arch's
|
||||||
|
# plugin.dylib in place so 'make dist' skips recompilation and ships the WRONG
|
||||||
|
# architecture (x86_64 in the arm64 package). Remove the products outright.
|
||||||
|
rm -rf build dist plugin.dylib plugin.so 2>/dev/null || true
|
||||||
|
find . -maxdepth 3 \( -name '*.o' -o -name '*.d' \) -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
# The mac Rack SDK's plugin.mk dist target calls Apple-only tools by bare name:
|
||||||
|
# \$(STRIP) \$(INSTALL_NAME_TOOL) \$(OTOOL) and (unconditionally for mac) \$(CODESIGN).
|
||||||
|
# osxcross provides the first three as <triple>-prefixed binaries; point the make
|
||||||
|
# vars at them. CODESIGN we override to a no-op (true) and instead ad-hoc sign with
|
||||||
|
# rcodesign below — the SDK's default 'codesign -f -s -' is a native-macOS binary
|
||||||
|
# that does not exist on Linux (was Error 127). rsync is needed for the res/ copy.
|
||||||
|
#
|
||||||
|
# arch.mk derives the target arch from \`\$(CC) -dumpmachine\`. The osxcross clang
|
||||||
|
# wrappers are all symlinks to one binary, so -dumpmachine reports the host default
|
||||||
|
# (x86_64) regardless of the prefix — which made arch.mk pick the x64 path
|
||||||
|
# (-march=nehalem) even for the arm64 wrapper, silently producing an x86_64 dylib in
|
||||||
|
# the arm64 package. Pass CROSS_COMPILE=<triple> so arch.mk classifies correctly
|
||||||
|
# (-> ARCH_ARM64, -march=armv8-a) and append an explicit -arch to CC/CXX so the
|
||||||
|
# wrapper emits the right Mach-O architecture.
|
||||||
|
export MACOSX_DEPLOYMENT_TARGET=10.15
|
||||||
|
RACK_DIR=/tmp/Rack-SDK \
|
||||||
|
CROSS_COMPILE="\${TRIPLE}" \
|
||||||
|
CC="\${TRIPLE}-clang -arch \${ARCH_FLAG}" \
|
||||||
|
CXX="\${TRIPLE}-clang++ -arch \${ARCH_FLAG}" \
|
||||||
|
STRIP="\${TRIPLE}-strip" \
|
||||||
|
INSTALL_NAME_TOOL="\${TRIPLE}-install_name_tool" \
|
||||||
|
OTOOL="\${TRIPLE}-otool" \
|
||||||
|
CODESIGN=true \
|
||||||
|
make -j${CPUS} dist
|
||||||
|
|
||||||
|
# Ad-hoc sign the dylib inside the produced .vcvplugin, if rcodesign is available.
|
||||||
|
local PKG="\$(ls dist/MEMLNaut-*-\${PLAT}.vcvplugin)"
|
||||||
|
local BASENAME="\$(basename "\$PKG")"
|
||||||
|
if [ -n "\$RCODESIGN" ] && [ -n "\$PKG" ]; then
|
||||||
|
echo "==> [container] ad-hoc signing dylib in \$PKG"
|
||||||
|
local TMP=/tmp/sign-\${PLAT}
|
||||||
|
rm -rf "\$TMP" && mkdir -p "\$TMP"
|
||||||
|
tar --use-compress-program=unzstd -xf "\$PKG" -C "\$TMP"
|
||||||
|
local DYLIB="\$(find "\$TMP" -name plugin.dylib | head -1)"
|
||||||
|
if [ -n "\$DYLIB" ]; then
|
||||||
|
"\$RCODESIGN" sign "\$DYLIB" && echo " signed: \$DYLIB"
|
||||||
|
# \$PKG is relative to /src; resolve to absolute before the cd into \$TMP.
|
||||||
|
( cd "\$TMP" && tar --use-compress-program=zstd -cf "/src/\$PKG" MEMLNaut )
|
||||||
|
echo "SIGNED_\${PLAT}=yes" >> /src/out/_sign_report.txt
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "SIGNED_\${PLAT}=no" >> /src/out/_sign_report.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stash the (signed) artefact in /src/out/ BEFORE the next arch's 'make clean'
|
||||||
|
# wipes dist/ — clean runs 'rm -rf dist', which would otherwise delete this file.
|
||||||
|
cp "\$PKG" /src/out/
|
||||||
|
echo "==> [container] verifying \$BASENAME architecture (cputype: 16777223=x86_64, 16777228=arm64):"
|
||||||
|
"\${TRIPLE}-otool" -h "\$TMP/MEMLNaut/plugin.dylib" 2>/dev/null | sed -n '3,4p' || true
|
||||||
|
ls -la "/src/out/\$BASENAME"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p /src/out
|
||||||
|
: > /src/out/_sign_report.txt
|
||||||
|
build_arch mac-x64 x64 "x86_64-apple-${DARWIN_TARGET}" x86_64
|
||||||
|
build_arch mac-arm64 arm64 "arm64-apple-${DARWIN_TARGET}" arm64
|
||||||
|
|
||||||
|
echo "==> [container] final artefacts:"
|
||||||
|
ls -la /src/out/*.vcvplugin
|
||||||
|
cat /src/out/_sign_report.txt
|
||||||
|
|
||||||
|
# Hand the staged tree back to the invoking host user so the cleanup trap and
|
||||||
|
# dist copy work without root-owned leftovers.
|
||||||
|
chown -R \${HOST_UID}:\${HOST_GID} /src 2>/dev/null || true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "==> building macOS plugins in a bounded container (cpus=$CPUS, mem=$MEM, no extra swap)"
|
||||||
|
free -h
|
||||||
|
docker run --rm "${CAPS[@]}" \
|
||||||
|
-e RACK_SDK_VERSION="$RACK_SDK_VERSION" \
|
||||||
|
-e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \
|
||||||
|
-v "$WORK:/src" "$TOOLCHAIN_IMAGE" bash /src/_in-container.sh
|
||||||
|
free -h
|
||||||
|
|
||||||
|
mkdir -p "$SCRIPT_DIR/dist"
|
||||||
|
cp "$WORK"/out/*-mac-x64.vcvplugin "$SCRIPT_DIR/dist/"
|
||||||
|
cp "$WORK"/out/*-mac-arm64.vcvplugin "$SCRIPT_DIR/dist/"
|
||||||
|
echo "==> done:"
|
||||||
|
ls -la "$SCRIPT_DIR"/dist/*-mac-*.vcvplugin
|
||||||
Loading…
Reference in a new issue