Skip to content

Many fixes and improvements to mise. #100

Many fixes and improvements to mise.

Many fixes and improvements to mise. #100

Workflow file for this run

# This file is flagrantly "vibe-coded". It may not be up to the standards of most libCat code.

Check failure on line 1 in .github/workflows/ci.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/ci.yml

Invalid workflow file

(Line: 131, Col: 27): Unrecognized named-value: 'runner'. Located at position 1 within expression: runner.temp
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
toolchain:
# Prime the mise cache once so parallel jobs do not all download and extract
# the Clang package set on a cold cache in the same workflow run.
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
lscpu:
# Log host CPU and ISA up front so a CPU-dependent failure
# anywhere in the matrix is diagnosable from the run log. The
# `ubuntu-24.04` label does not pin a microarchitecture. In
# practice based on previous runs, the Azure SKU rotates
# across Intel Ice Lake-SP (Xeon Platinum 8370C) and AMD
# EPYC (Milan-era 7763, Genoa-era 9V74) within the same
# hour, with no advance notice and with materially different
# feature flags (AVX-512, AVX-VNNI, GFNI-on-VEX, etc.).
runs-on: ubuntu-24.04
needs: toolchain
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
- name: System specs
run: |
set -eu
flags=$(awk -F: '/^flags/ { gsub(/^ +/, "", $2); print $2; exit }' /proc/cpuinfo)
avx512_flags=$(printf '%s\n' ${flags} \
| grep -Ex 'avx512.*' \
| sort -u \
| tr '\n' ' ' || true)
if [ -n "${avx512_flags}" ]; then
printf 'AVX-512: **yes** (%s)\n' "${avx512_flags}"
else
echo 'AVX-512: **no**'
fi
echo "::group::OS / kernel"
if [ -r /etc/os-release ]; then
. /etc/os-release
printf 'distro: %s %s (%s)\n' \
"${NAME:-?}" "${VERSION:-?}" "${VERSION_CODENAME:-?}"
fi
printf 'kernel: %s\n' "$(uname -srv)"
printf 'arch: %s\n' "$(uname -m)"
echo "::endgroup::"
echo "::group::CPU (lscpu)"
lscpu | grep -E \
'^(Architecture|Byte Order|Vendor ID|Model name|CPU family|Model|Stepping|CPU\(s\)|Thread|Socket|Core|CPU max MHz)' \
|| true
echo "::endgroup::"
echo "::group::ISA extensions (/proc/cpuinfo flags)"
show() {
label="$1"; pat="$2"; matches=
matches=$(printf '%s\n' ${flags} \
| grep -Ex "${pat}" \
| sort -u \
| tr '\n' ' ' || true)
printf '%-10s %s\n' "${label}:" "${matches:--}"
}
show 'SIMD' 'sse|sse2|pni|ssse3|sse4_1|sse4_2|avx|avx2|avx512.*|f16c|fma|xop|3dnow.*'
show 'Bitmanip' 'bmi1|bmi2|abm|tbm|lzcnt|popcnt|movbe|adx'
show 'Crypto' 'aes|vaes|sha_ni|sha512|pclmulqdq|vpclmulqdq|gfni|rdrand|rdseed'
show 'Other' 'rdtscp|clflushopt|clwb|fsgsbase|cldemote|movdiri|movdir64b|waitpkg|serialize'
echo "::endgroup::"
echo "::group::Clang -march=native predefined feature macros"
# Clang's source of truth for codegen. Cross-check against
# /proc/cpuinfo above when a feature appears in one but not
# the other (e.g. AVX-512 masked off by clang's CPU model).
clang++ -dM -E -x c++ -march=native /dev/null \
| grep -E '^#define __(SSE|AVX|F16C|FMA|XOP|BMI|LZCNT|POPCNT|MOVBE|AES|VAES|SHA|PCLMUL|VPCLMULQDQ|GFNI|ADX|RDRND|RDSEED|CLFLUSHOPT|CLWB|FSGSBASE|MWAIT|MWAITX|CLDEMOTE|RDTSCP|MOVDIR|WAITPKG|SERIALIZE)' \
| sort
echo "::endgroup::"
changes:
runs-on: ubuntu-24.04
outputs:
gdb_symlink: ${{ steps.changed.outputs.gdb_symlink }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect gdb_symlink changes
id: changed
run: |
set -eu
if [ "${{ github.event_name }}" = "pull_request" ]; then
base="${{ github.event.pull_request.base.sha }}"
else
base="${{ github.event.before }}"
fi
if [ -z "${base}" ] || [ "${base}" = "0000000000000000000000000000000000000000" ]; then
changed="true"
elif git diff --name-only "${base}" "${{ github.sha }}" \
| grep -qx 'cmake/gdb_symlink.cmake'
then
changed="true"
else
changed="false"
fi
echo "gdb_symlink=${changed}" >> "${GITHUB_OUTPUT}"
echo "gdb_symlink changed: ${changed}"
lint:
# Lightweight static checks on the source tree. Runs in parallel
# with the heavier compile / test jobs to gate invariants that don't
# require compiling.
runs-on: ubuntu-24.04
needs: toolchain
env:
CAT_LINT_BUILD_DIR: ${{ runner.temp }}/cat-build-lint
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
- name: Verify unique basenames under src/
# Every file under `src/` must have a unique name. This makes searching
# the codebase easier and is important for the `cat-intermediaries` target.
run: |
set -eu
dups=$(find src -type f \
! -name 'CMakeLists.txt' \
! -name '*.ld' \
-printf '%f\n' \
| sort | uniq -d)
if [ -n "${dups}" ]; then
echo 'WARNING: duplicate basenames under src/:'
while IFS= read -r name; do
printf '\n %s:\n' "${name}"
find src -type f -name "${name}" -printf ' %p\n'
done <<< "${dups}"
echo "::warning title=Duplicate source basenames::Duplicate basenames under src. See this step log for the full file list."
fi
count=$(find src -type f \
! -name 'CMakeLists.txt' \
! -name '*.ld' \
| wc -l)
printf 'OK: %d files under src/ have unique basenames\n' "${count}"
- name: Verify src files are listed in src/CMakeLists.txt
run: |
set -eu
missing="${RUNNER_TEMP:-/tmp}/cat-missing-src-cmake-files"
: > "${missing}"
find src -type f -print | sort | while IFS= read -r file; do
if [ "${file#src/libraries/}" != "${file}" ]; then
needle="${file#src/libraries/}"
else
needle="${file#src/}"
fi
if ! grep -F -q -- "${needle}" src/CMakeLists.txt; then
printf '%s\n' "${file}" >> "${missing}"
fi
done
if [ -s "${missing}" ]; then
echo 'FAIL: files under src/ missing from src/CMakeLists.txt:'
while IFS= read -r file; do
printf ' %s\n' "${file}"
done < "${missing}"
exit 1
fi
count=$(find src -type f -print | wc -l)
printf 'OK: %d files under src/ are mentioned in src/CMakeLists.txt\n' "${count}"
- name: Configure CMake lint targets
run: |
cmake -S . -B "${CAT_LINT_BUILD_DIR}" \
-DCMAKE_BUILD_TYPE=Release \
-DCAT_BUILD_ALL_EXAMPLES=OFF \
-DCAT_PCH=OFF
- name: cat-format-check
# Advisory. Emits a warning annotation instead of gating the merge. Fix
# locally with
# `cmake --build "${CAT_LINT_BUILD_DIR}" --target cat-format`.
run: |
if ! cmake --build "${CAT_LINT_BUILD_DIR}" --target cat-format-check
then
echo "::warning title=cat-format-check::Formatting drift detected! Run the `cat-format` target to fix it."
fi
- name: cat-restyle-comments-check
# Same advisory policy as `cat-format-check` above.
run: |
if ! cmake --build "${CAT_LINT_BUILD_DIR}" --target cat-restyle-comments-check
then
echo "::warning title=cat-restyle-comments-check::Comment styling drift detected! Run the `cat-restyle-comments` target to fix it."
fi
debug:
# Debug build with sanitizers.
runs-on: ubuntu-24.04
needs: [toolchain, changes]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
- name: Configure, build, and test (Debug)
run: |
cmake -S . -B build-debug \
-DCMAKE_BUILD_TYPE=Debug \
-DCAT_USE_SANITIZERS=ON
cmake --build build-debug
ctest --test-dir build-debug --verbose --output-on-failure
- name: GDB pretty printers (-O0 Debug)
run: cmake --build build-debug --target check-gdb-pretty-printers
- name: GDB pretty printers (-O0 Debug, PCH=OFF)
run: |
cmake -S . -B build-debug-no-pch \
-DCMAKE_BUILD_TYPE=Debug \
-DCAT_USE_SANITIZERS=ON \
-DCAT_PCH=OFF
cmake --build build-debug-no-pch --target check-gdb-pretty-printers
- name: GDB pretty printers (RelWithDebInfo, PCH=ON)
run: |
cmake -S . -B build-gdb-relwithdebinfo \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCAT_PCH=ON
cmake --build build-gdb-relwithdebinfo --target check-gdb-pretty-printers
- name: Verify .gdbinit symlink (Debug)
if: ${{ needs.changes.outputs.gdb_symlink == 'true' }}
# Single-config layout per `cmake/gdb_symlink.cmake`. The
# multi-config variant lives in the `tools` job.
run: |
set -eu
if [ ! -f build-debug/.gdbinit ]; then
echo "FAIL: missing build-debug/.gdbinit"
ls -la build-debug/ || true
exit 1
fi
echo "OK: build-debug/.gdbinit"
release:
# Release build and tests.
runs-on: ubuntu-24.04
needs: [toolchain, changes]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
- name: Configure, build, and test (Release)
run: |
cmake -S . -B build-release \
-DCMAKE_BUILD_TYPE=Release \
cmake --build build-release
ctest --test-dir build-release --verbose --output-on-failure
- name: Verify .gdbinit symlink (Release)
if: ${{ needs.changes.outputs.gdb_symlink == 'true' }}
run: |
set -eu
if [ ! -f build-release/.gdbinit ]; then
echo "FAIL: missing build-release/.gdbinit"
ls -la build-release/ || true
exit 1
fi
echo "OK: build-release/.gdbinit"
shared:
# End-to-end `libcat.so` flow: build, install, then have a
# downstream `find_package(cat)` consumer link it.
runs-on: ubuntu-24.04
needs: toolchain
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
- name: Configure, build, install, and consume (CAT_USE_SHARED=ON, Debug)
# `CAT_USE_SHARED=ON` swaps `cat`'s INTERFACE-linked impl to
# `cat-impl-shared`. Install rules from `cmake/cat_install.cmake`
# are registered unconditionally, so `cmake --install` just
# works. The downstream smoke binary's "Hello from
# find_package(cat)!" output confirms `_start.cpp`, `libcat.ld`,
# `global_includes.hpp`, headers, and the .so were all installed
# and located correctly.
run: |
set -eu
cmake -S . -B build-shared \
-DCMAKE_BUILD_TYPE=Debug \
-DCAT_USE_SANITIZERS=ON \
-DCAT_USE_SHARED=ON \
-DCMAKE_INSTALL_PREFIX="${PWD}/install-shared"
cmake --build build-shared
ctest --test-dir build-shared --verbose --output-on-failure
# Headline artifact for this configuration.
test -f build-shared/libcat.so
# `libcat.so` must not export libCat's libc / libm
# substitutes, or it would interpose for any process that
# loads it. Pins the `[[gnu::visibility("hidden")]]`
# behaviour in `src/libraries/{string,math}/implementations/*.cpp`.
if nm -D --defined-only build-shared/libcat.so \
| grep -E ' T (memset|memcpy|memmove|memcmp|strlen|sqrt|sqrtf|pow|powf)$'
then
echo 'FAIL: libcat.so is exporting libc/libm symbols (would interpose at runtime).'
exit 1
fi
cmake --install build-shared
# Downstream `find_package(cat)` consumer.
mkdir -p consumer-shared
cat > consumer-shared/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.24)
project(cat_consumer CXX)
find_package(cat REQUIRED)
add_executable(consumer_hello consumer_hello.cpp)
target_compile_definitions(consumer_hello PRIVATE NO_ARGC_ARGV)
target_link_libraries(consumer_hello PRIVATE cat::cat)
EOF
cat > consumer-shared/consumer_hello.cpp <<'EOF'
#include <cat/string>
auto main() -> int {
cat::println("Hello from find_package(cat)!");
return 0;
}
EOF
cmake -S consumer-shared -B build-consumer \
-DCMAKE_PREFIX_PATH="${PWD}/install-shared"
cmake --build build-consumer
out=$(LD_LIBRARY_PATH="${PWD}/install-shared/lib" build-consumer/consumer_hello)
printf 'consumer output: %s\n' "${out}"
test "${out}" = "Hello from find_package(cat)!"
tools:
# Tooling-adjacent invariants:
# * Multi-Config `.gdbinit` symlink layout.
# * `clang-repl-libcat` wrapper across the {Debug, Release}
# x {ASan, no ASan} matrix.
# The SDE-pinned `cpuid` run (commented out below)
# also lands here so the cached toolchain is reused.
runs-on: ubuntu-24.04
needs: [toolchain, changes]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
- name: Configure and minimal build (Ninja Multi-Config, Debug and Release)
if: ${{ needs.changes.outputs.gdb_symlink == 'true' }}
# Smallest surface that still pulls in `cat-gdb-tests` and
# `cat-gdb-examples` so we can symlink `.gdbinit`.
run: |
cmake -S . -B build-multi -G 'Ninja Multi-Config' \
-DCAT_BUILD_UNIT_TESTS=OFF \
-DCAT_BUILD_ALL_EXAMPLES=OFF \
-DCAT_BUILD_EXAMPLE_HELLO=ON
cmake --build build-multi --config Debug --target test_dummy hello
cmake --build build-multi --config Release --target test_dummy hello
- name: Verify .gdbinit symlinks (multi-config)
if: ${{ needs.changes.outputs.gdb_symlink == 'true' }}
# Multi-config layout: per-subdirectory per-config files, no
# top-level. Single-config equivalents are checked in the
# Debug and Release jobs.
run: |
set -eu
check_file() {
path="$1"
if [ ! -f "${path}" ]; then
echo "FAIL: missing ${path}"
ls -la "$(dirname "${path}")" || true
exit 1
fi
echo "OK: ${path}"
}
check_file build-multi/tests/Debug/.gdbinit
check_file build-multi/examples/Debug/.gdbinit
if [ -e build-multi/.gdbinit ]; then
echo "FAIL: build-multi/.gdbinit should NOT exist in multi-config builds!"
exit 1
fi
- name: cat-repl (Debug, sanitizers=ON)
# Smoke test the `clang-repl-libcat` wrapper across the
# {Debug, Release} x {ASan, no ASan} matrix. Catches
# regressions in the `%lib` injection, the
# `compile_commands.json` flag extractor, and the optional
# ASan runtime preload all at once. `--eval` runs libCat
# code one-shot (no interactive plumbing), so we can grep
# stdout for the marker the snippet prints.
#
# Was a `repl:` matrix job; folded in here as four
# sequential steps so the cached toolchain isn't restored
# on four extra runners. `if: ${{ !cancelled() }}` on the
# next three preserves the original `fail-fast: false`
# behaviour: a failure in one variant still surfaces
# results from the rest. Each variant gets its own build
# dir to avoid CMake reconfiguration churn between runs.
run: |
set -eu
cmake -S . -B build-repl-debug-asan \
-DCMAKE_BUILD_TYPE=Debug \
-DCAT_BUILD_SHARED=ON \
-DCAT_USE_SANITIZERS=ON \
-DCAT_BUILD_UNIT_TESTS=OFF \
-DCAT_BUILD_ALL_EXAMPLES=OFF
cmake --build build-repl-debug-asan --target cat-impl-shared
out=$(./build-repl-debug-asan/clang-repl-libcat \
--eval '#include <cat/string>' \
--eval 'auto _ = cat::println("Meow world!");' \
2>&1)
printf 'cat-repl output:\n%s\n' "${out}"
if ! printf '%s\n' "${out}" | grep -qF 'Meow world!'; then
echo 'FAIL: cat-repl Debug+ASan did not produce the expected marker.'
exit 1
fi
echo 'OK: cat-repl Debug+ASan produced "Meow world!"'
- name: cat-repl (Debug, sanitizers=OFF)
if: ${{ !cancelled() }}
run: |
set -eu
cmake -S . -B build-repl-debug \
-DCMAKE_BUILD_TYPE=Debug \
-DCAT_BUILD_SHARED=ON \
-DCAT_USE_SANITIZERS=OFF \
-DCAT_BUILD_UNIT_TESTS=OFF \
-DCAT_BUILD_ALL_EXAMPLES=OFF
cmake --build build-repl-debug --target cat-impl-shared
out=$(./build-repl-debug/clang-repl-libcat \
--eval '#include <cat/string>' \
--eval 'auto _ = cat::println("Meow world!");' \
2>&1)
printf 'cat-repl output:\n%s\n' "${out}"
if ! printf '%s\n' "${out}" | grep -qF 'Meow world!'; then
echo 'FAIL: cat-repl Debug (no sanitizers) did not produce the expected marker.'
exit 1
fi
echo 'OK: cat-repl Debug (no sanitizers) produced "Meow world!"'
- name: cat-repl (Release, sanitizers=ON)
if: ${{ !cancelled() }}
run: |
set -eu
cmake -S . -B build-repl-release-asan \
-DCMAKE_BUILD_TYPE=Release \
-DCAT_BUILD_SHARED=ON \
-DCAT_USE_SANITIZERS=ON \
-DCAT_BUILD_UNIT_TESTS=OFF \
-DCAT_BUILD_ALL_EXAMPLES=OFF
cmake --build build-repl-release-asan --target cat-impl-shared
out=$(./build-repl-release-asan/clang-repl-libcat \
--eval '#include <cat/string>' \
--eval 'auto _ = cat::println("Meow world!");' \
2>&1)
printf 'cat-repl output:\n%s\n' "${out}"
if ! printf '%s\n' "${out}" | grep -qF 'Meow world!'; then
echo 'FAIL: cat-repl Release+ASan did not produce the expected marker.'
exit 1
fi
echo 'OK: cat-repl Release+ASan produced "Meow world!"'
- name: cat-repl (Release, sanitizers=OFF)
if: ${{ !cancelled() }}
run: |
set -eu
cmake -S . -B build-repl-release \
-DCMAKE_BUILD_TYPE=Release \
-DCAT_BUILD_SHARED=ON \
-DCAT_USE_SANITIZERS=OFF \
-DCAT_BUILD_UNIT_TESTS=OFF \
-DCAT_BUILD_ALL_EXAMPLES=OFF
cmake --build build-repl-release --target cat-impl-shared
out=$(./build-repl-release/clang-repl-libcat \
--eval '#include <cat/string>' \
--eval 'auto _ = cat::println("Meow world!");' \
2>&1)
printf 'cat-repl output:\n%s\n' "${out}"
if ! printf '%s\n' "${out}" | grep -qF 'Meow world!'; then
echo 'FAIL: cat-repl Release (no sanitizers) did not produce the expected marker.'
exit 1
fi
echo 'OK: cat-repl Release (no sanitizers) produced "Meow world!"'
# SDE-pinned Skylake run for `unit_tests_cpuid`. SDE intercepts
# CPUID so the emulated CPU is always Skylake regardless of
# the underlying hardware, which the test's
# `__builtin_cpu_is("skylake")` check requires.
#
# TODO: Once `tests/unit_tests.cpp`'s runner accepts a test-name
# filter (or the cpuid baseline lives behind a runtime guard in
# `src/libraries/cpuid/cat/cpuid`), drop the standalone
# `unit_tests_cpuid` binary and just
# `sde64 -skl -- unit_tests --gtest-style-filter cpuid`.
# - name: Install xz-utils for SDE tarball
# run: sudo apt-get install -y xz-utils
#
# - name: Download Intel SDE 9.58.0
# # Pinning the URL by version + SHA256 keeps CI reproducible
# # and aborts on a swapped tarball before any binary runs.
# run: |
# set -eu
# sde_url=https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
# sde_sha256=f849acecad4c9b108259c643b2688fd65c35723cd23368abe5dd64b917cc18c0
# wget -q -O sde.tar.xz "${sde_url}"
# echo "${sde_sha256} sde.tar.xz" | sha256sum --check --status
# mkdir -p "${RUNNER_TEMP}/sde"
# tar -xJf sde.tar.xz -C "${RUNNER_TEMP}/sde" --strip-components=1
# echo "CAT_SDE_EXECUTABLE=${RUNNER_TEMP}/sde/sde64" >> "${GITHUB_ENV}"
# "${RUNNER_TEMP}/sde/sde64" -version | head -3
#
# - name: Configure, build, and run UnitTestsCpuid under SDE
# # Sanitizers and sccache off: ASan and Pin-based binary
# # instrumentation do not coexist cleanly.
# run: |
# cmake -S . -B build-sde \
# -DCMAKE_BUILD_TYPE=Release \
# -DCMAKE_CXX_COMPILER=clang++ \
# -DCAT_USE_SANITIZERS=OFF \
# -DCAT_USE_SCCACHE=OFF \
# -DCAT_SDE_EXECUTABLE="${CAT_SDE_EXECUTABLE}"
# cmake --build build-sde --target unit_tests
# ctest --test-dir build-sde -R '^UnitTestsCpuid$' --verbose --output-on-failure