Skip to content

Patch gfan to build on most systems - #4643

Open
d-torrance wants to merge 1 commit into
Macaulay2:developmentfrom
d-torrance:gfan
Open

Patch gfan to build on most systems#4643
d-torrance wants to merge 1 commit into
Macaulay2:developmentfrom
d-torrance:gfan

Conversation

@d-torrance

@d-torrance d-torrance commented Aug 13, 2026

Copy link
Copy Markdown
Member

In #4268, we started requiring gfan 0.8, with the idea that eventually we'll need some of its new features (and bug fixes!) in the gfanInterface package. Currently, the package still works just fine with older gfan releases like 0.6.2 and 0.7 if you comment out the check. We've had to do this in multiple places like Debian/Ubuntu, spack, RHEL, and nix because currently, 0.8 only compiles on modern systems and only when compiling with GCC.

At the Macaulay2 workshop at Warwick this summer, the package was updated so that we actually will need 0.8 for several features (see #4466), so patching out the version requirement won't work once those changes are merged.

Claude and I worked on a series of patches (also submitted upstream) that enable us to build gfan on most systems using either GCC 7+ or Clang. They're squashed into a single patch to fit our M2/libraries patch convention.

AI Disclosure

Most code was written by Claude after lots of discussion. I'll have it comment below with a summary of changes.

Draft for now to test the builds Builds passed!

@d-torrance d-torrance added build issue platform specific issues involving compiling M2, generating examples, or running tests dependencies Pull requests that update a dependency file AI-generated This PR contains AI-generated code labels Aug 13, 2026
@d-torrance

Copy link
Copy Markdown
Member Author

Written by Claude Opus 5, per the AI disclosure above. I made the changes and ran the builds below; @d-torrance reviewed them.

What was actually blocking gfan 0.8beta

Ten separate things, not one. Each row is a distinct reason a build fails, which is why the workarounds linked above ended up being "require an older gfan" rather than a fix:

Blocker Where it bites
std::execution::par for parallel loops libc++ has no <execution> at all; libstdc++ only gained it in GCC 9
std::counting_semaphore GCC 11, Xcode 14
std::pmr, woven through the core matrix/vector types GCC 9
std::erase_if, std::filesystem GCC 9 (and -lstdc++fs on GCC 8)
operator!= left to C++20's rewritten operator== GCC 9, and anything at -std=c++17
A multimap default-constructing a lambda comparator GCC 8, GCC 7
hardcoded -std=c++20 GCC 9 calls it c++2a; GCC 7 has no such flag
Makefile pins gcc-15/g++-15 on macOS macOS with the system compiler
-fno-guess-branch-probability on one object any clang
unconditional -march=native Apple Silicon, aarch64

After these, gfan compiles as C++17 and needs no compiler newer than GCC 7.5.

No new dependencies. TBB was already an unconditional link option in gfan's Makefile, so calling it directly adds nothing. The std::pmr fallback uses <experimental/memory_resource> — the Library Fundamentals TS that std::pmr came from, which libstdc++ has shipped since GCC 6 inside the library gfan already links. Nothing to add to libraries/.

Notes on the M2-side changes

  • -march=native is a no-op for us. BUILDOPTIONS replaces gfan's OPTFLAGS wholesale, so M2 builds never saw -march=native in the first place. The change matters for people building gfan directly; it does not alter what M2 produces.
  • The macOS win comes from the compiler pin. We do not pass CC/CXX to gfan, so gfan's own Makefile was choosing the compiler — and on Darwin it forced a Homebrew gcc-15. That is why building gfan needed GCC on macOS. With the pin gone, Apple Clang works, which is what the cmake/Makefile.in comments about gcc-15 were documenting and why they are deleted here.
  • VLIMIT = unlimited is a pre-existing bug fix, separable from the patch. gfan 0.8beta's make check fails 3 of 50 tests under our default ulimit -v 400000, with or without this patch — I verified both. _tropicalprevariety defaults to 8 threads, each building two 20 MB StackResources, and RLIMIT_AS counts reserved address space (8 MB per thread stack, 64 MB per glibc arena), so the requirement scales with hardware concurrency: on 2 cores it passes at 1.2 GB, on 12 cores it needs 1.6 GB. Any fixed number would be wrong on a bigger machine. There is precedent — libraries/tbb/Makefile.in already sets VLIMIT = unlimited, and eight other libraries override it. The ulimit -t 100 guard still applies; ulimit -m was already unenforced, since Linux ignores RLIMIT_RSS. If you would rather land that separately, it is one line.
  • The patch applies at both -p0 and -p1, since its paths carry the gfan0.8beta/ prefix: Makefile.library strips nothing and applies from the untar directory, while the new cmake PATCH_COMMAND strips one component and applies inside SOURCE_DIR.

Verified

Each row is a full gfan build plus gfan _test:

System Compiler Tests
Ubuntu 18.04 GCC 7.5.0 50/50
Ubuntu 20.04 GCC 9.4.0 50/50
Ubuntu 22.04 GCC 11.4.0 50/50
Ubuntu 24.04 GCC 13.3.0 50/50
Ubuntu 25.04 GCC 14.2.0 50/50
Rocky Linux 8 GCC 8.5.0 50/50
Ubuntu 24.04 Clang 18 50/50
macOS 26, arm64 Apple Clang 50/50

Also through our own machinery: a clean make -C BUILD/build/libraries/gfan check patches 14 files with no fuzz and reports 50/50.

Two gaps worth knowing about:

  • I have not run the cmake path. The PATCH_COMMAND added to build-libraries.cmake is new here and only the autotools route was exercised locally, so the CI run on this PR is the first real test of it.
  • Rocky 8 has no cddlib package, in EPEL or anywhere else, so cddlib has to be built from source there. That is unrelated to this patch, but it is a real obstacle for an RHEL 8 build.

Not addressed

  • 32-bit systems still cannot build gfan at all: the tropical homotopy code is built on __int128. Debian carries a separate patch using Abseil for its 32-bit ports.
  • gfan's PREFIX variable means both the install prefix and a path prefix for the compiler binaries, and the second assignment clears the first, so a plain make install targets /bin. Harmless for us, since we pass PREFIX on the command line, so I left it out to keep this to build fixes.

@d-torrance

Copy link
Copy Markdown
Member Author

Currently, gfan only builds on systems using a pretty modern gcc,
e.g., we've had to use gcc from homebrew on macOS instead of using
clang.  We've also had to patch gfanInterface on some older
systems (RHEL 8/9, Ubuntu 18.04) so that it would accept gfan 0.6 &
0.7 (🫲🤪🫱).  However, after the gfanInterface changes at the 2026
Warwick workshop, gfan 0.8 will be a hard requirement.

Patch written with significant help from Claude, and also submitted
upstream.

We also set VLIMIT to be unlimited, as we ran out of memory sometimes
running the tests.

[ci skip]
@d-torrance
d-torrance marked this pull request as ready for review August 13, 2026 23:54
@d-torrance

Copy link
Copy Markdown
Member Author

I had Claude put together a GitHub workflow that builds gfan on a bunch of systems, both before and after the patch, to help with reviewing:

https://github.com/d-torrance/M2/actions/runs/32182233589

@antonleykin

Copy link
Copy Markdown
Contributor

https://github.com/d-torrance/M2/actions/runs/32182233589

I guess this workflow checks only gfan (patched) build(s), but not cheking whether gfanInterface tests pass on the corresponding M2 build. Would it be easy to add the package check to this automation?

d-torrance added a commit to d-torrance/M2 that referenced this pull request Aug 20, 2026
Asked for in review: the matrix showed that the patch makes gfan build and
pass its own tests, but said nothing about whether M2 can then use the result.
Macaulay2#4643 (comment)

A new m2check phase installs a released Macaulay2 from a binary package, the
way a user of that system would -- ppa:macaulay2/macaulay2 on Ubuntu,
macaulay2.com/Repositories on Debian and Rocky, Macaulay2/tap on macOS -- puts
the gfan the patched build just produced where gfanInterface will find it, and
runs that package's tests.  Every row of the matrix has such a package, on both
architectures, so no row has to sit the question out.

Nothing is compiled in the new phase, which is the point: M2 comes from a
binary, so the only thing that differs from a stock install is the gfan
underneath it -- and it is last release's M2, not this branch, since the patch
has to work with the M2 people are running.  findProgram looks in programPaths
first, so that is what m2check.m2 sets; run.sh also moves any gfan in M2's own
programs directory aside and puts the new binary at the front of PATH, for the
tests that check runs in a subprocess rather than capturing in the same one.
If M2 nonetheless resolves gfan somewhere else, the row fails rather than
reporting a result about the wrong binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@d-torrance

Copy link
Copy Markdown
Member Author

Would it be easy to add the package check to this automation?

It's easy for Claude!

https://github.com/d-torrance/M2/actions/runs/32430419795

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

Labels

AI-generated This PR contains AI-generated code build issue platform specific issues involving compiling M2, generating examples, or running tests dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants