Help creating a -sys crate


#1

I’d like to create a -sys crate for libspatialindex. Ideally, I’d like to provide targets for *nix, macOS, and Windows. The library has a cmake-style build process, and it doesn’t have any exotic dependencies, as far as I can tell. I can build it locally, generate bindings using bindgen, and my tests are passing, so it certainly works at a basic level. Some questions:

  • Are there some clear examples of cross-platform build.rs scripts for libraries that use cmake?
  • What do I do in cases where the library already exists on the system? I’d prefer to be conservative here and not even check for it, always using the version built from source. Is this OK?
  • Is there a convention for an install location; is it OK to keep the built library within the crate, as opposed to putting it in e.g. /usr/lib?
  • The crates.io documentation says sys crates should not provide bindings, only declarations. Is it OK to autogenerate these using e.g. bindgen?

#2
  • The cmake crate can be used in a buildscript to handle compilation for you: https://crates.io/crates/cmake.
  • Approaches here vary. Unless you need a very specific version of the library, it’s generally preferred to link to the system-installed library when available. Some -sys crates have a Cargo feature to switch from that to building a vendored copy.
  • It can be just fine, but you should be careful of some of the edge cases. Bindgen creates bindings that are correct for the platform on which it was run, so if there is any platform-dependent compile time logic in the C headers, your bindings can be inaccurate on other platforms. The ctest crate can verify the correctness of your bindings against the C headers, which is incredibly useful: https://crates.io/crates/ctest.

#3

I’ve written a full guide here https://kornel.ski/rust-sys-crate


Generally the approach is to offer two options:

  • dynamic linking with an already-installed system-wide library
  • static linking with own build

You could allow the mode to be selected either via Cargo feature (if cfg!(feature="static")) or an env var.

You’re not supposed to modify anything on the system. Don’t write to /usr. Only write to $OUT_DIR.

For dynamic linking packages generally use the pkg-config crate, but it’s not Windows-compatible. For Windows there’s also vcpkg crate, but I haven’t used it. On Windows I support only static builds.

For static build you can use the cmake crate to launch it. Or just process::Command. If the library is simple (just needs a bunch of .c files compiled), you may be able to skip cmake entirely and use gcc-rs crate to compile the files.

For static builds I put a copy of the entire library in vendor/ directory in my own crate. I’ve tried downloading the source on the fly, but it’s a lot of hassle (extra dependencies for downloads, decompression, and needs extra care to handle errors and aborts).

For simple libraries with stable interface you usually can just hardcode bindgen’s output. It’s going to be fine if it uses primitive C types (int), opaque pointers (struct Foo *), fixed-size types (uint32_t), and a few types that Rust’s libc knows about.

Libraries that have unstable ABI (e.g. make breaking changes, have their interface configurable via C macros, embed system-specific structs in their own structs) will be problematic. Keep in mind that if you use pkg-config it may find an older or newer version of the library. If such library is a “moving target” then you may need to use bindgen in your build.rs, even though it’s a heavy dependency.