Skip to content

Add a portability workflow beside the existing matrix - #113

Merged
sheredom merged 8 commits into
sheredom:mainfrom
bernardladenthin:ci-portability
Aug 19, 2026
Merged

Add a portability workflow beside the existing matrix#113
sheredom merged 8 commits into
sheredom:mainfrom
bernardladenthin:ci-portability

Conversation

@bernardladenthin

@bernardladenthin bernardladenthin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

cmake.yml covers x86-64 Linux, Windows and macOS across four build types and
four compilers, and covers them well. What no matrix over runs-on can reach is
anything the runner does not select for you.

subprocess.h chooses its implementation from compile-time probes. On any given
machine exactly one combination is ever taken and the others are, at best,
compiled. The paths added by #104 and #106 have — as far as I can tell — never
been executed anywhere, only compiled. Every runner is also 64-bit, and no BSD
or non-x86 architecture appears at all.

This adds portability.yml next to cmake.yml rather than extending it, so the
existing matrix stays readable and either can be adjusted without disturbing the
other.

The red checks are the point

This PR is expected to be red, and every failure is a real defect on current
main rather than a problem with the workflow.
Where each one stands today:

Job fails because fixed by state
windows-x86 subprocess_size_t is not SIZE_T on Win32 #117 merged
linux-32-bit, linux armv7 utest.h's 64-bit constants need unsigned long long on ILP32, and PRIu64 then expands to a ll modifier that C++98 rejects sheredom/utest.h#188 merged upstream
netbsd 10.1 PRIu64 is undefined - NetBSD makes the PRI macros visible in C++ only with __STDC_FORMAT_MACROS or from C++11 on sheredom/utest.h#188 merged upstream
netbsd 9.4 posix_spawn_file_actions_addchdir only exists from NetBSD 10.0, but #102's guard has no version test #112 open
openbsd 7.9 OpenBSD has no chdir file action under either spelling, and the probe's #else says it does #112 open
linux ppc64le, linux riscv64 subprocess_fail_divzero asserts a trap that those architectures do not perform #112 open

linux s390x passes, which is the useful contrast for the last row: s390x does
trap on integer division by zero, so the same assertion is correct there and only
PowerPC and RISC-V need the guard.

windows-x86 was not in this table when the PR was opened. It went red on
the merge that brought #116 in, days later, and #117 fixed it. A defect found
within a day by a job that exists only because of this workflow is the clearest
argument for it, so it belongs on the record here.

The three utest.h rows need one more step, and the earlier wording glossed
over it.
This PR used to say that once sheredom/utest.h#188 landed those jobs
would go green. It has landed and they are still red, because the fix reaches
this repository only through the vendored test/utest.h, which is still the old
copy. Refreshing that file turns exactly those three green - measured on my fork
against a control branch differing in that one file, with the control
reproducing all eight failures job for job. That is routine vendor maintenance
and belongs in its own PR, not in this one.

What each job covers

Job Dimension Cost
capability-paths forces each combination of SUBPROCESS_HAVE_CWD, SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS, SUBPROCESS_SPAWN_VIA_FORK 3 ubuntu jobs
linux-32-bit, windows-x86 ILP32, where uint64_t becomes unsigned long long 2 jobs
musl a second libc 1 container job
foreign-arch ppc64le, riscv64, s390x, armv7 qemu-user
bsd real FreeBSD 14.3, NetBSD 9.4 and 10.1, OpenBSD 7.9 VMs 4 VM jobs
macos-deployment-target 10.14, 10.15, 11.0 3 macOS jobs

Why these, specifically

  • capability-paths is the cheapest and catches the most. It is also the
    answer to your question on Initial port for AIX #109 about testing for AIX: with Support AIX by forking where posix_spawn cannot change directory #112, the fork
    implementation is selected by a macro, so -DSUBPROCESS_SPAWN_VIA_FORK=1
    exercises it on an ordinary Ubuntu runner with no AIX in sight. That macro
    does not exist yet, so that one combination is a no-op until Support AIX by forking where posix_spawn cannot change directory #112 lands; the
    workflow says so in a comment rather than letting the green tick look like
    evidence.
  • 32-bit because uint64_t is unsigned long on LP64 and
    unsigned long long on ILP32, and -Wpedantic rejects the latter in the C90
    and C++98 modes this suite compiles under. Nothing in CI is currently 32-bit.
  • NetBSD twice, at 9.4 and 10.1, because
    posix_spawn_file_actions_addchdir arrived in 10.0 and is spelled without the
    _np suffix, so the two releases take different paths through the header.
  • macOS deployment targets because SUBPROCESS_HAVE_CWD flips at 10.15, and
    building only against the runner's own SDK never sees the older side.

Why the emulated jobs run only part of the suite

foreign-arch builds everything but runs only --filter=*divzero.

The first run of this workflow failed all four architectures on
create_ex_subprocess_create_failure_preserves_error, including s390x, which
has no architectural difference in play at all. qemu-user does not propagate the
errno that glibc's posix_spawn uses to report a failed exec — I confirmed the
same behaviour on emulated aarch64, where glibc 2.36 also fails to report, which
nobody would claim is a property of aarch64.

Running the whole suite under emulation therefore cannot go green regardless of
the code, and a permanently red job is worse than none. The build still covers
compilation for each architecture; only the process-creation tests are skipped,
and the file explains why so it does not read as suppressing inconvenient
failures.

Not included

  • Anything requiring paid or account-gated infrastructure.
  • AIX and Solaris. Free access exists through the GCC Compile Farm, but it is
    interactive SSH rather than a CI runner.

Action versions were checked against their current documentation:
cross-platform-actions/action@v1.4.0 and uraimo/run-on-arch-action@v3.

Separately and unrelated: cmake.yml still uses actions/checkout@v2, which
runs on a deprecated Node version. Happy to send that as its own one-line PR if
useful.

cmake.yml covers x86-64 Linux, Windows and macOS across four build types and
four compilers, and it covers them well. What it cannot reach is anything that
is not selected by the runner it happens to be on: the capability probes only
ever resolve one way per machine, every runner is 64-bit, and no BSD or non-x86
architecture is present at all.

The new workflow adds one job per dimension:

  capability-paths          force each combination of SUBPROCESS_HAVE_CWD,
                            SUBPROCESS_SPAWN_REPORTS_EXEC_ERRORS and
                            SUBPROCESS_SPAWN_VIA_FORK on an ordinary runner
  linux-32-bit, windows-x86 ILP32, where uint64_t becomes unsigned long long
  musl                      a second libc
  foreign-arch              ppc64le, riscv64, s390x, armv7 under qemu-user
  bsd                       real FreeBSD, NetBSD 9.4 and 10.1, OpenBSD VMs
  macos-deployment-target   10.14, 10.15 and 11.0, either side of the boundary
                            that SUBPROCESS_HAVE_CWD turns on

capability-paths is the cheapest and catches the most. The library selects its
implementation at compile time, so on any given runner the other paths are never
executed — including the ones added by sheredom#104 and sheredom#106, which as far as I can tell
had never actually run anywhere before, only compiled.

NetBSD is listed at both 9.4 and 10.1 on purpose: posix_spawn_file_actions_addchdir
arrived in 10.0 and is spelled without the _np suffix, so the two take different
paths through the header.

foreign-arch runs under qemu-user, which is faithful for instruction-set
semantics but not for posix_spawn's exec-error reporting — it does not propagate
the errno, and a create_ex failure there would be an artifact rather than a
platform property. That is noted in the file so nobody reads it the wrong way.
The first run of this workflow failed all four foreign-arch jobs, and for a
reason that has nothing to do with the architectures: qemu-user does not
propagate the errno that glibc's posix_spawn uses to report a failed exec, so
create_ex_subprocess_create_failure_preserves_error fails there regardless of
the target. s390x failed identically to ppc64le despite having no architectural
difference in play, which is what gives it away.

Running the whole suite under emulation therefore cannot go green, and a job
that is permanently red is worse than no job. These now build the whole suite —
which still covers compilation for each architecture — and run only the tests
whose outcome actually depends on the instruction set:

    ./subprocess_test '--filter=*divzero'

That is the reason the jobs exist: integer division by zero traps on x86, Arm
and s390x but not on PowerPC or RISC-V. The comment explains the narrowing so it
does not read as suppressing inconvenient failures.

Also noted in the file that the SUBPROCESS_SPAWN_VIA_FORK combination in
capability-paths is a no-op until sheredom#112 lands, so its green tick is not mistaken
for evidence today.
@bernardladenthin

Copy link
Copy Markdown
Contributor Author

The red checks are the point rather than a defect in the workflow. Each one is a
real, currently unfixed problem on main:

Job fails because fixed by
linux-32-bit utest.h constants and ll modifier on ILP32 sheredom/utest.h#188
netbsd 10.1 PRIu64 is undefined — NetBSD's libc gates the PRI macros sheredom/utest.h#188
netbsd 9.4 posix_spawn_file_actions_addchdir does not exist before 10.0 #112
openbsd 7.9 no chdir file action under either spelling #112
linux ppc64le, linux riscv64 subprocess_fail_divzero asserts a trap that does not happen #112

s390x and armv7 pass, which is the useful contrast: those architectures do
trap, so the same test is correct there.

I would rather leave them red than paper over them — they are exactly what the
workflow is for. Once #112 and sheredom/utest.h#188 land, all of these go green.

Comment thread .github/workflows/portability.yml Outdated
cmake --build build -j
ctest --test-dir build --output-on-failure

# On ILP32, uint64_t is unsigned long long, and -Wpedantic then rejects both

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLM has put unwarranted context in the yml. Please do a pass to remove comments like these.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ty! I've improved, please check again.

Reviewer feedback on sheredom#113: the file carried explanatory prose that repeated
what the job and matrix names state. The paragraphs above capability-paths,
linux-32-bit, musl and macos-deployment-target are gone entirely — "no chdir
file action, no exec error reporting" and "macOS deployment target 10.14"
already say it.

Two survive as single lines, both where deleting the comment would invite a
wrong edit rather than merely lose background:

  foreign-arch  why it runs only *divzero. Without it the narrowing looks
                like an oversight and the obvious "fix" is a permanently
                red job.
  bsd           why NetBSD is listed twice, so the duplicate does not get
                tidied away and take the 9.x path with it.

The note that SUBPROCESS_SPAWN_VIA_FORK is a no-op until sheredom#112 lands is
dropped as well. It was accurate when written but describes a moment rather
than the workflow, and it goes stale the day sheredom#112 merges.

No job, step or matrix entry is touched: 7 jobs, 17 runs, before and after.
The reasoning that left the file is preserved in the two commits that added
it and in the PR description.
@catap

catap commented Aug 15, 2026

Copy link
Copy Markdown

Here a thread where used posix_spawn_file_actions_addchdir() is suggested to OpenBSD: https://marc.info/?l=openbsd-tech&m=178601904627458&w=2

It is not yet commited and it can be part of the next release: 8.0

@bernardladenthin

Copy link
Copy Markdown
Contributor Author

Where all of this stands

subprocess.h

PR State
#112 AIX via fork/exec. All review comments addressed. Two macOS jobs red until #118 lands.
#113 This one. Red by design — every failing job is a real defect on main, each fixed by one of the others.
#117 Fixes the 32-bit Windows build broken by #116. 24/24 green.
#118 Keeps pipe ends off fd 0, 1 and 2. 24/24 green.

utest.h

PR State
sheredom/utest.h#188 32-bit under -Wpedantic, plus NetBSD C++98. Reworked to the fallback you asked for.
sheredom/utest.h#189 AIX support in utest_ns().
sheredom/utest.h#190 Test filter silently dropping tests it should run.

All three are on conclusion: action_required, so none has had a CI run since
the rework.

All seven are finished and need nothing further from me. I would rather stop
adding to the set for now: these are your projects, not mine, and I don't want
to create discussion or work you didn't ask for. My time is limited too, and
I'd rather spend it on what is actually wanted than on widening a queue.

Happy to keep helping — I have AIX access and the portability matrix set up, so
platform questions are cheap for me to answer. If that is useful, the practical
order would be to decide the current seven first. Closing the ones you don't
want is a perfectly good outcome; nothing is wasted on my side either way.

@bernardladenthin

Copy link
Copy Markdown
Contributor Author

Here a thread where used posix_spawn_file_actions_addchdir() is suggested to OpenBSD: https://marc.info/?l=openbsd-tech&m=178601904627458&w=2

It is not yet commited and it can be part of the next release: 8.0

@catap Thanks — I checked master today and it is still as you describe: no
posix_spawn_file_actions_addchdir in include/spawn.h, and no implementation
in lib/libc/gen/posix_spawn.c.

Until it ships, the behaviour in #112 is already correct for OpenBSD: it takes
the fork/exec path, which does support a working directory — just more
heavily than a file action would. When the function does land, the gate is a
one-liner mirroring the NetBSD one, using the OpenBSD macro (year and month)
from <sys/param.h>; OpenBSD8_0 is already defined on -current, so the version
side is straightforward once the function itself exists.

I would rather not gate on a release that does not carry it yet — that would be
a guess, and it compiles either way, so nothing would catch it being wrong.
Happy to add it once the commit is in.

@sheredom

Copy link
Copy Markdown
Owner

Your other PRs are merged. Time to update this to main and see if we can't get it green?

The old copy is a pristine snapshot of utest.h@f4610ee. Since then upstream
fixed the ILP32 constants and the NetBSD PRIu64 problem (#188), the test
filter dropping tests it should run (#190) and added AIX support (#189).
<sys/param.h>, included here since sheredom#112 for __NetBSD_Version__, pulls in
<sys/inttypes.h>. That header gates <machine/int_fmtio.h> on
__STDC_FORMAT_MACROS for C++ before C++11 and sits behind an include guard,
so anything defining the macro afterwards cannot get PRId64 and PRIu64 back.
Consumers including subprocess.h first lost them silently; the test suite's
C++98 translation unit is one.
sheredom#105 added -std=c++2a for GCC 8 and clang 9, which do not know -std=c++20.
GCC 7 and older know neither, so the fallback passed a flag those compilers
reject and the build stopped before running anything. NetBSD 9.4 ships GCC
7.5.0 and hit exactly this.

test20.cpp now drops the standard flag when neither spelling is available,
which is what already happens for test11.c without c_std_11 and for MSVC,
where no -std flag is passed at all. The suite then runs that translation
unit at the compiler default rather than not at all.

Measured in GCC 7.5.0: -std=c++20 and -std=c++2a are both rejected, and the
file compiles clean at the default gnu++14 under the flags CMakeLists sets.
GCC 13 still selects -std=c++20 and the suite stays at 444/444.
@sheredom

Copy link
Copy Markdown
Owner

Very good work here!

@sheredom
sheredom merged commit 8a4715c into sheredom:main Aug 19, 2026
41 checks passed
@bernardladenthin
bernardladenthin deleted the ci-portability branch August 22, 2026 11:20
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