kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

kit build commands

Forward-looking roadmap for the kit-native build verbs — build-exe, build-lib, build-obj — that replace the compile tool. They compile a mixed-language set of sources entirely in memory and produce a final artifact (executable / static or shared library / object) in one invocation, with full control over both the per-source compile and the whole-build link. Design doc when shipped: ../DRIVER.md.

Distinct from BUILD.md (the CAS-backed incremental build coordinator) and from ../BUILD.md (kit's own Makefile build). This is about the driver's single-shot build commands.

Motivation

Today the kit-native compile path splits awkwardly:

So the in-memory, no-temp-files, polyglot compile+link pipeline already exists and is proven inside cc's link path (driver/cmd/cc.c cc_run_link_exe). What is missing is a kit-native front door to that pipeline: one that is polyglot, forwards per-language frontend flags, exposes the full link-flag surface, and lets the caller scope compile flags to individual sources or groups of sources — without pretending to be gcc.

The public API already supports every output we need:

Artifact API
executable KitLinkSession + KIT_LINK_OUTPUT_EXEkit_link_session_emit
shared library KitLinkSession + KIT_LINK_OUTPUT_SHARED
combined relocatable object KitLinkSession + KIT_LINK_OUTPUT_RELOCATABLE
single object kit_obj_builder_emit (include/kit/object.h)
static archive kit_obj_builder_emit each member, then kit_ar_write (include/kit/archive.h)

This work is therefore almost entirely a driver-layer reorganization, not new core machinery: lift cc's link path into a shared engine, add a kit-native argument grammar on top, and retire compile.

The command set

A Zig-inspired trio. Every command is polyglot, compiles in memory, and writes no intermediate files.

Command Produces Backend
kit build-exe executable link session, OUTPUT_EXE
kit build-lib static .a (default) or shared library (-dynamic) kit_ar_write / OUTPUT_SHARED
kit build-obj a single object; or --emit=asm\|c\|ir; or -fsyntax-only check one KitObjBuilder, or OUTPUT_RELOCATABLE for multi-source

build-obj is the full replacement for compile: it keeps --emit=obj|asm|c|ir, -fsyntax-only, the single-frontend-or-polyglot source handling, and frontend flag forwarding — and it gains the ability to combine several sources into one relocatable object (ld -r style) via KIT_LINK_OUTPUT_RELOCATABLE. The standalone kit check (cc.c driver_check) and cc's own --emit=/-S are unaffected and remain available.

The three share ~90% of their code; like cc/check they live in one file (driver/cmd/build.c) with three thin entry points (driver_build_exe, driver_build_lib, driver_build_obj) over a shared parse+run parameterized by output kind.

Command-line grammar

Two flag tiers

  1. Global / per-output flags apply to the whole build and may appear anywhere outside a group. These are everything that must agree across the link, plus the optimization and debug knobs (per the decision below):

    • -target TRIPLE / --target=, and target-feature flags
    • -O0|-O1|-O2, -g
    • -fPIC|-fPIE, -fvisibility=hidden|default
    • -ffunction-sections, -fdata-sections
    • all link flags: -l, -L, -e, -T, -static/-dynamic, -pie/-no-pie, --build-id=, -Wl,…, soname/rpath, subsystem, …
    • all output flags: -o, --emit=, -S, -fsyntax-only
    • -Werror, -fmax-errors=N
  2. Scopable flags may appear globally (baseline for every source) and inside a --group (override for that group's sources only). The scopable set is intentionally small — only what is genuinely per-translation-unit:

    • preprocessor: -I, -isystem, -D, -U
    • language selection: -x LANG
    • frontend-specific: -X<lang> FLAG (see below)

Placing a global flag inside a --group is a usage error with a pointed diagnostic (e.g. -O is a per-output flag; place it before any --group). This keeps the rule a one-liner: outside a group = whole build; inside a group = those sources.

Groups

--group [scopable flags…] -- source [source…]

Each --group bundles scopable overrides with the sources listed up to the next --group or the end of arguments. The -- separates the group's flags from its sources. Sources listed outside any group ("bare" sources) receive only the global flags.

Inheritance and precedence within a group, relative to the global baseline:

Link order is the left-to-right order of source/object/archive appearance; a group contributes its sources at the group's position. Bare inputs (.o/.a/ .so) keep their command-line position for the linker.

Per-language frontend flags: -X<lang>

compile could forward leftover flags unambiguously because it resolved exactly one frontend. A polyglot build cannot, so frontend flags are explicitly language-scoped:

-X<lang> FLAG     # e.g.  -Xwasm -mfeature=simd128

-X<lang> consumes exactly one following token and routes it to that frontend's kit_frontend_parse_options (the same entry compile uses). Repeatable. <lang> is c|asm|toy|wasm. Works both globally and inside a group. (Current kit frontend flags are single-token; a multi-token form is a future extension if ever needed.)

Naming conventions (hybrid)

Keep kit/cc's established output vocabulary; adopt Zig's clearer link-kind selectors:

Output defaults

-o - writes the emit to stdout for all emit forms (obj/asm/c/ir), reusing the existing driver_stdout_writer that cc uses — natural for pipelines (e.g. build-obj --emit=ir -o - kernel.wat | less). Binary objects to a tty are unusual but harmless and not specially rejected.

A single -target governs the whole build — mixing targets in one invocation is an error (one link, one machine).

Worked examples

# Polyglot executable: C + a hand-written asm TU + a Wasm module, in memory.
kit build-exe -target aarch64-linux-gnu -O2 -o app \
  main.c util.c \
  --group -DFAST -Iinc/fast        -- hot1.c hot2.c \
  --group -Xwasm -mfeature=simd128 -- kernel.wat \
  prebuilt.o -Llib -lfoo

# Static library from mixed sources (default kind).
kit build-lib -O2 -o libmix.a a.c b.toy c.s

# Shared library with a soname.
kit build-lib -dynamic -fPIC -Wl,-soname=libmix.so.1 -o libmix.so.1 a.c b.c

# Combine three TUs into one relocatable object (ld -r).
kit build-obj -O1 -o combined.o a.c b.c c.c

# Inspect: emit IR for a Wasm module compiled with a frontend feature flag.
kit build-obj -O1 --emit=ir -Xwasm -mfeature=simd128 -o k.ir kernel.wat

# Check only, no output.
kit build-obj -fsyntax-only main.c util.c

Implementation plan

The work is a factor-out + new-grammar exercise. Proposed file moves:

  1. driver/lib/link_engine.{h,c} — lift the body of cc.c cc_run_link_exe into a reusable step. Input: a populated link plan (in-memory KitObjBuilder* list, byte-loaded objects/archives/DSOs, an ordered KitLinkInputOrder list, and a filled KitLinkSessionOptions). It opens the writer, builds the KitLinkSession, adds inputs in order, and emits. cc_run_link_exe becomes a thin caller, so cc and build-* share one link path (no behavior change to cc). The runtime-archive insertion (libkit_rt.a), hosted-libc wiring (driver/lib/hosted), and -l/-L resolution (driver/lib/lib_resolve) are already factored and are reused as-is.

  2. driver/lib/archive_engine.{h,c} (small) — driver_archive_emit(objs[], names[], n, writer): kit_obj_builder_emit each member to bytes, then kit_ar_write. Used by build-lib (static) and reusable by a future ar pipeline.

  3. driver/cmd/build.c — the new grammar and the three entry points. Reuses driver_compile_run (driver/lib/compile_engine.h) for the per-source compile, DriverCflags (driver/lib/cflags) for -I/-D/-U, and driver_target_features_*. New here: the --group … -- parser, the global-vs-scoped validation, the -X<lang> router, and per-group cflag/ frontend-option contexts (one DriverCflags baseline plus per-group deltas).

  4. driver/main.c — register build-exe/build-lib/build-obj in driver_tools[], gated by new KIT_TOOL_BUILD_*_ENABLED flags (include/kit/config.h); add them to the default install group. Remove the compile entry and its KIT_TOOL_COMPILE_ENABLED gate.

  5. Remove driver/cmd/compile.c and its help. Its capabilities are fully covered by build-obj.

Per-group compile state

The compile loop already builds one KitObjBuilder* per source through a shared KitCompiler. The only new state is per-group compile options: each source carries (a) a KitPreprocessOptions derived from global cflags + the group's cflag delta, (b) a resolved KitLanguage (group -x or suffix), and (c) the lang_extra from that group's -X<lang> flags. This mirrors how compile already calls kit_frontend_parse_options per frontend — now keyed per group.

Migration

Future work (post-v1)

Verification notes

Decisions (2026-06-04)

Decision Choice
Replace compile? Yes — trio build-exe/build-lib/build-obj; build-obj subsumes compile.
Flag scoping syntax Explicit --group [flags] -- sources blocks. Outside = global/per-output, inside = scoped; a group of one = per-source.
Global (per-output) flags -O, -g, -fPIC/-fPIE, -fvisibility are all global (plus -target, all link, all output flags).
Scopable-in-group set -I/-isystem/-D/-U, -x, -X<lang> frontend flags.
Naming conventions Hybrid: keep kit --emit=/-o; adopt Zig -static/-dynamic for link kind.
Inspection / check home build-obj keeps --emit=asm\|c\|ir, -fsyntax-only, and gains multi-source → relocatable .o.
-shared on build-lib Accepted as a hidden alias for -dynamic (not shown in help).
build-obj multi-source Relocatable combine ships in v1, gated by ld -r parity tests.
-o - to stdout Supported for all emit forms (obj/asm/c/ir) via driver_stdout_writer.
v1 input ergonomics --group grammar only; @file and attach-by-name deferred to post-v1.