Open and answer this issue on GitHub.
no labels · opened 2026-10-06 · updated 2026-10-09 · 5 comments
The question started from Cargo’s build-script problem and how Nikaia avoids it. While preparing it, Part III 13.4 turned out to describe build.nika: a program compiled and run before the build, with a std::build API (build::cc, build::rustc_link_lib). That is Cargo’s build.rs again. It had no ADR and was not built, and it contradicted ADR-287 D14 and ADR-321 D15.
The need behind it is real. examples/sqlite says “nikaia has no option for linking a C library, so -l sqlite3 goes to rustc through RUSTFLAGS“.
What was checked:
build.zig is as powerful as build.rs), Go (#cgo directives, plus a flag allow list since CVE-2018-6574) and SwiftPM.sqlx speak the protocol natively. C is needed for in-process libraries: SQLite, codecs, crypto, OS APIs.#include "/…/secret.txt" printed the file’s content in the compiler’s error. C23 #embed puts it into the object (GCC 15, Clang 19; here GCC 13 and Clang 18 lack it). No GCC or Clang switch forbids absolute paths, so it gets past ADR-310.ADR-324, Part III 13.4, NK1224, NK1225:
build.nika, no std::build.extern "C" blocks need. Every program that uses the package links them, and its author writes nothing.pkg-config where a pkg-config name is given, else -l name. The compiler forms the arguments. Not found: NK1224, naming the package, the library and the pkg-config name.pkg-config --libs, only -l, -L, -pthread, and on macOS -framework and -F. Any other flag: NK1225.Open:
extern "C" block, in the manifest, or both), the next question;Blocked on the spelling question (D2) for steps 1-2.
pkg-config name, run pkg-config --libs <name> and filter by D4, else use -l<name>.rustc for the program, with no RUSTFLAGS needed.NK1224 when pkg-config fails or the library is not found at link time. Test with a declared library nikaia_test_missing.NK1225. Test with a PKG_CONFIG_PATH pointing to a test .pc whose Libs: contains -Wl,-plugin,/x.so. The build is refused and the flag is named.examples/sqlite: declare sqlite3, drop the RUSTFLAGS note, and check that crates/nikaia/tests/foreign_pointers.rs’s end-to-end test runs without -l sqlite3.sqlite3 builds without naming the library itself.The extern block names its library, and the manifest says how the library is found. Both are required.
extern(library: "sqlite3") { … }
[library.sqlite3]
pkg-config = "sqlite3"
How it came up: the owner disliked extern "C", and proposed the subject ; options form, extern(extern.C; library: …).
What was checked:
"C" is the only convention on every target.extern "C" library "…" would reserve library.extern.C is no path this language writes, and foreign::Abi::C is an enum with a single variant.So the convention is left out entirely. A second one, if ever needed, would come in as a subject before a ;.
The owner then rejected a pkg-config-with--l-fallback default, because the name would appear several times and the default would be invisible. pkg-config is a required value, “written so rarely that explicit is better to copy”.
Rules now:
extern(library: "x") needs a [library.x] entry, and pkg-config is required in it.NK1226.library links nothing of its own.pkg-config. No -l fallback. If pkg-config is missing, or the library is not found, the build stops with NK1224.extern { … }, extern(library: …) { … }, pub extern fn, pub extern struct.extern "C" is NK1227, with extern as the help.extern blocks are fragments to today’s parser; EXPECTED.txt and COMPILES.txt record that.Not decided: a minimum version, cross-compiling, Windows (no pkg-config there).
extern with an optional (library: "<name>"), before a block.pub extern fn and pub extern struct.extern "C" gives NK1227, help extern.ast.nika’s abi field, which is no longer written.[library.<name>] with a required pkg-config string.NK1226 both ways: a block naming a library without an entry, and an entry no block names.pkg-config --libs <pkg-config>, filter it by D4, and pass the result to rustc for the program.extern:
examples/sqlite, and crates/nikaia-std/src/tools/*.nika;examples/sqlite’s RUSTFLAGS note, and add [library.sqlite3] pkg-config = "sqlite3" to its project;foreign_pointers.rs runs without -l sqlite3.tests/specification/EXPECTED.txt and COMPILES.txt. The four extern blocks of Part III 15.1 compile again.Generated by Claude Code
Taking this one (session 01DGERcXMpRWbGxPuAFEzp6E). I’ll start with the parser and the rewrite to extern (steps 1, 4, 5), then do the manifest and linking (steps 2, 3).
Generated by Claude Code
extern { … } and extern(library: "<name>") { … } parse. library is a keyword only in that position, like opaque/released/by.ast.nika, Item::Extern now has abi: String? (set only where a convention was written) and library: String?.NK1227: extern "C", or any other string, still parses. The checker then refuses it with extern takes no convention, help extern { … } / extern(library: "name") { … }. The emitter refuses the same thing as a backstop. The lowering is Rust’s unsafe extern "C" { as before.examples/sqlite now uses extern(library: "sqlite3"). Its RUSTFLAGS note stays until step 3.foreign_pointers.rs and foreign_declarations.rs use extern.refusal_lines.rs now expects NK1227’s message."C" and "stdcall".extern(library: "sqlite3") lower and compile again (EXPECTED.txt, COMPILES.txt).Not part of this: pub extern fn (with a body, an entry point) and pub extern struct. They were not built under the old extern "C" spelling either: the parser has no rule for them. I left them out rather than parse something nothing lowers.
Next: step 2 ([library.<name>] with a required pkg-config, and NK1226 both ways), then step 3 (pkg-config --libs, filtered by D4, handed to rustc for the program only; NK1224, NK1225). After that examples/sqlite becomes a project with [library.sqlite3], and the -l sqlite3 in foreign_pointers.rs’s end-to-end test goes.
Generated by Claude Code
nikaia.toml reads [library.<name>], with pkg-config required. Any other key in the table is refused (manifest.rs).NK1226, both ways, per package, before anything is linked:
extern(library: "x") with no entry. The help shows the table to add..nika files, parsed even on a cache hit (libraries::declared).pkg-config --libs runs for each entry (libraries::link_flags):
NK1224 where pkg-config is missing or does not find the name. The message names the package, the library and the name looked for.-l, -L, -pthread, and on macOS -framework/-F. Anything else is NK1225, naming the flag.rustc arguments (-l x, -L native=dir), only to the --crate-type bin crate.examples/sqlite is a project with [library.sqlite3]. foreign_pointers.rs’s end-to-end test runs nikaia run --project on a copy and passes no -l. It skips where pkg-config does not know sqlite3.tests/system_libraries.rs):
NK1226 both ways.NK1224 with nikaia_test_missing.NK1225 with a .pc file whose Libs: holds -Wl,-plugin,/x.so.-l in its link. It is checked with a .pc naming an absent library, whose link fails by name.What is left: step 2’s carry the libraries in the package’s ledger.
Generated by Claude Code
registry is crates.io , so you can use cargo