Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 27 additions & 32 deletions .github/workflows/build-kbox.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Build kbox and run full test suite.
# Zero root required -- everything runs as an unprivileged user.
#
# Parallelism (5 independent jobs, 1 sequential):
# coding-style -- clang-format + newline checks (fast)
# static-analysis -- security patterns + cppcheck
# unit-tests -- no LKL dependency, ASAN/UBSAN
# build-kbox -- fetches LKL, compiles kbox + guest/stress bins, builds rootfs
# integration -- needs build-kbox artifacts, runs integration + stress tests
# Parallelism (4 independent jobs, 1 sequential):
# commit-hygiene -- Change-Id + subject format (needs full history)
# lint -- clang-format, newline, security, cppcheck (one apt install)
# unit-tests -- no LKL dependency, ASAN/UBSAN
# build-kbox -- fetches LKL, compiles kbox + guest/stress bins, builds rootfs
# integration -- needs build-kbox artifacts, runs integration + stress tests
#
# coding-style, static-analysis, unit-tests, and build-kbox run in parallel.
# commit-hygiene, lint, unit-tests, and build-kbox run in parallel.
# integration-tests waits for build-kbox only.
name: Build and Test

Expand Down Expand Up @@ -49,36 +49,31 @@ jobs:
scripts/check-commitlog.sh
fi

# ---- Coding style: formatting checks (fast, ~10s) ----
coding-style:
# ---- Lint: formatting + static analysis (consolidated, one apt install) ----
lint:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install clang-format
- name: Cache apt packages
uses: actions/cache@v5
with:
path: ~/apt-cache
key: apt-lint-${{ runner.os }}-${{ hashFiles('.github/workflows/build-kbox.yml') }}
- name: Install tools
run: |
mkdir -p ~/apt-cache
sudo apt-get update
sudo apt-get install -y clang-format-20
sudo apt-get install -y -o Dir::Cache::Archives=$HOME/apt-cache \
clang-format-20 cppcheck

- name: Check trailing newline
run: .ci/check-newline.sh

- name: Check clang-format
run: .ci/check-format.sh

# ---- Static analysis: security patterns + cppcheck ----
static-analysis:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install cppcheck
run: |
sudo apt-get update
sudo apt-get install -y cppcheck

- name: Security checks
run: .ci/check-security.sh

Expand All @@ -92,11 +87,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v6

- name: Install compiler
run: |
sudo apt-get update
sudo apt-get install -y build-essential

- name: Run unit tests (ASAN/UBSAN)
run: make check-unit

Expand All @@ -107,10 +97,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v6

- name: Cache apt packages
uses: actions/cache@v5
with:
path: ~/apt-cache
key: apt-build-${{ runner.os }}-${{ hashFiles('.github/workflows/build-kbox.yml') }}
- name: Install dependencies
run: |
mkdir -p ~/apt-cache
sudo apt-get update
sudo apt-get install -y build-essential e2fsprogs
sudo apt-get install -y -o Dir::Cache::Archives=$HOME/apt-cache \
e2fsprogs

- name: Fetch prebuilt LKL
run: ./scripts/fetch-lkl.sh
Expand All @@ -122,7 +119,7 @@ jobs:
path: |
alpine.ext4
deps/
key: rootfs-${{ hashFiles('scripts/alpine-sha256.txt', 'scripts/mkrootfs.sh', 'tests/guest/*.c', 'tests/stress/*.c', 'Makefile') }}
key: rootfs-${{ hashFiles('scripts/alpine-sha256.txt', 'scripts/common.sh', 'scripts/mkrootfs.sh', 'tests/guest/*.c', 'tests/stress/*.c', 'Makefile') }}

- name: Configure (defconfig)
run: make defconfig
Expand All @@ -148,7 +145,6 @@ jobs:
tests/guest/*-test
tests/stress/*
!tests/stress/*.c
scripts/

# ---- Integration + stress tests: needs kbox binary + rootfs ----
integration-tests:
Expand All @@ -169,7 +165,6 @@ jobs:
chmod +x kbox
chmod +x tests/guest/*-test 2>/dev/null || true
chmod +x tests/stress/* 2>/dev/null || true
chmod +x scripts/*.sh

- name: Integration tests
run: ./scripts/run-tests.sh ./kbox alpine.ext4
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/build-lkl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,20 @@ jobs:
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Cache apt packages
uses: actions/cache@v5
with:
path: ~/apt-cache
key: apt-lkl-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('.github/workflows/build-lkl.yml') }}
- name: Install build dependencies
run: |
mkdir -p ~/apt-cache
sudo apt-get update
sudo apt-get install -y build-essential flex bison bc libelf-dev
sudo apt-get install -y -o Dir::Cache::Archives=$HOME/apt-cache \
build-essential flex bison bc libelf-dev

- name: Build LKL from source
env:
Expand Down
4 changes: 3 additions & 1 deletion mk/tests.mk
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@ check-unit: $(TEST_TARGET)

# Unit tests are built WITHOUT linking LKL.
# We define LKL stubs for functions referenced by test support code.
TEST_LDFLAGS = $(filter-out -L$(LKL_DIR) -L$(LKL_DIR)/lib,$(LDFLAGS))

$(TEST_TARGET): $(TEST_SRCS) $(TEST_SUPPORT_SRCS) $(wildcard .config)
@echo " LD $@"
$(Q)$(CC) $(CFLAGS) -DKBOX_UNIT_TEST -o $@ $(TEST_SRCS) $(TEST_SUPPORT_SRCS) $(LDFLAGS) -lpthread
$(Q)$(CC) $(CFLAGS) -DKBOX_UNIT_TEST -o $@ $(TEST_SRCS) $(TEST_SUPPORT_SRCS) $(TEST_LDFLAGS) -lpthread

check-integration: $(TARGET) guest-bins stress-bins $(ROOTFS)
@echo " RUN check-integration"
Expand Down
67 changes: 48 additions & 19 deletions scripts/build-lkl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,74 @@

set -eu

case "${1:-$(uname -m)}" in
x86_64 | amd64) ARCH="x86_64" ;;
aarch64 | arm64) ARCH="aarch64" ;;
*)
echo "error: unsupported architecture: ${1:-$(uname -m)}" >&2
exit 1
;;
esac
. "$(cd "$(dirname "$0")" && pwd)/common.sh"

detect_arch "${1:-}"

LKL_DIR="${LKL_DIR:-lkl-${ARCH}}"
LKL_SRC="${LKL_SRC:-build/lkl-src}"
LKL_UPSTREAM="https://github.com/lkl/linux"

die()
is_commit_sha()
{
echo "error: $*" >&2
exit 1
case "$1" in
[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]*)
case "$1" in
*[!0-9a-fA-F]*)
return 1
;;
esac
return 0
;;
esac

return 1
}

checkout_lkl_ref()
{
ref="$1"

echo " CHECKOUT ${ref}"
if is_commit_sha "$ref"; then
# Bare commit SHAs are not advertised as remote refs, so a shallow
# ref fetch is not reliable. Ensure full history before checkout.
if git -C "${LKL_SRC}" rev-parse --is-shallow-repository > /dev/null 2>&1 \
&& [ "$(git -C "${LKL_SRC}" rev-parse --is-shallow-repository)" = "true" ]; then
git -C "${LKL_SRC}" fetch --unshallow origin
else
git -C "${LKL_SRC}" fetch origin
fi
git -C "${LKL_SRC}" checkout "${ref}"
else
git -C "${LKL_SRC}" fetch --depth=1 origin "${ref}"
git -C "${LKL_SRC}" checkout FETCH_HEAD
fi
}

# ---- Clone or update source tree ----------------------------------------

if [ -d "${LKL_SRC}/.git" ]; then
echo " SRC ${LKL_SRC} (exists, skipping clone)"
if [ -n "${LKL_REF:-}" ]; then
echo " CHECKOUT ${LKL_REF}"
git -C "${LKL_SRC}" fetch --depth=1 origin "${LKL_REF}"
git -C "${LKL_SRC}" checkout FETCH_HEAD
checkout_lkl_ref "${LKL_REF}"
fi
else
echo " CLONE ${LKL_UPSTREAM} -> ${LKL_SRC}"
mkdir -p "$(dirname "${LKL_SRC}")"

if [ -n "${LKL_REF:-}" ]; then
# Shallow clone of a specific ref.
git clone --depth=1 --branch "${LKL_REF}" \
"${LKL_UPSTREAM}" "${LKL_SRC}"
if [ -n "${LKL_REF:-}" ] && is_commit_sha "${LKL_REF}"; then
# Commit SHAs require full history because remotes typically do not
# advertise arbitrary object IDs as shallow-fetchable refs.
git clone "${LKL_UPSTREAM}" "${LKL_SRC}"
checkout_lkl_ref "${LKL_REF}"
else
# Shallow clone of default branch.
# Shallow clone of default branch, then checkout specific branch/tag if
# requested. git clone --branch does not accept bare commit SHAs.
git clone --depth=1 "${LKL_UPSTREAM}" "${LKL_SRC}"
if [ -n "${LKL_REF:-}" ]; then
checkout_lkl_ref "${LKL_REF}"
fi
fi
fi

Expand Down
102 changes: 95 additions & 7 deletions scripts/common.sh
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#!/usr/bin/env bash
# common.sh -- shared helpers for kbox scripts and git hooks.

# shared helpers for kbox scripts and git hooks.
#
# All functions use portable shell (dash/ash/bash). `local` is not
# strictly POSIX but is supported by every sh implementation we target.
# Source via: . "$(cd "$(dirname "$0")" && pwd)/common.sh"

# --- Terminal colors ---
# Call set_colors before using RED/GREEN/YELLOW/NC etc.

set_colors()
{
local default_color
default_color=$(git config --get color.ui 2> /dev/null || echo 'auto')
if [[ "$default_color" == "always" ]] || [[ "$default_color" == "auto" && -t 1 ]]; then
if [ -t 1 ]; then
RED='\033[1;31m'
GREEN='\033[1;32m'
YELLOW='\033[1;33m'
Expand All @@ -24,19 +30,101 @@ set_colors()
fi
}

# Print a fatal error message and exit.
# --- Error handling ---

# Simple fatal error (no colors, works before set_colors).
die()
{
echo "error: $*" >&2
exit 1
}

# Formatted fatal error (uses RED/NC from set_colors).
throw()
{
local fmt="$1"
shift
printf "\n${RED}[!] $fmt${NC}\n" "$@" >&2
# shellcheck disable=SC2059
printf "\n${RED}[!] ${fmt}${NC}\n" "$@" >&2
exit 1
}

# Skip hooks entirely when running in CI (GitHub Actions, etc.).
# --- CI detection ---

check_ci()
{
if [ -n "${CI:-}" ] || [ -d "/home/runner/work" ]; then
exit 0
fi
}

# --- Architecture detection ---
# Sets ARCH to x86_64 or aarch64. Accepts an optional override argument;
# defaults to the host architecture via uname -m.

detect_arch()
{
case "${1:-$(uname -m)}" in
x86_64 | amd64) ARCH="x86_64" ;;
aarch64 | arm64) ARCH="aarch64" ;;
*) die "unsupported architecture: ${1:-$(uname -m)}" ;;
esac
}

# --- Download helpers ---
# download_file URL OUTPUT -- fetch URL to OUTPUT via curl or wget.

download_file()
{
local url="$1"
local output="$2"

if command -v curl > /dev/null 2>&1; then
curl -fSL -o "$output" "$url" || die "download failed: $url"
elif command -v wget > /dev/null 2>&1; then
wget -q -O "$output" "$url" || die "download failed: $url"
else
die "neither curl nor wget found"
fi
}

# --- SHA256 verification ---
# verify_sha256 FILE SHA256_FILE [PATTERN]
# Looks up PATTERN (default: basename of FILE) in SHA256_FILE and
# verifies the hash. Removes FILE on mismatch. No-op if SHA256_FILE
# is missing or has no matching entry.

verify_sha256()
{
local file="$1"
local sha256_file="$2"
local pattern="${3:-$(basename "$file")}"

[ -f "$sha256_file" ] || return 0

local expected
expected=$(awk -v f="$pattern" '$2 == f { print $1 }' "$sha256_file")
[ -n "$expected" ] || return 0

local actual
actual=$(sha256sum "$file" | awk '{print $1}')
if [ "$actual" != "$expected" ]; then
rm -f "$file"
die "SHA256 mismatch for ${pattern}"
fi
echo "SHA256 verified."
}

# --- Timeout command detection ---
# Sets TIMEOUT_CMD to "timeout", "gtimeout", or "" (not available).

find_timeout_cmd()
{
if command -v timeout > /dev/null 2>&1; then
TIMEOUT_CMD="timeout"
elif command -v gtimeout > /dev/null 2>&1; then
TIMEOUT_CMD="gtimeout"
else
TIMEOUT_CMD=""
fi
}
Loading
Loading