¶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.
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