Kconfig's conf in Crystal, so that the Linux and U-Boot builds can be configured in the BQ2
seed without flex and bison. It writes the same files as the real conf, byte for byte:
.config, include/config/auto.conf and its .cmd, include/generated/autoconf.h, Linux's
rustc_cfg, U-Boot's tristate.conf, and the include/config/ dependency files. Make then
never needs to build or run conf.
cleave-kconfig [--dialect linux|u-boot|qemu|zephyr] [--srctree DIR] [--functions FILE] \
(--olddefconfig | --defconfig FILE | --syncconfig) Kconfig
It runs in the object tree, with the environment make gives conf, because Kconfig files
read it ($(CC), $(srctree), and so on). The simplest way to get that environment is to
put cleave-kconfig where kbuild expects scripts/kconfig/conf.
Dialects
linux: Linux 6.12's Kconfig (lexer.l,parser.y,menu.c,symbol.c,confdata.c).u-boot: the Linux 4.20-era Kconfig that U-Boot 2025.07 carries. It differs in these ways:- keywords are keywords only as a line's first word;
optionlines;- choices have their own value and a member list;
- a menu's dependencies come from its parent's prompt;
implyneeds the direct dependencies;- numbers default to
""; - automatic submenus can take entries out of a choice;
- values depend on the order symbols are calculated in;
- the output formats are older.
This dialect lives in src/cleave/kconfig/uboot.cr.
qemu: Linux's, except for QEMU'ssourcelines (as of QEMU 11.1.2). Their file names may be unquoted, and are relative to the directory of the file that has them:source hw/Kconfig, thensource arm/Kconfiginside it.zephyr: Linux's, with what Kconfiglib (Zephyr'sscripts/kconfig, v4.4.2) adds:sourceglobs its quoted pattern under srctree as Python'sglobdoes (*,?and[...]within a path part; no**recursion or{a,b};*skips names starting with.unless the part starts with one) and reads the matches in sorted order;rsourceis relative to the including file's directory;osourceandorsource(and the oldergsourceandgrsource) may match nothing;- a quoted string with a
$or\in it also has$NAMEand${NAME}of set environment variables expanded (Python'sos.path.expandvars), and a backslash at the end of a line continues the string on the next; optionalchoices, which select nothing until the user selects a member;boolean, anddef_int,def_hexanddef_string;configdefault SYM, whosedefaultlines (the only kind it takes) neither widen the symbol's direct dependencies nor hold beyond them;- a definition with no dependencies makes a symbol's direct dependencies
y, where Linux's C leaves out an unconditional definition; - Zephyr's preprocessor functions (
kconfigfunctions.py):normalize_upper,shields_list_contains,substring,add,sub,mul,div,mod,max,min,inc,decand their_hexforms, and everydt_*function. With no devicetree they answer as Kconfiglib does when it has none:nfor a test,0or0x0for a number,""for a label or path. A caller gives a devicetree's answers instead: in code, asfunctionstoConfig.load; from the command line, with--functions FILE, a JSON object whose keys are a function's name (every call) or a whole call,name(a,b), its arguments as written. Calls are checked againstkconfigfunctions.py's argument counts, with its error, and Linux's limit of 15 arguments does not apply; - values as Kconfiglib calculates them where it differs from Linux's C:
- an
implycounts only while the symbol's direct dependencies are met, and never takes a default away, so an implied symbol whose dependencies are unmet is not written; - an int or hex user value outside the active
rangeis passed over for the defaults (Linux clamps it); a default outside it, or no value, is clamped and written as Python writes the number (0x2a, where Linux copies the bound,0x2A); the bounds are read in the symbol's own base; - a choice takes the user's selection, else its defaults, else its first visible member;
a member set to
nchanges nothing (Linux 6.12 then passes over that member).
Checked against Zephyr v4.4.2's whole tree for qemu_cortex_a53, with the files Zephyr's
CMake generates under KCONFIG_BINARY_DIR written by hand: it reads all of it and writes
a .config with no warnings. Glint's values from Kconfiglib for the several definitions of
PRIVILEGED_STACK_SIZE (arm64 and x86) are a spec.
How it parses
It uses the crystal-robots multipass pattern, like cleave-dtc:
- Lexing.
LEXICALandSTRING_LEXICALinsrc/cleave/kconfig/parse.crare tables of regexes, tried in order. The lexer also does what the C lexer does as it reads:- expands
$(...)macros (src/cleave/kconfig/preprocess.cr); - applies variable assignments;
- splices
sourced files in where they are named; - skips help texts by their indentation.
- expands
- Reduction.
Kconfig::GRAMMARreduces in priority order:- expressions: comparisons, then
!,&&,||; - one attribute line each;
- entries, with blocks (
if,menu,choice) reducing innermost first.
- expressions: comparisons, then
A syntax error names the first line left unreduced inside the blocks, not the line that opens the block holding it.
- Semantics.
src/cleave/kconfig/config.crapplies the parser's actions and thenmenu.c's finalization andsymbol.c's value calculation, method for method. - Order. Symbols are created in the order their names first appear, and iterated in
hash-table order: FNV-32 into 16,384 buckets (Linux) or 9,973 (U-Boot), newest first.
Files that list symbols in
for_all_symbolsorder therefore come out in the same order.
Not done
- Interactive modes,
savedefconfig, theall*configandrandconfigmodes. - Linux's automatic submenus. They change neither values nor output there.
- In U-Boot's automatic submenus, the "superset of dependencies" case. Only entries that depend on the symbol outright are moved under it.
- In the
zephyrdialect:- a devicetree (only answers given by the caller);
- Kconfiglib's
optionlines, which Zephyr's tree does not use; - tristate symbols and
m(Zephyr has none): Kconfiglib's rules for them are not checked; - Kconfiglib's warnings, which are not all given, and none change a value: a
selectof a symbol whose direct dependencies are unmet, a default of the wrong type, and so on; - checked by reading
kconfiglib.py, not by running it: no output of Kconfiglib's is compared byte for byte, except Glint's.
Checked
- Fixtures.
spec/kconfig_spec.crcompares both dialects against the output of the realconf, on fixtures covering:- macros, menus,
ifandvisible if; - choices, including a member that becomes a submenu;
select,imply, ranges, hex, tristate withmodules, and quoted strings.
- macros, menus,
- Linux
v6.12.49-ti-arm64-r56. Armbian's vendor config plus our audio fragment, run through kbuild'solddefconfigandsyncconfig. All outputs and all 2,846 dependency files are identical. - U-Boot 2025.07.
am67a_beagley_ai_r5_defconfigandam67a_beagley_ai_a53_defconfig, run through U-Boot's defconfig andsyncconfig. All outputs and allinclude/config/*.hfiles are identical.