diff --git a/scripts/new b/scripts/new index f4ebb99..8c67dad 100755 --- a/scripts/new +++ b/scripts/new @@ -5,10 +5,6 @@ set -eu -# Force the C locale so the [a-z0-9._-] range below means ASCII — under -# some locales the collation order folds uppercase into [a-z]. -export LC_ALL=C - # Resolve the template relative to this script (which lives in scripts/), # so it works from any working directory. SRC="$(cd "$(dirname "$0")/.." && pwd)/template" @@ -20,13 +16,15 @@ if [ -z "$DEST" ]; then fi NAME="${2:-$(basename "$DEST")}" -# npm package names: lowercase letters, digits, dash, underscore, dot. -case "$NAME" in - *[!a-z0-9._-]*) - echo "error: invalid project name '$NAME' (use [a-z0-9._-])" >&2 - exit 1 - ;; -esac +# npm package names: lowercase letters, digits, dash, underscore, dot. The +# check runs in the C locale so the [a-z0-9._-] range means ASCII — under +# some locales the collation order folds uppercase into [a-z]. Only the check: +# the rest of the script (and the make it calls) keeps the user's locale so +# the output marks match the build's. +if printf '%s' "$NAME" | LC_ALL=C grep -q '[^a-z0-9._-]'; then + echo "error: invalid project name '$NAME' (use [a-z0-9._-])" >&2 + exit 1 +fi if [ ! -d "$SRC" ]; then echo "error: template/ not found — nothing to copy" >&2 @@ -57,6 +55,15 @@ done # Finalize via the project's Makefile (git init with the vendored git). The # generation logic lives in `make postgen`, not here — this just calls it. -make -C "$DEST" postgen || echo "warning: 'make postgen' failed" >&2 +make --no-print-directory -C "$DEST" postgen || echo "warning: 'make postgen' failed" >&2 -echo "created '$NAME' in $DEST — cd there, run 'yeet build', then 'yeet run'" +# Match the project's build output (build/ui.mk): a row with a tag column, +# marks in UTF-8 locales only, color only on a terminal. +if [ -t 2 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-}" != dumb ]; then + fmt='\033[1;32m%s\033[0m \033[1;36m%-10s\033[0m %s\n' +else + fmt='%s %-10s %s\n' +fi +case "$(locale charmap 2>/dev/null)" in UTF-8) ok=✔ ;; *) ok=+ ;; esac +printf "$fmt" "$ok" created "$NAME $DEST" >&2 +printf "$fmt" ' ' next "cd $DEST && make && yeet run ." >&2 diff --git a/template/.github/workflows/kernel-matrix.yml b/template/.github/workflows/kernel-matrix.yml index daecdb8..0ac1c37 100644 --- a/template/.github/workflows/kernel-matrix.yml +++ b/template/.github/workflows/kernel-matrix.yml @@ -71,8 +71,9 @@ jobs: make bpf # Resolve the vendored static veristat the same way build/toolchain.mk - # does, and stage it into bin/ so the VM finds it under /host. It is - # fully static, so it runs in any kernel image's rootfs. + # does, and stage it into .kmatrix/ (the matrix workspace, not bin/, + # which holds only build products) so the VM finds it under /host. + # It is fully static, so it runs in any kernel image's rootfs. . build/toolchain.lock arch="$(uname -m)"; [ "$arch" = arm64 ] && arch=aarch64 cache="${XDG_CACHE_HOME:-$HOME/.cache}/yeet/toolchain/v${TOOLCHAIN_VERSION}/${arch}" @@ -80,8 +81,8 @@ jobs: echo "::error::veristat is not in the pinned toolchain (v${TOOLCHAIN_VERSION}). Bump build/toolchain.lock to a toolchain release that ships veristat." exit 1 fi - install -Dm755 "$cache/veristat" bin/veristat - file bin/veristat bin/probe.bpf.o + install -Dm755 "$cache/veristat" .kmatrix/veristat + file .kmatrix/veristat bin/probe.bpf.o - name: Verify on kernel ${{ matrix.kernel }} uses: cilium/little-vm-helper@v0.0.30 diff --git a/template/.gitignore b/template/.gitignore index 49e70d5..faba519 100644 --- a/template/.gitignore +++ b/template/.gitignore @@ -1,11 +1,10 @@ -# build artifacts (the esbuild bundle lands in dist/index.jsx) +# build artifacts (the esbuild bundle lands in dist/index.js) /node_modules/ /dist/ /.build/ -# compiled BPF objects + generated CO-RE header -/bin/* -!/bin/.gitkeep +# compiled BPF objects (bin/ exists only once built) + generated CO-RE header +/bin/ /src/bpf/include/vmlinux.h # kernel-matrix run output (build/kernel-matrix.sh) diff --git a/template/Makefile b/template/Makefile index 264c617..78c3e1b 100644 --- a/template/Makefile +++ b/template/Makefile @@ -30,19 +30,17 @@ include build/bpf.mk BUILD_START := $(shell date +%s%N 2>/dev/null) all: bpf bundle - @end=$$(date +%s%N 2>/dev/null); t=""; \ - case "$(BUILD_START)$$end" in \ - ""|*[!0-9]*) ;; \ - *) t=" in $$(awk "BEGIN { printf \"%.2fs\", ($$end - $(BUILD_START)) / 1e9 }")" ;; \ - esac; \ - printf '$(STATUS_FMT)' 'Finished' "build$$t" >&2 + $(call done,$(notdir $(CURDIR))) + $(call artifact,$(BPF_OUT)) + $(call artifact,dist/index.js) # Bundle the entry with the vendored esbuild. esbuild honors tsconfig `paths` # (so `@/` resolves at bundle time), while `yeet:*` builtins and `*.bpf.o` -# objects stay external. The bundle is written to dist/index.jsx: the entry +# objects stay external. The bundle is written to dist/index.js: the entry # ladder tries dist/ before src/, so once built, that is what runs — and the -# generated file stays out of the source tree (dist/ is gitignored). -# The .jsx extension keeps the bundle eligible for component auto-mount. +# generated file stays out of the source tree (dist/ is gitignored). Plain +# .js is fine: JSX is compiled away by esbuild, and src/main.jsx mounts its +# root component explicitly, so the bundle needs no auto-mount. # Compiled BPF objects in bin/ are loaded by path at runtime, never imported, # so they are not bundled. # @@ -53,11 +51,10 @@ all: bpf bundle ESBUILD_FLAGS := --bundle --format=esm --platform=neutral \ --main-fields=module,main --conditions=import,module \ --define:import.meta.main=false \ - --outfile=dist/index.jsx --jsx=automatic --jsx-import-source=yeet:tui + --outfile=dist/index.js --jsx=automatic --jsx-import-source=yeet:tui bundle: | toolchain - $(Q)$(ESBUILD) src/main.jsx $(ESBUILD_FLAGS) --log-level=warning '--external:yeet:*' '--external:*.bpf.o' - @printf '$(STATUS_FMT)' 'Bundling' "src/main.jsx -> dist/index.jsx ($$(wc -c < dist/index.jsx | awk '{ printf "%.1fkb", $$1 / 1024 }'))" >&2 + $(call step,bundle,dist/index.js,$(ESBUILD) src/main.jsx $(ESBUILD_FLAGS) --log-level=warning '--external:yeet:*' '--external:*.bpf.o') # Post-generation finalize: initialize a git repository with the vendored git # (fetched via `vendored-git`). Idempotent — skipped if this is already a repo. @@ -66,16 +63,17 @@ bundle: | toolchain postgen: | vendored-git @g="$(GIT)"; $$g --version >/dev/null 2>&1 || g="$$(command -v git 2>/dev/null || true)"; \ if [ -e .git ]; then \ - printf '$(STATUS_FMT)' 'Skipping' 'git init (already a repository)' >&2; \ + printf '$(STATUS_FMT)' 'git' 'already a repository' >&2; \ elif [ -n "$$g" ]; then \ - printf '$(STATUS_FMT)' 'Initializing' 'git repository' >&2; \ + printf '$(STATUS_FMT)' 'git' 'initialized repository' >&2; \ $$g -c init.templateDir= init -q . || printf '$(WARN_FMT)' 'git init failed' >&2; \ else \ printf '$(WARN_FMT)' 'no git available (vendored or host); skipping git init' >&2; \ fi clean: clean-bpf - $(call status,Cleaning,node_modules dist) + $(call status,clean,node_modules) + $(call status,clean,dist) $(Q)rm -rf node_modules dist .PHONY: all bundle clean postgen diff --git a/template/README.md b/template/README.md index b29f264..b71275f 100644 --- a/template/README.md +++ b/template/README.md @@ -49,7 +49,7 @@ src/components/*.jsx pure UI: titlebar, heatmap, detail, histogram, footer src/lib/format.js pure render helpers (rate, duration, heat color) src/bpf/cpusched.bpf.c sched_switch program + the runtime knob src/bpf/runqlat.bpf.c wakeup→on-CPU latency histogram -bin/ the linked BPF object lands here +bin/ the linked BPF object lands here (created by the build) dist/ the esbuild JS bundle lands here (gitignored) .github/workflows/kernel-matrix.yml CI: verify the object across kernels build/verify-kernel.sh the in-VM gate that workflow runs @@ -65,12 +65,12 @@ the `@/` alias; `main.jsx` wires them together and owns input. ```sh make # compile BPF (clang + bpftool) + bundle JS (esbuild) make V=1 # same, showing every command behind the status lines -yeet run . # runs the bundled dist/index.jsx (needs root for BPF) +yeet run . # runs the bundled dist/index.js (needs root for BPF) ``` `make` runs two independent compilers: **clang + bpftool** compile `src/bpf/*.bpf.c` and link them into one loadable object `bin/probe.bpf.o`; -**esbuild** bundles `src/main.jsx` into `dist/index.jsx`, resolving the `@/` +**esbuild** bundles `src/main.jsx` into `dist/index.js`, resolving the `@/` alias (and inlining any npm/jsr deps you add) and leaving `yeet:*` builtins external. esbuild is vendored by the toolchain, so the build needs no node/npm. diff --git a/template/bin/.gitkeep b/template/bin/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/template/build/bpf.mk b/template/build/bpf.mk index f54c804..7e8a2f5 100644 --- a/template/build/bpf.mk +++ b/template/build/bpf.mk @@ -47,28 +47,29 @@ bpf: $(BPF_OUT) # the cache before any rule shells out to them, without forcing rebuilds. $(VMLINUX): | toolchain @command -v $(firstword $(BPFTOOL)) >/dev/null 2>&1 || { $(call fail,bpftool not found — install bpftool / linux-tools); } - $(call status,Generating,$@) - $(Q)sh build/gen-vmlinux.sh "$(BPFTOOL)" $@ + $(call step,vmlinux.h,$@,sh build/gen-vmlinux.sh "$(BPFTOOL)" $@) # Compile each unit to an intermediate object. .build/bpf/%.bpf.o: src/bpf/%.bpf.c $(VMLINUX) | toolchain @command -v $(firstword $(CLANG)) >/dev/null 2>&1 || { $(call fail,clang not found — install clang); } - $(call status,Compiling,$(notdir $<)) $(Q)mkdir -p $(dir $@) - $(Q)$(CLANG) $(BPF_CFLAGS) -c $< -o $@ + $(call step,$(basename $(basename $(notdir $<))),$@,$(CLANG) $(BPF_CFLAGS) -c $< -o $@) # Statically link every unit into the single loadable object. $(BPF_OUT): $(BPF_OBJS) | bin toolchain @command -v $(firstword $(BPFTOOL)) >/dev/null 2>&1 || { $(call fail,bpftool not found — install bpftool / linux-tools); } - $(call status,Linking,$@) - $(Q)$(BPFTOOL) gen object $@ $(BPF_OBJS) + $(call step,link,$@,$(BPFTOOL) gen object $@ $(BPF_OBJS)) bin: - mkdir -p bin + $(Q)mkdir -p bin +# bin/ is a build product: it holds the linked object (and whatever the kernel +# matrix stages beside it), is not tracked, and the `bin` rule recreates it. clean-bpf: - $(call status,Cleaning,$(BPF_OUT) .build $(VMLINUX)) - $(Q)rm -rf $(BPF_OUT) .build $(VMLINUX) + $(call status,clean,bin) + $(call status,clean,.build) + $(call status,clean,$(VMLINUX)) + $(Q)rm -rf bin .build $(VMLINUX) # Load the linked object with veristat to confirm THIS kernel's verifier # accepts every program, and to see per-program complexity (insns/states) — a @@ -79,8 +80,7 @@ clean-bpf: .PHONY: veristat veristat: $(BPF_OUT) | toolchain @command -v $(firstword $(VERISTAT)) >/dev/null 2>&1 || { $(call fail,veristat not found ($(VERISTAT)) — bump build/toolchain.lock to a toolchain that ships veristat, or install veristat on PATH); } - $(call status,Verifying,$(BPF_OUT)) - $(Q)$(VERISTAT) $(BPF_OUT) + $(call step,verify,$(BPF_OUT),$(VERISTAT) $(BPF_OUT)) # Run the same verifier check across a matrix of kernels locally (Linux + KVM), # the local counterpart to .github/workflows/kernel-matrix.yml. Boots @@ -88,7 +88,7 @@ veristat: $(BPF_OUT) | toolchain # KERNELS="6.6-main bpf-next-main" or rely on the script's default spread. .PHONY: veristat-matrix veristat-matrix: $(BPF_OUT) | toolchain - $(call status,Verifying,$(BPF_OUT) across kernels) + $(call status,verify,$(BPF_OUT) across kernels) $(Q)VERISTAT="$(VERISTAT)" TOOLCHAIN_DIR="$(TOOLCHAIN_DIR)" sh build/kernel-matrix.sh $(KERNELS) # Write a local .clangd so the editor resolves vmlinux.h, the libbpf SDK @@ -111,6 +111,6 @@ clangd: ' - -D__TARGET_ARCH_$(ARCH)' \ ' - -D__BPF_TRACING__' \ > .clangd - $(call status,Writing,.clangd (libbpf headers: $(if $(BPF_SYSINCLUDE),$(BPF_SYSINCLUDE),))) + $(call status,clangd,.clangd (libbpf headers: $(if $(BPF_SYSINCLUDE),$(BPF_SYSINCLUDE),))) .PHONY: bpf clean-bpf diff --git a/template/build/fetch-toolchain.sh b/template/build/fetch-toolchain.sh index 9d91222..0cfc3b9 100755 --- a/template/build/fetch-toolchain.sh +++ b/template/build/fetch-toolchain.sh @@ -14,7 +14,7 @@ set -eu # Status lines in the build's format (STATUS_FMT comes from the Makefile; the # default matches it uncolored). curl shows a progress bar only on a terminal. -status() { printf "${STATUS_FMT:-%12s %s\n}" "$1" "$2" >&2; } +status() { printf "${STATUS_FMT:-* %-10s %s\n}" "$1" "$2" >&2; } if [ -t 2 ]; then CURL_PROGRESS=--progress-bar; else CURL_PROGRESS=-s; fi DIR="${1:?usage: fetch-toolchain.sh [tool...]}" @@ -71,7 +71,7 @@ fetch_bin() { name="$1" want "$name" || return 0 [ -x "$DIR/$name" ] && return 0 - status Downloading "${name} (${ARCH}, v${TOOLCHAIN_VERSION})" + status download "${name} (${ARCH}, v${TOOLCHAIN_VERSION})" tmp="$DIR/.${name}.$$" curl -fSL $CURL_PROGRESS --retry 3 -o "$tmp" "${REL}/${name}-${ARCH}" verify "$tmp" "$(get_sha "$(echo "$name" | tr a-z A-Z)")" "$name" || { rm -f "$tmp"; exit 1; } @@ -91,7 +91,7 @@ fetch_bin gh # per-arch tool dirs ($key/include/bpf/*.h). INC="$(dirname "$DIR")/include" if want headers && [ ! -e "$INC/bpf/bpf_helpers.h" ]; then - status Downloading "libbpf headers (v${TOOLCHAIN_VERSION})" + status download "libbpf headers (v${TOOLCHAIN_VERSION})" td="$(mktemp -d)" curl -fSL $CURL_PROGRESS --retry 3 -o "$td/h.tgz" "${REL}/libbpf-headers.tar.gz" verify "$td/h.tgz" "${LIBBPF_HEADERS_SHA256:-}" "libbpf-headers" || { rm -rf "$td"; exit 1; } @@ -100,4 +100,4 @@ if want headers && [ ! -e "$INC/bpf/bpf_helpers.h" ]; then rm -rf "$td" fi -status Toolchain "$DIR${FILTER:+ ($FILTER)}" +status toolchain "$DIR${FILTER:+ ($FILTER)}" diff --git a/template/build/kernel-matrix.sh b/template/build/kernel-matrix.sh index dca6a57..5dcd5af 100755 --- a/template/build/kernel-matrix.sh +++ b/template/build/kernel-matrix.sh @@ -92,8 +92,11 @@ fi [ -e /dev/kvm ] && ACCEL="--cpu-kind host" || { ACCEL="--no-hw-accel"; echo "note: /dev/kvm absent — running under TCG emulation (slow)"; } # --- build the object and stage the vendored static veristat ------------------ +# veristat goes into .kmatrix/ (the run's workspace, gitignored), not bin/, +# which holds only build products and is removed by `make clean`. echo ">> building $OBJ" make bpf >/dev/null +OUTDIR="$PWD/.kmatrix"; rm -rf "$OUTDIR"; mkdir -p "$OUTDIR" VERISTAT="${VERISTAT:-}" if [ -z "$VERISTAT" ] && [ -f build/toolchain.lock ]; then . ./build/toolchain.lock @@ -101,11 +104,10 @@ if [ -z "$VERISTAT" ] && [ -f build/toolchain.lock ]; then fi [ -n "$VERISTAT" ] && [ -x "$VERISTAT" ] || VERISTAT="$(command -v veristat || true)" [ -n "$VERISTAT" ] && [ -x "$VERISTAT" ] || { echo "error: veristat not found — bump build/toolchain.lock to a toolchain that ships it, or install veristat" >&2; exit 1; } -install -Dm755 "$VERISTAT" bin/veristat +install -Dm755 "$VERISTAT" "$OUTDIR/veristat" # --- per-kernel: pull image, boot, verify, stop ------------------------------- WORK="$(mktemp -d)" -OUTDIR="$PWD/.kmatrix"; rm -rf "$OUTDIR"; mkdir -p "$OUTDIR" overall=0 stop_vm() { printf 'quit\n' | { nc -N 127.0.0.1 "$MON_PORT" 2>/dev/null || nc 127.0.0.1 "$MON_PORT" 2>/dev/null; } || true; sleep 1; } diff --git a/template/build/toolchain.mk b/template/build/toolchain.mk index dffc4aa..0edbdb7 100644 --- a/template/build/toolchain.mk +++ b/template/build/toolchain.mk @@ -32,9 +32,9 @@ # Status-line formats, normally set by the project's build/ui.mk (included # before this file); these fallbacks keep the glue usable on its own. Exported # so build/fetch-toolchain.sh prints the same way. -STATUS_FMT ?= %12s %s\n -WARN_FMT ?= warning: %s\n -ERROR_FMT ?= error: %s\n +STATUS_FMT ?= * %-10s %s\n +WARN_FMT ?= ! %s\n +ERROR_FMT ?= x %s\n export STATUS_FMT WARN_FMT ERROR_FMT UNAME_S := $(shell uname -s) @@ -108,10 +108,10 @@ GH ?= gh toolchain: ifneq ($(YEET),) @$(YEET_EXEC) clang -print-prog-name=clang >/dev/null 2>&1 || { \ - printf '$(STATUS_FMT)' 'Installing' 'yeet toolchain $(TOOLCHAIN_WANT) (none installed yet)' >&2; \ + printf '$(STATUS_FMT)' 'install' 'yeet toolchain $(TOOLCHAIN_WANT) (none installed yet)' >&2; \ $(YEET) toolchain install $(TOOLCHAIN_WANT) --default; } @d="$$($(YEET_EXEC) clang -print-prog-name=clang)"; d="$${d%/*}"; v="$${d##*/}"; \ - printf '$(STATUS_FMT)' 'Toolchain' "yeet $$v ($$d)" >&2; \ + printf '$(STATUS_FMT)' 'toolchain' "$$v ~$${d#$$HOME}" >&2; \ if [ -n "$(TOOLCHAIN_KEY)" ] && [ "$$v" != "$(TOOLCHAIN_KEY)" ]; then \ printf '$(WARN_FMT)' "yeet's default toolchain is $$v but build/toolchain.lock pins $(TOOLCHAIN_KEY) — 'yeet toolchain default $(TOOLCHAIN_KEY)' to match it, or 'make YEET=' to build from the pinned cache" >&2; \ fi diff --git a/template/build/ui.mk b/template/build/ui.mk index 0408fcb..2992568 100644 --- a/template/build/ui.mk +++ b/template/build/ui.mk @@ -1,22 +1,28 @@ -# Build output styling — cargo-like status lines: +# Build output styling — a checklist: one row per step with what it +# produced, then a summary row with the total time, and the artifacts: # -# Toolchain yeet v0.10.1 (~/.local/share/yeet/toolchain/linux/x86_64/v0.10.1) -# Generating src/bpf/include/vmlinux.h -# Compiling cpusched.bpf.c -# Compiling runqlat.bpf.c -# Linking bin/probe.bpf.o -# Bundling src/main.jsx -> dist/index.jsx (15.5kb) -# Finished build in 0.84s +# ● toolchain v0.10.1 ~/.local/share/yeet/toolchain/linux/x86_64/v0.10.1 +# ✔ vmlinux.h src/bpf/include/vmlinux.h +# ✔ cpusched .build/bpf/cpusched.bpf.o +# ✔ runqlat .build/bpf/runqlat.bpf.o +# ✔ link bin/probe.bpf.o +# ✔ bundle dist/index.js +# ✔ done myproject (0.66s) # -# A bold green verb, right-aligned to 12 columns, then the message — on -# stderr, like cargo, so the build's stdout stays clean for piping. The -# commands themselves are hidden; `make V=1` echoes each one under its status -# line. Color is used only when stderr is a terminal, TERM isn't dumb and -# NO_COLOR (https://no-color.org) is unset. Compiler diagnostics pass through -# untouched, so a warning or error is never hidden. +# ==> bin/probe.bpf.o (8.1kb) +# ==> dist/index.js (15.5kb) +# +# Rows go to stderr, so the build's stdout stays clean for piping. While a +# step runs, its row shows a spinner, then settles into the check. The +# commands themselves are hidden; `make V=1` echoes each one before its +# row. A failed step is marked ✘ in red after its own diagnostics, so a +# compiler error is never hidden. Color is used only when stderr is a +# terminal, TERM isn't dumb and NO_COLOR (https://no-color.org) is unset; +# the spinner needs the same, plus a serial build (parallel steps would +# fight over the line). The marks fall back to ASCII outside a UTF-8 locale. # # Included first by the project Makefile, so build/toolchain.mk and -# build/bpf.mk can use `$(Q)` and `$(call status,Verb,message)`. +# build/bpf.mk can use the macros below. ifeq ($(V),1) Q := @@ -25,21 +31,95 @@ else endif COLOR := $(shell [ -t 2 ] && [ -z "$$NO_COLOR" ] && [ "$$TERM" != dumb ] && echo 1) +UTF8 := $(if $(findstring UTF-8,$(shell locale charmap 2>/dev/null)),1) +SPIN := $(if $(and $(COLOR),$(if $(filter -j% --jobserver%,$(MAKEFLAGS)),,1)),1) + +# Marks: done, pending/info, failed, warning; and the spinner frames. +ifeq ($(UTF8),1) + M_OK := ✔ + M_ON := ● + M_NO := ✘ + M_WN := ▲ + SPIN_FRAMES := ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ +else + M_OK := + + M_ON := * + M_NO := x + M_WN := ! + SPIN_FRAMES := '\' '|' '/' '-' +endif + +# Row formats take (tag, artifact, trailer); see `row`. ifeq ($(COLOR),1) - STATUS_FMT := \033[1;32m%12s\033[0m %s\n - WARN_FMT := \033[1;33mwarning\033[0m: %s\n - ERROR_FMT := \033[1;31merror\033[0m: %s\n + STEP_FMT := \033[1;32m$(M_OK)\033[0m \033[1;36m%-10s\033[0m %s\n + DONE_FMT := \033[1;32m$(M_OK) %-10s\033[0m \033[1m%s\033[0m \033[1;32m(%s)\033[0m\n + ART_FMT := \033[1m==>\033[0m \033[35m%s\033[0m \033[1;32m(%s)\033[0m\n + FAIL_FMT := \033[1;31m$(M_NO)\033[0m \033[1;36m%-10s\033[0m %s \033[1;31m(%s)\033[0m\n + SPIN_FMT := \033[1;33m%s\033[0m \033[1;36m%-10s\033[0m %s + STATUS_FMT := \033[1;34m$(M_ON)\033[0m \033[1;36m%-10s\033[0m %s\n + WARN_FMT := \033[1;33m$(M_WN)\033[0m %s\n + ERROR_FMT := \033[1;31m$(M_NO)\033[0m %s\n else - STATUS_FMT := %12s %s\n - WARN_FMT := warning: %s\n - ERROR_FMT := error: %s\n + STEP_FMT := $(M_OK) %-10s %s\n + DONE_FMT := $(M_OK) %-10s %s (%s)\n + ART_FMT := ==> %s (%s)\n + FAIL_FMT := $(M_NO) %-10s %s (%s)\n + STATUS_FMT := $(M_ON) %-10s %s\n + WARN_FMT := $(M_WN) %s\n + ERROR_FMT := $(M_NO) %s\n endif # The shell scripts the build calls print the same way (fetch-toolchain.sh). export STATUS_FMT WARN_FMT ERROR_FMT -# $(call status,Verb,message) and $(call warn,message) are whole recipe lines; -# $(call fail,message) is a shell fragment for `|| { $(call fail,…); }`. -# Messages must not contain single quotes. -status = @printf '$(STATUS_FMT)' '$(1)' '$(2)' >&2 -warn = @printf '$(WARN_FMT)' '$(1)' >&2 -fail = printf '$(ERROR_FMT)' '$(1)' >&2; exit 1 +# Elapsed time between $s and now as "0.21s", empty where date lacks %N. +elapsed = e=$$(date +%s%N 2>/dev/null); case "$$s$$e" in ""|*[!0-9]*) t="" ;; *) t=$$(awk "BEGIN { printf \"%.2fs\", ($$e - $$s) / 1e9 }") ;; esac + +# $(call row,FMT,tag,artifact[,trailer]) — a shell fragment printing a row; +# a trailer (the total time, a size, or "failed") follows the artifact in +# parentheses where the format has a slot for it. The artifact and trailer +# are expanded by the shell, so they may hold a $$(…) substitution (see +# `filesize`). +row = printf '$(1)' $(if $(2),"$(2)" )"$(3)" $(if $(4),"$(4)" )>&2 + +# $(call step,tag,artifact,command) — run the command, then print its row; +# on failure print it as failed and stop. With the +# spinner, the command runs in the background with its output captured, +# the row animates until it exits, then the output (diagnostics) is +# replayed above the final row. Tag and artifact must not contain double +# quotes; a comma in the artifact needs $(comma). +define step +$(if $(filter 1,$(V)),$(info $(3)))@if [ -n "$(SPIN)" ]; then \ + out=$$(mktemp); ( $(3) ) >"$$out" 2>&1 & pid=$$!; \ + trap 'kill $$pid 2>/dev/null; printf "\r\033[2K" >&2' INT TERM; \ + set -- $(SPIN_FRAMES); n=$$#; i=0; \ + while kill -0 $$pid 2>/dev/null; do \ + eval "f=\$${$$((i % n + 1))}"; \ + printf '\r\033[2K$(SPIN_FMT)' "$$f" "$(1)" "$(2)" >&2; \ + i=$$((i + 1)); sleep 0.08; \ + done; \ + wait $$pid; rc=$$?; printf '\r\033[2K' >&2; cat "$$out" >&2; rm -f "$$out"; \ +else ( $(3) ); rc=$$?; fi; \ +if [ $$rc -eq 0 ]; then $(call row,$(STEP_FMT),$(1),$(2)); \ +else $(call row,$(FAIL_FMT),$(1),$(2),failed); exit $$rc; fi +endef + +# $(call done,artifact) — the summary row, timed from BUILD_START (set by +# the Makefile at parse time). +done = @s=$(BUILD_START); $(elapsed); $(call row,$(DONE_FMT),done,$(1),$$t); printf '\n' >&2 + +# $(call artifact,path) — a row listing a build output with its size, for +# the lines under the summary row: "==> path", no tag column. +artifact = @$(call row,$(ART_FMT),,$(1),$(call filesize,$(1))) + +# $(call status,tag,message) — an informational row, no timing. +status = @printf '$(STATUS_FMT)' "$(1)" "$(2)" >&2 +# $(call warn,message); $(call fail,message) is a shell fragment for +# `|| { $(call fail,…); }`. +warn = @printf '$(WARN_FMT)' "$(1)" >&2 +fail = printf '$(ERROR_FMT)' "$(1)" >&2; exit 1 + +# $(call filesize,path) — a shell substitution yielding "15.5kb", for use +# inside a step's artifact text; "…" while the file doesn't exist yet (the +# spinner row is printed before the step has produced it). +comma := , +filesize = $$(if [ -f $(1) ]; then wc -c < $(1) | awk '{ printf "%.1fkb"$(comma) $$1 / 1024 }'; else printf '$(if $(UTF8),…,...)'; fi) diff --git a/template/build/verify-kernel.sh b/template/build/verify-kernel.sh index c4ba606..7927ddc 100755 --- a/template/build/verify-kernel.sh +++ b/template/build/verify-kernel.sh @@ -3,7 +3,7 @@ # vendored static veristat and fails if the running kernel's verifier rejects # any program. Driven by .github/workflows/kernel-matrix.yml, which boots each # kernel with cilium's little-vm-helper and mounts the project at /host; the -# workflow stages the static veristat into bin/ before booting. +# workflow stages the static veristat into .kmatrix/ before booting. # # sh build/verify-kernel.sh [bpf-object] (default: bin/probe.bpf.o) # @@ -19,7 +19,7 @@ set -eu OBJ="${1:-bin/probe.bpf.o}" -VERISTAT="${VERISTAT:-./bin/veristat}" +VERISTAT="${VERISTAT:-./.kmatrix/veristat}" # verdict LAST so the gate below can match it at end-of-line. veristat's CSV # header uses each stat's canonical name, so the columns come out as # file_name,prog_name,total_insns,total_states,verdict. diff --git a/toolchain/embed/fetch-toolchain.sh b/toolchain/embed/fetch-toolchain.sh index 9d91222..0cfc3b9 100755 --- a/toolchain/embed/fetch-toolchain.sh +++ b/toolchain/embed/fetch-toolchain.sh @@ -14,7 +14,7 @@ set -eu # Status lines in the build's format (STATUS_FMT comes from the Makefile; the # default matches it uncolored). curl shows a progress bar only on a terminal. -status() { printf "${STATUS_FMT:-%12s %s\n}" "$1" "$2" >&2; } +status() { printf "${STATUS_FMT:-* %-10s %s\n}" "$1" "$2" >&2; } if [ -t 2 ]; then CURL_PROGRESS=--progress-bar; else CURL_PROGRESS=-s; fi DIR="${1:?usage: fetch-toolchain.sh [tool...]}" @@ -71,7 +71,7 @@ fetch_bin() { name="$1" want "$name" || return 0 [ -x "$DIR/$name" ] && return 0 - status Downloading "${name} (${ARCH}, v${TOOLCHAIN_VERSION})" + status download "${name} (${ARCH}, v${TOOLCHAIN_VERSION})" tmp="$DIR/.${name}.$$" curl -fSL $CURL_PROGRESS --retry 3 -o "$tmp" "${REL}/${name}-${ARCH}" verify "$tmp" "$(get_sha "$(echo "$name" | tr a-z A-Z)")" "$name" || { rm -f "$tmp"; exit 1; } @@ -91,7 +91,7 @@ fetch_bin gh # per-arch tool dirs ($key/include/bpf/*.h). INC="$(dirname "$DIR")/include" if want headers && [ ! -e "$INC/bpf/bpf_helpers.h" ]; then - status Downloading "libbpf headers (v${TOOLCHAIN_VERSION})" + status download "libbpf headers (v${TOOLCHAIN_VERSION})" td="$(mktemp -d)" curl -fSL $CURL_PROGRESS --retry 3 -o "$td/h.tgz" "${REL}/libbpf-headers.tar.gz" verify "$td/h.tgz" "${LIBBPF_HEADERS_SHA256:-}" "libbpf-headers" || { rm -rf "$td"; exit 1; } @@ -100,4 +100,4 @@ if want headers && [ ! -e "$INC/bpf/bpf_helpers.h" ]; then rm -rf "$td" fi -status Toolchain "$DIR${FILTER:+ ($FILTER)}" +status toolchain "$DIR${FILTER:+ ($FILTER)}" diff --git a/toolchain/embed/toolchain.mk b/toolchain/embed/toolchain.mk index dffc4aa..0edbdb7 100644 --- a/toolchain/embed/toolchain.mk +++ b/toolchain/embed/toolchain.mk @@ -32,9 +32,9 @@ # Status-line formats, normally set by the project's build/ui.mk (included # before this file); these fallbacks keep the glue usable on its own. Exported # so build/fetch-toolchain.sh prints the same way. -STATUS_FMT ?= %12s %s\n -WARN_FMT ?= warning: %s\n -ERROR_FMT ?= error: %s\n +STATUS_FMT ?= * %-10s %s\n +WARN_FMT ?= ! %s\n +ERROR_FMT ?= x %s\n export STATUS_FMT WARN_FMT ERROR_FMT UNAME_S := $(shell uname -s) @@ -108,10 +108,10 @@ GH ?= gh toolchain: ifneq ($(YEET),) @$(YEET_EXEC) clang -print-prog-name=clang >/dev/null 2>&1 || { \ - printf '$(STATUS_FMT)' 'Installing' 'yeet toolchain $(TOOLCHAIN_WANT) (none installed yet)' >&2; \ + printf '$(STATUS_FMT)' 'install' 'yeet toolchain $(TOOLCHAIN_WANT) (none installed yet)' >&2; \ $(YEET) toolchain install $(TOOLCHAIN_WANT) --default; } @d="$$($(YEET_EXEC) clang -print-prog-name=clang)"; d="$${d%/*}"; v="$${d##*/}"; \ - printf '$(STATUS_FMT)' 'Toolchain' "yeet $$v ($$d)" >&2; \ + printf '$(STATUS_FMT)' 'toolchain' "$$v ~$${d#$$HOME}" >&2; \ if [ -n "$(TOOLCHAIN_KEY)" ] && [ "$$v" != "$(TOOLCHAIN_KEY)" ]; then \ printf '$(WARN_FMT)' "yeet's default toolchain is $$v but build/toolchain.lock pins $(TOOLCHAIN_KEY) — 'yeet toolchain default $(TOOLCHAIN_KEY)' to match it, or 'make YEET=' to build from the pinned cache" >&2; \ fi