Skip to content

make check fails in test/CXX with GCC 15: enum {false=0, true=1} in testutils.h #240

Closed

Description

@xylar

Summary

With GCC 15 (which defaults to C23), ./configure reports checking for stdbool.h that conforms to C99... no. As a result HAVE_STDBOOL_H is never defined and test/common/testutils.h falls back to enum {false=0, true=1};, which is a hard error when the header is included from C++ in test/CXX:

In file included from nctst.cpp:20:
./../common/testutils.h:26:7: error: expected identifier before 'false'
   26 | enum {false=0, true=1};

Seen with PnetCDF 1.15.1 (also affects 1.15.0) on Linux x86_64 and aarch64 with GCC 15 and MPICH 5, OpenMPI 5 and MVAPICH, while building the conda-forge package (conda-forge/libpnetcdf-feedstock#14). The same fallback is present in src/utils/ncmpidump/ncmpidump.h, so ncmpidump is silently built with typedef int boolean instead of <stdbool.h> under C23 as well.

Cause

The shipped configure is generated by Autoconf 2.71, whose AC_HEADER_STDBOOL test contains #ifndef bool / #error "bool is not defined" for non-C++ compilers. In C23, bool, true and false are keywords rather than macros, so the test fails even though <stdbool.h> is fully conforming. Autoconf 2.72 fixed this test. The check can be reproduced by compiling the conftest.c from the configure log with gcc -std=gnu2x.

Suggested fixes

  • Regenerate configure with Autoconf >= 2.72, and/or
  • Make the fallback in testutils.h and ncmpidump.h robust: skip the enum when __cplusplus is defined (C++ already has true/false), e.g. #if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L).

Workaround

Passing ac_cv_header_stdbool_h=yes in the environment when running ./configure restores the intended behaviour.

Activity

  1. wkliao commented on Sep 21, 2026

    @wkliao
    Member

    Thanks for reporting.
    However, I do not have an access to GCC 15.
    Can you confirm that the solution of using Autoconf 2.72 alone fixes the problem?

  2. xylar commented on Sep 21, 2026

    @xylar
    Author

    Confirmed: regenerating configure with Autoconf 2.72 alone fixes it. Tested on the pnetcdf-1.15.1 tarball with GCC 15.3.0 and MPICH 5 on Linux x86_64, no other changes.

    checking for stdbool.h ... config.h make -C test/CXX check
    shipped configure (Autoconf 2.71) no /* #undef HAVE_STDBOOL_H */ fails: testutils.h:26:7: error: expected identifier before 'false'
    after autoreconf -fi (Autoconf 2.72) yes #define HAVE_STDBOOL_H 1 PASS: nctst, PASS: test_classic

    The full library builds fine in both cases; only the C++ tests break with the shipped script.

    If you don't have GCC 15 handy, the easiest way to reproduce is with a conda environment, which provides GCC 15, MPICH 5 and Autoconf 2.72 without touching the system. With miniforge (or any conda/mamba) installed:

    # one-off environment: GCC 15 toolchain, MPICH 5, Autoconf 2.72
    conda create -y -p ./gcc15env -c conda-forge \
        "gcc_linux-64=15" "gxx_linux-64=15" "gfortran_linux-64=15" \
        "mpich=5" "autoconf>=2.72" automake libtool m4 make
    conda activate ./gcc15env
    
    # the conda mpicc/mpicxx/mpifort wrappers pick up GCC 15 via $CC/$CXX/$FC
    export MPICC=mpicc MPICXX=mpicxx MPIF77=mpifort MPIF90=mpifort
    
    # A: shipped configure -> reproduces the failure
    tar xzf pnetcdf-1.15.1.tar.gz && mv pnetcdf-1.15.1 srcA
    (cd srcA && ./configure --enable-shared --disable-static | grep stdbool && make -j8 && make -C test/CXX check)
    
    # B: regenerate with Autoconf 2.72 -> passes
    tar xzf pnetcdf-1.15.1.tar.gz && mv pnetcdf-1.15.1 srcB
    (cd srcB && autoreconf -fi && ./configure --enable-shared --disable-static | grep stdbool && make -j8 && make -C test/CXX check)

    (On aarch64 replace linux-64 with linux-aarch64 in the package names.)

    The enum {false=0, true=1}; fallback in testutils.h and ncmpidump.h is still not valid C++, so a #ifndef __cplusplus guard there would make the headers robust regardless of which Autoconf produced configure, but it is not needed to fix this report.


    Posted by Claude Code on behalf of @xylar.

  3. xylar commented on Sep 21, 2026

    @xylar
    Author

    @wkliao, I let Claude Code to the testing but I stand by the finding. Thanks for having a look!

  4. wkliao commented on Sep 21, 2026

    @wkliao
    Member

    I have created PR #241 to address this issue.
    It adopts your suggestion. Please let me know if it looks OK with you.

  5. wkliao commented on Sep 21, 2026

    @wkliao
    Member

    @xylar
    Also, thanks for providing the instructions for running and setting the condo environment.
    I wonder if they can be done as a Github CI workflow.

  6. xylar commented on Sep 21, 2026

    @xylar
    Author

    @wkliao, yes for sure they can be.

  7. wkliao commented on Sep 21, 2026

    @wkliao
    Member

    Do you have an example I can follow to write one for PnetCDF?

  8. xylar commented on Sep 22, 2026

    @xylar
    Author

    @wkliao, here are a few projects whose GitHub Actions CI builds and tests Fortran/MPI codes using the conda-forge toolchain. I have picked ones with short, readable workflows rather than the conda-forge feedstock machinery, which is more complex than you need.

    For PnetCDF specifically, a minimal workflow following the same pattern would look like the one below. It is the GitHub Actions equivalent of the steps I posted above, and I have verified the underlying commands locally: on the 1.15.1 tarball with GCC 15.3 and MPICH 5 from conda-forge, autoreconf -fi && ./configure && make && make check passes all 115 tests. The point of the gcc matrix axis is that conda-forge lets you pick any recent GCC (13, 14, 15) on the same ubuntu-latest runner, independent of what Ubuntu ships, so you can catch issues like this one before they reach downstream packagers. The wrappers mpicc/mpicxx/mpifort from conda-forge's mpich and openmpi automatically use the conda compilers via $CC/$CXX/$FC, so no extra configuration is needed. Since the workflow regenerates configure with the environment's Autoconf, it also means the shipped configure is never what gets tested; if you want to test that too, simply drop the autoreconf step in one of the matrix entries.

    name: CI - conda-forge toolchain
    
    on: [push, pull_request, workflow_dispatch]
    
    jobs:
      build:
        runs-on: ubuntu-latest
        strategy:
          fail-fast: false
          matrix:
            mpi: [mpich, openmpi]
            gcc: ["14", "15"]
        defaults:
          run:
            shell: bash -el {0}
        steps:
          - uses: actions/checkout@v4
    
          - uses: mamba-org/setup-micromamba@v2
            with:
              environment-name: ci
              create-args: >-
                gcc_linux-64=${{ matrix.gcc }}
                gxx_linux-64=${{ matrix.gcc }}
                gfortran_linux-64=${{ matrix.gcc }}
                ${{ matrix.mpi }}
                autoconf>=2.72 automake libtool m4 make
    
          - name: Build and test
            env:
              MPICC: mpicc
              MPICXX: mpicxx
              MPIF77: mpifort
              MPIF90: mpifort
              # let OpenMPI start more ranks than the runner has cores
              OMPI_MCA_rmaps_base_oversubscribe: "yes"
            run: |
              autoreconf -fi
              ./configure --enable-shared --disable-static
              make -j4
              make check

    macOS can be added as a second runs-on value by swapping the three *_linux-64 packages for clang_osx-arm64 clangxx_osx-arm64 gfortran_osx-arm64, or by using the platform-neutral c-compiler cxx-compiler fortran-compiler metapackages if you do not need to pin a specific GCC version.


    Posted by Claude Code on behalf of @xylar.

  9. wkliao commented on Sep 23, 2026

    @wkliao
    Member

    Hi @xylar

    PnetCDF requires libtool version 2.5.4.
    How do I modify this line to specify the version number?

                autoconf>=2.72 automake libtool m4 make
    

    I tried libtool=2.5.4, but it did not work.

  10. wkliao commented on Sep 23, 2026

    @wkliao
    Member

    Please ignore my question. I misspelled the command.
    Continue trying the CI using Conda-forge.

  11. wkliao commented on Sep 23, 2026

    @wkliao
    Member

    One question. Once the Conda-forge environment is set, I can see the followings.

        gcc_impl_linux-64         14.4.0   h33e224c_5             conda-forge
        gcc_linux-64              14.4.0   hc469d41_2             conda-forge
        gfortran_impl_linux-64    14.4.0   he75539a_5             conda-forge
        gfortran_linux-64         14.4.0   ha2afc21_2             conda-forge
        gxx_impl_linux-64         14.4.0   hc436dd5_5             conda-forge
        gxx_linux-64              14.4.0   h0047a55_2             conda-forge
    

    But command gcc is still pointing to /usr/bin/gcc.
    What would be the right command for gcc?
    Is it gcc_impl_linux-64 or gcc_linux-64 ?

  12. xylar commented on Sep 24, 2026

    @xylar
    Author

    Short answer: nothing is wrong, and you do not need to change anything. gcc still resolving to /usr/bin/gcc is intentional, and the conda-forge compiler is nevertheless the one your build uses, because PnetCDF is configured through the MPI wrappers. You can verify that directly with:

    $ mpicc -show
    x86_64-conda-linux-gnu-cc -I$CONDA_PREFIX/include ... -lmpi
    
    $ mpifort -show
    x86_64-conda-linux-gnu-gfortran -I$CONDA_PREFIX/include ... -lmpifort -lmpi

    conda-forge deliberately does not install a plain gcc into the environment, so that the system compiler is not shadowed. The compilers are installed under their cross-compilation triplet names instead, and on linux-64 the commands are:

    $ ls $CONDA_PREFIX/bin/x86_64-conda-linux-gnu-*
    x86_64-conda-linux-gnu-gcc       x86_64-conda-linux-gnu-g++     x86_64-conda-linux-gnu-gfortran
    x86_64-conda-linux-gnu-cc        x86_64-conda-linux-gnu-c++     x86_64-conda-linux-gnu-ar  (etc.)

    To answer the literal question, neither of the two names you listed is a command. gcc_impl_linux-64 is the package holding the actual compiler; gcc_linux-64 is the thin "activation" package that you install, and it is what provides the triplet-prefixed wrappers and sets the environment variables below when the environment is activated. The same pattern applies to gxx_linux-64 and gfortran_linux-64.

    Rather than spelling out the triplet, the portable way to refer to the compilers is through the variables that these packages export on activation. On my machine, with gcc_linux-64=15:

    variable value
    CC x86_64-conda-linux-gnu-cc
    CXX x86_64-conda-linux-gnu-c++
    FC, F77, F90, F95 $CONDA_PREFIX/bin/x86_64-conda-linux-gnu-gfortran
    GCC, GXX, GFORTRAN the gcc / g++ / gfortran names
    CPP, AR, RANLIB, LD, NM, AS, STRIP, OBJDUMP, READELF, ... matching binutils
    HOST, BUILD x86_64-conda-linux-gnu
    CFLAGS, CXXFLAGS, FFLAGS, CPPFLAGS, LDFLAGS conda-forge's default optimization/hardening flags
    DEBUG_CFLAGS, DEBUG_CXXFLAGS, DEBUG_FFLAGS, ... the -Og -g -Wall counterparts

    So $CC --version works anywhere, and in a GitHub Actions job you can assert the version you think you are testing:

    - run: $CC --version   # x86_64-conda-linux-gnu-cc (conda-forge gcc 15.3.0-5) 15.3.0

    To discover this for yourself in any environment, the activation scripts are plain shell and can be listed and read:

    $ ls $CONDA_PREFIX/etc/conda/activate.d/
    activate-binutils_linux-64.sh  activate-gcc_linux-64.sh  activate-gfortran_linux-64.sh  activate-gxx_linux-64.sh

    and this prints exactly what activation changed:

    $ env | sort > /tmp/before.txt
    $ conda activate ./myenv
    $ env | sort > /tmp/after.txt
    $ diff /tmp/before.txt /tmp/after.txt | grep -v CONDA_BACKUP

    The reference documentation is conda-forge's knowledge base on compilers and conda-build's compiler-tools page, which lists the full set of variables.

    Two small clarifications, one of which corrects something I wrote earlier:

    • I previously said the MPI wrappers pick up the conda compiler "via $CC". That is not the mechanism: conda-forge's mpicc has the compiler baked in at build time (CC="x86_64-conda-linux-gnu-cc" near the top of the script), which is why it keeps working even with CC unset. The practical outcome is the same, but if you ever do want to override it, the wrapper honours MPICH_CC / MPICH_FC (and OMPI_CC / OMPI_FC for OpenMPI), not CC.
    • Activation also sets CPPFLAGS to include -DNDEBUG -O2, and CFLAGS to a portable baseline such as -march=nocona -mtune=haswell. configure will inherit these. That is usually what you want in CI, but if you want a debug build you should pass your own CFLAGS/CPPFLAGS to configure explicitly, or use the DEBUG_* variants.

    Also, from the list you posted the environment resolved GCC to 14.4.0 rather than 15. If you want to be sure you are exercising the C23 default that triggered this issue, pin all three to the same major version, e.g. gcc_linux-64=15 gxx_linux-64=15 gfortran_linux-64=15, and check with $CC --version as above.


    Posted by Claude Code on behalf of @xylar.

  13. wkliao commented on Sep 24, 2026

    @wkliao
    Member

    Thanks. These information are very helpful for a Conda-Forge beginner like me.

    I encountered an error related to running MPI jobs.

    [1790203248.379116] [runnervmtr4k5:56180:0]      ucp_worker.c:1492 UCX  ERROR uct_iface_open(ud_verbs/mana_0:1) failed: Address not valid
    Abort(740443279): Fatal error in internal_Init: Other MPI error, error stack:
    internal_Init(70)........: MPI_Init(argc=0x7fff8aaad0cc, argv=0x7fff8aaad0c0) failed
    

    The Github Copilt suggests this solution.

            - name: Configure UCX for GitHub-hosted runners
              if: ${{ matrix.mpi_vendor == 'mpich' }}
              run: |
                {
                  echo "UCX_TLS=tcp,sm,self"
                  echo "UCX_LOG_LEVEL=error"
                } >> "$GITHUB_ENV"
    

    It dose solve the problem, However, I found that running 6 MPI processes is every
    slow, much slower than 4 processes. It is also much slower than not under Conda-Forge.
    Is this a known issue, or is there a fix to improve the speed?
    Many of PnetCDF tests (when running command make ptest) run more than 4 MPI processes.

  14. xylar commented on Sep 24, 2026

    @xylar
    Author

    I think this seems like a limitation of CI. running MPI on CI is very slow because it often only provides 2 cores (maybe 4 with threading) and 6 is badly over-subscribing. But I can only say this based on my vague intuition. I don't really know the details. Let's see what Claude has to say...

  15. xylar commented on Sep 24, 2026

    @xylar
    Author

    @wkliao, @xylar's intuition is right: this is oversubscription. The reason it is so much worse than your existing CI is the MPICH device, not conda-forge.

    • Your current workflows build MPICH with --with-device=ch3:sock. A rank that is waiting blocks in poll() on a socket and gives up its core, so running more ranks than cores costs little.
    • conda-forge's MPICH (on linux-64) is built with --with-device=ch4:ucx,ofi and uses ch4 shared memory within a node. Waiting ranks busy-poll. With 6 ranks on the runner's 4 vCPUs, the ranks that are spinning take CPU time away from the ranks they are waiting for, and every collective falls off a cliff. The UCX error was a separate problem: UCX finds the runner's Azure mana RDMA device, which it cannot use. UCX_TLS=tcp,sm,self is a fine fix for that, but it has no effect on the polling.

    MPICH has a CVAR for exactly this. After a number of empty polls it calls usleep(1):

    MPIR_CVAR_CH4_PROGRESS_THROTTLE: "1"
    MPIR_CVAR_CH4_PROGRESS_THROTTLE_NO_PROGRESS_COUNT: "64"

    I measured this locally with conda-forge MPICH 5.0.1. The test was a small loop of imbalanced compute plus Allreduce/Sendrecv/Barrier, with the ranks pinned to 4 cores using taskset to mimic the runner:

    ranks on 4 cores default throttle only (count 4096) throttle, count 64
    4 0.29 s 0.31 s 0.30 s
    6 14.7 s 7.2 s 0.71 s
    8 22.0 s 16.5 s 0.89 s
    10 – – 0.99 s

    6 ranks on 2 cores took 0.95 s with the same settings. Lowering the count is what matters. The default of 4096 only helps halfway, and 16–64 worked best. Throttling made no measurable difference when ranks ≤ cores. Two other settings did not help: MPIR_CVAR_POLLS_BEFORE_YIELD=10 (together with MPIR_CVAR_POSIX_POLL_FREQUENCY=10) still took 11 s at 6 ranks, and switching the netmod to OFI (MPIR_CVAR_CH4_NETMOD=ofi FI_PROVIDER=tcp) took 10 s.

    So I would add these to the job's env: next to the UCX settings, only for the MPICH entries:

    UCX_TLS: tcp,sm,self
    UCX_LOG_LEVEL: error
    MPIR_CVAR_CH4_PROGRESS_THROTTLE: "1"
    MPIR_CVAR_CH4_PROGRESS_THROTTLE_NO_PROGRESS_COUNT: "64"

    I have not run make ptest itself with these settings, so please let us know whether it gets back to the timings you see with ch3:sock.


    Posted by Claude Code on behalf of @xylar.

  16. wkliao commented on Sep 24, 2026

    @wkliao
    Member

    That trick does make the CI run faster when using MPICH, but not OpenMPI.
    Do you know (or maybe ask Claude Code) a similar trick for OpenMPI?

  17. xylar commented on Sep 25, 2026

    @xylar
    Author

    @wkliao, yes. OpenMPI has two problems on these runners, and three environment variables fix both:

    OMPI_MCA_pml: ob1
    OMPI_MCA_btl: self,sm
    OMPI_MCA_mpi_yield_when_idle: "1"
    • Slow start-up. By default, every MPI_Init checks for network hardware (libfabric/OFI and UCX). That adds more than 1 second per mpirun, and the test scripts call mpirun about 15 times per test. That is why OpenMPI's make check was slow even with few processes. Everything runs on one node, so the first two settings limit OpenMPI to shared memory. Start-up drops from about 1.8 s to about 0.5 s.
    • Oversubscription. This is the same busy-polling problem as MPICH. mpi_yield_when_idle is OpenMPI's equivalent of the MPICH throttle setting: a rank that is waiting gives up its core.

    On a local machine with the tests pinned to 4 cores, make ptest6 in examples/C took 1098 s with the defaults, 450 s with the first two settings, and 318 s with all three.

    I tried this on a test PR in my fork: xylar#1 (CI run). It is your conda-forge-CI branch plus one commit, which you can cherry-pick:

    xylar@18a852e

    It adds a step that sets these variables only for the OpenMPI jobs.

    OpenMPI jobs your last run with the settings
    make check 7–8 min 2–3 min
    make ptests cancelled after 100+ min 33–39 min

    All 8 jobs pass, and the OpenMPI jobs now finish before the MPICH ones.


    Posted by Claude Code on behalf of @xylar.

  18. wkliao commented on Sep 25, 2026

    @wkliao
    Member

    Thanks. That fix works.

    I will proceed to create 2 PRs.
    One is related to the GCC 15 issue and the other a new workflow for Conda-forge CI.

  19. xylar commented on Sep 26, 2026

    @xylar
    Author

    Wonderful, thanks so much!

  20. added a commit that references this issue on Sep 26, 2026
  21. wkliao commented on Sep 27, 2026

    @wkliao
    Member

    Hi @xylar
    PR #241 and #243 have been created based on your suggestions and merged into the master branch.
    Many thanks !

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions