macos: link libomp statically so the bundle stays one file (#42)

package_macos.sh rejects a binary that links anything outside /usr/lib
and /System/Library, and Homebrew's libomp.dylib is exactly that, so
`--target macos_app` failed on any Mac where CMake found OpenMP.
ss_mac_prefer_static() (cmake/SsMacBundle.cmake) links the archive
beside the dylib instead, the way MoltenVK is already static.

libomp is keg-only, so build_develop.bash now passes -DOpenMP_ROOT at
the keg: without it CMake finds no OpenMP and meshing, UV unwrap and
metrics run serial with nothing said. Configure now reports either way.
This commit is contained in:
(Frank) Yu Cheng Gu
2026-09-01 19:21:23 -04:00
committed by GitHub
parent 616f9b8512
commit 44635cbbea
5 changed files with 92 additions and 16 deletions
+19
View File
@@ -40,6 +40,25 @@ if command -v python3 >/dev/null 2>&1; then
python3 tools/check_comment_length.py || exit 1
fi
# Homebrew's libomp is keg-only: nothing points at it, so CMake finds no
# OpenMP and meshing, UV unwrap and metrics run serial (cmake/SsMacBundle.cmake
# then has no dylib to make static either).
if [ "$(uname)" = "Darwin" ]; then
case " $* " in
*" -DOpenMP_ROOT="*) ;;
*)
for omp_root in "$(brew --prefix libomp 2>/dev/null)" \
/opt/homebrew/opt/libomp /usr/local/opt/libomp; do
[ -n "$omp_root" ] || continue
[ -f "$omp_root/lib/libomp.a" ] ||
[ -f "$omp_root/lib/libomp.dylib" ] || continue
set -- "$@" "-DOpenMP_ROOT=$omp_root"
echo "OpenMP: passing -DOpenMP_ROOT=$omp_root (keg-only formula)"
break
done ;;
esac
fi
cmake -G Ninja -B build "$@" || exit $?
# Repair the ninja dependency log.
+6
View File
@@ -13,6 +13,7 @@ message(STATUS "SS_BACKEND=vulkan: portable engine layer + "
set(SS_BUILD_CLI ON)
include(SsVulkan)
include(SsMacBundle)
ss_vulkan_lib()
find_package(Threads REQUIRED)
@@ -35,7 +36,12 @@ target_link_libraries(csrc_portable PUBLIC ss_i18n)
find_package(OpenMP)
if(OpenMP_CXX_FOUND)
ss_mac_prefer_static(OpenMP::OpenMP_CXX
"`brew install libomp` ships the archive; \
-DCMAKE_DISABLE_FIND_PACKAGE_OpenMP=ON drops OpenMP instead.")
target_link_libraries(csrc_portable PUBLIC OpenMP::OpenMP_CXX)
else()
message(STATUS "No OpenMP: meshing, UV unwrap and metrics run serial")
endif()
# ---------------------------------------------------------------------------
+40
View File
@@ -0,0 +1,40 @@
# The macOS bundle carries one binary (docs/build.md#packaging), so nothing it
# links may sit outside /usr/lib and /System/Library: a recorded /opt/homebrew
# path runs only on the machine that built it. MoltenVK is static for that
# reason (cmake/SsVulkan.cmake), tools/package_macos.sh rejects a bundle that
# breaks the rule, and a dependency found as a dylib goes through here.
# ss_mac_prefer_static(<imported target> [<hint>]) -- links the .a beside each
# dylib in the target's link interface. Warns with <hint> and leaves the target
# alone when an archive is missing; a no-op off macOS.
function(ss_mac_prefer_static target)
if(NOT APPLE)
return()
endif()
get_target_property(_libs ${target} INTERFACE_LINK_LIBRARIES)
if(NOT _libs)
return()
endif()
set(_static "")
foreach(_lib IN LISTS _libs)
# Anything that is not an absolute dylib path -- a genex, a link flag,
# another target -- is not ours to rewrite.
if(NOT _lib MATCHES "\\.dylib$")
list(APPEND _static "${_lib}")
continue()
endif()
string(REGEX REPLACE "\\.dylib$" ".a" _archive "${_lib}")
if(NOT EXISTS "${_archive}")
message(WARNING "No ${_archive}, so ${target} keeps ${_lib}, which "
"packaging rejects (tools/package_macos.sh). ${ARGV1}")
return()
endif()
list(APPEND _static "${_archive}")
endforeach()
set_property(TARGET ${target} PROPERTY INTERFACE_LINK_LIBRARIES "${_static}")
list(JOIN _static " " _joined)
message(STATUS "${target}: ${_joined} linked statically")
endfunction()
+21 -12
View File
@@ -30,6 +30,7 @@ Always build through the dev scripts.
| `SsEmbed.cmake` | `ss_embed_file()` — bake a file into a byte-array header |
| `SsApps.cmake` | the `spirula` executable — every tool the build has, in one binary (backend-agnostic) |
| `SsPackage.cmake` | the `macos_app` / `macos_dmg` targets ([Packaging](#packaging)) |
| `SsMacBundle.cmake` | `ss_mac_prefer_static()` — the static linking a one-file bundle depends on ([Packaging](#packaging)) |
| `SsChecks.cmake` | the source lints ([Lints](#lints)) — included last, so every target above depends on them |
Exactly one backend module runs. It leaves behind `SS_WITH_TORCH` and
@@ -204,15 +205,19 @@ embedded into the binary. On an offline machine, transfer a matching `slangc`
and point `-DSS_SLANGC=` at it.
**macOS.** Vulkan backend only, through MoltenVK; `build_develop.bash` works
as on Linux. Dependencies: `brew install cmake ninja`. Four things are
macOS-only in the build: `cmake/SsVulkan.cmake` fetches a pinned universal
MoltenVK and links it *statically* (`SS_MACOS_VULKAN=static`, the default) so
the binary carries its own driver and copies to any Mac — the release tarball
supplies the Vulkan headers too, so nothing comes from Homebrew;
`cmake/SsSlang.cmake` pins a different Slang release (the one this project
pins publishes no macOS assets); `build_develop.bash` reads free memory from
`vm_stat` rather than `/proc`; and `ss_i18n` links CoreFoundation, which
`i18n/Locale.cpp` asks for the user's locale.
as on Linux. Dependencies: `brew install cmake ninja libomp`. The last is
keg-only, so nothing finds it on its own — `build_develop.bash` passes
`-DOpenMP_ROOT` at the keg, and a build without OpenMP runs meshing, UV unwrap
and metrics serial. Five things are macOS-only in the build:
`cmake/SsVulkan.cmake` fetches a pinned universal MoltenVK and links it
*statically* (`SS_MACOS_VULKAN=static`, the default) so the binary carries its
own driver and copies to any Mac — the release tarball supplies the Vulkan
headers too, so nothing comes from Homebrew; `cmake/SsSlang.cmake` pins a
different Slang release (the one this project pins publishes no macOS assets);
`build_develop.bash` reads free memory from `vm_stat` rather than `/proc`;
`ss_i18n` links CoreFoundation, which `i18n/Locale.cpp` asks for the user's
locale; and `cmake/SsMacBundle.cmake` links the archive beside a dependency's
dylib, which is what keeps the bundle one file ([Packaging](#packaging)).
A static build has no loader, so it cannot load validation layers.
`-DSS_MACOS_VULKAN=loader` links the installed loader instead (needs
@@ -297,10 +302,14 @@ taskbar. The banner carries no text — the product name and tagline are drawn
over it by ImGui, so they stay translatable.
The bundle carries **one binary**. That is only honest because a default
macOS build links MoltenVK statically (`cmake/SsVulkan.cmake`), and the script
checks rather than trusts it: `otool -L` output naming anything outside
macOS build links MoltenVK statically (`cmake/SsVulkan.cmake`) and swaps every
other dependency found as a dylib for the archive beside it
(`ss_mac_prefer_static()` in `cmake/SsMacBundle.cmake` — libomp today), and the
script checks rather than trusts it: `otool -L` output naming anything outside
`/usr/lib` or `/System/Library` fails the packaging, since a bundle missing a
dylib works on the build machine and nowhere else.
dylib works on the build machine and nowhere else. Homebrew builds its archives
for the host alone, so a bundle linking one is arm64-only and inherits that
keg's minimum macOS version.
Signing is ad-hoc (`--sign -`) by default. That is not optional decoration:
Apple silicon kills an unsigned arm64 binary on exec, and copying the
+6 -4
View File
@@ -5,9 +5,9 @@
# bash tools/package_macos.sh [--build-dir DIR] [--out DIR]
# [--sign IDENTITY] [--dmg]
#
# The bundle carries one binary and its icon, which only works because a static
# MoltenVK build links nothing outside the system frameworks
# (cmake/SsVulkan.cmake) -- verified below, not assumed.
# The bundle carries one binary and its icon, which only works because MoltenVK
# and libomp are linked statically (cmake/SsVulkan.cmake,
# cmake/SsMacBundle.cmake) -- verified below, not assumed.
#
# --sign defaults to ad-hoc ("-"): enough for a Mac the app is copied to
# directly, not enough to survive the quarantine flag a download attaches.
@@ -121,7 +121,9 @@ STRAY=$(otool -L "$APP/Contents/MacOS/spirula" | tail -n +2 | awk '{print $1}' \
if [ -n "$STRAY" ]; then
echo "package_macos.sh: the binary links libraries this bundle does not carry:" >&2
echo "$STRAY" | sed 's/^/ /' >&2
echo " build with -DSS_MACOS_VULKAN=static (the default) to avoid it" >&2
echo " such a bundle runs only on this machine. libvulkan: rebuild with" >&2
echo " -DSS_MACOS_VULKAN=static (the default). Anything else: install its" >&2
echo " .a so cmake/SsMacBundle.cmake links that (brew install libomp)." >&2
exit 1
fi