Skip to content

Instantly share code, notes, and snippets.

@virtualritz
Last active April 22, 2022 17:38
Show Gist options
  • Save virtualritz/2fe2d61ee2bae6d211acbef55aac41fe to your computer and use it in GitHub Desktop.
Save virtualritz/2fe2d61ee2bae6d211acbef55aac41fe to your computer and use it in GitHub Desktop.
Making Rust FFI Wrapper Crates Build on Docs.rs

TLDR;

Check for env::var("DOCS_RS") in your build.rs and omit linking if it is set.

Where to find this: read the complete About section on docs.rs.

Long Version

If you’ve written wrapper crates, e.g. using bindgen, you may have ran into this. You have:

  1. A low level, possibly auto-generated wrapper that sits directly atop the FFI of some C/C++ lib.
  2. A nifty, oxidized, safe, high level wrapper you wrote. Including excellent documentation of course.

Now you want to publish on crates.io. And certainly, you don’t want people to have to build the documentation for your high level wrapper locally. They should just go to docs.rs/libfoo.

The issue is that the high level wrapper will not build without the low-level wrapper and the low level wrapper, docs.rs/libfoo-sys, won’t build if the lib it wraps can’t be linked against. And having that C/C++ lib around involves building it and possibly a plethora of dependencies it has beforehand. At least in the common case, it seems.

One option is to include the full FFI dependencies in the low level crate and use cc, cmake or the like from within build.rs to build the required artifacts. This is often impractical. Crate sizes are limited and the amount of work involved making this work in the first place can be excessive.

However, docs.rs does not actually run any Rust code in your crate when building the documentation. I.e. it does not run tests or the like. So it doesn’t need to link at all.

Which means we do not actually need to have the C/C++ lib artifact(s) around. All we need are the files defining the FFI for Rust.

If you use bindgen these are the C/C++ headers exporting the FFI. And that’s it. All we need is to determine if we’re building on docs.rs.

And luckily this is made easiy because the DOCS_RS environment variable is defined if we are.

So your build.rs you just do this:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    ...
    
    // Regular code path we take when we're not on docs.rs.
    if env::var("DOCS_RS").is_err() {
        ...  
        println!("cargo:rustc-link-search={}", libfoo.display());
        // Link to libfoo.
        println!("cargo:rustc-link-lib=foo");
        
    // Code path we take when on docs.rs.
    } else {
        // Do not link and maybe do something else.
    }
     
    ...
    
    // Run e.g. bindgen or the like to generate the wrapper.
    ...
}

Bonus: Faster Docs.rs Builds

Condition: your crate docs look the same on all hosts.

This is nice to do in general as we want to use less electrity, if possible.

[package.metadata.docs.rs]
# This sets the default target to `x86_64-unknown-linux-gnu`
# and only builds that target
targets = ["x86_64-unknown-linux-gnu"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment