So at work I am rewriting a C component as an open source (as of yet not public) Rust component. It will be developed out of tree in a separate repository.
mycrate has the following directory structure:
.
├── Cargo.toml (workspace)
├── mycrate (Rust API)
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── mycrate-capi (C API)
├── build.rs
├── Cargo.toml
├── include
│ └── mycrate.h (generated by cbindgen via build.rs, checked into github)
└── src
└── lib.rs
We use the following in mycrate-capi/build.rs as suggested by cbindgen's documentation:
extern crate cbindgen;
use std::env;
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
println!("cargo:rerun-if-changed=src/lib.rs");
cbindgen::generate(&crate_dir).unwrap().write_to_file(
"include/mycrate.h",
);
}
I've set up mycrate-capi/Cargo.toml to output mycrate.a and mycrate.dylib by specifying the following:
# ...
[lib]
name = "mycrate"
crate-type = ["staticlib", "cdylib"]
So now I want to add this to my company's repository. I want to have a shared workspace for the repository, sharing a common top level target directory and Cargo.lock file. We might start adding proprietory Rust code as well, so more subcrates might be added in the future. But for now we just leave it blank. I'll add mycrate-capi as a depencency:
[workspace]
members = []
[dependencies]
mycrate-capi = { git = "ssh://git@git@github.com/mycompany/mycrate.git" }
Here is where we run into our first problem! Cargo does not see the dependencies section in a virtual crate. So we need to add a hacky top-level crate to allow this:
[package]
name = "myproject"
publish = false
[workspace]
members = []
[dependencies]
mycrate-capi = { git = "ssh://git@git@github.com/mycompany/mycrate.git" }
Next problem is that although the build C API is now built to target/<target>/deps/mycrate.a, the header file is nowhere to be seen! In fact it lives up in the global cargo cache, in ~/.cargo/git/checkouts/mycrate-*/*/mycrate-capi/include/mycrate.h. This is kind of concerning! Seems like mycrate-capi's build.rs is altering the global state of my cargo cache. I also have no good way of accessing that output from my project.
Any thoughts on what I could do instead? Some ideas:
- Develop the C API in-tree for now, just keeping the Rust code public until better workflows are figured out. This seems to be what Mozilla is doing for the URL parser.
- Add a shell script to just copy the source code in-tree - kinda ugly, but this is what Mozilla seems to do with the mp4 metadata parser
- Set an environment variable like
MYCRATE_INCLUDE_DIRthat could be preferred overCARGO_MANIFEST_DIRto allow me to output the headers in a custom location. In my project's build system I could then runMYCRATE_INCLUDE_DIR=./target/$RUST_TARGET/include rustup run $RUST_TOOLCHAIN cargo build --all $CARGO_FLAGS. This is a bit of a hack, and is not very nice from the perspective of having a general solution for the problem across the ecosystem, but perhaps could get us over the line in the interim.
EDIT:
For now I've updated my library crate to have the following:
extern crate cbindgen;
use std::env;
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let header_out_dir =
env::var("MYCRATE_HEADER_OUT_DIR").unwrap_or_else(|_| "include".to_owned());
println!("cargo:rerun-if-changed=src/lib.rs");
cbindgen::generate(&crate_dir).unwrap().write_to_file(
&(header_out_dir +
"/mycrate.h"),
);
}
Related: How to retrieve .h files from dependencies into top-level crate’s target/
cc. @aturon @alexcrichton