Cargo mutually exclusive features and ergonomics

I have a Rust cdylib, which contains bindings to a C ffi api (github link, if relevant). This API has several versions, all slightly incompatible with each other. Using cargo features like version1, version2, etc has worked well with some downfalls:

  1. Someone is able to specify more than one version (eg. they want their library to be compatible with version1 and version2, but in reality it's one or the other).
  2. There isn't a default feature, as one of the version features must be selected to compile sucessfully. This causes cargo package to fail as there is not a way to communicate "hey package up everything, but for verification let's use version1". Using cargo package --no-verify feels dirty.

I know that the Mutually exclusive features has already been raised in the cargo repo. Using environment variables in build.rs was presented, but how is the ergonomics of this approach? I feel like environment variables are treated as second class compared to features. Will end users of my library need a build script (where one wasn't needed previously)? Will I need to extract all my tiny #[cfg(feature = "version1")] sections into a separate files as (AFAIK) cfg doesn't support environment variables?

Is sticking with features my best bet, should I invest into a build system driven through environment variables, or is there a better solution?

Can your build script figure out which version of collectd exists at compile time? That's the approach rust-openssl takes.

No

No. Your build script can turn the environment variables into cfgs.

Potentially, it'd pretty much involve a set of heuristics like dpkg -s <packagename> | grep '^Version:'. Of course, those that build on version1 should be able to specify version2 server version as necessary.

Nice, I didn't know custom cfg's were possible (taken from openssl's build)

    match env::var("DEP_OPENSSL_VERSION") {
        Ok(ref v) if v == "101" => {
            println!("cargo:rustc-cfg=ossl101");
            println!("cargo:rustc-cfg=ossl10x");
        }
    // ...

Thanks for giving me a bunch to think about. I'll probably emulate openssl :smile:

btw, do you package openssl with a plain cargo package?

Yep

1 Like

Alternatively use major version of your package as the way to select the underlying API version. If you have links attribute in your Cargo.toml, Cargo will complain loudly if there are two versions in the same project.