WIP: build(macos): discover dual-architecture toolchains and form Mach-O commands #453
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!453
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/build-flags"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this is for: anyone building LS on a Mac. Today the builder takes macOS for Linux, so Apple's tools get ELF
linker options and x86 CPU flags, and nothing links. This PR gives the build scripts a macOS command path of their own:
Mach-O, native arm64 or x86_64 under Rosetta, macOS 14.4 as the oldest system. It is the build-command floor of the macOS
port (#420), not a working game on a Mac. What is measured: on an Apple M4 Max with macOS 26.7.1, a new game-free test
fails on
main(13 failures in 15 tests) and passes here (15 of 15), two runs each, interleaved. The 36 Windows andLinux command arrays are identical to
main's. Real C/C++ links with Apple clang 21 and LLVM 23.1.5, for arm64 andx86_64, load and run. The whole game-free suite is compared by test name with
main. Not measured: Windows and Linuxruns, a complete LS build or a game run on a Mac, macOS 14.4 itself, and an Intel Mac.
Why
On a Mac,
toolchain.windows()is false, so the builder takes the Linux route. It passes ELF options (-fuse-ld=lld,--gc-sections,-Bsymbolic,-static-libgcc),-march=x86-64-v3and-mcx16on Apple silicon, and.sonames.None of that builds on macOS.
What changed
nx2/build.py:DARWINis set besideLINUX, and macOS is no longerLINUX.RX_TARGET=windowsworks as before.host_flags,renderer_flags,executable_linkandrenderer_link. The Windows and Linux arrays are unchanged, element for element (Measurements).-arch,-mmacosx-version-min=14.4and-isysroot;-Xlinkerpairs:-map,-dead_strip,-exported_symbol _hos_posix_*and
-rpath @executable_path;longshot_vk.dylibwith-install_name @rpath/longshot_vk.dylib,-rpath @loader_path,-undefined errorand the system's libc++;-mcx16, and no-marchunlessRX_MARCHnames an ARM level. An x86 level is refused there;work/hot_order.txtis left as it is, and a copy with Mach-O's leading underscores,hot_order.txt.macos,orders the link;
flag_textadds the compiler and SDK identity, so a new compiler rebuilds the objects;nx2/darwin_build.py(new): the macOS build policy:host/posix/darwin_*.c, but not Linux'sprof.c;pkg-config, or the prefix inLONGSHOT_FFMPEG;compiler_key, and the post-link note (Mach-O's strict link stands in for ELF'snm -Dcheck).nx2/darwin_toolchain.py(new): a64-kit'stools/toolchain.pyandlift/build_emit.py, made to form Mach-Ocommands, from outside
third_party/:RX_ARCHchooses the game's, never the helpers' that Python loads (a64emit,zbic), which follow the Python process;EMSDKprefix (bin/or emsdk'supstream/bin/), thenPATH, thenxcrun;SDKROOT, elsexcrun's), and a Mach-O linker:ld64.lldwhen there is one, else Apple'sld;a64emitasliba64emit-<process arch>.dylib, rebuilt when the compiler, its version or the flags change.kit.pyandmake_game.pyinstall it on macOS only, over four of toolchain's functions (find,need,native_target,native_link) and over build_emit'sOUTandbuild. This is the waynx2/insn.pyoverridesa64emit.translate. On Windows and Linux nothing imports the file.nx2/make_game.py:check_darwin_prerequisiteschecks the Python modules, the renderer headers (or a prebuilt renderer's Mach-O slice),FFmpeg and SDL2 with a test link, and each compiler's actual version;
nx2/host_cpu.py: arm64 gets no-march, an x86_64 Mac getsx86-64-v2, and neither gets-mtune.nx2/zbic.py:zbic-<process arch>.dylib, rebuilt when the compiler or the flags change.nx2/kit.py: three lines that installdarwin_toolchainon macOS..gitignore:*.dyliband*.dylib.sha256.tests/test_macos_build_flags.py(new), withtests/build_flags_main.json:RX_RELEASE,RX_PGO=gen,RX_MTUNE,LONGSHOT_SYSROOT, NGX, FFmpeg, wgpu): 6 configurations of 6 arrays (target, lifted C, host C,renderer C++, executable link, renderer link). They were captured from
main's own expressions(
--capture-main), and are compared on a POSIX and on a Windows path host;-Xlinkerarguments;pkg-configquoting;kit.pyandmake_game.pyinstalldarwin_toolchainon a Mac (a child Python each);check exports, symbol order, install names,
minos 14.4, local strong-symbol binding, and that an unresolvedsymbol is refused. The default is the Python process's architecture;
--arch(repeatable) and--llvm-prefixadd routes.
13 existing tests that run code cut from
build.py,make_game.pyorhost_cpu.pynow select Windows or Linuxexplicitly (
DARWINFalse, orsys.platformset tolinux), so on a Mac they still test what they tested. Noassertion was removed. They are
test_build_cleanup_scope,test_cpm_discovery,test_cpp_preflight,test_cpu_target,test_dlss_sdk_cache,test_driver_cache_folder,test_ffx_sdk,test_finish_output,test_link_path_comma,test_linux_preflight,test_make_game_ngx,test_shader_stamp_cacheandtest_system_header_cache.docs/development.md: a macOS paragraph, and rows forRX_ARCH,EMSDK/SDKROOT,LONGSHOT_FFMPEG/LONGSHOT_SDL2,RX_MARCHandLONGSHOT_VK_PREBUILT.Unchanged:
third_party/(byte for bytemain's;scripts/check_third_party.pypasses), every Windows and Linuxcommand, and every source under
host/andgpu/.Flags and settings
RX_ARCH=arm64/x86_64(new)RX_MARCH(changed on macOS)x86-64-v2; Windows and Linux unchangedEMSDK(on macOS)bin/orupstream/bin/), beforePATHandxcrunPATH, thenxcrunSDKROOT(on macOS)xcrun --sdk macosx --show-sdk-pathLONGSHOT_FFMPEG,LONGSHOT_SDL2(on macOS).pcfiles; an SDL2.dylibpkg-config(PATH, then Homebrew)LONGSHOT_VK_PREBUILT(on macOS)longshot_vk.dylib; its Mach-O slice is checked--arch(repeatable),--llvm-prefix,--source,--capture-mainMeasurements
main:
5e072fc8c2adadba5ea610148be1c47800ba0cca(2026-11-14)this PR:
mac/build-flags@4723ee36a494cfafb82b18ccc685feaf2caeb964Hardware: Apple M4 Max, 16 cores, 64 GiB, macOS 26.7.1 (25G241); Apple clang 21.0.0, LLVM 23.1.5, SDK 27.0. No GPU.
Scene: none. Game-free test and suite; no game, no window.
Method:
tests/test_macos_build_flags.pyon this PR and againstmain's tree (--source), interleaved, two runs each.The 36 arrays:
main's recaptured from its own expressions, compared with the frozen file, which this PR's buildersequal. Real links once with both compilers and both architectures. The whole suite on this PR, compared by test name
with a
mainrun on the same Mac.How to reproduce:
5e072fc84723ee36tests/test_macos_build_flags.py(run 1 / run 2)FAILED (failures=13), 15 tests / the sameOK, 15 tests / the samemain's expressions: equal to the frozen filethird_party/main;third_party: 11 parts as their notes sayThe other 9 failures on
mainname the same missing pieces: no Darwin source selection, no compiler identity, nohost/target architecture split, no
pkg-configparser. The three tests that pass onmainare the two frozen-arraycomparisons and the architecture-selection helper. The frozen file says
base 514591a3: it was captured there, andnx2/build.pyand a64-kit'stoolchain.pyare byte-identical at5e072fc8.Testing
Tested: the new test both ways, the dual route and the arrays, as above. And the whole suite,
tests/run_all.py, 6 atonce, on the same Mac (the runner labels macOS
linux):tests/test_macos_build_flags.py(pass). No other check changedstatus, and no crash report was left by this run.
main, which the port's other PRsaddress:
test_appimage_package_retry,test_collect_wide,test_control,test_framegen_resources,test_game_layout,test_low_memory_notice,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_module_ids,test_oot_state_tap,test_pad_layout,test_pcache_header_read,test_pipeline_cache_file,test_pipeline_cache_save,test_pipeline_play_cache,test_prof_symbols,test_program_cache_cleanup,test_program_cache_one_variant.test_macos_build_flags,test_lift_cache(the lift's sources), andtest_spawn_emitandtest_fpx_sticky, whichreplace
build_emit.buildthemselves.Not tested:
cases as before.
host/,gpu/) is in other PRs.minos 14.4in a binary is not a run on 14.4.libclang_rt.osx.ahas no x86_64slice. The fixtures need none of it; a larger program may.
python third_party/a64-kit/lift/build_emit.py, run by hand on a Mac, builds a64-kit's ownliba64emit.so, as onmain. The build loadsliba64emit-<arch>.dylib, throughkit.py.Needs other testers:
main(request to open).python -I -B tests/test_macos_build_flags.py, expecting 15 of 15 (request to open).Screenshots
Nothing on screen.
Risk and rollback
main's, but not run there in this PR.nx2/kit.pyand the newnx2/darwin_toolchain.pyareamong the lift's sources (
make_game.py'slift_sourcesfollows every import of the lift's, inside aniftoo), sothe lift key changes. It costs one lift, minutes. Off macOS the lift runs none of the new code, so its output should be
the same; that lift was not run here.
kit.pyormake_game.pyis imported. A script that importsa64-kit's
toolchainwithout either getsmain's behaviour on a Mac, as today.Reviewer notes
nx2/darwin_toolchain.pyand its two install sites (kit.py,make_game.py), thenbuild.py's four newfunctions and
nx2/darwin_build.py.da3132bc,06c20446and37587502are this work as first written (4766930e,9de9a7f2and7ed96d16), which editedthird_party/a64-kitwith an updatedPROVENANCE.md.4723ee36restores a64-kit tomain's bytes and moves those edits intonx2/darwin_toolchain.py(the owner's decision, after CONTRIBUTING). So thefirst commit's message still describes the a64-kit edit; the squash has none.
compiles at
7ed96d16and at4723ee36, for arm64 and x86_64, plain and with every option.da3132bcto4723ee36: the whole branch is this PR's own (it depends on no other PR).Issues
See #420.
Checklist
For everyone
type(scope): description).main. (Branched from5e072fc8.)python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). (526 passed,17 skipped, and the same 19 failures as
mainon this Mac, named under Testing.)requests are still to open.
build: the test's output onmainand here, two interleaved runs each.)tests/build_flags_main.jsonis command data from thesource's own expressions.)
third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.row.)
mainand passing here.tests/that reads changed source updated in this PR, not weakened.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.