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
31 changes: 22 additions & 9 deletions template/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# 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
# make QUIET=1 — quiet: status lines only, no echoed commands
#
# This is the build *frontend*: it orchestrates two independent
# compilers — clang for the BPF objects, esbuild for the JS bundle.
Expand All @@ -16,15 +17,25 @@
# 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.
# build/toolchain.mk (the yeet CLI's installed toolchain when `yeet` is on
# PATH, else a shared per-machine cache pinned by build/toolchain.lock) — so
# the build needs no system C/BPF toolchain.

.DEFAULT_GOAL := all

include build/ui.mk
include build/toolchain.mk
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

# Bundle the entry with the vendored esbuild. esbuild honors tsconfig `paths`
# (so `@/` resolves at bundle time), while `yeet:*` builtins and `*.bpf.o`
Expand All @@ -45,24 +56,26 @@ ESBUILD_FLAGS := --bundle --format=esm --platform=neutral \
--outfile=dist/index.jsx --jsx=automatic --jsx-import-source=yeet:tui

bundle: | toolchain
$(ESBUILD) src/main.jsx $(ESBUILD_FLAGS) '--external:yeet:*' '--external:*.bpf.o'
$(call status,Bundling,src/main.jsx)
$(Q)$(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.
# 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)"; \
@g="$(GIT)"; $$g --version >/dev/null 2>&1 || g="$$(command -v git 2>/dev/null || true)"; \
if [ -e .git ]; then \
echo "postgen: already a git repository"; \
printf '$(STATUS_FMT)' 'Skipping' 'git init (already a repository)' >&2; \
elif [ -n "$$g" ]; then \
echo "postgen: git init"; \
"$$g" -c init.templateDir= init -q . || echo "warning: 'git init' failed" >&2; \
printf '$(STATUS_FMT)' 'Initializing' 'git repository' >&2; \
$$g -c init.templateDir= init -q . || printf '$(WARN_FMT)' 'git init failed' >&2; \
else \
echo "warning: no git available (vendored or host); skipping 'git init'" >&2; \
printf '$(WARN_FMT)' 'no git available (vendored or host); skipping git init' >&2; \
fi

clean: clean-bpf
rm -rf node_modules dist
$(call status,Cleaning,node_modules dist)
$(Q)rm -rf node_modules dist

.PHONY: all bundle clean postgen
11 changes: 11 additions & 0 deletions template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ the `@/` alias; `main.jsx` wires them together and owns input.

```sh
make # compile BPF (clang + bpftool) + bundle JS (esbuild)
make QUIET=1 # same, quiet: status lines only, no echoed commands
yeet run . # runs the bundled dist/index.jsx (needs root for BPF)
```

Expand Down Expand Up @@ -144,6 +145,16 @@ feed the components from any source that exposes the same signals.
- `clang` and `bpftool` (for the BPF leg; `bpftool` generates
`src/bpf/include/vmlinux.h` from the host kernel, which needs `CONFIG_DEBUG_INFO_BTF`)

Both come from the static toolchain, resolved by `build/toolchain.mk`. With
the `yeet` CLI on PATH the build runs them as `yeet toolchain exec <tool>`
from the toolchain `yeet toolchain default` points at — one install shared
with the CLI, kept current with `yeet toolchain update`. If none is installed
yet, the first `make` installs the pinned version. Without `yeet`, the build
fetches the version pinned in `build/toolchain.lock` into a per-machine cache
(`~/.cache/yeet/toolchain/`) once; `make YEET=` forces that path even when
`yeet` is installed. `make clangd` writes a `.clangd` pointing at whichever
one resolved.

No node/npm is required: esbuild is vendored by the toolchain, and the starter
has no third-party deps. (You only need a package manager if you add npm/jsr
dependencies of your own — see *npm / jsr packages* above.)
40 changes: 24 additions & 16 deletions template/build/bpf.mk
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
# 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
# toolchain or shared cache), included before this file. These are the last
# resort when neither is available: whatever is on PATH. bpftool frequently
# CLANG/BPFTOOL are normally resolved by build/toolchain.mk (the yeet CLI's
# toolchain via `yeet toolchain exec`, or the lock-pinned shared cache),
# included before this file. These are the last resort when neither is
# available: whatever is on PATH. A resolved tool may be a multi-word command,
# so the existence checks below probe its first word. bpftool frequently
# lives in /usr/sbin, which isn't always on a non-root user's PATH; fall back
# to it before giving up.
CLANG ?= clang
Expand Down Expand Up @@ -44,25 +46,29 @@ bpf: $(BPF_OUT)
# `| toolchain` (order-only) ensures the vendored clang/bpftool are present in
# 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) $@
@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)" $@

# 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 $@
@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 $@

# 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)
@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)

bin:
mkdir -p bin

clean-bpf:
rm -rf $(BPF_OUT) .build $(VMLINUX)
$(call status,Cleaning,$(BPF_OUT) .build $(VMLINUX))
$(Q)rm -rf $(BPF_OUT) .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
Expand All @@ -72,21 +78,23 @@ clean-bpf:
# 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; }
$(VERISTAT) $(BPF_OUT)
@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)

# Run the same verifier check across a matrix of kernels locally (Linux + KVM),
# the local counterpart to .github/workflows/kernel-matrix.yml. Boots
# quay.io/lvh-images/kind images with cilium's lvh + QEMU; pass kernels as
# 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)
$(call status,Verifying,$(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
# headers and __u* types using the *resolved* toolchain include path — unlike
# the committed .clangd (which only covers in-repo editing), this picks up the
# shared-cache path in a scaffolded project. Run after `make` so vmlinux.h
# yeet toolchain or shared-cache path in a scaffolded project. Run after `make` so vmlinux.h
# exists. The result can hold a machine-specific cache path, so leave it
# untracked. Falls back to host headers when no vendored toolchain is found.
.PHONY: clangd
Expand All @@ -103,6 +111,6 @@ clangd:
' - -D__TARGET_ARCH_$(ARCH)' \
' - -D__BPF_TRACING__' \
> .clangd
@echo "wrote .clangd (libbpf headers: $(if $(BPF_SYSINCLUDE),$(BPF_SYSINCLUDE),<host include path>))"
$(call status,Writing,.clangd (libbpf headers: $(if $(BPF_SYSINCLUDE),$(BPF_SYSINCLUDE),<host include path>)))

.PHONY: bpf clean-bpf
15 changes: 10 additions & 5 deletions template/build/fetch-toolchain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@

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; }
if [ -t 2 ]; then CURL_PROGRESS=--progress-bar; else CURL_PROGRESS=-s; fi

DIR="${1:?usage: fetch-toolchain.sh <dest-dir> <arch> <lock> [tool...]}"
ARCH="${2:?missing arch}"
LOCK="${3:?missing lock}"
Expand Down Expand Up @@ -66,9 +71,9 @@ fetch_bin() {
name="$1"
want "$name" || return 0
[ -x "$DIR/$name" ] && return 0
echo ">> fetch ${name} (${ARCH}, v${TOOLCHAIN_VERSION})"
status Downloading "${name} (${ARCH}, v${TOOLCHAIN_VERSION})"
tmp="$DIR/.${name}.$$"
curl -fSL --retry 3 -o "$tmp" "${REL}/${name}-${ARCH}"
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; }
chmod +x "$tmp"
mv -f "$tmp" "$DIR/$name"
Expand All @@ -86,13 +91,13 @@ 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
echo ">> fetch libbpf headers (v${TOOLCHAIN_VERSION})"
status Downloading "libbpf headers (v${TOOLCHAIN_VERSION})"
td="$(mktemp -d)"
curl -fSL --retry 3 -o "$td/h.tgz" "${REL}/libbpf-headers.tar.gz"
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; }
mkdir -p "$INC"
tar xzf "$td/h.tgz" -C "$INC" # tarball holds a top-level bpf/ dir
rm -rf "$td"
fi

echo ">> toolchain ready: $DIR${FILTER:+ ($FILTER)}"
status Toolchain "$DIR${FILTER:+ ($FILTER)}"
15 changes: 12 additions & 3 deletions template/build/gen-vmlinux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
# every BPF program in this project includes.
#
# build/gen-vmlinux.sh <bpftool> <output-path>
#
# <bpftool> may be a multi-word command (`yeet toolchain exec bpftool`), so it
# is word-split on use, deliberately.

set -eu

Expand All @@ -15,14 +18,20 @@ if [ ! -r /sys/kernel/btf/vmlinux ]; then
fi

mkdir -p "$(dirname "$OUT")"
err="$(mktemp)"
trap 'rm -f "$err"' EXIT
# Mark the generated header as a system header so clang keeps -Wall quiet
# about it. The dump is not warning-clean: the kernel builds with tagged
# anonymous members (`struct foo;` as an unnamed struct/union member), which
# bpftool emits verbatim and clang >= 22 flags with -Wmissing-declarations.
# Nothing in it is ours to fix, and the pragma only affects diagnostics —
# CO-RE relocations and preserve_access_index are untouched.
# On failure, drop the partial header so make doesn't take it for a good one.
{
echo "#pragma clang system_header"
"$BPFTOOL" btf dump file /sys/kernel/btf/vmlinux format c
} >"$OUT"
echo "generated $OUT"
# shellcheck disable=SC2086
$BPFTOOL btf dump file /sys/kernel/btf/vmlinux format c
} >"$OUT" 2>"$err" || { cat "$err" >&2; rm -f "$OUT"; exit 1; }
# bpftool notes that the kernel BTF "will be loaded as base" — expected here,
# so keep it out of the build output; anything else it said goes through.
grep -v 'will be loaded as base' "$err" >&2 || true
14 changes: 9 additions & 5 deletions template/build/kernel-matrix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@ need() { command -v "$1" >/dev/null 2>&1 || { echo "error: '$1' not found — $2
sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}'; else shasum -a 256 "$1" | awk '{print $1}'; fi; }
need ssh "install openssh-client"

# --- resolve qemu: prefer the vendored static qemu from the toolchain release,
# 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
# --- resolve qemu: prefer the static qemu inside the yeet-managed toolchain
# (TOOLCHAIN_DIR, passed by the Makefile), then the vendored one from the
# toolchain release, then 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 bin dir to PATH — no -L plumbing into lvh.
if [ -n "${TOOLCHAIN_DIR:-}" ] && [ -x "${TOOLCHAIN_DIR%/}/qemu/bin/$QEMU" ]; then
PATH="${TOOLCHAIN_DIR%/}/qemu/bin:$PATH"; export PATH
echo ">> using yeet toolchain qemu: ${TOOLCHAIN_DIR%/}/qemu/bin/$QEMU"
elif [ -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"
Expand Down
Loading
Loading