From e1f6d0ed2f9c5dcc9d9e14cb151551ba6f4fa87c Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 20 Sep 2026 19:01:47 +0200 Subject: [PATCH 1/2] ci: modernize the matrix, add a 32-bit Linux row, require Nim >= 2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CI config had drifted away from what it claimed to test: - `version-1-6` asserted nothing. nim-lang/nightlies stopped producing dated 1.6 builds and its `latest-version-1-6` alias has resolved to a 2024-07-10 binary ever since, so those rows exercised a two-year-old compiler. Drop them and bump the `.nimble` floor to `nim >= 2.0.0` to match; `version-2-0` is now the oldest row and holds that floor honest. - The macOS rows asked setup-nim for `amd64`, but `macos-latest` is arm64 — that installed an x64 Nim running under Rosetta. Now `cpu: arm64` on a pinned `macos-15`, as on the other pinned runners. - "Restore Nim from cache" was dead: it gated on its own step id (always empty, so the condition was always true) and cached `nim--`, a path setup-nim never writes — it unpacks into `nim/`. Removed. Adds the 32-bit Linux rows this repo has wanted since they were first commented out. There is no 32-bit runner, so the target is reached the way Nimony's CI reaches it: `gcc`/`g++` wrappers that prepend `-m32`, placed ahead of the real ones on PATH. gcc-multilib also pulls in the libc6-i386 the 32-bit Nim nightly needs in order to run at all. This is far cheaper coverage of the pointer-sized atomics and channel alignment assumptions than the windows-i386 row, which is the only place they were exercised before. tests/nim.cfg grows a matching branch: 32-bit x86 builds with gcc and without ThreadSanitizer. TSan ships no 32-bit x86 runtime, and clang is deliberately not wrapped — left on clang this row would have silently emitted 64-bit objects and failed at link time. Verified via --genScript that all three branches resolve as intended: amd64 -> clang + TSan, linux-i386 -> gcc without TSan, windows-i386 -> clang + -m32 without TSan. Also: `--gc:arc` -> `--mm:arc`, and the test step is plain `nimble test`. Passing `--threads:on --gc:arc` there only restated tests/nim.cfg and earned a deprecation warning on every single compile. actions/cache v3 -> v5, winlibs MinGW bumped to the GCC+LLVM bundle (nim.cfg uses clang on Windows too, so clang and ld.lld have to ship alongside gcc), plus concurrency cancellation for superseded runs. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 209 ++++++++++++++++++++++++--------------- tests/nim.cfg | 34 +++++-- threading.nimble | 2 +- 3 files changed, 152 insertions(+), 93 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bcde6d..9f250a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,16 @@ name: threading -on: [push, pull_request] +on: + push: + branches: + - 'master' + pull_request: + +# One in-flight run per branch/PR. `master` is excluded from cancellation +# (github.sha makes its group unique per commit) so the history of green +# builds there stays complete. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref == 'refs/heads/master' && github.sha || '' }} + cancel-in-progress: true jobs: build: @@ -8,88 +19,145 @@ jobs: max-parallel: 20 matrix: branch: [master] + # `builder` lives on the target rather than in a keyed `include` + # because the rows need runners an os-keyed include cannot express + # (and so a future linux/arm64 row can pick the arm image). + # + # Nim branches: `devel` and `version-2-4` get the full platform + # sweep; the older lines get a smoke row each. `version-1-6` is gone + # on purpose — nim-lang/nightlies stopped building it and its + # `latest-version-1-6` alias has pointed at a 2024-07-10 binary ever + # since, so that row was asserting nothing about today's 1.6. The + # `.nimble` floor moved to `>= 2.0.0` to match, so `version-2-0` is + # now the oldest row and it is the one that holds that floor honest. target: - os: linux cpu: amd64 nim_branch: devel + builder: ubuntu-24.04 + # 32-bit is where the pointer-sized atomics and the channel's + # alignment assumptions actually differ. Linux i386 is cheap + # (a gcc-multilib wrapper, see below) and catches those far + # faster than the windows-i386 row does. - os: linux - cpu: amd64 - nim_branch: version-2-0 - - os: linux - cpu: amd64 - nim_branch: version-1-6 - # - os: linux - # cpu: i386 - # nim_branch: devel - # - os: linux - # cpu: i386 - # nim_branch: version-2-0 - # - os: linux - # cpu: i386 - # nim_branch: version-1-6 - - os: macos - cpu: amd64 + cpu: i386 nim_branch: devel + builder: ubuntu-24.04 - os: macos - cpu: amd64 - nim_branch: version-2-0 - - os: macos - cpu: amd64 - nim_branch: version-1-6 + cpu: arm64 + nim_branch: devel + builder: macos-15 - os: windows cpu: amd64 nim_branch: devel + builder: windows-2025 - os: windows + cpu: i386 + nim_branch: devel + builder: windows-2025 + + - os: linux cpu: amd64 - nim_branch: version-2-0 + nim_branch: version-2-4 + builder: ubuntu-24.04 + - os: linux + cpu: i386 + nim_branch: version-2-4 + builder: ubuntu-24.04 + - os: macos + cpu: arm64 + nim_branch: version-2-4 + builder: macos-15 - os: windows cpu: amd64 - nim_branch: version-1-6 + nim_branch: version-2-4 + builder: windows-2025 - os: windows cpu: i386 - nim_branch: devel + nim_branch: version-2-4 + builder: windows-2025 + + - os: linux + cpu: amd64 + nim_branch: version-2-2 + builder: ubuntu-24.04 - os: windows - cpu: i386 + cpu: amd64 + nim_branch: version-2-2 + builder: windows-2025 + + # The oldest line still built by nightlies, kept as the floor check. + - os: linux + cpu: amd64 nim_branch: version-2-0 - - os: windows - cpu: i386 - nim_branch: version-1-6 - include: - - target: - os: linux - builder: ubuntu-latest - - target: - os: macos - builder: macos-latest - - target: - os: windows - builder: windows-latest + builder: ubuntu-24.04 name: '${{ matrix.target.os }}-${{ matrix.target.cpu }}-nim-${{ matrix.target.nim_branch }} (${{ matrix.branch }})' - runs-on: ${{ matrix.builder }} - env: - NIM_DIR: nim-${{ matrix.target.nim_branch }}-${{ matrix.target.cpu }} - NIM_BRANCH: ${{ matrix.target.nim_branch }} - NIM_ARCH: ${{ matrix.target.cpu }} + runs-on: ${{ matrix.target.builder }} steps: + - name: set `core.autocrlf` to false + shell: bash + run: | + git config --global core.autocrlf false + git config --global init.defaultBranch master + - name: Checkout threading uses: actions/checkout@v5 with: path: threading submodules: false + - name: Install dependencies (Linux i386) + if: runner.os == 'Linux' && matrix.target.cpu == 'i386' + shell: bash + working-directory: ${{ github.workspace }} + run: | + # There is no 32-bit GitHub runner: the host stays x86_64 and the + # 32-bit target is reached by putting `gcc -m32` wrappers ahead of + # the real ones on PATH. gcc-multilib also pulls in libc6-i386, + # which is what lets the 32-bit Nim nightly run here at all. + # + # tests/nim.cfg switches this row to `--cc:gcc` for the same + # reason: clang is deliberately NOT wrapped, and would otherwise + # emit 64-bit objects that fail to link. + sudo dpkg --add-architecture i386 + sudo apt-get update -qq + sudo DEBIAN_FRONTEND='noninteractive' apt-get install \ + --no-install-recommends -yq gcc-multilib g++-multilib \ + libssl-dev:i386 + + mkdir -p external/bin + cat << EOF > external/bin/gcc + #!/bin/bash + + exec $(which gcc) -m32 "\$@" + EOF + + cat << EOF > external/bin/g++ + #!/bin/bash + + exec $(which g++) -m32 "\$@" + EOF + + chmod 755 external/bin/gcc external/bin/g++ + + echo "$PWD/external/bin" >> "${GITHUB_PATH}" + - name: Restore MinGW-W64 (Windows) from cache if: runner.os == 'Windows' id: windows-mingw-cache - uses: actions/cache@v3 + uses: actions/cache@v5 with: path: external/mingw-${{ matrix.target.cpu }} - key: 'mingw-${{ matrix.target.cpu }}' + # Bump the suffix when changing MINGW_URL below — the old cache + # would otherwise keep delivering the previous bundle even though + # the archive name and contents changed. + key: 'mingw-${{ matrix.target.cpu }}-gcc14-llvm19' - name: Restore Nim DLLs dependencies (Windows) from cache if: runner.os == 'Windows' id: windows-dlls-cache - uses: actions/cache@v3 + uses: actions/cache@v5 with: path: external/dlls-${{ matrix.target.cpu }} key: 'dlls-${{ matrix.target.cpu }}' @@ -101,11 +169,14 @@ jobs: shell: bash run: | mkdir -p external + # The GCC+LLVM winlibs variant, not the gcc-only one: tests/nim.cfg + # builds with clang on Windows too, so `clang` and `ld.lld` have to + # ship alongside `gcc` here. if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then - MINGW_URL="https://github.com/brechtsanders/winlibs_mingw/releases/download/14.1.0posix-18.1.8-12.0.0-ucrt-r3/winlibs-x86_64-posix-seh-gcc-14.1.0-mingw-w64ucrt-12.0.0-r3.7z" + MINGW_URL="https://github.com/brechtsanders/winlibs_mingw/releases/download/14.2.0posix-19.1.7-12.0.0-msvcrt-r3/winlibs-x86_64-posix-seh-gcc-14.2.0-llvm-19.1.7-mingw-w64msvcrt-12.0.0-r3.7z" ARCH=64 else - MINGW_URL="https://github.com/brechtsanders/winlibs_mingw/releases/download/14.1.0posix-18.1.8-12.0.0-ucrt-r3/winlibs-i686-posix-dwarf-gcc-14.1.0-mingw-w64ucrt-12.0.0-r3.7z" + MINGW_URL="https://github.com/brechtsanders/winlibs_mingw/releases/download/14.2.0posix-19.1.7-12.0.0-msvcrt-r3/winlibs-i686-posix-dwarf-gcc-14.2.0-llvm-19.1.7-mingw-w64msvcrt-12.0.0-r3.7z" ARCH=32 fi curl -L "$MINGW_URL" -o "external/mingw-${{ matrix.target.cpu }}.7z" @@ -123,23 +194,12 @@ jobs: 7z x -y external/windeps.zip -oexternal/dlls-${{ matrix.target.cpu }} - name: Path to cached dependencies (Windows) - if: > - runner.os == 'Windows' + if: runner.os == 'Windows' shell: bash run: | echo '${{ github.workspace }}'"/external/mingw-${{ matrix.target.cpu }}/bin" >> $GITHUB_PATH echo '${{ github.workspace }}'"/external/dlls-${{ matrix.target.cpu }}" >> $GITHUB_PATH - - name: Restore Nim from cache - if: > - steps.nim-compiler-cache.outputs.cache-hit != 'true' && - matrix.target.nim_branch != 'devel' - id: nim-compiler-cache - uses: actions/cache@v3 - with: - path: '${{ github.workspace }}/nim-${{ matrix.target.nim_branch }}-${{ matrix.target.cpu }}' - key: 'nim-${{ matrix.target.cpu }}-${{ matrix.target.nim_branch }}' - - name: Setup Nim uses: alaviss/setup-nim@0.1.1 with: @@ -148,26 +208,11 @@ jobs: architecture: ${{ matrix.target.cpu }} - name: Run tests + # No flags here: `--threads:on`, `--mm:arc` and the per-target + # compiler/sanitizer choice all live in tests/nim.cfg, which nimble + # picks up because it is next to the test files. Passing `--gc:arc` + # on the command line only re-stated it and earned a deprecation + # warning on every single compile. run: | cd threading - nimble --threads:on --gc:arc test - - # - name: Build docs - # if: matrix.branch == 'devel' - # run: nimble docs - - # - name: Deploy docs - # # to view docs on your own fork: push a gh-pages branch on your fork, - # # enable gh-pages in your fork - # # and remove `github.ref == 'refs/heads/master'` below - # if: | - # github.event_name == 'push' && github.ref == 'refs/heads/master' && - # matrix.target.os == 'linux' && matrix.target.cpu == 'amd64' && - # matrix.branch == 'devel' - # uses: crazy-max/ghaction-github-pages@v1 - # with: - # build_dir: fusion/htmldocs - # target_branch: gh-pages - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + nimble test diff --git a/tests/nim.cfg b/tests/nim.cfg index b65e936..4bc5d2d 100644 --- a/tests/nim.cfg +++ b/tests/nim.cfg @@ -1,14 +1,28 @@ --path:"../" ---gc:arc +--mm:arc --threads:on --define:useMalloc ---cc:clang -@if not windows: - --debugger:native - --passc:"-fsanitize=thread -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" - --passl:"-fsanitize=thread -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" + +@if i386: + @if windows: + # The MinGW bundle CI installs for this row is the i686 one, but the Nim + # nightly still drives it as a cross-compiler, so the -m32 has to be + # spelled out. + --cc:clang + --passc:"-m32" + --passl:"-m32" + @else: + # ThreadSanitizer ships no 32-bit x86 runtime, so the sanitised build below + # cannot be used here. The 32-bit toolchain CI installs is the + # gcc-multilib wrapper pair (see .github/workflows/ci.yml); clang is not + # wrapped and would silently emit 64-bit objects. + --cc:gcc + @end +@else: + --cc:clang + @if not windows: + --debugger:native + --passc:"-fsanitize=thread -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" + --passl:"-fsanitize=thread -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" + @end @end -@if windows and i386: - --passc:"-m32" - --passl:"-m32" -@end \ No newline at end of file diff --git a/threading.nimble b/threading.nimble index 578f083..6c9d89e 100644 --- a/threading.nimble +++ b/threading.nimble @@ -7,4 +7,4 @@ license = "MIT" # Dependencies -requires "nim >= 1.6.0" +requires "nim >= 2.0.0" From 0058af1ac194a03e513f203ea45863b53512eee9 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 20 Sep 2026 22:17:58 +0200 Subject: [PATCH 2/2] ci: seed the nimble package list so windows-i386 can run the tests The two windows-i386 rows were the only red ones in the previous run, and they never reached a compiler. `nimble test` refreshes the official package list on a runner with no `~/.nimble` yet, and the 32-bit Windows nimble binary segfaults doing it: Downloading Official package list SIGSEGV: Illegal storage access. (Attempt to read from nil?) Making the test step a bare `nimble test` is what put that refresh on the critical path. The crash is nimble's own, not this library's and not the stdlib's: the same 32-bit Nim completes an HTTPS fetch of the same URL through std/httpclient, a plain-http list URL crashes identically (so it is not SSL), 64-bit nimble refreshes fine from the same machine, and `--offline` does not skip the download. Compiling and running the tests directly with the row's own toolchain passes 14/14, so nothing under threading/ or tests/nim.cfg is at fault. Writing the list ourselves leaves nimble nothing to refresh. It is taken from the packages repo rather than packages.nim-lang.org, which answers 403 to anything that is not nimble; the result is checksum-identical to what a working `nimble refresh` leaves behind. Verified against the CI toolchain reproduced locally -- the windows_x32 devel and version-2-4 nightlies, the same winlibs i686 GCC+LLVM bundle and windeps DLLs, with a pristine nimble home. Both rows segfault without this step and report `Success: All tests passed` with it. On a 64-bit row the step is inert: nimble skips the download and goes straight to compiling. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f250a9..7b8038d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -207,6 +207,33 @@ jobs: version: ${{ matrix.target.nim_branch }} architecture: ${{ matrix.target.cpu }} + - name: Seed the nimble package list + shell: bash + run: | + # `nimble test` refreshes the official package list on a runner that + # has no `~/.nimble` yet, and the 32-bit Windows nimble binary + # segfaults doing it: + # + # Downloading Official package list + # SIGSEGV: Illegal storage access. (Attempt to read from nil?) + # + # It dies there before compiling a single test, which is why the two + # windows-i386 rows were the only red ones. The crash is nimble's + # own: the same 32-bit Nim fetches the same URL over HTTPS fine via + # std/httpclient, a plain-http list URL crashes identically, and + # `--offline` does not skip the download. Writing the list ourselves + # means nimble has nothing to refresh. Harmless on the rows that + # already work, and it keeps every row off nimble's network path. + # + # Fetched from the packages repo rather than from + # packages.nim-lang.org, which serves a 403 to anything that is not + # nimble (curl gets it with or without a browser User-Agent). The + # two are the same bytes: the file this writes is checksum-identical + # to the one a working `nimble refresh` leaves behind. + mkdir -p "$HOME/.nimble" + curl -fsSL https://raw.githubusercontent.com/nim-lang/packages/master/packages.json \ + -o "$HOME/.nimble/packages_official.json" + - name: Run tests # No flags here: `--threads:on`, `--mm:arc` and the per-target # compiler/sanitizer choice all live in tests/nim.cfg, which nimble