From de058c4b5d7b6487d7dee064ac0cb515ea5c6e2f Mon Sep 17 00:00:00 2001 From: actuallyitsnathaniel Date: Thu, 29 Oct 2026 10:47:47 -0700 Subject: [PATCH] Build macOS with the Command Line Tools instead of full Xcode RT64 now embeds its MSL shader source on macOS and plume compiles it at runtime, so the build no longer needs Xcode's offline Metal toolchain. iOS keeps precompiled metallibs. N64Recomp emits a null statement before instruction hooks so Apple clang 16 (the newest for macOS 14) accepts a declaring hook after a branch label under C17. Setup installs only missing Homebrew tools, avoiding source builds of tools already present. Co-Authored-By: Claude Opus 5.5 --- docs/BUILDING.md | 5 ++-- patches/manifest.json | 12 ++++++++ ...l-statement-before-instruction-hooks.patch | 16 +++++++++++ .../0003-compile-msl-source-shaders.patch | 23 +++++++++++++++ .../rt64/0021-macos-runtime-msl-shaders.patch | 28 +++++++++++++++++++ runtime-recomp/tests/build_tooling_tests.py | 2 +- scripts/lib/apple-build.sh | 2 +- scripts/macos-host-tools.sh | 25 +++++++++++------ 8 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 patches/n64recomp/0003-null-statement-before-instruction-hooks.patch create mode 100644 patches/plume/0003-compile-msl-source-shaders.patch create mode 100644 patches/rt64/0021-macos-runtime-msl-shaders.patch diff --git a/docs/BUILDING.md b/docs/BUILDING.md index cece7684..49683e99 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -168,7 +168,8 @@ that basic build as reproduction of the full experimental Public Build. Run `./Setup-macOS.sh` to install and check prerequisites, or `./Setup-macOS.sh --skip-deps` to check without installing. Setup and Build's -preparation share the same checks: full Xcode with Metal, GNU Make 4+ (`gmake`), +preparation share the same checks: the Command Line Tools (`xcode-select --install`; +full Xcode is not needed, RT64's Metal shaders compile at runtime), GNU Make 4+ (`gmake`), CMake, Ninja, Python, SDL2, PCRE2, and a `sha1sum` supporting `-c --quiet` (Homebrew `coreutils`). The verifier is checked before ROM extraction. @@ -188,7 +189,7 @@ Set `DKR_MAC_BUNDLE_VERSION=N[.N[.N]]` for an explicit release/CI build number Packaging checks the version metadata copied beside the executable after linking, so a new package label cannot silently rename a build of a different version. -With Xcode setup and Homebrew installed: +With the Command Line Tools and Homebrew installed: ```bash ./Build-macOS.sh --rom-v77 "/path/to/US 1.0.z64" --rom-v80 "/path/to/US Rev A.z64" diff --git a/patches/manifest.json b/patches/manifest.json index 5cfd7e29..537e46d8 100644 --- a/patches/manifest.json +++ b/patches/manifest.json @@ -132,6 +132,10 @@ { "path": "patches/n64recomp/0002-name-recomp-context-struct.patch", "sha256": "6cf31eebf6a5f0a9a31c2912f562f8ef977c0a43dfffd76531f3ceb94ae2bee7" + }, + { + "path": "patches/n64recomp/0003-null-statement-before-instruction-hooks.patch", + "sha256": "f864ffd285ff4b09f2a3f4e453e5f0e5b2c39e345bc05dd88f47a46b01d3dac7" } ] }, @@ -226,6 +230,10 @@ { "path": "patches/rt64/0020-ios-imgui-pixel-size.patch", "sha256": "d346ecb854a422b3c9b0349a72b8217a08e4b0b5dc86e22f5b1ab995d25a7ca5" + }, + { + "path": "patches/rt64/0021-macos-runtime-msl-shaders.patch", + "sha256": "441fe5f1dc8edf646d8f4ea3a76d25a65e86fc836aa8ae9cea637c9bde22128b" } ] }, @@ -309,6 +317,10 @@ { "path": "patches/plume/0002-ios-metal-device-os27-upstream.patch", "sha256": "a1c559877eaa59a5cf8878c4e0879f15aa5ffbd0455b8e5a5d593289348709ce" + }, + { + "path": "patches/plume/0003-compile-msl-source-shaders.patch", + "sha256": "80947b240d7a3bd5e3f9434df7501fc54d130d4e06942e36705f2238d8fb3565" } ] }, diff --git a/patches/n64recomp/0003-null-statement-before-instruction-hooks.patch b/patches/n64recomp/0003-null-statement-before-instruction-hooks.patch new file mode 100644 index 00000000..23bf1459 --- /dev/null +++ b/patches/n64recomp/0003-null-statement-before-instruction-hooks.patch @@ -0,0 +1,16 @@ +diff --git a/src/recompilation.cpp b/src/recompilation.cpp +index c228433..afdfe63 100644 +--- a/src/recompilation.cpp ++++ b/src/recompilation.cpp +@@ -128,7 +128,10 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con + + auto hook_find = func.function_hooks.find(instr_index); + if (hook_find != func.function_hooks.end()) { +- fmt::print(output_file, " {}\n", hook_find->second); ++ // The leading null statement lets a hook that declares something follow a ++ // branch label under C17 (Clang before 18 rejects it). It is not braced: ++ // hooks declare locals that later hooks in the function read. ++ fmt::print(output_file, " ; {}\n", hook_find->second); + if (indent) { + print_indent(); + } diff --git a/patches/plume/0003-compile-msl-source-shaders.patch b/patches/plume/0003-compile-msl-source-shaders.patch new file mode 100644 index 00000000..131ff0c8 --- /dev/null +++ b/patches/plume/0003-compile-msl-source-shaders.patch @@ -0,0 +1,23 @@ +diff --git a/plume_metal.cpp b/plume_metal.cpp +index b670ed3..f0ca614 100644 +--- a/plume_metal.cpp ++++ b/plume_metal.cpp +@@ -1342,8 +1342,16 @@ namespace plume { + functionName->retain(); + + NS::Error *error = nullptr; +- const dispatch_data_t dispatchData = dispatch_data_create(data, size, dispatch_get_main_queue(), ^{}); +- library = device->mtl->newLibrary(dispatchData, &error); ++ // Precompiled libraries start with the "MTLB" magic. Anything else is MSL source, ++ // compiled by the OS so macOS builds don't need Xcode's offline Metal compiler. ++ if ((size >= 4) && (memcmp(data, "MTLB", 4) == 0)) { ++ const dispatch_data_t dispatchData = dispatch_data_create(data, size, dispatch_get_main_queue(), ^{}); ++ library = device->mtl->newLibrary(dispatchData, &error); ++ } ++ else { ++ const std::string source(static_cast(data), size); ++ library = device->mtl->newLibrary(NS::String::string(source.c_str(), NS::UTF8StringEncoding), nullptr, &error); ++ } + + if (error != nullptr) { + fprintf(stderr, "MTLDevice newLibraryWithSource: failed with error %s.\n", error->localizedDescription()->utf8String()); diff --git a/patches/rt64/0021-macos-runtime-msl-shaders.patch b/patches/rt64/0021-macos-runtime-msl-shaders.patch new file mode 100644 index 00000000..d00be920 --- /dev/null +++ b/patches/rt64/0021-macos-runtime-msl-shaders.patch @@ -0,0 +1,28 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 17563c1..d86dfec 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -163,17 +163,15 @@ function(build_shader_msl_impl TARGETOBJ FILENAME TARGET_NAME OUTNAME) + add_custom_command(OUTPUT ${OUTNAME}.metallib + COMMAND xcrun -sdk ${_dkr_ios_metal_sdk} metallib ${OUTNAME}.ir -o ${OUTNAME}.metallib + DEPENDS ${OUTNAME}.ir) ++ set(_dkr_msl_blob ${OUTNAME}.metallib) + else() +- add_custom_command(OUTPUT ${OUTNAME}.ir +- COMMAND xcrun -sdk macosx metal -o ${OUTNAME}.ir -c ${OUTNAME}.metal $<$:-frecord-sources> +- DEPENDS ${OUTNAME}.metal) +- add_custom_command(OUTPUT ${OUTNAME}.metallib +- COMMAND xcrun -sdk macosx metallib ${OUTNAME}.ir -o ${OUTNAME}.metallib +- DEPENDS ${OUTNAME}.ir) ++ # macOS embeds the MSL source; plume compiles it at runtime, so the ++ # build needs no Xcode Metal toolchain. ++ set(_dkr_msl_blob ${OUTNAME}.metal) + endif() + add_custom_command(OUTPUT ${OUTNAME}.metal.c +- COMMAND file_to_c ${OUTNAME}.metallib ${TARGET_NAME}BlobMSL ${OUTNAME}.metal.c ${OUTNAME}.metal.h +- DEPENDS ${OUTNAME}.metallib file_to_c ++ COMMAND file_to_c ${_dkr_msl_blob} ${TARGET_NAME}BlobMSL ${OUTNAME}.metal.c ${OUTNAME}.metal.h ++ DEPENDS ${_dkr_msl_blob} file_to_c + BYPRODUCTS ${OUTNAME}.metal.h) + target_sources(${TARGETOBJ} PRIVATE ${OUTNAME}.metal.c) + endfunction() diff --git a/runtime-recomp/tests/build_tooling_tests.py b/runtime-recomp/tests/build_tooling_tests.py index 76528a33..df9c23cc 100644 --- a/runtime-recomp/tests/build_tooling_tests.py +++ b/runtime-recomp/tests/build_tooling_tests.py @@ -915,7 +915,7 @@ def executable(name, body="exit 0"): p.chmod(0o755) for name in ( - "xcodebuild", + "xcrun", "git", "python3", "cmake", diff --git a/scripts/lib/apple-build.sh b/scripts/lib/apple-build.sh index 079f89f1..d1817fd6 100644 --- a/scripts/lib/apple-build.sh +++ b/scripts/lib/apple-build.sh @@ -3,7 +3,7 @@ dkr_require_macos() { [[ "$(uname -s)" == Darwin ]] || { - echo 'Apple builds require macOS with the full Xcode application installed.' >&2 + echo 'Apple builds require macOS.' >&2 return 1 } } diff --git a/scripts/macos-host-tools.sh b/scripts/macos-host-tools.sh index 1ed56b7e..dbae23d1 100644 --- a/scripts/macos-host-tools.sh +++ b/scripts/macos-host-tools.sh @@ -4,13 +4,27 @@ source "${BASH_SOURCE[0]%/*}/lib/apple-build.sh" dkr_macos_host_tools() { local skip_deps="$1" make_version probe dkr_require_macos || return 1 - dkr_require_xcode || return 1 + # Command Line Tools are enough: RT64's Metal shaders compile at runtime. + xcrun --sdk macosx --show-sdk-path >/dev/null 2>&1 || { + echo 'Install the Command Line Tools: xcode-select --install' >&2 + return 1 + } if [[ "$skip_deps" -eq 0 ]]; then command -v brew >/dev/null || { echo 'Install Homebrew: https://brew.sh/' >&2 return 1 } - HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_UPGRADE=1 brew install --quiet cmake ninja make pcre2 python sdl2 coreutils || return 1 + # Install only what is missing: on macOS releases Homebrew no longer + # bottles, reinstalling a present tool (python) means a failing source build. + local pair missing=() + for pair in cmake:cmake ninja:ninja make:gmake pcre2:pcre2-config sdl2:sdl2-config coreutils:sha1sum; do + command -v "${pair#*:}" >/dev/null || missing+=("${pair%%:*}") + done + # tomllib needs 3.11; the Command Line Tools ship 3.9. + python3 -c 'import sys; sys.exit(sys.version_info < (3, 11))' 2>/dev/null || missing+=(python) + if [[ "${#missing[@]}" -gt 0 ]]; then + HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_UPGRADE=1 brew install --quiet "${missing[@]}" || return 1 + fi fi dkr_require_tools git python3 cmake ninja c++ shasum sha1sum pcre2-config sdl2-config || { echo 'Run ./Setup-macOS.sh (sha1sum is supplied by brew install coreutils).' >&2 @@ -39,11 +53,4 @@ dkr_macos_host_tools() { return 1 fi rm -rf -- "$probe" - if ! xcrun metal -v >/dev/null 2>&1; then - if [[ "$skip_deps" -eq 1 ]]; then - echo 'Install Metal: xcodebuild -downloadComponent MetalToolchain' >&2 - return 1 - fi - xcodebuild -downloadComponent MetalToolchain || return 1 - fi }