Issue #483: ADR-324: no build script; a package links a C library by name (pkg-config or -l), with listed linker flags

Open and answer this issue on GitHub.

no labels · opened 2026-10-06 · updated 2026-10-09 · 5 comments

How it came up

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:

Decision (owner)

ADR-324, Part III 13.4, NK1224, NK1225:

Open:

To build

Blocked on the spelling question (D2) for steps 1-2.

  1. Read a package’s library declarations, wherever D2’s answer puts them, and carry them in the package’s ledger, so a consuming program knows them without the package’s sources.
  2. Linking:
    • For each library: with a pkg-config name, run pkg-config --libs <name> and filter by D4, else use -l<name>.
    • Pass the result to rustc for the program, with no RUSTFLAGS needed.
    • Per ADR-321 D14, never pass it for the build-time library.
  3. NK1224 when pkg-config fails or the library is not found at link time. Test with a declared library nikaia_test_missing.
  4. 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.
  5. 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.
  6. Test: a program depending on a package that declares sqlite3 builds without naming the library itself.

Comments

keywan-ghadami, 2026-10-06

Spelling decided (owner): ADR-324 D2, D3 and D6 revised

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:

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:

Not decided: a minimum version, cross-compiling, Windows (no pkg-config there).

To build, replacing steps 1-2 and 5 above

  1. Parser:
    • extern with an optional (library: "<name>"), before a block.
    • pub extern fn and pub extern struct.
    • extern "C" gives NK1227, help extern.
    • Update ast.nika’s abi field, which is no longer written.
  2. Manifest:
    • Read [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.
    • Carry the libraries in the package’s ledger for consuming programs.
  3. Linking:
    • For each named library, run pkg-config --libs <pkg-config>, filter it by D4, and pass the result to rustc for the program.
    • Never pass it for the build-time library (ADR-321 D14).
  4. Rewrite everything to extern:
    • the 15 spellings in the specification (done in text), examples/sqlite, and crates/nikaia-std/src/tools/*.nika;
    • drop examples/sqlite’s RUSTFLAGS note, and add [library.sqlite3] pkg-config = "sqlite3" to its project;
    • the end-to-end test in foreign_pointers.rs runs without -l sqlite3.
  5. Regenerate tests/specification/EXPECTED.txt and COMPILES.txt. The four extern blocks of Part III 15.1 compile again.

Generated by Claude Code

keywan-ghadami, 2026-10-06

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

keywan-ghadami, 2026-10-06

Step 1, and steps 4 and 5 in part: 0.0.563 (2cf4d8d)

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

keywan-ghadami, 2026-10-06

Steps 2-6 built: 0.0.567 (c95a7c1)

What is left: step 2’s carry the libraries in the package’s ledger.


Generated by Claude Code

keywan-ghadami, 2026-10-09

registry is crates.io , so you can use cargo

Back to Open Issues