ci(macos): cache thirdparty lib/include outputs across runs#6100
Merged
Conversation
build_thirdparty.sh on macOS spends ~4 min compiling the source-built
thirdparty libs (libE57Format, OpenCTM, googletest, parallel-hashmap,
glad, mrbind-pybind11, tinygltf, laz-perf, clip, fastmcpp) into ./lib
and ./include. The submodules pinned to those SHAs rarely change
between runs, so the work repeats unchanged on nearly every job.
Add an actions/cache@v4 step keyed on:
- matrix.instance + matrix.compiler (runner image + toolchain)
- brew prefix hash (self-hosted ARM fleet may use different prefixes)
- SHAs of the source-built submodules (git ls-tree HEAD)
- hash of build_thirdparty.sh, install_brew_requirements.sh,
thirdparty/{clip,fastmcpp}.sh, thirdparty/CMakeLists.txt,
requirements/macos.txt
Only ./lib and ./include get cached -- those hold the final .dylib/.a +
headers produced by `cmake --install`. ./thirdparty_build (the CMake
build dir holding *.o, CMakeFiles/, etc.) is NOT cached; it's rebuilt
on a miss and discarded on a hit.
build_thirdparty.sh gains a MESHLIB_THIRDPARTY_SKIP_BUILD=1 short-circuit
that runs after install_brew_requirements.sh (so brew packages always
get reinstalled on fresh hosted runners for dyld) but before the
rm -rf + cmake stage. The macOS workflow sets the env var iff the
cache restored.
The pre-existing "Compute brew-prefix hash for MRBind cache key" step
gets moved up and its `if: mrbind` guard dropped so both caches can
share it; the Build MRBind reference (steps.brew-hash.outputs.hash)
is unchanged.
Cache key prefix `v1-` is hardcoded so future incompatibilities can
be busted by a one-line bump.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the version already used by build-mrbind, install-msys2-mrbind, and install-cuda in this repo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
oitel
reviewed
May 15, 2026
|
|
||
| # CI: reuse cached ./lib and ./include from a previous run. Brew install above | ||
| # still runs so dyld can resolve runtime deps. | ||
| if [[ "${MESHLIB_THIRDPARTY_SKIP_BUILD:-}" == "1" ]]; then |
Contributor
There was a problem hiding this comment.
What's the point of early-exiting within the script instead of skipping it?
| # build_thirdparty.sh actually compiles into ./lib + ./include on macOS. | ||
| # `git ls-tree` reads the gitlink entries from the index, so it works | ||
| # whether or not the submodules are checked out. | ||
| - name: Compute thirdparty submodule SHA for cache key |
Contributor
There was a problem hiding this comment.
Compute all hashes in a single step.
| # different paths (e.g. `/Users/runner/.homebrew` vs `/opt/homebrew`) get | ||
| # distinct cache entries -- the prefix is baked into thirdparty rpaths | ||
| # and into mrbind's rpath. | ||
| - name: Compute brew-prefix hash for cache keys |
Contributor
There was a problem hiding this comment.
Compute all hashes in a single step.
| path: | | ||
| lib | ||
| include | ||
| key: v1-thirdparty-${{ matrix.instance }}-${{ matrix.compiler }}-${{ steps.brew-hash.outputs.hash }}-${{ steps.thirdparty-src-hash.outputs.hash }}-${{ hashFiles('scripts/build_thirdparty.sh', 'scripts/install_brew_requirements.sh', 'scripts/thirdparty/clip.sh', 'scripts/thirdparty/fastmcpp.sh', 'thirdparty/CMakeLists.txt', 'requirements/macos.txt') }} |
Contributor
There was a problem hiding this comment.
Compute all hashes in a single step.
Address review feedback:
- Restore scripts/build_thirdparty.sh to master state. The
MESHLIB_THIRDPARTY_SKIP_BUILD short-circuit is no longer needed --
the workflow now gates the build step directly.
- Split the old "Install thirdparty libs" step into two:
1. "Install brew requirements" -- always runs (cached .dylibs link
against brew libs at runtime, so packages must be on hosted
runners even on cache hit). Calls install_brew_requirements.sh
directly.
2. "Build thirdparty libs" -- gated with
`if: steps.thirdparty-cache.outputs.cache-hit != 'true'`. Calls
build_thirdparty.sh, which still invokes
install_brew_requirements.sh internally (one redundant invocation
on the rare cache-miss path).
- Merge "Compute brew-prefix hash" + "Compute thirdparty submodule SHA"
into one step "Compute cache key inputs" (id: cache-keys) with two
outputs (brew-hash, thirdparty-src-hash). Build MRBind's reference
updated from `steps.brew-hash.outputs.hash` to
`steps.cache-keys.outputs.brew-hash`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
oitel
approved these changes
May 15, 2026
Comment on lines
+138
to
+139
| echo "brew-hash=$(printf %s "$BREW_PREFIX" | shasum -a 256 | cut -c1-16)" >> "$GITHUB_OUTPUT" | ||
| echo "thirdparty-src-hash=$(git ls-tree HEAD \ |
Contributor
There was a problem hiding this comment.
Is it safe to compute a unified hash based on these two?
oitel
approved these changes
May 15, 2026
| path: | | ||
| lib | ||
| include | ||
| key: v1-thirdparty-${{ matrix.instance }}-${{ matrix.compiler }}-${{ steps.cache-keys.outputs.brew-hash }}-${{ steps.cache-keys.outputs.thirdparty-src-hash }}-${{ hashFiles('scripts/build_thirdparty.sh', 'scripts/install_brew_requirements.sh', 'scripts/thirdparty/clip.sh', 'scripts/thirdparty/fastmcpp.sh', 'thirdparty/CMakeLists.txt', 'requirements/macos.txt') }} |
Contributor
There was a problem hiding this comment.
Can the hashFiles(...) be computed in the cache-keys step too?
Address review feedback on the cache-keys step: instead of two separate
outputs feeding into the cache key alongside an inline hashFiles(...),
compute a single `thirdparty-hash` output that incorporates everything
that should bust the thirdparty cache when changed:
- brew prefix (baked into produced .dylibs' rpaths)
- submodule SHAs pinned in HEAD (the source we compile)
- scripts/build_thirdparty.sh, scripts/install_brew_requirements.sh,
scripts/thirdparty/{clip,fastmcpp}.sh, thirdparty/CMakeLists.txt,
requirements/macos.txt
Cache key shortens to
v1-thirdparty-${matrix.instance}-${matrix.compiler}-${thirdparty-hash}
`brew-hash` is kept as a separate output since Build MRBind below
consumes it on its own.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The "Install thirdparty libs" step on macOS takes ~9 min on the x64 hosted runner: brew installs + compiling the source-built thirdparty libs (libE57Format, OpenCTM, googletest, parallel-hashmap, glad, mrbind-pybind11, tinygltf, laz-perf, clip, fastmcpp) into
./lib+./include. The submodules feeding that compile rarely change between runs, so the work repeats unchanged on nearly every job. Reference cache-miss baseline: job 76017787493.This PR adds
actions/cache@v5around./lib+./includeso the compile is skipped on cache hit. (v5 to matchbuild-mrbind,install-msys2-mrbind, andinstall-cudain this repo.)What changed in the workflow
scripts/build_thirdparty.shis unchanged from master. All logic lives in.github/workflows/build-test-macos.yml:Compute cache key inputsproduces two outputs in a single bash invocation:brew-hash— hash of$(brew --prefix), consumed byBuild MRBindbelow to discriminate self-hosted ARM fleet members.thirdparty-hash— one unified hash of everything that influences the thirdparty./lib+./includeoutputs (brew prefix + submodule SHAs fromgit ls-tree HEAD+ contents ofscripts/build_thirdparty.sh,scripts/install_brew_requirements.sh,scripts/thirdparty/{clip,fastmcpp}.sh,thirdparty/CMakeLists.txt,requirements/macos.txt).Cache thirdparty build outputswith keyv1-thirdparty-${matrix.instance}-${matrix.compiler}-${thirdparty-hash}.Install thirdparty libsstep is split into two:Install brew requirements— always runs, callsscripts/install_brew_requirements.shdirectly. Needed on cache hit too because cached.dylibs link against brew libs at runtime.Build thirdparty libs— gatedif: steps.thirdparty-cache.outputs.cache-hit != 'true', callsscripts/build_thirdparty.sh.Measured impact
Step "Install thirdparty libs" duration, measured on an earlier commit of this branch that still ran the steps as a single unit:
macos-15-intel)macos-14)The savings come from skipping the cmake compile portion (now the gated
Build thirdparty libsstep).Install brew requirementsruns on both paths. The x64 hosted leg gets the biggest win because that runner image has fewer of the required bottles pre-cached. The self-hosted ARM already has brew packages persisted across runs, so its cache-miss is short to begin with; the saving there is just the cmake compile portion.What gets cached
./lib— final.dylib/.afromcmake --install./include— installed headersWhat does not get cached:
./thirdparty_build/— the CMake build dir containing*.o,CMakeFiles/, generated Makefiles, etc. Rebuilt from scratch on a miss and discarded on a hit.Cache sizes observed: ~3.7-3.8 MiB per leg (confirming
*.ois not included; the CMake build dir would be hundreds of MB).Cache key
v1-thirdparty-${matrix.instance}-${matrix.compiler}-${thirdparty-hash}wherethirdparty-hashis the unified hash described above. Thev1-prefix is hardcoded so future incompatibilities can be busted by a one-line bump.Test plan
Build thirdparty libsstep skipped (if: cache-hit != 'true'false),Install brew requirementsstill runs.Build/Generate ... bindings/MRMesh Exported Symbols/ unit tests / pkg creation all pass on cache hit../thirdparty_build/*.ois not included.