From be375c44de03e320aaa25169a26eeebb302fc1ff Mon Sep 17 00:00:00 2001 From: Julian Goldstein Date: Tue, 30 Jun 2026 23:35:21 -0500 Subject: [PATCH 1/5] template: revamp the build (.build/ machinery, styled output, dist/) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make `yeet build` the frontend for a generated script and clean up what the build shows and produces: - Relocate the low-level machinery (Makefile, *.mk, toolchain scripts + lock) under .build/, behind a thin root Makefile shim that forwards to it. A generated project gitignores .build/ wholesale (it's regenerable); the template keeps it committed as the source scripts/new materializes from, and sync-toolchain rewrites the embed glue's paths into .build/. - Style the build output: a header, one status line per step, and a sizes summary; plain ASCII off-TTY. Raw commands are hidden unless `make V=1`, the vendored "toolchain ready" line is gated the same way, and esbuild's own summary is silenced. - Bundle to dist/index.jsx instead of src/index.jsx (gitignored), keeping the build output out of the source tree. - Drop the unrollable #pragma from the runqlat log2 loop — it's a bounded loop the verifier accepts, so no more -Wpass-failed warning. Note: dist/ resolution needs the companion CLI change (yeet run prefers a built dist/ entry over raw src/main.jsx); ship the two together. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/release.yml | 2 +- .gitignore | 8 +- Makefile | 18 ++-- README.md | 2 +- scripts/new | 23 ++++- template/.build/Makefile | 87 +++++++++++++++++++ template/{build => .build}/bpf.mk | 29 ++++--- template/{build => .build}/fetch-toolchain.sh | 4 +- template/{build => .build}/gen-vmlinux.sh | 10 ++- template/{build => .build}/kernel-matrix.sh | 20 ++--- template/.build/pretty.mk | 15 ++++ template/.build/pretty.sh | 53 +++++++++++ template/{build => .build}/toolchain.lock | 0 template/{build => .build}/toolchain.mk | 10 +-- template/{build => .build}/verify-kernel.sh | 2 +- template/.clangd | 2 +- template/.github/workflows/kernel-matrix.yml | 14 ++- template/.gitignore | 11 ++- template/Makefile | 76 ++++------------ template/README.md | 35 +++++--- template/src/bpf/runqlat.bpf.c | 6 +- 21 files changed, 300 insertions(+), 127 deletions(-) create mode 100644 template/.build/Makefile rename template/{build => .build}/bpf.mk (81%) rename template/{build => .build}/fetch-toolchain.sh (95%) rename template/{build => .build}/gen-vmlinux.sh (50%) rename template/{build => .build}/kernel-matrix.sh (90%) create mode 100644 template/.build/pretty.mk create mode 100644 template/.build/pretty.sh rename template/{build => .build}/toolchain.lock (100%) rename template/{build => .build}/toolchain.mk (86%) rename template/{build => .build}/verify-kernel.sh (96%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fbd1c19..3035228 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -101,7 +101,7 @@ jobs: # The static toolchain lives in its own repo (yeet-src/toolchain) and # is fetched on demand into a per-machine cache, so nothing heavy # ships in this archive. The toolchain/ subtree is dev-time source - # only (build recipe + embed glue) — the payload under template/build/ + # only (build recipe + embed glue) — the payload under template/.build/ # already carries its synced copies, so exclude it from the archive. repo="${GITHUB_REPOSITORY##*/}" tar \ diff --git a/.gitignore b/.gitignore index 2ea1e04..cd94b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,12 @@ # nothing is built here. Guard against stray artifacts if someone runs a # build inside template/ while editing it. template/node_modules/ -template/src/index.jsx -template/.build/ +template/dist/ +# template/.build/ is the committed build machinery (the frontend Makefile, +# *.mk, the shell scripts, toolchain.lock) that `scripts/new` materializes into +# a generated project's own .build/ (which that project gitignores wholesale). +# Only the artifacts written *inside* it are ignored here, not the machinery. +template/.build/bpf/ template/bin/* !template/bin/.gitkeep template/src/bpf/include/vmlinux.h diff --git a/Makefile b/Makefile index 162206b..5669dd9 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ # The static build toolchain (clang, bpftool, esbuild, make, git) lives in its # own repo, yeet-src/toolchain, vendored here under toolchain/ and pinned to a # release tag. The template payload doesn't read toolchain/ directly — it -# carries its own copies under template/build/ (the embed glue + a +# carries its own copies under template/.build/ (the embed glue + a # toolchain.lock) so a generated project stays self-contained. `sync-toolchain` # refreshes both from a tag. TOOLCHAIN_REPO ?= git@github.com:yeet-src/toolchain.git @@ -37,10 +37,18 @@ sync-toolchain: @split=$$(git rev-parse FETCH_HEAD); \ echo ">> replacing toolchain/ with $(TOOLCHAIN_TAG) ($$split)"; \ rm -rf toolchain && mkdir toolchain && git archive FETCH_HEAD | tar -x -C toolchain; \ - cp toolchain/embed/toolchain.mk template/build/toolchain.mk; \ - cp toolchain/embed/fetch-toolchain.sh template/build/fetch-toolchain.sh; \ - cp toolchain/build/versions.env template/build/toolchain.lock; \ - git add -A toolchain template/build/toolchain.mk template/build/fetch-toolchain.sh template/build/toolchain.lock; \ + : 'The embed glue assumes the machinery dir is named build/; we vendor it'; \ + : 'into .build/, so rewrite that path prefix as we copy (keeps the sync'; \ + : 'robust no matter what the upstream toolchain names it). We also gate the'; \ + : 'always-on "toolchain ready" summary behind YEET_TOOLCHAIN_QUIET so a'; \ + : 'normal build stays quiet (pretty.mk sets it; V=1 shows it). If upstream'; \ + : 'reworks that line the gate simply no-ops and the summary returns.'; \ + sed 's|build/|.build/|g' toolchain/embed/toolchain.mk > template/.build/toolchain.mk; \ + sed -e 's|build/|.build/|g' \ + -e 's#^echo ">> toolchain ready:#[ -n "$${YEET_TOOLCHAIN_QUIET:-}" ] || echo ">> toolchain ready:#' \ + toolchain/embed/fetch-toolchain.sh > template/.build/fetch-toolchain.sh; \ + cp toolchain/build/versions.env template/.build/toolchain.lock; \ + git add -A toolchain template/.build/toolchain.mk template/.build/fetch-toolchain.sh template/.build/toolchain.lock; \ if git diff --cached --quiet; then \ echo ">> already at $(TOOLCHAIN_TAG) — nothing to commit"; \ else \ diff --git a/README.md b/README.md index 5170c6c..8316c52 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ make new DEST= [NAME=] # or: scripts/new [name] The static build toolchain (clang, bpftool, esbuild, make, git) lives in its own repo, [yeet-src/toolchain](https://github.com/yeet-src/toolchain), vendored here under `toolchain/` and pinned to a release tag. Generated projects don't read -`toolchain/` — they carry their own copies under `template/build/` (the embed +`toolchain/` — they carry their own copies under `template/.build/` (the embed glue + a `toolchain.lock`). Refresh both from a toolchain release with: ```sh diff --git a/scripts/new b/scripts/new index f4ebb99..830d272 100755 --- a/scripts/new +++ b/scripts/new @@ -40,11 +40,28 @@ if [ -d "$DEST" ] && [ -n "$(ls -A "$DEST" 2>/dev/null)" ]; then fi mkdir -p "$DEST" -# Copy the payload (including dotfiles) into the destination. +# Copy the payload (including dotfiles) into the destination. This is what +# materializes the build back end: .build/ (the frontend Makefile, *.mk, the +# toolchain scripts + the toolchain.lock pin) is copied along with everything +# else, so the generated project can `yeet build` immediately — even though it +# gitignores .build/ wholesale and `yeet build` can re-materialize it later. cp -R "$SRC/." "$DEST/" -# The toolchain pin (build/toolchain.lock) ships in the template, so it is -# copied along with everything else above — no stamping needed here. +# Drop any build outputs a dirty template tree may have carried in, so a fresh +# scaffold starts clean (all of these are regenerated by the build). These are +# gitignored in the template, so a clean checkout won't have them anyway. +rm -rf "$DEST/.build/bpf" "$DEST/node_modules" "$DEST/dist" \ + "$DEST/src/bpf/include/vmlinux.h" "$DEST/.kmatrix" +find "$DEST/bin" -type f ! -name .gitkeep -delete 2>/dev/null || true + +# The build back end under .build/ ships committed in the template (this repo's +# source of truth), but a generated project treats the whole thing as +# regenerable — `yeet build` materializes it — so ignore it wholesale. The +# template's own .gitignore only ignores .build/bpf/ (so the machinery stays +# tracked here); a scaffold widens that to all of .build/. Idempotent. +if [ -f "$DEST/.gitignore" ] && ! grep -qxF '/.build/' "$DEST/.gitignore"; then + printf '\n# Regenerable build back end (`yeet build` materializes it).\n/.build/\n' >>"$DEST/.gitignore" +fi # Substitute __NAME__ in the files that carry it. Portable in-place edit # (BSD and GNU sed disagree on -i), so go through a temp file. diff --git a/template/.build/Makefile b/template/.build/Makefile new file mode 100644 index 0000000..45cb6c0 --- /dev/null +++ b/template/.build/Makefile @@ -0,0 +1,87 @@ +# Low-level build machinery for the yeet script project — the back end behind +# `yeet build`. It and its siblings (bpf.mk, toolchain.mk, the shell scripts, +# toolchain.lock) live under .build/, which a generated project gitignores +# wholesale; `yeet build` (or the thin root Makefile shim, which forwards +# `make` here from the project root) drives it. Every path below is written +# relative to the *project root*, since that stays the working directory: +# +# yeet build — build everything (BPF objects + JS bundle) [= all] +# make bpf — compile bpf/*.bpf.c into bin/* only +# make veristat — load the built object with veristat (verifier check on this kernel) +# make bundle — bundle the JS entry with esbuild +# make postgen — finalize a freshly generated project (git init) +# make clangd — write a local .clangd pointing at the resolved toolchain +# make clean — remove build artifacts +# +# This is the build *frontend* logic: it orchestrates two independent +# compilers — clang for the BPF objects, esbuild for the JS bundle. +# Neither understands the other; the JS references compiled objects in +# bin/ only by path, resolved at runtime. `yeet run` invokes the build +# automatically when running this project from a trusted remote source, +# so the default goal must leave the project runnable. +# +# clang, bpftool and esbuild come from the static toolchain resolved by +# .build/toolchain.mk (a shared per-machine cache, or binaries vendored in +# the bootstrap repo) — so the build needs no system C/BPF toolchain. + +.DEFAULT_GOAL := all + +include .build/pretty.mk +include .build/toolchain.mk +include .build/bpf.mk + +# `banner` prints first (leftmost prerequisite, built before bpf/bundle in a +# serial build) and the recipe's summary prints last, so a build reads as a +# titled progress list. Both are cheap, so they show even on an up-to-date +# build — a consistent frame around whatever work actually ran. +all: banner bpf bundle + @b=$(BPF_OUT); j=dist/index.jsx; \ + bs=$$(wc -c <"$$b" 2>/dev/null || echo 0); js=$$(wc -c <"$$j" 2>/dev/null || echo 0); \ + $(SAY) done "$$(printf '%s (%d KiB) · %s (%d KiB)' "$$b" "$$((bs/1024))" "$$j" "$$((js/1024))")" + +banner: + @$(SAY) head "building $(notdir $(CURDIR))" + +# 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 (a gitignored +# build-output dir), which the entry resolver prefers over the raw src/main.jsx +# — so once built, that is what `yeet run .` runs. The .jsx extension keeps the +# bundle eligible for component auto-mount. +# Compiled BPF objects in bin/ are loaded by path at runtime, never imported, +# so they are not bundled. +# +# The build needs no npm/node: the starter imports only `yeet:*` builtins and +# local `@/` modules, which esbuild resolves on its own. If you add third-party +# packages to package.json, install them into node_modules with the package +# manager of your choice — esbuild inlines whatever it finds there. +# `--log-level=warning` drops esbuild's own success summary (the outfile/size +# line and "Done in Nms") so the build shows just our one `bundle` step — real +# warnings and errors still print. +ESBUILD_FLAGS := --bundle --format=esm --platform=neutral \ + --main-fields=module,main --conditions=import,module --log-level=warning \ + --outfile=dist/index.jsx --jsx=automatic --jsx-import-source=yeet:tui + +bundle: | toolchain + @$(SAY) step bundle "src/main.jsx → dist/index.jsx" + $(Q)$(ESBUILD) src/main.jsx $(ESBUILD_FLAGS) '--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. +# The scaffolders (`yeet new`, `scripts/new`) run `make postgen` after creating +# the project, so the CLI itself stays a thin caller of make. +postgen: | vendored-git + @g="$(GIT)"; [ -x "$$g" ] || g="$$(command -v git 2>/dev/null || true)"; \ + if [ -e .git ]; then \ + echo "postgen: already a git repository"; \ + elif [ -n "$$g" ]; then \ + echo "postgen: git init"; \ + "$$g" -c init.templateDir= init -q . || echo "warning: 'git init' failed" >&2; \ + else \ + echo "warning: no git available (vendored or host); skipping 'git init'" >&2; \ + fi + +clean: clean-bpf + rm -rf node_modules dist + +.PHONY: all banner bundle clean postgen diff --git a/template/build/bpf.mk b/template/.build/bpf.mk similarity index 81% rename from template/build/bpf.mk rename to template/.build/bpf.mk index 0650851..f12c483 100644 --- a/template/build/bpf.mk +++ b/template/.build/bpf.mk @@ -10,7 +10,7 @@ # To add another independent object, give it its own link target alongside # bin/probe.bpf.o below — there is intentionally no per-object magic here. -# CLANG/BPFTOOL are normally resolved by build/toolchain.mk (vendored static +# CLANG/BPFTOOL are normally resolved by .build/toolchain.mk (vendored static # toolchain or shared cache), included before this file. These are the last # resort when neither is available: whatever is on PATH. bpftool frequently # lives in /usr/sbin, which isn't always on a non-root user's PATH; fall back @@ -25,8 +25,9 @@ ARCH := $(ARCH:aarch64=arm64) VMLINUX := src/bpf/include/vmlinux.h BPF_SRCS := $(wildcard src/bpf/*.bpf.c) -# One intermediate object per unit. They live under .build/ so they are -# never mistaken for the loadable object in bin/. +# One intermediate object per unit. They live under .build/bpf/ (beside the +# build machinery, both hidden under .build/) so they are never mistaken for +# the loadable object in bin/. BPF_OBJS := $(patsubst src/bpf/%.bpf.c,.build/bpf/%.bpf.o,$(BPF_SRCS)) # The single linked object. Its `.bpf.o` suffix is what the JS side loads # with `import probe from "../bin/probe.bpf.o"` (the loader's @@ -35,7 +36,7 @@ BPF_OUT := bin/probe.bpf.o BPF_CFLAGS ?= -g -O2 -Wall -target bpf -D__TARGET_ARCH_$(ARCH) -mcpu=v3 -I src/bpf/include # Add the vendored libbpf program headers (, …) when a -# vendored toolchain supplies them; resolved by build/toolchain.mk. Without +# vendored toolchain supplies them; resolved by .build/toolchain.mk. Without # it, the build falls back to a host libbpf-dev on the default include path. BPF_CFLAGS += $(if $(BPF_SYSINCLUDE),-I$(BPF_SYSINCLUDE)) @@ -45,34 +46,38 @@ bpf: $(BPF_OUT) # the cache before any rule shells out to them, without forcing rebuilds. $(VMLINUX): | toolchain @command -v $(BPFTOOL) >/dev/null 2>&1 || { echo "error: bpftool not found — install bpftool / linux-tools"; exit 1; } - sh build/gen-vmlinux.sh $(BPFTOOL) $@ + @$(SAY) step gen "vmlinux.h (kernel BTF)" + $(Q)sh .build/gen-vmlinux.sh $(BPFTOOL) $@ >/dev/null # Compile each unit to an intermediate object. .build/bpf/%.bpf.o: src/bpf/%.bpf.c $(VMLINUX) | toolchain @command -v $(CLANG) >/dev/null 2>&1 || { echo "error: clang not found — install clang"; exit 1; } @mkdir -p $(dir $@) - $(CLANG) $(BPF_CFLAGS) -c $< -o $@ + @$(SAY) step cc "$<" + $(Q)$(CLANG) $(BPF_CFLAGS) -c $< -o $@ # Statically link every unit into the single loadable object. $(BPF_OUT): $(BPF_OBJS) | bin toolchain @command -v $(BPFTOOL) >/dev/null 2>&1 || { echo "error: bpftool not found — install bpftool / linux-tools"; exit 1; } - $(BPFTOOL) gen object $@ $(BPF_OBJS) + @$(SAY) step link "$@" + $(Q)$(BPFTOOL) gen object $@ $(BPF_OBJS) bin: mkdir -p bin clean-bpf: - rm -rf $(BPF_OUT) .build $(VMLINUX) + rm -rf $(BPF_OUT) .build/bpf $(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 # local counterpart to the kernel-matrix CI, which runs the same check across # many kernels. Loading BPF programs needs privileges, so run with sudo (as -# `yeet run` does). VERISTAT is resolved by build/toolchain.mk (the vendored +# `yeet run` does). VERISTAT is resolved by .build/toolchain.mk (the vendored # static binary, or `veristat` on PATH). .PHONY: veristat veristat: $(BPF_OUT) | toolchain - @command -v $(VERISTAT) >/dev/null 2>&1 || { echo "error: veristat not found ($(VERISTAT)) — bump build/toolchain.lock to a toolchain that ships veristat, or install veristat on PATH"; exit 1; } + @command -v $(VERISTAT) >/dev/null 2>&1 || { echo "error: veristat not found ($(VERISTAT)) — bump .build/toolchain.lock to a toolchain that ships veristat, or install veristat on PATH"; exit 1; } + @$(SAY) step veristat "$(BPF_OUT)" $(VERISTAT) $(BPF_OUT) # Run the same verifier check across a matrix of kernels locally (Linux + KVM), @@ -81,7 +86,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 - VERISTAT="$(VERISTAT)" sh build/kernel-matrix.sh $(KERNELS) + VERISTAT="$(VERISTAT)" sh .build/kernel-matrix.sh $(KERNELS) # Write a local .clangd so the editor resolves vmlinux.h, the libbpf SDK # headers and __u* types using the *resolved* toolchain include path — unlike @@ -92,7 +97,7 @@ veristat-matrix: $(BPF_OUT) | toolchain .PHONY: clangd clangd: @printf '%s\n' \ - '# Generated by `make clangd` — editor flags mirroring build/bpf.mk.' \ + '# Generated by `make clangd` — editor flags mirroring .build/bpf.mk.' \ '# Regenerate after moving machines or bumping the toolchain version.' \ 'CompileFlags:' \ ' Add:' \ diff --git a/template/build/fetch-toolchain.sh b/template/.build/fetch-toolchain.sh similarity index 95% rename from template/build/fetch-toolchain.sh rename to template/.build/fetch-toolchain.sh index eff6a87..e6b852a 100755 --- a/template/build/fetch-toolchain.sh +++ b/template/.build/fetch-toolchain.sh @@ -4,7 +4,7 @@ # toolchain repo (releases/download/v/) and is # checksum-verified against the pins in the lock. # -# build/fetch-toolchain.sh [tool...] +# .build/fetch-toolchain.sh [tool...] # # With no trailing tool names, all tools are fetched (the build's `toolchain` # target). Pass names (e.g. `git`) to fetch only those — `postgen` uses this to @@ -95,4 +95,4 @@ if want headers && [ ! -e "$INC/bpf/bpf_helpers.h" ]; then rm -rf "$td" fi -echo ">> toolchain ready: $DIR${FILTER:+ ($FILTER)}" +[ -n "${YEET_TOOLCHAIN_QUIET:-}" ] || echo ">> toolchain ready: $DIR${FILTER:+ ($FILTER)}" diff --git a/template/build/gen-vmlinux.sh b/template/.build/gen-vmlinux.sh similarity index 50% rename from template/build/gen-vmlinux.sh rename to template/.build/gen-vmlinux.sh index a51768b..db35594 100644 --- a/template/build/gen-vmlinux.sh +++ b/template/.build/gen-vmlinux.sh @@ -2,7 +2,7 @@ # Generate vmlinux.h from the running kernel's BTF — the CO-RE header # every BPF program in this project includes. # -# build/gen-vmlinux.sh +# .build/gen-vmlinux.sh set -eu @@ -15,5 +15,11 @@ if [ ! -r /sys/kernel/btf/vmlinux ]; then fi mkdir -p "$(dirname "$OUT")" -"$BPFTOOL" btf dump file /sys/kernel/btf/vmlinux format c >"$OUT" +# bpftool prints a benign "skipping /sys/kernel/btf/vmlinux (will be loaded as +# base)" note to stderr even on success. Capture stderr and replay it only if +# the dump actually fails, so a normal build stays quiet. +if ! err="$("$BPFTOOL" btf dump file /sys/kernel/btf/vmlinux format c 2>&1 >"$OUT")"; then + [ -n "$err" ] && printf '%s\n' "$err" >&2 + exit 1 +fi echo "generated $OUT" diff --git a/template/build/kernel-matrix.sh b/template/.build/kernel-matrix.sh similarity index 90% rename from template/build/kernel-matrix.sh rename to template/.build/kernel-matrix.sh index 3f128c7..919c7f2 100755 --- a/template/build/kernel-matrix.sh +++ b/template/.build/kernel-matrix.sh @@ -1,10 +1,10 @@ #!/bin/sh -# Boot a matrix of kernels locally and run the verifier check (build/verify- +# Boot a matrix of kernels locally and run the verifier check (.build/verify- # kernel.sh) in each — the local counterpart to .github/workflows/kernel- # matrix.yml. This is a TEST HARNESS, not part of the build, and needs a # Linux host (ideally with /dev/kvm; without it QEMU falls back to slow TCG). # -# build/kernel-matrix.sh [kernel ...] # default: an LTS spread + bpf-next +# .build/kernel-matrix.sh [kernel ...] # default: an LTS spread + bpf-next # make veristat-matrix # same, via the Makefile # # It uses cilium's lvh + QEMU to boot quay.io/lvh-images/kind: images. @@ -12,7 +12,7 @@ # host KVM/root, not self-contained build tools), so both are fetched on demand: # - lvh: an `lvh` on PATH, else extracted from the quay.io/lvh-images/lvh image. # - qemu: the vendored static qemu-.tar.gz from the toolchain release -# (checksum-pinned in build/toolchain.lock), extracted to the toolchain +# (checksum-pinned in .build/toolchain.lock), extracted to the toolchain # cache and prepended to PATH; falls back to a system qemu-system. # veristat is the vendored static binary, resolved like the build. @@ -46,8 +46,8 @@ need ssh "install openssh-client" # fall back to a system qemu-system on PATH. lvh finds qemu via PATH and the # static qemu finds its firmware blobs relative to its own binary, so we just # prepend the extracted bin dir to PATH — no -L plumbing into lvh needed. -if [ -f build/toolchain.lock ]; then - . ./build/toolchain.lock +if [ -f .build/toolchain.lock ]; then + . ./.build/toolchain.lock qsha="$(eval "printf '%s' \"\${QEMU_SHA256_${ARCH}:-}\"")" QDIR="${XDG_CACHE_HOME:-$HOME/.cache}/yeet/toolchain/v${TOOLCHAIN_VERSION}/${ARCH}/qemu" if [ ! -x "$QDIR/bin/$QEMU" ] && [ -n "$qsha" ] && [ -n "${TOOLCHAIN_BASE_URL:-}" ]; then @@ -70,7 +70,7 @@ if [ -f build/toolchain.lock ]; then echo ">> using vendored static qemu: $QDIR/bin/$QEMU" fi fi -need "$QEMU" "no vendored qemu in build/toolchain.lock and none on PATH — bump the lock to a toolchain that ships qemu, or install qemu-system" +need "$QEMU" "no vendored qemu in .build/toolchain.lock and none on PATH — bump the lock to a toolchain that ships qemu, or install qemu-system" # --- resolve lvh (PATH, else extract from the OCI image with docker) ---------- LVH="$(command -v lvh || true)" @@ -91,12 +91,12 @@ fi echo ">> building $OBJ" make bpf >/dev/null VERISTAT="${VERISTAT:-}" -if [ -z "$VERISTAT" ] && [ -f build/toolchain.lock ]; then - . ./build/toolchain.lock +if [ -z "$VERISTAT" ] && [ -f .build/toolchain.lock ]; then + . ./.build/toolchain.lock VERISTAT="${XDG_CACHE_HOME:-$HOME/.cache}/yeet/toolchain/v${TOOLCHAIN_VERSION}/${ARCH}/veristat" 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; } +[ -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 # --- per-kernel: pull image, boot, verify, stop ------------------------------- @@ -129,7 +129,7 @@ for k in $KERNELS; do # run the gate in the VM; CSV lands in the host-mounted .kmatrix via /host if ssh -p "$SSH_PORT" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@127.0.0.1 \ - "cd /host && OUT_CSV=/host/.kmatrix/$k.csv sh build/verify-kernel.sh"; then :; else overall=1; fi + "cd /host && OUT_CSV=/host/.kmatrix/$k.csv sh .build/verify-kernel.sh"; then :; else overall=1; fi stop_vm done trap - EXIT INT TERM diff --git a/template/.build/pretty.mk b/template/.build/pretty.mk new file mode 100644 index 0000000..c119856 --- /dev/null +++ b/template/.build/pretty.mk @@ -0,0 +1,15 @@ +# Pretty build output, shared by the other build fragments. +# +# SAY renders a styled status line (see .build/pretty.sh). Q hides the raw tool +# command behind each step so a normal build shows tidy step lines instead of +# full clang/bpftool/esbuild invocations — run `make V=1` (or `yeet build V=1`) +# to see the exact commands. Errors always surface: only stdout on success is +# suppressed, never a tool's stderr. +SAY := sh .build/pretty.sh +Q := $(if $(V),,@) + +# Silence the vendored toolchain fetch's "toolchain ready" summary in a normal +# build (it prints on every build, warm cache included); `V=1` shows it, along +# with the raw commands. Exported so the fetch script sees it. Real "fetch …" +# download progress is never suppressed. +export YEET_TOOLCHAIN_QUIET := $(if $(V),,1) diff --git a/template/.build/pretty.sh b/template/.build/pretty.sh new file mode 100644 index 0000000..8d7d2c8 --- /dev/null +++ b/template/.build/pretty.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Styled status lines for the build — one tidy, colored line per step, so a +# normal `yeet build` reads as a clean progress list instead of a dump of raw +# compiler commands. The build fragments call this (as `$(SAY)`) for every +# step; the raw tool commands are hidden unless `make V=1`. +# +# Color + glyphs engage only on a terminal with NO_COLOR unset. Piped or CI +# logs fall back to plain ASCII markers, so nothing here corrupts a log file. +# +# pretty.sh head rule + title (build start) +# pretty.sh step ▸ tag msg (a build step) +# pretty.sh info · msg (dim note) +# pretty.sh done ✔ msg + rule (build finished) +set -eu + +kind=${1:-} +[ $# -gt 0 ] && shift + +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + B=$(printf '\033[1m') # bold + D=$(printf '\033[2m') # dim + R=$(printf '\033[0m') # reset + C=$(printf '\033[36m') # cyan — step tags + G=$(printf '\033[32m') # green — success + arrow='▸'; bullet='●'; dot='·'; check='✔' + rule='────────────────────────────────────────────' +else + B=; D=; R=; C=; G= + arrow='>'; bullet='*'; dot='-'; check='✓' + rule='--------------------------------------------' +fi + +case "$kind" in + head) + printf '\n%s%s %s%s\n%s%s%s%s\n' \ + "$B$C" "$bullet" "$*" "$R" "$D" "$rule" "$R" "" + ;; + step) + tag=${1:-} + [ $# -gt 0 ] && shift + printf ' %s%s%s %s%-6s%s %s\n' "$B" "$arrow" "$R" "$C" "$tag" "$R" "$*" + ;; + info) + printf ' %s%s%s %s%s%s\n' "$D" "$dot" "$R" "$D" "$*" "$R" + ;; + done) + printf '%s%s%s %s%s%s\n%s%s%s\n\n' \ + "$B$G" "$check" "$R" "$B" "$*" "$R" "$D" "$rule" "$R" + ;; + *) + printf '%s\n' "$*" + ;; +esac diff --git a/template/build/toolchain.lock b/template/.build/toolchain.lock similarity index 100% rename from template/build/toolchain.lock rename to template/.build/toolchain.lock diff --git a/template/build/toolchain.mk b/template/.build/toolchain.mk similarity index 86% rename from template/build/toolchain.mk rename to template/.build/toolchain.mk index 6cb5f18..e335efe 100644 --- a/template/build/toolchain.mk +++ b/template/.build/toolchain.mk @@ -1,9 +1,9 @@ # Resolve the static build toolchain (clang, bpftool, veristat, esbuild, git, gh) — included -# by the project Makefile before build/bpf.mk, so the tools are set before any +# by the project Makefile before .build/bpf.mk, so the tools are set before any # rule uses them. A `make CLANG=…` CLI override beats this. # # Tools come from a shared, per-machine cache keyed by the project's pinned -# toolchain version (build/toolchain.lock). `make toolchain` fills it, +# toolchain version (.build/toolchain.lock). `make toolchain` fills it, # downloading each missing tool once from the vendored toolchain release # (github.com/yeet-src/toolchain). The cache key is the toolchain version, # never the template version — so updating the template reuses an existing @@ -24,7 +24,7 @@ UNAME_M := $(UNAME_M:arm64=aarch64) # Only engage the vendored cache on Linux; elsewhere TOOLCHAIN_LOCK stays empty # so the PATH fallbacks below win and the fetch targets become no-ops. ifeq ($(UNAME_S),Linux) -TOOLCHAIN_LOCK := $(firstword $(wildcard build/toolchain.lock)) +TOOLCHAIN_LOCK := $(firstword $(wildcard .build/toolchain.lock)) endif ifneq ($(TOOLCHAIN_LOCK),) include $(TOOLCHAIN_LOCK) @@ -53,7 +53,7 @@ GH ?= gh .PHONY: toolchain toolchain: ifneq ($(TOOLCHAIN_LOCK),) - @sh build/fetch-toolchain.sh "$(TOOLCHAIN_DIR)" "$(UNAME_M)" "$(TOOLCHAIN_LOCK)" + @sh .build/fetch-toolchain.sh "$(TOOLCHAIN_DIR)" "$(UNAME_M)" "$(TOOLCHAIN_LOCK)" else @: endif @@ -63,7 +63,7 @@ endif .PHONY: vendored-git vendored-git: ifneq ($(TOOLCHAIN_LOCK),) - @sh build/fetch-toolchain.sh "$(TOOLCHAIN_DIR)" "$(UNAME_M)" "$(TOOLCHAIN_LOCK)" git \ + @sh .build/fetch-toolchain.sh "$(TOOLCHAIN_DIR)" "$(UNAME_M)" "$(TOOLCHAIN_LOCK)" git \ || echo "note: vendored git unavailable; postgen will fall back to host git" >&2 else @: diff --git a/template/build/verify-kernel.sh b/template/.build/verify-kernel.sh similarity index 96% rename from template/build/verify-kernel.sh rename to template/.build/verify-kernel.sh index c4ba606..2ad4c81 100755 --- a/template/build/verify-kernel.sh +++ b/template/.build/verify-kernel.sh @@ -5,7 +5,7 @@ # kernel with cilium's little-vm-helper and mounts the project at /host; the # workflow stages the static veristat into bin/ before booting. # -# sh build/verify-kernel.sh [bpf-object] (default: bin/probe.bpf.o) +# sh .build/verify-kernel.sh [bpf-object] (default: bin/probe.bpf.o) # # Set OUT_CSV= to also write a machine-readable result (file,prog,verdict, # insns,states) — the workflow points it at the mounted workspace so the runner diff --git a/template/.clangd b/template/.clangd index 28192a0..e860acb 100644 --- a/template/.clangd +++ b/template/.clangd @@ -1,4 +1,4 @@ -# Tell clangd how the BPF units are compiled (mirrors build/bpf.mk), so the +# Tell clangd how the BPF units are compiled (mirrors .build/bpf.mk), so the # editor resolves vmlinux.h, the libbpf headers, and the __u* types instead of # flagging them. vmlinux.h is generated by `make`, so run it once for full # resolution. diff --git a/template/.github/workflows/kernel-matrix.yml b/template/.github/workflows/kernel-matrix.yml index daecdb8..529691f 100644 --- a/template/.github/workflows/kernel-matrix.yml +++ b/template/.github/workflows/kernel-matrix.yml @@ -66,18 +66,24 @@ jobs: - name: Build BPF object + stage veristat run: | set -euo pipefail + # NOTE: the build machinery lives under .build/, which this project + # gitignores (it's regenerable — `yeet build` materializes it). A CI + # checkout therefore only has it if you commit it (`git add -f .build`) + # or add a step that runs `yeet build` first. `make bpf` below drives + # it through the root Makefile shim once .build/ is present. + # # Builds bin/probe.bpf.o with the vendored static toolchain, also # populating the per-machine toolchain cache (clang/bpftool/veristat). make bpf - # Resolve the vendored static veristat the same way build/toolchain.mk + # 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. - . build/toolchain.lock + . .build/toolchain.lock arch="$(uname -m)"; [ "$arch" = arm64 ] && arch=aarch64 cache="${XDG_CACHE_HOME:-$HOME/.cache}/yeet/toolchain/v${TOOLCHAIN_VERSION}/${arch}" if [ ! -x "$cache/veristat" ]; then - echo "::error::veristat is not in the pinned toolchain (v${TOOLCHAIN_VERSION}). Bump build/toolchain.lock to a toolchain release that ships veristat." + 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 @@ -93,7 +99,7 @@ jobs: install-dependencies: 'true' cmd: | cd /host - OUT_CSV=/host/.kmatrix/result.csv sh build/verify-kernel.sh + OUT_CSV=/host/.kmatrix/result.csv sh .build/verify-kernel.sh - name: Render kernel summary if: always() diff --git a/template/.gitignore b/template/.gitignore index 733707c..bd3b455 100644 --- a/template/.gitignore +++ b/template/.gitignore @@ -1,12 +1,17 @@ # build artifacts /node_modules/ -/src/index.jsx -/.build/ +# Compiled JS bundle (the `yeet build` output the runtime prefers over src/). +/dist/ +# Compiled BPF intermediates. The rest of .build/ is the build back end +# (Makefile, *.mk, scripts, toolchain.lock); `yeet new` ignores that wholesale +# too (it appends `/.build/` here — the back end is regenerable), but this +# repo's own template keeps it committed as the source to materialize from. +/.build/bpf/ # compiled BPF objects + generated CO-RE header /bin/* !/bin/.gitkeep /src/bpf/include/vmlinux.h -# kernel-matrix run output (build/kernel-matrix.sh) +# kernel-matrix run output (.build/kernel-matrix.sh) /.kmatrix/ diff --git a/template/Makefile b/template/Makefile index 59f0eac..da05c64 100644 --- a/template/Makefile +++ b/template/Makefile @@ -1,66 +1,22 @@ -# Build the yeet script project. +# Thin shim → the real build machinery under .build/. # -# make — build everything (BPF objects + JS bundle) -# make bpf — compile bpf/*.bpf.c into bin/* only -# make veristat — load the built object with veristat (verifier check on this kernel) -# make bundle — bundle the JS entry with esbuild -# make postgen — finalize a freshly generated project (git init) -# make clangd — write a local .clangd pointing at the resolved toolchain -# make clean — remove build artifacts +# `yeet build` is the frontend for this script; the low-level orchestration +# (this project's two compilers — clang for BPF, esbuild for JS — plus the +# toolchain resolution and the kernel test harness) lives under .build/, which +# a generated project gitignores wholesale and `yeet build` materializes. This +# shim just forwards a bare `make` (and the `make` that `yeet run` invokes +# automatically) to .build/Makefile, keeping the project root as the working +# directory so every path inside resolves as written. # -# This is the build *frontend*: it orchestrates two independent -# compilers — clang for the BPF objects, esbuild for the JS bundle. -# Neither understands the other; the JS references compiled objects in -# bin/ only by path, resolved at runtime. `yeet run` invokes `make` -# automatically when running this project from a trusted remote source, -# so the default goal must leave the project runnable. -# -# clang, bpftool and esbuild come from the static toolchain resolved by -# build/toolchain.mk (a shared per-machine cache, or binaries vendored in -# the bootstrap repo) — so the build needs no system C/BPF toolchain. - -.DEFAULT_GOAL := all - -include build/toolchain.mk -include build/bpf.mk +# Command-line variables (e.g. `make veristat-matrix KERNELS="6.6-main"`) and +# overrides (`make CLANG=…`) propagate to the sub-make automatically via +# MAKEFLAGS, so they need no plumbing here. -all: bpf bundle +REAL := .build/Makefile -# 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 src/index.jsx, which the -# entry ladder prefers over src/main.jsx — so once built, that is what runs. -# The .jsx extension keeps the bundle eligible for component auto-mount. -# Compiled BPF objects in bin/ are loaded by path at runtime, never imported, -# so they are not bundled. -# -# The build needs no npm/node: the starter imports only `yeet:*` builtins and -# local `@/` modules, which esbuild resolves on its own. If you add third-party -# packages to package.json, install them into node_modules with the package -# manager of your choice — esbuild inlines whatever it finds there. -ESBUILD_FLAGS := --bundle --format=esm --platform=neutral \ - --main-fields=module,main --conditions=import,module \ - --outfile=src/index.jsx --jsx=automatic --jsx-import-source=yeet:tui - -bundle: | toolchain - $(ESBUILD) src/main.jsx $(ESBUILD_FLAGS) '--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. -# The scaffolders (`yeet new`, `scripts/new`) run `make postgen` after creating -# the project, so the CLI itself stays a thin caller of make. -postgen: | vendored-git - @g="$(GIT)"; [ -x "$$g" ] || g="$$(command -v git 2>/dev/null || true)"; \ - if [ -e .git ]; then \ - echo "postgen: already a git repository"; \ - elif [ -n "$$g" ]; then \ - echo "postgen: git init"; \ - "$$g" -c init.templateDir= init -q . || echo "warning: 'git init' failed" >&2; \ - else \ - echo "warning: no git available (vendored or host); skipping 'git init'" >&2; \ - fi +.DEFAULT_GOAL := all -clean: clean-bpf - rm -rf node_modules dist src/index.jsx +all bpf bundle veristat veristat-matrix postgen clangd clean: + @$(MAKE) --no-print-directory -f $(REAL) $@ -.PHONY: all bundle clean postgen +.PHONY: all bpf bundle veristat veristat-matrix postgen clangd clean diff --git a/template/README.md b/template/README.md index ddc8b2c..024d6fb 100644 --- a/template/README.md +++ b/template/README.md @@ -1,7 +1,7 @@ # __NAME__ A yeet script: a reactive JSX TUI bundled with esbuild, an `@/` source -alias, and a BPF program — all driven by one `make`. +alias, and a BPF program — all driven by `yeet build`. The starter is **cpusched**: a live scheduler dashboard. The top is a cores × time heatmap of context-switch rate (one row per CPU, newest column @@ -36,9 +36,11 @@ shares its `control`; each probe module attaches its own maps. ## Layout ``` -Makefile build frontend — orchestrates the two compilers -build/bpf.mk clang + bpftool rules: src/bpf/*.bpf.c -> bin/probe.bpf.o -build/gen-vmlinux.sh generates src/bpf/include/vmlinux.h from kernel BTF +Makefile thin shim → .build/Makefile (keeps `make`/`yeet run` working) +.build/ build back end — gitignored, materialized by `yeet build`: +.build/Makefile orchestrates the two compilers +.build/bpf.mk clang + bpftool rules: src/bpf/*.bpf.c -> bin/probe.bpf.o +.build/gen-vmlinux.sh generates src/bpf/include/vmlinux.h from kernel BTF package.json project manifest + optional npm/jsr deps tsconfig.json `#/` -> project root, `@/` -> ./src path aliases src/main.jsx entry — composition root: input + mount @@ -51,7 +53,7 @@ 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 .github/workflows/kernel-matrix.yml CI: verify the object across kernels -build/verify-kernel.sh the in-VM gate that workflow runs +.build/verify-kernel.sh the in-VM gate that workflow runs ``` The JS is layered: `probes/` is the only BPF-aware code (it owns the object @@ -62,16 +64,23 @@ the `@/` alias; `main.jsx` wires them together and owns input. ## Build & run ```sh -make # compile BPF (clang + bpftool) + bundle JS (esbuild) -yeet run . # runs the bundled src/index.jsx (needs root for BPF) +yeet build # compile BPF (clang + bpftool) + bundle JS (esbuild) +yeet run . # runs the bundled dist/index.jsx (needs root for BPF) ``` -`make` runs two independent compilers: **clang + bpftool** compile +`yeet build` is the frontend; the low-level orchestration lives under `.build/` +(gitignored and materialized on demand). Bare `make` still works — the root +`Makefile` is a thin shim that forwards to `.build/Makefile` — which is also how +`yeet run` auto-builds before running. The build prints a styled step list; +pass `V=1` (`yeet build V=1` / `make V=1`) to see the raw compiler commands. + +It drives 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 `src/index.jsx`, resolving the `@/` +**esbuild** bundles `src/main.jsx` into `dist/index.jsx`, 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. +node/npm. `yeet run .` prefers that built `dist/index.jsx` over the raw +`src/main.jsx`, so once built the bundle is what runs. The data layer loads the object at runtime: @@ -99,7 +108,7 @@ kernel in its matrix it builds `bin/probe.bpf.o`, boots that kernel in a VM ([cilium's little-vm-helper](https://github.com/cilium/little-vm-helper), images from `quay.io/lvh-images`), and runs the vendored static **veristat** against the object — failing the job if the verifier rejects any program. The check is -`build/verify-kernel.sh`. +`.build/verify-kernel.sh`. Edit `matrix.kernel` to the kernels you care about (tags at [quay.io/lvh-images/kind](https://quay.io/repository/lvh-images/kind?tab=tags); @@ -113,9 +122,9 @@ boots those kernel images with [`lvh`](https://github.com/cilium/little-vm-helpe `make veristat-matrix KERNELS="6.6-main bpf-next-main"`. Both `lvh` and a static `qemu` are fetched on demand (VM infra, not part of the build toolchain) — the vendored static qemu comes from the toolchain release, checksum-pinned in -`build/toolchain.lock`, falling back to a system `qemu-system` if absent. +`.build/toolchain.lock`, falling back to a system `qemu-system` if absent. -> Requires a toolchain pin (`build/toolchain.lock`) that ships `veristat`; the +> Requires a toolchain pin (`.build/toolchain.lock`) that ships `veristat`; the > workflow says so explicitly if the pinned version predates it. `#/` (project root) and `@/` (source root) are **bundle-time aliases** that diff --git a/template/src/bpf/runqlat.bpf.c b/template/src/bpf/runqlat.bpf.c index 0745afd..21f692f 100644 --- a/template/src/bpf/runqlat.bpf.c +++ b/template/src/bpf/runqlat.bpf.c @@ -60,10 +60,12 @@ int on_switch_lat(struct trace_event_raw_sched_switch *ctx) __u64 delta = bpf_ktime_get_ns() - *tsp; bpf_map_delete_elem(&runq_start, &pid); - // slot = floor(log2(delta_ns)) + // slot = floor(log2(delta_ns)). A bounded loop (≤ MAX_SLOTS iterations): + // modern verifiers accept these directly, so it needs no `#pragma unroll` + // — which couldn't unroll it anyway (the data-dependent break makes the + // trip count non-constant) and only produced a warning. __u32 slot = 0; __u64 d = delta; -#pragma unroll for (int i = 0; i < MAX_SLOTS; i++) { if (d <= 1) { break; From 66b95931dd5a9553ff093783578b63fb58f62d80 Mon Sep 17 00:00:00 2001 From: Julian Goldstein Date: Tue, 30 Jun 2026 23:39:37 -0500 Subject: [PATCH 2/5] template: define import.meta.main=false when bundling Bundling collapses every module into one file that shares the entry's import.meta, so the `import.meta.main` self-test guards (AGENTS.md) would all see main === true and fire in the built app. Define it to false so they compile to `if (false)` and drop; a standalone `yeet run src/probes/foo.js` isn't bundled, so its guard still runs. Co-Authored-By: Claude Opus 4.8 --- template/.build/Makefile | 8 ++++++++ template/AGENTS.md | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/template/.build/Makefile b/template/.build/Makefile index 45cb6c0..5c2bef0 100644 --- a/template/.build/Makefile +++ b/template/.build/Makefile @@ -58,8 +58,16 @@ banner: # `--log-level=warning` drops esbuild's own success summary (the outfile/size # line and "Done in Nms") so the build shows just our one `bundle` step — real # warnings and errors still print. +# +# `--define:import.meta.main=false` compiles out the `import.meta.main` +# self-test guards (see AGENTS.md). Bundling collapses every module into one +# file that shares the entry's `import.meta`, so without this each guarded +# block would see `main === true` and all fire at once in the built app; +# defined to false they become `if (false)` and drop. A standalone +# `yeet run src/probes/foo.js` isn't bundled, so its guard still runs. ESBUILD_FLAGS := --bundle --format=esm --platform=neutral \ --main-fields=module,main --conditions=import,module --log-level=warning \ + --define:import.meta.main=false \ --outfile=dist/index.jsx --jsx=automatic --jsx-import-source=yeet:tui bundle: | toolchain diff --git a/template/AGENTS.md b/template/AGENTS.md index 1bc4fa1..ac592df 100644 --- a/template/AGENTS.md +++ b/template/AGENTS.md @@ -40,7 +40,11 @@ blank panel could mean anything. Before any JSX, confirm the kernel actually gives you the fields and types you think it does. Guard a self-test with `import.meta.main` — it's `true` **only** when this module is the run entry, so the block runs when you point `yeet run` -at the module and stays dormant once `main.jsx` imports it. +at the module. The build bundles with `import.meta.main` defined to `false`, so +these guards compile out of `dist/index.jsx` entirely — they never fire in the +built app, only when you invoke the module directly. (Don't rely on plain +`main.jsx` imports to keep them dormant: bundling collapses every module into +one, so without that define they'd all see the entry's `main === true`.) Verify the **raw source**, not a `from()` signal (a `from()` producer doesn't run until something watches it — there's no UI here): From 7b524413fb7a9dc6a599e537bd5f20312975a8c9 Mon Sep 17 00:00:00 2001 From: Julian Goldstein Date: Wed, 1 Jul 2026 00:17:25 -0500 Subject: [PATCH 3/5] template: Give clean a colorful progress display Mirror the build's titled progress frame in `make clean`: a head banner, a step per artifact group (BPF objects via clean-bpf, then the JS outputs dist/ and node_modules/), and a done summary. clean-bpf's rm now rides $(Q) so it stays quiet unless V=1, like the rest of the build. Co-Authored-By: Claude Opus 4.8 --- template/.build/Makefile | 15 ++++++++++++--- template/.build/bpf.mk | 3 ++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/template/.build/Makefile b/template/.build/Makefile index 5c2bef0..ea29d19 100644 --- a/template/.build/Makefile +++ b/template/.build/Makefile @@ -89,7 +89,16 @@ postgen: | vendored-git echo "warning: no git available (vendored or host); skipping 'git init'" >&2; \ fi -clean: clean-bpf - rm -rf node_modules dist +# Mirrors the build's titled progress list: a head banner, one step per +# artifact group removed (BPF objects via clean-bpf, then the JS build +# outputs), and a done summary. clean-banner is the leftmost prerequisite so +# it prints before clean-bpf's own step, exactly as `banner` fronts `all`. +clean: clean-banner clean-bpf + @$(SAY) step js "dist/ · node_modules/" + $(Q)rm -rf dist node_modules + @$(SAY) done "cleaned $(notdir $(CURDIR))" -.PHONY: all banner bundle clean postgen +clean-banner: + @$(SAY) head "cleaning $(notdir $(CURDIR))" + +.PHONY: all banner bundle clean clean-banner postgen diff --git a/template/.build/bpf.mk b/template/.build/bpf.mk index f12c483..46e97ea 100644 --- a/template/.build/bpf.mk +++ b/template/.build/bpf.mk @@ -66,7 +66,8 @@ bin: mkdir -p bin clean-bpf: - rm -rf $(BPF_OUT) .build/bpf $(VMLINUX) + @$(SAY) step bpf "$(BPF_OUT) · .build/bpf" + $(Q)rm -rf $(BPF_OUT) .build/bpf $(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 From a001aad4ef8a9d1bc77a2a14189fc1c4682077e3 Mon Sep 17 00:00:00 2001 From: Julian Goldstein Date: Wed, 1 Jul 2026 00:45:58 -0500 Subject: [PATCH 4/5] template: Document that BPF needs no root via the CLI Clarify in AGENTS.md that loading/attaching programs and creating maps are privileged, but the agent never performs them: scripts run inside yeetd, the CLI holds no privilege and delegates the work to the daemon, which carries the BPF capability. Run yeet run/build as a normal user. Co-Authored-By: Claude Opus 4.8 --- template/AGENTS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/template/AGENTS.md b/template/AGENTS.md index ac592df..477b417 100644 --- a/template/AGENTS.md +++ b/template/AGENTS.md @@ -281,6 +281,14 @@ fails silently. Map methods: `lookup/update/delete/entries/lookupBatch` (hash), `lookup/update` (array), `read/patch` (data-sec), per-CPU lookups return an array per CPU. +**No `sudo`, no root — ever.** Loading programs, attaching them, and creating +maps are privileged operations, but *you* never perform them: the script runs +inside **yeetd**, and the `yeet` CLI holds no privilege of its own — it hands +the privileged work to the daemon, which carries the delegated BPF capability. +So BPF runs with the daemon's authorization, not yours. Run `yeet run` / +`yeet build` as your normal user; if something needs elevation, that's a daemon +setup concern, never a reason to re-run the CLI under root. + ## Input — global `tty` ```js From 4dbaaf6f0bcf3af150964255814acebf33c529d5 Mon Sep 17 00:00:00 2001 From: Julian Goldstein Date: Wed, 1 Jul 2026 00:52:28 -0500 Subject: [PATCH 5/5] template: Document CO-RE / stable-hook preference and docs link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two AGENTS.md notes: prefer CO-RE probes and pick the most stable attach point (tracepoints/LSM, then fentry/fexit) before falling back to kprobes on internal functions; and a top-of-file caveat that this yeet's APIs move fast (and share a name with an older project) — verify against https://yeet.cx/docs rather than training-data memory. Co-Authored-By: Claude Opus 4.8 --- template/AGENTS.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/template/AGENTS.md b/template/AGENTS.md index 477b417..95b7837 100644 --- a/template/AGENTS.md +++ b/template/AGENTS.md @@ -5,6 +5,12 @@ isolate, fed by live kernel data (eBPF + a process/system graph). This file is the API contract and gotcha list for editing it. For build/run mechanics, layout, and the `@/`/`#/` aliases, see `README.md` — don't duplicate that here. +> ⚠️ **This may not be the yeet you already know.** The runtime and its APIs +> move quickly, and there's an unrelated older project by the same name. If +> anything here conflicts with your priors, the live docs are authoritative — +> **https://yeet.cx/docs**. When an API feels unfamiliar, check there before +> relying on training-data memory. + ## Mental model It reads like React but it is **signals, not a vdom**. No hooks, no @@ -281,6 +287,17 @@ fails silently. Map methods: `lookup/update/delete/entries/lookupBatch` (hash), `lookup/update` (array), `read/patch` (data-sec), per-CPU lookups return an array per CPU. +**Prefer CO-RE and stable hooks.** Write probes Compile Once – Run Everywhere: +build against `vmlinux.h` + BTF, read fields with `BPF_CORE_READ` and never +hard-code struct offsets, so one object loads across kernels. Choosing an +attach point, reach for stability first — **tracepoints** and **LSM** hooks are +ABI-stable and should be your default; `fentry`/`fexit` on a stable exported +function next. Drop to `kprobe`/`kretprobe` on internal kernel functions only +when nothing stable exposes what you need: those functions get renamed, +inlined, or removed between kernels, so a kprobe that loads today can fail to +attach tomorrow. Pick the most stable hook that can see the event, not the +first one that works. + **No `sudo`, no root — ever.** Loading programs, attaching them, and creating maps are privileged operations, but *you* never perform them: the script runs inside **yeetd**, and the `yeet` CLI holds no privilege of its own — it hands