feat(pkg): phase 15 - a package can carry a native library

native "<target>" "<path>" in a package's package.ludic; the compiler records the libraries of
every package a program imports and writes them into the IR (; ludic-native:), so ludicc -o,
ludic build, ludic test and ludic bundle all link one list. macOS: an rpath to the package and to
Contents/Frameworks, where ludic bundle copies and signs each library and drops the build
machine's rpath. Windows: the import library, the .dll copied beside the exe (--natives-out for
the bundle). tools/native/lib.sh builds from a pinned, checksummed source with clang on both
machines; ludic.nativeecho is the worked example; the shim rules are in packages/README.md.
Linked at build time rather than dlopen (docs/PACKAGES.md says why). Reseeded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-26 23:35:45 +03:00
parent 5df0747642
commit 5d3a799e33
25 changed files with 54474 additions and 51707 deletions

View file

@ -235,6 +235,47 @@ clang -O2 game.ll $(ludic link-flags) -o game
not ship the build target, `ludic get` fails — build from source instead where the
package offers it.
## Native libraries - a source package that carries a C/C++ library (phase 15)
A source package may link a native library of its own - a physics engine, a navmesh builder, an
audio renderer - by naming it per target in its `package.ludic`:
```
native "macos-arm64" "lib/macos-arm64/libjoltc.dylib"
native "windows-x64" "lib/windows-x64/joltc.dll"
```
The targets are `macos-arm64`, `macos-x64`, `windows-x64` and `linux-x64`
(`$LUDIC_NATIVE_TARGET` names another). The package's Ludic reaches the library through
`extern function` declarations, which only a package (or the runtime) may call (L7).
**Linked at build time, not opened with `dlopen`.** When the compiler loads a file from a
package, it reads that package's manifest once and keeps the libraries for the build's target;
they are written into the IR as `; ludic-native: <path>` lines, and every link reads that one
list - `ludicc -o`, `ludic build`, `ludic test` and `ludic bundle`. A program that imports no
such package links nothing new. Linking was chosen over a Vulkan-style loader because a missing
or mismatched library then fails at start-up, with the loader's own message naming it, rather
than at the first call deep in a frame; because an `extern function` is all the binding needs (no
generated thunk table per library); and because nothing here is optional the way Vulkan is - a
game that imports the physics package cannot run without it.
- **macOS**: the dylib's install name is `@rpath/lib<name>.dylib`. A build gets an rpath to the
package's `lib/<target>/` (so `ludic run` and `ludic test` work in place) and one to
`@executable_path/../Frameworks`. `ludic bundle` copies each library into
`Contents/Frameworks` - never `Contents/MacOS`, which holds mach-o executables only (a Velopack
update strips a detached signature) - removes the build machine's rpath from the executable,
and signs every library before the app.
- **Windows**: the `.dll` is linked through its import library, `<name>.lib` beside it; `ludicc`
copies the `.dll` beside the executable it writes, and `ludic bundle` beside the game's `.exe`.
**Built here from a pinned source.** A package's `native/build.sh` fetches the upstream tag,
checks its SHA-256, compiles the library and the package's shim with clang, and writes
`lib/<target>/` (committed through Git LFS). `tools/native/lib.sh` is what every such script
shares - `native_fetch`, `native_link`, the target and compiler - and it runs unchanged in Git
Bash on Windows with the LLVM installer's clang. The shim's rules (what may cross, how a
callback comes back) are in [packages/README.md](../packages/README.md#native-libraries);
`ludic.nativeecho` is the worked example.
## Offline / hermetic builds
`ludic vendor` copies the resolved packages out of the store into `./vendor`. Build