Documentation
¶
Overview ¶
Package kbuild builds Linux kernels through BuildKit. A Spec describes the build; KernelLLB turns it into an LLB graph; GatewaySolve runs that graph as a gateway frontend (the #syntax= path) or through the client driver (Build). ParseKernelfile reads the Kernelfile format that docker build delegates to the frontend image.
Index ¶
- Constants
- func GatewaySolve(ctx context.Context, c gwclient.Client, spec Spec, opts GatewayOpts) (*gwclient.Result, error)
- func KernelLLB(s Spec) (llb.State, error)
- func ParseCacheEntry(spec, ak, sk string) (client.CacheOptionsEntry, error)
- func ParseKernelfile(r io.Reader, spec *Spec) error
- func Prune(ctx context.Context, addr string) error
- func S3CacheURL(bucketURL, region string) (string, error)
- func SourceExt(name string) (string, error)
- func SourceURLFor(version string) string
- func Timestamp(epoch string) (string, error)
- func VertexLabel(name string) string
- type BuildConfig
- type BuildResult
- type GatewayOpts
- type Spec
- type VertexTiming
Constants ¶
const HelperPath = "/helper/kbuild-step"
HelperPath is where the compile exec finds the kbuild-step runner: the helper state (frontend image or client-provided dir) is mounted at /helper.
Variables ¶
This section is empty.
Functions ¶
func GatewaySolve ¶
func GatewaySolve(ctx context.Context, c gwclient.Client, spec Spec, opts GatewayOpts) (*gwclient.Result, error)
GatewaySolve is the one solve path shared by the gateway frontend and the client driver (kbuild.Build runs it inside client.Build). It does what the graph alone cannot: pin the base image by digest through the daemon's resolver (the object tree and the seed are keyed by the Base string, so a moving tag must never reach KernelLLB), confirm that the build context has the files the graph mounts (BuildKit's own failure for a missing selector path is an opaque checksum error), then generate and solve the graph.
func KernelLLB ¶
KernelLLB builds the LLB graph and returns the captured /out state holding the selected target artifacts (default: vmlinux). This IS the frontend — the build graph is generated in Go, not parsed from a Dockerfile.
Caching has three layers:
Coarse, automatic, content-addressed (BuildKit vertex cache): the toolchain vertex and the compile vertex are cache-keyed by content, the source identity (pinned sha256) among the inputs — an identical build is an instant full hit. Exportable to S3/R2 or a registry via the client's cache export options, so a fresh worker gets the full hit too.
Fine, object-level incremental (persistent cache mount): the kernel tree lives in a locked per-version cache mount at /build. A config change re-runs the compile vertex, but kbuild's own dependency tracking recompiles only the objects the changed CONFIG symbols touch. No ccache.
Remote object-tree seed (ours): BuildKit's cache exporters do NOT cover cache-mount contents, so layer 2 alone dies with the worker. When a seed is configured (via the seed_cfg/seed_access_key/seed_secret_key secrets), a cold mount hydrates from S3-compatible storage before compiling, and a CI build can push the tree back after compiling.
The compile vertex runs the kbuild-step Go binary directly (argv, no shell): stamp/self-heal, seed transfer, fetch, extraction, and patching are Go; the only program it executes is `make`. Cache-key hygiene: everything that affects the output (source identity, config, patches, epoch, CA file) is env or graph structure — in the key. Everything that doesn't (proxy via llb.WithProxy, seed destination + credentials via secrets) — out of the key.
func ParseCacheEntry ¶
func ParseCacheEntry(spec, ak, sk string) (client.CacheOptionsEntry, error)
ParseCacheEntry parses buildctl's cache syntax ("type=registry,ref=...", "type=s3,bucket=...") into a CacheOptionsEntry — the standard BuildKit remote-cache surface, backend-agnostic. For type=s3 entries without explicit credentials, the given creds are injected (the daemon makes the S3 calls and has no env of its own).
func ParseKernelfile ¶
ParseKernelfile reads the tiny build-description format that makes `docker build -f Kernelfile` work via the #syntax= directive:
#syntax=ghcr.io/emirb/kernelbuild-buildkit KERNEL 6.18.20 CONFIG kernel.config SHA256 837a5abd... EPOCH 1785542400 PATCHES on PROXY_CA ca-bundle.crt
Lines are KEY VALUE; # starts a comment (whole-line or trailing, after whitespace); unknown keys are an error (a typo must not silently build something else), and so is a key given twice (the second value would win silently, and a stale line left above a new one is the same kind of typo). Values land in the Spec, which is still validated afterwards — this parser adds no trust.
func Prune ¶
Prune wipes ALL local buildkitd state — vertex cache and cache mounts. The integration suite uses it to simulate a fresh worker.
func S3CacheURL ¶
S3CacheURL expands an https://host/bucket URL into a full s3 cache entry spec in buildctl syntax. region is the bucket's region; empty means "auto", which region-less stores (R2, MinIO) accept and real AWS S3 rejects for SigV4 — so it is a parameter, not a constant.
func SourceExt ¶
SourceExt returns the tarball extension (".gz", ".xz", ".zst") for a source URL or filename, or an error for an unsupported one.
func SourceURLFor ¶
SourceURLFor returns the kernel.org tarball URL for a given version. The directory is keyed by MAJOR version (v6.x, v7.x, ...), derived from the version string — a hardcoded v6.x would silently 404 (or worse, fetch the wrong tree) for 7.x kernels.
The default is .tar.gz, not .tar.xz: gzip decodes in pure Go (klauspost) faster than C xz, so the whole extract path needs no external codec. Both tarballs compress the same tar, so the extracted tree — and the built vmlinux — are byte-identical either way. A .tar.xz SOURCE_URL decodes via ulikunitz/xz (also pure Go); .tar.zst is supported for self-hosted mirrors.
func Timestamp ¶
Timestamp renders SOURCE_DATE_EPOCH exactly as
date -u -d @EPOCH '+%a %b %e %T %Z %Y'
does ("Sat Aug 1 00:00:00 UTC 2026" — %e is space-padded, hence _2). vmlinux embeds this string, so byte-reproducibility depends on it.
func VertexLabel ¶
VertexLabel compresses a vertex name (which for exec ops is the whole embedded script) to a single readable line.
Types ¶
type BuildConfig ¶
type BuildConfig struct {
Addr string // buildkitd address
ContextDir string // kernel.config (+ patches/, + CA file)
HelperBin string // path to the kbuild-step binary ("": next to the executable)
SrcDir string // local-source mode: dir with the tarball
OutDir string // artifact destination; "" solves WITHOUT exporting (bench: isolates solve+cache from the artifact copy)
// Remote cache, buildctl syntax ("type=registry,ref=..." / "type=s3,...").
CacheExports []string
CacheImports []string
// AWS-style credentials for the seed secrets and s3 cache entries.
AccessKey, SecretKey string
// Progress, when set, receives the LIVE build log stream (vertex output,
// ">> ..." notes, "KBF-PHASE <name> <ms>ms" markers) as it arrives —
// what a service streams to its user mid-build. The full transcript is
// still collected into BuildResult.Logs either way. Writes happen from
// the solve's status goroutine; the writer must be safe for that.
Progress io.Writer
// OnStatus, when set, is called with every raw BuildKit status packet
// (vertex state changes, log chunks, transfer progress) — the structured
// feed for consumers that want more than a byte stream. Same goroutine
// caveat as Progress.
OnStatus func(*client.SolveStatus)
// TracerProvider, when set, propagates OpenTelemetry trace context into
// buildkitd — the solve joins the caller's trace and the daemon's
// per-vertex spans (cache probe, exec, export) hang off it.
TracerProvider trace.TracerProvider
}
BuildConfig is everything Build needs beyond the Spec: where the daemon is, which local directories feed the graph, where the artifacts land, and the remote-cache wiring. It is the programmatic form of kbuildctl's flags, and what the integration suite drives directly.
type BuildResult ¶
type BuildResult struct {
Wall time.Duration
Vertices []VertexTiming
CC int // " CC ..." lines observed — objects compiled
Logs string
}
BuildResult reports what the solve did, precisely enough for tests to assert on: wall time, per-vertex timings, and the kbuild activity counted from the captured build logs (no log files, no grep).
func Build ¶
func Build(ctx context.Context, spec Spec, cfg BuildConfig) (*BuildResult, error)
Build solves the Spec's graph against a buildkitd and exports vmlinux (and friends) to cfg.OutDir.
type GatewayOpts ¶
type GatewayOpts struct {
ResolveMode string // image-resolve-mode ("pull" for docker build --pull)
CacheImports []gwclient.CacheOptionsEntry // --cache-from / cache-imports
}
GatewayOpts carries the per-invocation knobs the gateway frontend forwards from docker build; the client driver leaves them zero.
type Spec ¶
type Spec struct {
Base string // base image, e.g. "docker.io/library/ubuntu:24.04"
KernelVersion string // e.g. "6.18.20"
SourceURL string // https tarball (used unless SourceLocalName is set)
SourceSHA256 string // hex sha256 of the tarball; verified in-step and keys the compile vertex
SourceLocalName string // if set, take the tarball from llb.Local("src")/<name>
SourceDateEpoch string // reproducible-build epoch (SOURCE_DATE_EPOCH)
ConfigName string // config file inside the build context (llb.Local("context"))
// ExpectName, when set, names a file in the context with post-olddefconfig
// expectations the step validates BEFORE compiling (see kbuild-step's
// validateExpectations for the line grammar: "y CONFIG_X", "n CONFIG_X",
// "= CONFIG_X=val"). olddefconfig silently drops unknown symbols and unmet
// dependencies; a service composing configs wants that surfaced in seconds,
// not after a full compile.
ExpectName string
// BaseMake, when set, is a space-separated list of make config targets
// (e.g. "x86_64_defconfig kvm_guest.config") run against the tree FIRST;
// the context config is then appended as a fragment before olddefconfig.
// Empty keeps the plain behavior: the context config IS the whole input.
BaseMake string
ApplyPatches bool // apply patches/*.patch from the context before building
HTTPSProxy string // https_proxy for network steps; cache-neutral (llb.WithProxy)
HTTPProxy string // http_proxy for network steps (apt's stock mirrors are http); cache-neutral
NoProxy string // extra no_proxy entries (comma list) on top of localhost,127.0.0.1; cache-neutral
ToolchainReady bool // Base already has the kernel toolchain (e.g. tuxmake/*): skip the apt vertex
Arch string // target architecture ("x86_64" default, "arm64")
CrossCompile string // cross prefix override (derived from Arch when empty)
// Targets selects the artifacts exported to /out. Tokens: "vmlinux",
// "image" (bzImage on x86_64, Image on arm64), "modules"
// (modules.tar.zst via modules_install), "config" (the post-olddefconfig
// .config), "kconfigs" (the tree's Kconfig files bundled as
// kconfig.txt.gz — symbol-catalog input, no compilation). Empty means the
// arch default (vmlinux; arm64 also Image).
Targets []string
ProxyCAFile string // CA cert FILE IN THE CONTEXT to trust (MITM-proxy sandbox); "" = none
HelperRef string // image ref carrying /kbuild-step; "" = llb.Local("helper") from the client
NetworkHost bool // run build steps with host networking (docker build --network=host)
IgnoreCache bool // force the build vertices to execute (docker build --no-cache)
// Remote object-tree seed. BuildKit's cache exporters (registry/s3) cover
// the vertex/layer cache but NOT the contents of cache mounts, so on a
// fresh worker the /build mount is empty and a config change would compile
// cold. The seed closes that gap: after a build the object tree can be
// pushed to S3-compatible storage (R2), and a cold mount hydrates from it
// before compiling. Credentials come in as BuildKit secrets (ids "seed_access_key"
// and "seed_secret_key"), never as opts or env.
SeedURL string // bucket base URL on ANY S3-compatible store (AWS, MinIO, R2, Ceph, ...)
SeedRegion string // bucket region; "auto" (default) suits region-less stores
SeedPrefix string // key prefix inside the bucket (default "kbuild-seed")
SeedPush bool // push the object tree after a successful build (CI role)
}
Spec describes a kernel build. The frontend turns it into an LLB graph (see KernelLLB). It is deliberately small and JSON/opt-friendly so the same struct drives both the gateway frontend (build.kernel.v0) and the client driver (kbuildctl).
Several fields are interpolated into the build script or into BuildKit identifiers; Validate() MUST pass before the Spec reaches KernelLLB.
func DefaultSpec ¶
func DefaultSpec() Spec
DefaultSpec is the out-of-the-box build: Linux 6.18.20 from kernel.org with a pinned sha256, a fixed epoch, and kernel.config from the context.
func (Spec) SeedCfg ¶
SeedCfg renders the cache-neutral seed configuration that the client passes as the "seed_cfg" BuildKit secret. It lives in a secret, not env or opts, deliberately: whether (and where) the object tree is seeded has no effect on the built vmlinux, so it must not perturb the vertex cache key — and secrets are excluded from cache keys while env vars are not. Returns nil when seeding is disabled.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
kbuild-frontend
command
kbuild-frontend is the packaged BuildKit gateway frontend (build.kernel.v0).
|
kbuild-frontend is the packaged BuildKit gateway frontend (build.kernel.v0). |
|
kbuild-step
command
kbuild-step is the compile-vertex runner.
|
kbuild-step is the compile-vertex runner. |
|
kbuildctl
command
kbuildctl drives KernelLLB directly against a buildkitd and exports the requested artifacts to a local directory.
|
kbuildctl drives KernelLLB directly against a buildkitd and exports the requested artifacts to a local directory. |
|
llbdump
command
llbdump marshals the kernel build graph and prints every op with its inputs — the raw material for judging whether the graph is shaped well.
|
llbdump marshals the kernel build graph and prints every op with its inputs — the raw material for judging whether the graph is shaped well. |