Skip to content

Merge pull request #706 from sysprog21/fix-file-desc #4253

Merge pull request #706 from sysprog21/fix-file-desc

Merge pull request #706 from sysprog21/fix-file-desc #4253

Workflow file for this run

name: CI
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
detect-code-related-file-changes:
runs-on: ubuntu-24.04
outputs:
has_code_related_changes: ${{ steps.set_has_code_related_changes.outputs.has_code_related_changes }}
steps:
- name: Check out the repo
uses: actions/checkout@v6
- name: Test changed files
id: changed-files
uses: tj-actions/changed-files@v47
with:
files: |
.ci/**
.github/workflows/**
build/**
configs/**
mk/**
scripts/**
src/**
tests/**
tools/**
.clang-format
Dockerfile
Makefile
# Build system configuration
mk/kconfig.mk
mk/toolchain.mk
tools/detect-env.py
- name: Set has_code_related_changes
id: set_has_code_related_changes
run: |
if [[ ${{ steps.changed-files.outputs.any_changed }} == true ]]; then
echo "has_code_related_changes=true" >> $GITHUB_OUTPUT
else
echo "has_code_related_changes=false" >> $GITHUB_OUTPUT
fi
host-x64:
needs: [detect-code-related-file-changes]
if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true'
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
runs-on: ubuntu-24.04
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
submodules: 'true'
- name: Read LLVM Version
id: llvm-version
run: echo "version=$(cat .ci/llvm-version)" >> $GITHUB_OUTPUT
- name: Cache LLVM
id: cache-llvm
uses: actions/cache@v5
with:
path: /usr/lib/llvm-${{ steps.llvm-version.outputs.version }}
key: ${{ runner.os }}-${{ runner.arch }}-llvm-${{ steps.llvm-version.outputs.version }}
- name: Cache RISC-V Toolchain
id: cache-toolchain
uses: actions/cache@v5
with:
path: ${{ github.workspace }}/toolchain
key: ${{ runner.os }}-${{ runner.arch }}-riscv-toolchain-${{ hashFiles('.ci/riscv-toolchain-install.sh') }}
- name: Install dependencies
run: |
set +e # Disable errexit
# Configure apt to tolerate mirror sync failures
sudo tee /etc/apt/apt.conf.d/99-ci-reliability > /dev/null << 'EOF'
APT::Update::Error-Mode "any";
Acquire::IndexTargets::deb::DEP-11::DefaultEnabled "false";
Acquire::IndexTargets::deb::DEP-11-icons::DefaultEnabled "false";
Acquire::IndexTargets::deb::DEP-11-icons-hidpi::DefaultEnabled "false";
Acquire::Retries "3";
Acquire::Check-Valid-Until "false";
EOF
# Update with error tolerance
sudo apt-get update -q=2 2>&1
UPDATE_EXIT=$?
if [ $UPDATE_EXIT -ne 0 ]; then
echo "WARNING: apt update exited with code $UPDATE_EXIT, continuing..."
fi
sudo apt-get install -q=2 curl libsdl2-dev libsdl2-mixer-dev device-tree-compiler expect bc p7zip-full
set -e # Re-enable errexit
shell: bash
- name: Install RISC-V Toolchain
if: steps.cache-toolchain.outputs.cache-hit != 'true'
run: |
.ci/riscv-toolchain-install.sh
- name: Setup RISC-V Toolchain PATH
run: echo "${{ github.workspace }}/toolchain/bin" >> $GITHUB_PATH
- name: Install LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
.ci/fetch.sh -q -o llvm.sh https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh ${{ steps.llvm-version.outputs.version }}
- name: Setup LLVM PATH
run: echo "/usr/lib/llvm-${{ steps.llvm-version.outputs.version }}/bin" >> $GITHUB_PATH
- name: Install compiler
id: install_cc
uses: rlalik/setup-cpp-compiler@master
with:
compiler: ${{ matrix.compiler }}
- name: Setup emsdk
uses: mymindstorm/setup-emsdk@v14
with:
version: 3.1.51
actions-cache-folder: 'emsdk-cache'
- name: Set parallel jobs variable
run: |
echo "PARALLEL=-j$(nproc)" >> "$GITHUB_ENV"
echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp "$RUNNER_TEMP/tmpfile.XXXXXX"); \
sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \
. \${TMP_FILE}; \
.ci/boot-linux.sh; \
EXIT_CODE=\$?; \
sudo env TMP_FILE=\${TMP_FILE} BLK_DEV_EXT4=\${BLK_DEV_EXT4} \
BLK_DEV_SIMPLEFS=\${BLK_DEV_SIMPLEFS} .ci/boot-linux-prepare.sh cleanup; \
exit \${EXIT_CODE};" >> "$GITHUB_ENV"
- name: Cache build artifacts
uses: actions/cache@v5
with:
path: build/
key: rv32emu-artifacts-${{ runner.os }}-${{ hashFiles('mk/artifact.mk', 'mk/external.mk') }}
restore-keys: |
rv32emu-artifacts-${{ runner.os }}-
- name: Fetch artifacts
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
. .ci/common.sh
fetch_artifact ELF
fetch_artifact Linux-Image ENABLE_SYSTEM=1
rm -f build/.config # Clean config after ENABLE_SYSTEM=1 to prevent contamination
fetch_artifact sail ENABLE_ARCH_TEST=1
# get from rv32emu-prebuilt
.ci/fetch.sh -o build/shareware_doom_iwad.zip "https://raw.githubusercontent.com/sysprog21/rv32emu-prebuilt/doom-artifact/shareware_doom_iwad.zip"
unzip -o -d build/ build/shareware_doom_iwad.zip
# emcc builds run on x64 only - WebAssembly output is platform-independent
# This validates Emscripten compatibility without redundant builds on macOS
# Note: wasm_defconfig sets CONFIG_BUILD_WASM=y which auto-selects CC=emcc
- name: default build using emcc
if: success()
run: |
make distclean
make wasm_defconfig
make $PARALLEL
- name: default build for system emulation using emcc
if: success()
run: |
make distclean
make wasm_defconfig
make ENABLE_SYSTEM=1 ENABLE_GOLDFISH_RTC=1 $PARALLEL
make distclean
- name: Build with various optimization levels
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
set -euo pipefail
# Test boundary cases: -O0 (unoptimized), -O2 (standard), -Ofast (aggressive)
for opt_level in -O0 -O2 -Ofast; do
echo "Building with OPT_LEVEL=$opt_level"
if ! (make distclean && make defconfig && make OPT_LEVEL=$opt_level $PARALLEL); then
echo "ERROR: Build failed with OPT_LEVEL=$opt_level"
exit 1
fi
done
- name: Build system emulation with various optimization levels
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
set -euo pipefail
# Test boundary cases: -O0 (unoptimized), -O2 (standard), -Ofast (aggressive)
for opt_level in -O0 -O2 -Ofast; do
echo "Building system emulation with OPT_LEVEL=$opt_level"
if ! (make distclean && make system_defconfig && make OPT_LEVEL=$opt_level $PARALLEL); then
echo "ERROR: System emulation build failed with OPT_LEVEL=$opt_level"
exit 1
fi
done
- name: check + tests
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make cleanconfig
make defconfig
make check $PARALLEL
make tests $PARALLEL
make misalign $PARALLEL
make tool $PARALLEL
- name: core extension disable tests (interpreter + JIT)
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
set -euo pipefail
# Core extensions + bit manipulation + CSR/fence + performance features
# Tests both interpreter and JIT modes (12 × 2 = 24 builds)
# Consolidated from 25 separate builds (13 interpreter + 12 JIT)
# Use cleanconfig instead of distclean to preserve artifacts across iterations
for ext in ENABLE_EXT_M ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C \
ENABLE_Zba ENABLE_Zbb ENABLE_Zbc ENABLE_Zbs \
ENABLE_Zicsr ENABLE_Zifencei \
ENABLE_MOP_FUSION ENABLE_BLOCK_CHAINING; do
echo "Testing ${ext}=0 (interpreter)"
if ! (make cleanconfig && make defconfig && make ${ext}=0 check $PARALLEL); then
echo "ERROR: Interpreter test failed with ${ext}=0"
exit 1
fi
echo "Testing ${ext}=0 (JIT)"
if ! (make cleanconfig && make jit_defconfig && make ${ext}=0 check $PARALLEL); then
echo "ERROR: JIT test failed with ${ext}=0"
exit 1
fi
done
- name: SDL disable test
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
# SDL is graphics subsystem, interpreter-only test sufficient
make distclean && make defconfig && make ENABLE_SDL=0 check $PARALLEL
- name: misalignment test in block emulation
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make -C tests/system/alignment/
make distclean && make system_defconfig && make ENABLE_ELF_LOADER=1 ENABLE_EXT_C=0 misalign-in-blk-emu $PARALLEL
- name: MMU test
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make -C tests/system/mmu/
make distclean && make system_defconfig && make ENABLE_ELF_LOADER=1 mmu-test $PARALLEL
- name: gdbstub test
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make distclean && make defconfig && make ENABLE_GDBSTUB=1 gdbstub-test $PARALLEL
- name: JIT base test
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
# Base JIT test (extension disable tests handled in consolidated step above)
make distclean && make jit_defconfig && make check $PARALLEL
- name: undefined behavior test
if: success() || failure()
run: |
make distclean && make defconfig && make ENABLE_UBSAN=1 check $PARALLEL
make distclean && make jit_defconfig && make ENABLE_UBSAN=1 check $PARALLEL
# Boot tests moved to boot-tests-x64 job for parallel execution
- name: Architecture test
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
. .ci/common.sh
export LATEST_RELEASE=$(fetch_latest_release sail)
.ci/riscv-tests.sh
# Parallel boot tests for x64 - runs interpreter, JIT, and T2C boot tests concurrently
# T2C runs only on clang since it uses LLVM
boot-tests-x64:
needs: [detect-code-related-file-changes]
if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true'
timeout-minutes: 30 # T2C boot ~18min + build ~3min + setup ~3min
strategy:
fail-fast: false
matrix:
include:
- compiler: gcc
boot_type: interpreter
defconfig: system_defconfig
make_flags: ""
timeout: 60
- compiler: gcc
boot_type: jit
defconfig: system_defconfig
make_flags: "ENABLE_JIT=1 ENABLE_MOP_FUSION=0"
timeout: 90
- compiler: clang
boot_type: interpreter
defconfig: system_defconfig
make_flags: ""
timeout: 60
- compiler: clang
boot_type: jit
defconfig: system_defconfig
make_flags: "ENABLE_JIT=1 ENABLE_MOP_FUSION=0"
timeout: 90
- compiler: clang
boot_type: t2c
defconfig: system_jit_defconfig
make_flags: "ENABLE_MOP_FUSION=0"
timeout: 120
runs-on: ubuntu-24.04
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
submodules: 'true'
- name: Read LLVM Version
if: matrix.compiler == 'clang'
id: llvm-version
run: echo "version=$(cat .ci/llvm-version)" >> $GITHUB_OUTPUT
# LLVM needed for all clang builds (llvm-ar required for LTO)
- name: Cache LLVM
if: matrix.compiler == 'clang'
id: cache-llvm
uses: actions/cache@v5
with:
path: /usr/lib/llvm-${{ steps.llvm-version.outputs.version }}
key: ${{ runner.os }}-${{ runner.arch }}-llvm-${{ steps.llvm-version.outputs.version }}
- name: Install dependencies
run: |
sudo apt-get update -q=2 || true
sudo apt-get install -q=2 curl libsdl2-dev libsdl2-mixer-dev device-tree-compiler expect bc p7zip-full e2fsprogs
- name: Install LLVM
if: matrix.compiler == 'clang' && steps.cache-llvm.outputs.cache-hit != 'true'
run: |
.ci/fetch.sh -q -o llvm.sh https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh ${{ steps.llvm-version.outputs.version }}
- name: Setup LLVM PATH
if: matrix.compiler == 'clang'
run: echo "/usr/lib/llvm-${{ steps.llvm-version.outputs.version }}/bin" >> $GITHUB_PATH
- name: Install compiler
id: install_cc
uses: rlalik/setup-cpp-compiler@master
with:
compiler: ${{ matrix.compiler }}
- name: Set parallel jobs variable
run: |
echo "PARALLEL=-j$(nproc)" >> "$GITHUB_ENV"
echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp \"$RUNNER_TEMP/tmpfile.XXXXXX\"); \
sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \
. \${TMP_FILE}; \
.ci/boot-linux.sh; \
EXIT_CODE=\$?; \
sudo env TMP_FILE=\${TMP_FILE} BLK_DEV_EXT4=\${BLK_DEV_EXT4} \
BLK_DEV_SIMPLEFS=\${BLK_DEV_SIMPLEFS} .ci/boot-linux-prepare.sh cleanup; \
exit \${EXIT_CODE};" >> "$GITHUB_ENV"
# Cache key includes matrix variables to prevent cross-contamination between parallel jobs
- name: Cache build artifacts
uses: actions/cache@v5
with:
path: build/
key: rv32emu-boot-${{ runner.os }}-${{ matrix.compiler }}-${{ matrix.boot_type }}-${{ hashFiles('mk/artifact.mk', 'mk/external.mk') }}
restore-keys: |
rv32emu-boot-${{ runner.os }}-${{ matrix.compiler }}-${{ matrix.boot_type }}-
rv32emu-artifacts-${{ runner.os }}-
- name: Fetch Linux artifacts
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
. .ci/common.sh
fetch_artifact Linux-Image ENABLE_SYSTEM=1
rm -f build/.config
- name: boot Linux kernel test (${{ matrix.boot_type }})
env:
CC: ${{ steps.install_cc.outputs.cc }}
BOOT_TIMEOUT: ${{ matrix.timeout }}
run: |
# For T2C, verify LLVM 18 detection
if [ "${{ matrix.boot_type }}" = "t2c" ]; then
rm -f build/t2c.o
fi
make distclean && make ${{ matrix.defconfig }} && make INITRD_SIZE=32 ${{ matrix.make_flags }} $PARALLEL && make artifact $PARALLEL
if [ "${{ matrix.boot_type }}" = "t2c" ]; then
test -f build/t2c.o || { echo "ERROR: T2C disabled (build/t2c.o missing) - LLVM 18 detection failed"; exit 1; }
fi
bash -c "${BOOT_LINUX_TEST}"
# Native AArch64 on GitHub ARM runners - fast, no QEMU overhead
host-arm64:
needs: [detect-code-related-file-changes]
if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true'
timeout-minutes: 45
runs-on: ubuntu-24.04-arm
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: checkout code
uses: actions/checkout@v6
with:
submodules: 'true'
- name: Set LLVM version
run: echo "LLVM_VERSION=$(cat .ci/llvm-version)" >> $GITHUB_ENV
- name: Install dependencies
run: |
# Configure apt to tolerate ARM mirror sync failures
sudo tee /etc/apt/apt.conf.d/99-ci-reliability > /dev/null << 'EOF'
APT::Update::Error-Mode "any";
Acquire::IndexTargets::deb::DEP-11::DefaultEnabled "false";
Acquire::IndexTargets::deb::DEP-11-icons::DefaultEnabled "false";
Acquire::IndexTargets::deb::DEP-11-icons-hidpi::DefaultEnabled "false";
Acquire::Retries "3";
Acquire::Check-Valid-Until "false";
EOF
# Update with error tolerance
sudo apt-get update -q=2 || {
echo "WARNING: apt update failed, continuing with cached indexes..."
}
# Install packages with retry for critical ones
sudo apt-get install -q=2 -y make curl wget libsdl2-dev libsdl2-mixer-dev \
lsb-release software-properties-common gnupg bc device-tree-compiler expect p7zip-full e2fsprogs || {
echo "WARNING: Some packages failed, retrying critical ones..."
sudo apt-get install -q=2 -y --no-download make curl wget || true
}
- name: Install LLVM
run: |
.ci/fetch.sh -q -o llvm.sh https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh $LLVM_VERSION
- name: Setup LLVM PATH
run: echo "/usr/lib/llvm-${LLVM_VERSION}/bin" >> $GITHUB_PATH
- name: Set parallel jobs variable
run: |
echo "PARALLEL=-j$(nproc)" >> "$GITHUB_ENV"
echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp \"$RUNNER_TEMP/tmpfile.XXXXXX\"); \
sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \
. \${TMP_FILE}; \
.ci/boot-linux.sh; \
EXIT_CODE=\$?; \
sudo env TMP_FILE=\${TMP_FILE} BLK_DEV_EXT4=\${BLK_DEV_EXT4} \
BLK_DEV_SIMPLEFS=\${BLK_DEV_SIMPLEFS} .ci/boot-linux-prepare.sh cleanup; \
exit \${EXIT_CODE};" >> "$GITHUB_ENV"
- name: Fetch artifacts
env:
CC: clang-${{ env.LLVM_VERSION }}
run: |
. .ci/common.sh
fetch_artifact ELF
fetch_artifact Linux-Image ENABLE_SYSTEM=1
rm -f build/.config # Clean config after ENABLE_SYSTEM=1 to prevent contamination
# FIXME: gcc build fails on AArch64/Linux hosts
- name: check + tests
env:
CC: clang-${{ env.LLVM_VERSION }}
run: |
make cleanconfig
make defconfig
make check $PARALLEL
- name: JIT tests
env:
CC: clang-${{ env.LLVM_VERSION }}
run: |
set -euo pipefail
make distclean && make jit_defconfig && make check $PARALLEL
for ext in ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C; do
echo "JIT test with ${ext}=0"
make ENABLE_JIT=1 clean && make ${ext}=0 ENABLE_JIT=1 check $PARALLEL
done
- name: boot Linux kernel test (T2C)
if: success()
env:
CC: clang-${{ env.LLVM_VERSION }}
BOOT_TIMEOUT: 120 # T2C with O1 optimization (via defconfig) is faster than O3
run: |
# Remove stale t2c.o before build to ensure fresh detection
rm -f build/t2c.o
# system_jit_defconfig uses T2C_OPT_LEVEL=1 for ~50% faster LLVM compilation
make distclean && make system_jit_defconfig && make INITRD_SIZE=32 ENABLE_MOP_FUSION=0 $PARALLEL && make artifact $PARALLEL
# Verify T2C is actually compiled (build/t2c.o only exists when LLVM 18 detection succeeds)
test -f build/t2c.o || { echo "ERROR: T2C disabled (build/t2c.o missing) - LLVM 18 detection failed"; exit 1; }
bash -c "${BOOT_LINUX_TEST}"
make clean
macOS-arm64:
needs: [detect-code-related-file-changes]
if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true'
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
compiler: [gcc-15, clang]
runs-on: macos-latest # M1 chip
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
submodules: 'true'
- name: Read LLVM Version
id: llvm-version
run: echo "version=$(cat .ci/llvm-version)" >> $GITHUB_OUTPUT
- name: Cache RISC-V Toolchain
id: cache-toolchain
uses: actions/cache@v5
with:
path: ${{ github.workspace }}/toolchain
key: ${{ runner.os }}-${{ runner.arch }}-riscv-toolchain-${{ hashFiles('.ci/riscv-toolchain-install.sh') }}
- name: Cache Homebrew packages
id: cache-brew
uses: actions/cache@v5
with:
path: |
/opt/homebrew/Cellar/llvm@${{ steps.llvm-version.outputs.version }}
/opt/homebrew/Cellar/sdl2
/opt/homebrew/Cellar/dtc
key: ${{ runner.os }}-${{ runner.arch }}-brew-llvm${{ steps.llvm-version.outputs.version }}-${{ hashFiles('.github/workflows/main.yml') }}
- name: Install dependencies
run: |
brew install make dtc expect sdl2 bc e2fsprogs p7zip llvm@${{ steps.llvm-version.outputs.version }} dcfldd
brew install sdl2_mixer || echo "Warning: sdl2_mixer installation failed, continuing without SDL_MIXER support"
# Ensure symlinks exist after cache restore (cached Cellar may lack /opt/homebrew/bin links)
brew link dtc --overwrite 2>/dev/null || true
command -v dtc || { echo "ERROR: dtc not found in PATH after install"; exit 1; }
- name: Install RISC-V Toolchain
if: steps.cache-toolchain.outputs.cache-hit != 'true'
run: |
.ci/riscv-toolchain-install.sh
- name: Setup toolchain paths
run: |
echo "${{ github.workspace }}/toolchain/bin" >> $GITHUB_PATH
echo "$(brew --prefix llvm@${{ steps.llvm-version.outputs.version }})/bin" >> $GITHUB_PATH
- name: Set up Python 3.12
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install compiler
id: install_cc
uses: rlalik/setup-cpp-compiler@master
with:
compiler: ${{ matrix.compiler }}
# NOTE: emsdk removed - emcc builds run on host-x64 only (WebAssembly is platform-independent)
- name: Set parallel jobs variable
run: |
echo "PARALLEL=-j$(sysctl -n hw.logicalcpu)" >> "$GITHUB_ENV"
echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp "$RUNNER_TEMP/tmpfile.XXXXXX"); \
sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \
. \${TMP_FILE}; \
.ci/boot-linux.sh; \
EXIT_CODE=\$?; \
sudo env TMP_FILE=\${TMP_FILE} BLK_DEV_EXT4=\${BLK_DEV_EXT4} .ci/boot-linux-prepare.sh cleanup; \
exit \${EXIT_CODE};" >> "$GITHUB_ENV"
- name: Symlink gcc-15 due to the default /usr/local/bin/gcc links to system's clang
run: |
ln -s /opt/homebrew/opt/gcc/bin/gcc-15 /usr/local/bin/gcc-15
- name: Cache build artifacts
uses: actions/cache@v5
with:
path: build/
key: rv32emu-artifacts-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('mk/artifact.mk', 'mk/external.mk') }}
restore-keys: |
rv32emu-artifacts-${{ runner.os }}-${{ runner.arch }}-
- name: fetch artifact first to reduce HTTP requests
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
. .ci/common.sh
fetch_artifact ELF
fetch_artifact Linux-Image ENABLE_SYSTEM=1
rm -f build/.config # Clean config after ENABLE_SYSTEM=1 to prevent contamination
fetch_artifact sail ENABLE_ARCH_TEST=1
# get from rv32emu-prebuilt
.ci/fetch.sh -o build/shareware_doom_iwad.zip "https://raw.githubusercontent.com/sysprog21/rv32emu-prebuilt/doom-artifact/shareware_doom_iwad.zip"
unzip -o -d build/ build/shareware_doom_iwad.zip
# NOTE: emcc builds removed - they run on host-x64 only since WebAssembly output is platform-independent
- name: check + tests
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make cleanconfig
make defconfig
make check $PARALLEL
make tests $PARALLEL
make misalign $PARALLEL
make tool $PARALLEL
- name: core extension disable tests (interpreter + JIT)
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
set -euo pipefail
# Core extensions + bit manipulation + CSR/fence + performance features
# Tests both interpreter and JIT modes (12 × 2 = 24 builds)
# Consolidated from 25 separate builds (13 interpreter + 12 JIT)
# Use cleanconfig instead of distclean to preserve artifacts across iterations
for ext in ENABLE_EXT_M ENABLE_EXT_A ENABLE_EXT_F ENABLE_EXT_C \
ENABLE_Zba ENABLE_Zbb ENABLE_Zbc ENABLE_Zbs \
ENABLE_Zicsr ENABLE_Zifencei \
ENABLE_MOP_FUSION ENABLE_BLOCK_CHAINING; do
echo "Testing ${ext}=0 (interpreter)"
if ! (make cleanconfig && make defconfig && make ${ext}=0 check $PARALLEL); then
echo "ERROR: Interpreter test failed with ${ext}=0"
exit 1
fi
echo "Testing ${ext}=0 (JIT)"
if ! (make cleanconfig && make jit_defconfig && make ${ext}=0 check $PARALLEL); then
echo "ERROR: JIT test failed with ${ext}=0"
exit 1
fi
done
- name: SDL disable test
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
# SDL is graphics subsystem, interpreter-only test sufficient
make distclean && make defconfig && make ENABLE_SDL=0 check $PARALLEL
- name: gdbstub test, need RV32 toolchain
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make distclean && make defconfig && make ENABLE_GDBSTUB=1 gdbstub-test $PARALLEL
- name: JIT base test
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
# Base JIT test (extension disable tests handled in consolidated step above)
make distclean && make jit_defconfig && make check $PARALLEL
- name: undefined behavior test
if: (success() || failure()) && steps.install_cc.outputs.cc == 'clang' # gcc on macOS/arm64 does not support sanitizers
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make distclean && make defconfig && make ENABLE_UBSAN=1 check $PARALLEL
make distclean && make jit_defconfig && make ENABLE_UBSAN=1 check $PARALLEL
- name: boot Linux kernel test
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make distclean && make system_defconfig && make INITRD_SIZE=32 $PARALLEL && \
make artifact $PARALLEL
bash -c "${BOOT_LINUX_TEST}"
make clean
- name: boot Linux kernel test (JIT)
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
BOOT_TIMEOUT: 90 # JIT compilation adds significant overhead
run: |
make distclean && make system_defconfig && make INITRD_SIZE=32 ENABLE_JIT=1 ENABLE_MOP_FUSION=0 $PARALLEL && make artifact $PARALLEL
bash -c "${BOOT_LINUX_TEST}"
make clean
- name: Architecture test
if: success()
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
. .ci/common.sh
export LATEST_RELEASE=$(fetch_latest_release sail)
python3 -m venv venv
. venv/bin/activate
.ci/riscv-tests.sh
coding-style:
needs: [detect-code-related-file-changes]
if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true'
timeout-minutes: 15
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- name: coding convention
run: |
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.gpg >/dev/null
echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-20 main" | sudo tee /etc/apt/sources.list.d/llvm-20.list
sudo apt-get update -q=2
sudo apt-get install -q=2 clang-format-20 shfmt python3-pip
pip3 install black==25.1.0
curl -LSfs https://go.mskelton.dev/dtsfmt/install | sh -s -- -y
.ci/check-newline.sh
.ci/check-format.sh
shell: bash
static-analysis:
needs: [detect-code-related-file-changes]
if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true'
timeout-minutes: 45
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
with:
submodules: 'true'
# LLVM static analysis
- name: set up scan-build
run: |
set +e # Disable errexit
# Configure apt to tolerate mirror sync failures
sudo tee /etc/apt/apt.conf.d/99-ci-reliability > /dev/null << 'EOF'
APT::Update::Error-Mode "any";
Acquire::IndexTargets::deb::DEP-11::DefaultEnabled "false";
Acquire::IndexTargets::deb::DEP-11-icons::DefaultEnabled "false";
Acquire::IndexTargets::deb::DEP-11-icons-hidpi::DefaultEnabled "false";
Acquire::Retries "3";
Acquire::Check-Valid-Until "false";
EOF
# Update with error tolerance
sudo apt-get update -q=2 2>&1
UPDATE_EXIT=$?
if [ $UPDATE_EXIT -ne 0 ]; then
echo "WARNING: apt update exited with code $UPDATE_EXIT, continuing..."
fi
sudo apt-get install -q=2 curl libsdl2-dev libsdl2-mixer-dev
LLVM_VERSION=$(cat .ci/llvm-version)
.ci/fetch.sh -q -o llvm.sh https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh $LLVM_VERSION
sudo apt-get install -q=2 clang-${LLVM_VERSION} clang-tools-${LLVM_VERSION}
set -e # Re-enable errexit
shell: bash
- name: run scan-build without JIT
env:
LATEST_RELEASE: dummy
run: |
LLVM_VERSION=$(cat .ci/llvm-version)
make distclean && make defconfig && scan-build-${LLVM_VERSION} -v -o ~/scan-build --status-bugs --use-cc=clang-${LLVM_VERSION} --force-analyze-debug-code --show-description -analyzer-config stable-report-filename=true -enable-checker valist,nullability make ENABLE_EXT_F=0 ENABLE_SDL=0 ENABLE_JIT=0
- name: run scan-build with JIT
env:
LATEST_RELEASE: dummy
run: |
LLVM_VERSION=$(cat .ci/llvm-version)
make distclean && make jit_defconfig && scan-build-${LLVM_VERSION} -v -o ~/scan-build --status-bugs --use-cc=clang-${LLVM_VERSION} --force-analyze-debug-code --show-description -analyzer-config stable-report-filename=true -enable-checker valist,nullability make ENABLE_EXT_F=0 ENABLE_SDL=0 ENABLE_JIT=1
# https://docs.docker.com/build/ci/github-actions/multi-platform/
docker-hub-build-and-publish:
needs: [detect-code-related-file-changes]
if: needs.detect-code-related-file-changes.outputs.has_code_related_changes == 'true'
timeout-minutes: 60
runs-on: ubuntu-24.04
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
submodules: 'true'
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
if: github.event_name == 'push'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }}
- name: Get short commit SHA1
if: github.event_name == 'push'
shell: bash
run: |
echo "short_hash=$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV"
- name: Build and push
if: github.event_name == 'push'
uses: docker/build-push-action@v6
with:
push: true
context: .
platforms: linux/amd64,linux/arm64/v8
tags: sysprog21/rv32emu:latest, sysprog21/rv32emu:${{ env.short_hash }}