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_DIR
that could be preferred overCARGO_MANIFEST_DIR
to 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