Skip to content

A no-op build prints nothing (rowan-new#110) (#338) #433

A no-op build prints nothing (rowan-new#110) (#338)

A no-op build prints nothing (rowan-new#110) (#338) #433

Workflow file for this run

name: CI
on:
push:
branches: [ main ]
pull_request:
permissions:
contents: read
jobs:
build-test:
name: build & test (${{ matrix.label }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
label: linux x86-64
# macOS x86-64 (Intel, macos-13) temporarily disabled — runner is slow to allocate.
- os: macos-14
label: macOS arm64 (Apple Silicon)
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Build & test (debug)
run: |
cmake -B build-debug -DCMAKE_BUILD_TYPE=Debug
cmake --build build-debug -j
./bin/test
./bin/custom_packet_io_test
- name: Build & test (release)
run: |
cmake -B build-release -DCMAKE_BUILD_TYPE=Release
cmake --build build-release -j
./bin/test
./bin/custom_packet_io_test
windows:
name: build & test (windows MSVC x64, ${{ matrix.config }})
# Pinned to windows-2022 (Visual Studio 2022); the "Visual Studio 17 2022" CMake generator
# needs a matching VS install, which windows-latest no longer guarantees.
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
config: [Debug, Release]
steps:
- uses: actions/checkout@v4
- name: Configure (Visual Studio 2022, x64)
run: cmake -B build -G "Visual Studio 17 2022" -A x64
- name: Build (${{ matrix.config }} x64)
run: cmake --build build --config ${{ matrix.config }} -j
- name: Test (${{ matrix.config }} x64)
shell: pwsh
run: |
$exe = Get-ChildItem -Recurse -Filter test.exe | Select-Object -First 1
if (-not $exe) { Write-Error "test.exe not found"; exit 1 }
Write-Host "Running $($exe.FullName)"
& $exe.FullName
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$exe = Get-ChildItem -Recurse -Filter custom_packet_io_test.exe | Select-Object -First 1
if (-not $exe) { Write-Error "custom_packet_io_test.exe not found"; exit 1 }
Write-Host "Running $($exe.FullName)"
& $exe.FullName
exit $LASTEXITCODE
system-sodium:
name: build & test (linux, --sodium=system)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install libsodium
run: sudo apt-get update && sudo apt-get install -y libsodium-dev
- name: Build & test (system libsodium)
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Debug -DYOJIMBO_SYSTEM_SODIUM=ON
cmake --build build -j
./bin/test
system-deps:
name: build & test (linux, --system-deps)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
# Simulates the package-manager environment (e.g. homebrew): serialize, reliable and
# netcode installed into a prefix from their own releases, then yojimbo built against
# them with -DYOJIMBO_SYSTEM_DEPS=ON instead of the vendored copies. Versions pinned
# to match the vendored ones.
- name: Install serialize, reliable and netcode into a prefix
run: |
set -euo pipefail
sudo apt-get update && sudo apt-get install -y libsodium-dev
PREFIX="$HOME/deps"
# Clone outside the workspace: the yojimbo checkout already has serialize/,
# reliable/ and netcode/ directories (the vendored copies).
DEPS_SRC="$HOME/deps-src"
mkdir -p "$PREFIX/include" "$PREFIX/lib" "$DEPS_SRC"
# serialize: header-only
git clone --depth 1 --branch v1.4.3 https://github.com/mas-bandwidth/serialize.git "$DEPS_SRC/serialize"
cp "$DEPS_SRC/serialize/serialize.h" "$PREFIX/include/"
# reliable: build the library from source
git clone --depth 1 --branch v1.3.3 https://github.com/mas-bandwidth/reliable.git "$DEPS_SRC/reliable"
cc -O2 -c "$DEPS_SRC/reliable/reliable.c" -I"$DEPS_SRC/reliable" -o "$DEPS_SRC/reliable.o"
ar rcs "$PREFIX/lib/libreliable.a" "$DEPS_SRC/reliable.o"
cp "$DEPS_SRC/reliable/reliable.h" "$PREFIX/include/"
# netcode: has cmake install rules (built against the system libsodium, as a
# package manager would)
git clone --depth 1 --branch v1.3.2 https://github.com/mas-bandwidth/netcode.git "$DEPS_SRC/netcode"
cmake -B "$DEPS_SRC/netcode-build" -S "$DEPS_SRC/netcode" -DCMAKE_BUILD_TYPE=Release -DNETCODE_SYSTEM_SODIUM=ON -DCMAKE_INSTALL_PREFIX="$PREFIX"
cmake --build "$DEPS_SRC/netcode-build" -j
cmake --install "$DEPS_SRC/netcode-build"
- name: Build & test yojimbo against the installed dependencies
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Debug -DYOJIMBO_SYSTEM_DEPS=ON -DCMAKE_PREFIX_PATH="$HOME/deps"
cmake --build build -j
./bin/test
- name: Install yojimbo and verify the layout
run: |
cmake --install build --prefix "$HOME/yojimbo-install"
test -f "$HOME/yojimbo-install/include/yojimbo.h"
test -f "$HOME/yojimbo-install/lib/libyojimbo.a"
sanitizers:
name: sanitizers (linux, ASan+UBSan+LSan)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Build (sanitized)
# nonnull-attribute is excluded: netcode encrypts zero-length plaintexts, and
# libsodium's AEAD then does memcpy(dst, NULL, 0) — benign UB in upstream code.
run: |
SAN="-fsanitize=address,undefined -fno-sanitize=nonnull-attribute -fno-sanitize-recover=all -fno-omit-frame-pointer -g"
cmake -B build -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_FLAGS="$SAN" -DCMAKE_CXX_FLAGS="$SAN" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
cmake --build build -j --target test
- name: Test (sanitized)
run: ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./bin/test
fuzz:
name: fuzz (linux libFuzzer, ASan+UBSan)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
# Coverage-guided libFuzzer over the untrusted-input parsers. Each target is built
# without -DFUZZ_STANDALONE (libFuzzer supplies main) and time-boxed. Debug defines
# stay on so the libraries' asserts and the message-factory leak check are live —
# that check is what surfaces leaks in the connection deserialization error paths.
# nonnull-attribute is excluded for the same reason as the sanitizers job (libsodium
# memcpy(dst, NULL, 0) on zero-length AEAD plaintext).
- name: Build and run fuzz targets
run: |
set -euo pipefail
SAN="-fsanitize=fuzzer,address,undefined -fno-sanitize=nonnull-attribute -fno-sanitize-recover=all -fno-omit-frame-pointer -g"
RUN="-max_total_time=60 -print_final_stats=1 -rss_limit_mb=4096"
echo "::group::build fuzz_reliable"
clang $SAN -DRELIABLE_DEBUG -Ireliable -Ifuzz \
fuzz/fuzz_reliable.c reliable/reliable.c -o fuzz_reliable
echo "::endgroup::"
echo "::group::build fuzz_netcode"
clang $SAN -DNETCODE_DEBUG -Inetcode -Isodium -Ifuzz \
fuzz/fuzz_netcode.c sodium/sodium.c -o fuzz_netcode
echo "::endgroup::"
echo "::group::build fuzz_netcode_connect_token"
clang $SAN -DNETCODE_DEBUG -Inetcode -Isodium -Ifuzz \
fuzz/fuzz_netcode_connect_token.c sodium/sodium.c -o fuzz_netcode_connect_token
echo "::endgroup::"
for target in fuzz_connection fuzz_connection_structured; do
echo "::group::build $target"
clang++ -std=c++11 $SAN -DYOJIMBO_DEBUG -DNETCODE_DEBUG -DRELIABLE_DEBUG -DSERIALIZE_DEBUG \
-I. -Iinclude -Isodium -Itlsf -Inetcode -Ireliable -Iserialize -Ifuzz \
fuzz/$target.cpp source/*.cpp netcode/netcode.c reliable/reliable.c tlsf/tlsf.c sodium/sodium.c \
-o $target
echo "::endgroup::"
done
# Seed from the committed corpus (fuzz/corpus/<target>) so the short run starts at
# inputs that already reach the post-decrypt / reassembly code. libFuzzer reads all
# corpus dirs but only writes new finds to the first, so the committed seeds stay
# pristine and discoveries land in the ephemeral working dir.
for t in fuzz_reliable fuzz_netcode fuzz_netcode_connect_token fuzz_connection fuzz_connection_structured; do
echo "::group::run $t"
mkdir -p "corpus/$t"
UBSAN_OPTIONS=halt_on_error=1 ./"$t" "corpus/$t" "fuzz/corpus/$t" $RUN
echo "::endgroup::"
done
soak:
name: soak (linux, sanitized, time-boxed)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Build soak (sanitized)
# Same sanitizer flags and nonnull-attribute carve-out as the sanitizers job.
run: |
SAN="-fsanitize=address,undefined -fno-sanitize=nonnull-attribute -fno-sanitize-recover=all -fno-omit-frame-pointer -g"
cmake -B build -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_FLAGS="$SAN" -DCMAKE_CXX_FLAGS="$SAN" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
cmake --build build -j --target soak
- name: Run soak (time-boxed)
# Bounded iteration count so the normally-endless soak exits cleanly and the
# sanitizers run their end-of-process (incl. leak) checks. Output is verbose, so
# keep it in a file and only surface the tail on failure.
run: |
set -o pipefail
if ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
./bin/soak 20000 > soak.log 2>&1; then
echo "soak completed cleanly ($(grep -c 'received message' soak.log) messages exchanged)"
else
echo "soak failed (exit $?)"; tail -50 soak.log; exit 1
fi
msan:
name: fuzz (linux libFuzzer, MemorySanitizer)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
# MemorySanitizer catches *reads of uninitialized memory* — the class of bug the
# ChannelPacketData union-init crash was (Free() dereferencing a half-initialized
# block.message). ASan/UBSan do not catch that. The C++ targets are built WITHOUT
# -DYOJIMBO_DEBUG so the debug std::map leak trackers aren't compiled in; that leaves the
# binaries free of C++ STL, so MSan needs no separately-instrumented libc++ (linking an
# uninstrumented libc++ would produce false positives). Asserts and leak detection stay
# covered by the sanitizers (ASan+UBSan+LSan) job.
- name: Build and run fuzz targets under MSan
run: |
set -euo pipefail
MSAN="-fsanitize=fuzzer,memory -fno-omit-frame-pointer -g"
RUN="-max_total_time=45 -print_final_stats=1 -rss_limit_mb=4096"
echo "::group::build C targets"
clang $MSAN -DRELIABLE_DEBUG -Ireliable -Ifuzz \
fuzz/fuzz_reliable.c reliable/reliable.c -o fuzz_reliable
clang $MSAN -DNETCODE_DEBUG -Inetcode -Isodium -Ifuzz \
fuzz/fuzz_netcode.c sodium/sodium.c -o fuzz_netcode
clang $MSAN -DNETCODE_DEBUG -Inetcode -Isodium -Ifuzz \
fuzz/fuzz_netcode_connect_token.c sodium/sodium.c -o fuzz_netcode_connect_token
echo "::endgroup::"
# -DYOJIMBO_RELEASE selects the release path so the debug std::map leak trackers
# (Allocator::m_alloc_map, MessageFactory::allocated_messages) are not compiled in.
# That keeps the binary free of C++ STL, so MSan needs no instrumented libc++ — an
# uninstrumented libstdc++ std::map traversal reports a spurious use-of-uninitialized.
for target in fuzz_connection fuzz_connection_structured; do
echo "::group::build $target"
clang++ -std=c++11 $MSAN -DYOJIMBO_RELEASE -DNETCODE_DEBUG -DRELIABLE_DEBUG -DSERIALIZE_DEBUG \
-I. -Iinclude -Isodium -Itlsf -Inetcode -Ireliable -Iserialize -Ifuzz \
fuzz/$target.cpp source/*.cpp netcode/netcode.c reliable/reliable.c tlsf/tlsf.c sodium/sodium.c \
-o $target
echo "::endgroup::"
done
for t in fuzz_reliable fuzz_netcode fuzz_netcode_connect_token fuzz_connection fuzz_connection_structured; do
echo "::group::run $t"
mkdir -p "corpus/$t"
./"$t" "corpus/$t" "fuzz/corpus/$t" $RUN
echo "::endgroup::"
done
conformance:
name: STANDARD.md + STATE-MACHINE.md conformance
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
# The format checker needs no build: it decodes the committed fuzz corpus
# using ONLY what STANDARD.md states, with no reference to the source.
- name: Verify STANDARD.md
run: python3 tools/conformance/verify_standard.py
# The state-machine checker drives a real client and server over UDP, so
# it needs the library built first.
- name: Build
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release -DYOJIMBO_BUILD_TESTS=ON
cmake --build build -j
# A failure in either means the document and the code disagree — decide
# which is wrong and fix that one. A specification nothing verifies
# drifts, and a drifted spec is worse than none, because independent
# implementations are built on it and the failure is silent.
- name: Verify STATE-MACHINE.md
run: python3 tools/conformance/verify_state_machine.py --build build