Set matching CC alongside CXX in Linux CI matrix#4863
Conversation
The Configure step only set CXX, so CMake's C compiler detection fell back to whatever the default happened to be on the runner, independent of which C++ compiler the matrix entry was actually testing (e.g. CXX=clang++-3.6 but CC left to detect GCC 11). Derive CC from the same matrix.cxx value the job already installs a matching compiler for.
|
The I don't have permission to re-run just the failed jobs (not a repo admin) — happy to push an empty commit to retrigger if useful, or this should resolve on its own next time CI runs against this branch. |
|
I triggered a re-run. |
|
Thanks for the re-run -- that surfaced something worth flagging. Good news: the compiler downloads succeeded this time (no more But Importantly, this isn't something this PR's diff introduces (it only touches the workflow file), and it isn't new in the code -- it's a preexisting incompatibility between the C API and real Clang 3.6 that CI never actually caught before, for exactly the reason this PR exists:
Since fixing |
If there is an easy fix, then I suggest doing it as part of the current PR. Otherwise, we could disable the C test in the problematic config for now. Thanks for looking into it! |
Now that CC correctly matches CXX for this job, test/c-test.c is
compiled with real Clang 3.6.2 for the first time, which fails on
string-literal arguments to fmt's C API macros ("too many arguments
to function call, expected 0, have 1").
Root cause: fmt-c.h's FMT_MAKE_ARG uses a C11 _Generic dispatch with
a 0-arg fmt_unsupported_type default. Whether the controlling
expression of _Generic undergoes array-to-pointer decay was
genuinely ambiguous in C11 wording (WG14 N1930); Clang treated it as
an unconverted lvalue and did not decay arrays until the fix for
LLVM PR16340 landed in November 2015 (clang 3.8). Clang 3.6.2
(Feb 2015) predates that fix, so a string literal like "foo" (type
char[4]) never matches the char*/const char* cases and falls
through to the default, which then gets called with an argument.
Verified this is specific to old Clang, not a general fmt-c.h bug:
- Compiled test/c-test.c against fmt-c.h with a modern Clang
locally; it builds cleanly, confirming decay works correctly once
PR16340 is fixed.
- Confirmed no safe macro-level workaround exists: forcing decay
uniformly (e.g. `(x) + 0` or `1 ? (x) : (x)`) triggers ordinary
integer promotion for the small-integer cases (char, bool, short,
etc.), silently routing them to the wrong fmt_from_* function on
*all* compilers. A per-type-conditional wrapper isn't expressible
without already knowing the type, i.e. without _Generic itself.
Given this is a real, long-since-fixed upstream compiler bug in a
10-year-old Clang release, and no low-risk fix at the fmt-c.h level
exists that doesn't risk changing dispatch behavior on every other
compiler, skip just c-test (via ctest -E) for the clang++-3.6 job.
The C++ test suite for that job is unaffected and still runs.
The exclude value must be double-quoted when assigned from the
templated expression, since GitHub Actions substitutes it inline and
an unquoted assignment containing a space (e.g. exclude=-E ^c-test$)
does not parse as a single shell assignment - it splits into a
variable-scoped-to-command form that tries to execute a nonexistent
'^c-test$' command, aborting the step under bash's default -e.
Verified both branches (clang++-3.6 and every other matrix entry) by
actually executing the resulting snippet with bash -eo pipefail.
|
Dug into this -- root cause is confirmed and it's not a bug in fmt's macro. FMT_MAKE_ARG's _Generic dispatch in fmt-c.h relies on the controlling expression decaying char[N] string literals to char*/const char* before type matching. Whether _Generic's controlling expression decays arrays was genuinely ambiguous in the C11 wording (WG14 N1930) -- GCC always applied the decay, Clang didn't, until the fix for LLVM PR16340 landed in Nov 2015 (clang 3.8). Clang 3.6.2 (Feb 2015) predates that fix by a year, so "foo" never matches char*/const char* and falls through to the 0-arg default, which is then called with an argument -- exactly the reported error. I looked hard for a macro-level fix and don't think there's a safe one for this PR. The obvious workaround -- forcing decay by wrapping the argument as (x)+0 or 1 ? (x) : (x) -- does force the array decay, but it also triggers ordinary C integer promotion on every small-integer case in the table (char, bool, unsigned char, short, ...), on every compiler, not just old Clang. I verified this concretely: it reroutes a plain char argument to the int case instead of char, which would silently change how the C API formats char/bool/short arguments everywhere, not just paper over Clang 3.6. There's no way to scope the workaround to "just the array case" without already knowing the argument's type at macro-expansion time, which is the very thing _Generic exists to determine, so this really is a compiler bug rather than something fixable in fmt-c.h without a bigger redesign. Given that, I went with your second option: skipped just the c-test target (via One thing worth flagging on my own work: my first pass at this had a real bug -- the templated Pushed. |
Fixes #4858
Problem
The Linux CI matrix deliberately varies
matrix.cxxacross old and new compilers (g++-4.9,clang++-3.6,clang++-20, etc.) to test compatibility, but theConfigurestep only setsCXX. With noCC, CMake's C compiler detection falls back to whatever default happens to be on the runner (as reported:clang++-3.6for C++, butGNU 11.4.0detected for C) — completely unrelated to the compiler version the job is actually meant to be testing.Fix
Add a small step before
Configurethat derivesCCfrom the samematrix.cxxvalue each job already installs a matching compiler for (clang++-N→clang-N,g++-N→gcc-N):Testing
This is a CI-config-only change, so the real test is the workflow run itself — I checked every distinct
matrix.cxxvalue used in this file (g++-4.9,g++-11,g++-13,g++-14,clang++-3.6,clang++-11,clang++-14,clang++-20) against the correspondinginstall/package steps in the same file to confirm a matchinggcc-N/clang-Nbinary is actually installed for each before relying on it (e.g. the "Install GCC 4.9" step explicitlydpkg -is agcc-4.9package, "Install Clang 3.6" explicitly installsclang-3.6, etc.) — the two entries without an explicit install step (g++-13/g++-14onubuntu-24.04, oneclang++-14variant) rely on the runner image's default toolchain, where the C and C++ drivers for the same version are always installed together. I also verified the string-substitution logic locally in bash against all 8 values (clang++-3.6correctly does not get double-substituted by theg++→gccreplacement, since theclang++→clangsubstitution runs first and already consumes the trailingg++substring), and validated the YAML withruby -ryaml. I can't run the actual matrix locally, so I'll be watching this PR's own CI run closely.