Skip to content

Pin gcc to keep SOCRATES shim symbols exported on macOS CI - #865

Merged
timlichtenberg merged 9 commits into
mainfrom
tl/ci-agni-shim-before-precompile
Sep 9, 2026
Merged

timlichtenberg merged 9 commits into
mainfrom
tl/ci-agni-shim-before-precompile

Conversation

@timlichtenberg

Copy link
Copy Markdown
Member

Description

On macOS CI, a newer Homebrew gcc Darwin driver drops the SOCRATES shim's dlsym-visible symbols (PS_real_kind_bytes, PS_create_StrDim, ...) from libSOCRATES_C.so's export table, so Julia's dlsym lookup in AGNI fails at runtime and the Integration and Slow tiers fail with could not load symbol.

This pins gcc to the homebrew-core formula revision whose Darwin driver exports those symbols, installed through a throwaway tap and verified to be both present and the linked keg before the dependent packages install. It also adds a macOS step that runs nm -gU on the freshly rebuilt shim and fails fast if PS_real_kind_bytes is no longer exported, so a future toolchain regression is caught at setup rather than deep in a test run.

Validation of changes

I dispatched the full nightly science validation workflow against this branch (run 34328401618): all 21 jobs pass, including the macOS Integration tier and every Slow tier shard that previously failed on the dlsym lookup. The version gate and the shim symbol check were exercised on the GitHub-hosted macOS runner image.

Checklist

  • I have followed the contributing guidelines
  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • My changes generate no new warnings or errors
  • I have checked that the tests still pass on my computer
  • I have updated the docs, as appropriate
  • I have added tests for these changes, as appropriate
  • I have checked that all dependencies have been updated, as required

Log which libSOCRATES_C.so exports PS_real_kind_bytes, which file
Julia's Libdl resolves and opens, and any other libSOCRATES_C* copies
under the Julia depot or the restored SOCRATES tree that could shadow
the intended shim, ahead of building a fix for the precompile-ordering
mismatch on macOS Integration CI.

The step is gated behind a new shim-diagnostics input (default false)
so it runs only where a caller opts in, currently the nightly
integration-tier job on both OS legs, rather than on every
setup-proteus caller. It also sets JULIA_DEBUG=loading on that job's
pytest step. The step can never fail the job: every external command
that can plausibly fail falls back to an explanatory echo.
Log gfortran's version, the SOCRATES tree revision against the
tools/get_socrates.sh pin, the content of RAD_DIR/bin/Mk_cmd if
present, a direct grep for PS_real_kind_bytes in Utilities_CF.f90,
and an unfiltered (no -U) nm scan of Utilities_CF.o and
libSOCRATES_C.so for any real_kind symbol under a mangled name.
nm_check now captures nm's exit code inside the if condition instead
of a bare command substitution, since a failed nm would otherwise
abort the whole step under GitHub Actions' default -eo pipefail. The
verbose relink dump and each relink_probe's piped tail now tolerate a
failing link the same way.

Gate the step to runner.os == 'macOS': it currently runs unconditionally
on both matrix legs, and GNU nm on ubuntu-latest may not support the
same flags as the macOS toolchain.

Extend the Julia probe to call dlsym(PS_real_kind_bytes) directly,
matching AGNI's own load path instead of only checking dlopen/dlpath.
Extracts the exact linker symbol name for PS_real_kind_bytes from
nm -g output on the object file, with a literal fallback when no
match is found, then reproduces the SOCRATES Makefile link recipe
with -Wl,-exported_symbols_list,<file> as a fifth relink probe
alongside the existing baseline, -ld_classic, -exported_symbol, and
-export_dynamic probes. The verbose gfortran -shared -v dump is kept
in full for this probe, unlike the others, since it needs full
inspection for the auto-injected linker flags GitHub's toolchain adds.

The symbol extraction guards its command substitution so a no-match
result reaches the fallback branch instead of aborting the step
under GitHub Actions' default -eo pipefail.
…uild

Homebrew's gcc 16.2 Darwin driver stops exporting SOCRATES's Julia shim
symbol PS_real_kind_bytes as external, so any AGNI build step that
rebuilds libSOCRATES_C.so breaks Julia's dlsym lookup. 16.1 is the last
release without that regression. Formulae must live in a tap, so the
historical 16.1.0 formula is fetched into a throwaway local tap and
installed by tap-qualified name; pinned before netcdf-fortran so its
gcc dependency resolves to the pinned keg instead of upgrading it.

Unlink any gcc the runner image already ships before installing the
pinned keg, since a pre-existing keg under the same formula name would
otherwise conflict with linking the tap-qualified one.
brew pin took the plain formula name, which two taps now define
(homebrew-core and the throwaway pinned-toolchain tap holding the
16.1.0 formula), so it risks a tap-ambiguity error instead of
pinning the installed keg. Use the same tap-qualified name already
used for the install two lines above.
Drop the opt-in "Log AGNI shim diagnostics" step, since it was
scaffolding for tracking down the PS_real_kind_bytes export bug, and
it duplicates what the guard below now checks unconditionally.

Add a hard-failing guard that checks libSOCRATES_C.so's export table
with nm -gU on every macOS job, placed after "Build AGNI
(Pkg.instantiate)" rather than right after the SOCRATES-build cache
step. get_socrates.sh only produces radlib.a, never
socrates/julia/lib/libSOCRATES_C.so, per the note at
get_socrates.sh:204-208; that file is deps/build.jl's own output, and
build.jl runs unconditionally, on every job, regardless of any cache
hit or miss. Checking for it any earlier would fail every SOCRATES
cache-miss run, before AGNI even builds the shim.

Remove the now-unused shim-diagnostics input, its ci-nightly.yml
pass-through, and the JULIA_DEBUG env var on the integration job that
only that step read.
The new post-build guard piped nm -gU straight into grep -q under
set -o pipefail. Once grep -q found its first match it exited and
closed the pipe, so nm received SIGPIPE and exited non-zero; pipefail
then reported that as the pipeline's own status, so the guard failed
even when the symbol was present. A dispatched CI run on macOS hit
this today: the guard reported PS_real_kind_bytes missing, but its
own diagnostic fallback line, printed one step later from a second nm
call, showed the symbol correctly exported as external. That
confirmed the failure was the guard's own plumbing, not a real
regression in the shim.

Capture nm's output into a variable first, then grep the variable
with a here-string instead of a live pipe. Nothing downstream can
close early on nm anymore, so there is no writer left to receive
SIGPIPE, and the check can only fail when the symbol is truly
absent. Verified locally: the original form reproduces a false
failure against a finite fixture with the symbol present, and the
revised form reports it found under the same fixture.
…heck

Pin the gcc formula fetch to the full 40-char homebrew-core commit SHA instead of a 7-char short SHA, so the fetch stays unambiguous, and note in the comment that this commit provides gcc 16.1.0. Reword the pin comment to describe the current reason for the pin (this driver exports SOCRATES's dlsym-visible shim symbols, a newer gcc drops them) instead of narrating the migration that produced it.

Replace the prefix-match version gate with a token-and-linkage check. `brew unlink` does not remove a runner image's pre-existing gcc keg, so once a second keg coexists, `brew list --versions gcc` prints both versions on one line in Cellar order and the old prefix match fails even though the pinned version is installed. Check the version as its own token and confirm it is the linked keg.

Separate the nm capture from its failure check in the shim symbol-export guard. Under set -euo pipefail, a failing nm previously aborted the step before the custom diagnostic could print, so a real nm failure looked identical to an empty result. The existing SIGPIPE-safe here-string grep for the symbol-absent case is unchanged.
@timlichtenberg
timlichtenberg requested a review from a team as a code owner September 9, 2026 12:46
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.50%. Comparing base (6fd76e2) to head (6c0cc24).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #865      +/-   ##
==========================================
- Coverage   93.24%   91.50%   -1.75%     
==========================================
  Files         112      113       +1     
  Lines       16746    18437    +1691     
  Branches     2996     3411     +415     
==========================================
+ Hits        15615    16870    +1255     
- Misses       1125     1485     +360     
- Partials        6       82      +76     
Flag Coverage Δ
nightly 93.12% <ø> (-0.01%) ⬇️
unit-tests 87.25% <ø> (+0.53%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timlichtenberg
timlichtenberg merged commit 30bec52 into main Sep 9, 2026
34 of 35 checks passed
@timlichtenberg
timlichtenberg deleted the tl/ci-agni-shim-before-precompile branch September 9, 2026 13:20
@egpbos

egpbos commented Sep 9, 2026

Copy link
Copy Markdown
Member

If I'm not mistaken, this issue was already solved through FormingWorlds/SOCRATES#29 and FormingWorlds/SOCRATES#30. The version upper bound shouldn't be necessary anymore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants