dcreager.net

Build script links

2023-11-15

You have an “upstream” crate that wraps a C library and provides Rust bindings for it. You also have a “downstream” crate that wraps a different C library. The downstream C library depends on the upstream C library — for example, to compile the downstream C code, you need access to the header files of the upstream C code.

The problem: How do you tell the compiler where to find the upstream crate's header files when compiling the downstream library?

The answer: the package.links field and “build script metadata”. The upstream library needs to set package.links in its Cargo.toml. You can choose any value (which I'll call LINK_NAME), but convention is that it's the name of the library that the Rust crate is providing bindings for. In the upstream library's build.rs, you output additional cargo metadata:

cargo:VAR_NAME=VALUE

Then, in your downstream library, this custom metadata will be available in an environment variable that you can read: ‘DEP_${LINK_NAME}_${VAR_NAME}’.

For this compilation example, you output a carefully named include metadata variable, whose value is the actual location of the upstream header files. In your downstream build.rs, you read the ‘DEP_${LINK_NAME}_INCLUDE’ env var, and add that to the compiler's include search path.

The ‘links’ manifest key [The Cargo book]

An example

I encountered this situation specifically with the upstream lua crate, which provides Rust bindings for the Lua programming language, and the downstream ltreesitter crate, which provides Lua bindings to tree-sitter, which I wanted to make available to a Lua environment managed by Rust code.

‘lua’ crate

‘ltreesitter’ Lua package

My ‘ltreesitter’ fork with Rust bindings

I had to patch the lua crate to output the custom metadata, and then use the resulting env var in the ltreesitter build script.

Output the custom metadata upstream

Use the custom metadata downstream

..