Search code examples
rustbazel

Optional dependencies in Rust Bazel project


I am starting out with a Bazel project in Rust, without Cargo. I am trying to set up a feature-gated, optional dependency.

I have a crates_vendor set up:

crates_vendor(
    name = "crates_io",
    cargo_lockfile = ":Cargo.Bazel.lock",
    mode = "remote",
    packages = {
        "tonic": crate.spec(version = "*"),
        "serde": crate.spec(version = "1.0"),
        "serde_json": crate.spec(version = "1.0"),
    },
    repository_name = "crates_io",
    tags = ["manual"],
)

Now my Rust library is defined as:

rust_library(
    name = "foo",
    srcs = glob("**/*.rs"),
    deps = [
        "@crates_io//:serde",
        "@crates_io//:serde_json",
    ],
)

It is @crates_io//:serde_json that I want to feature-gate.

The Cargo.toml code for that would be:

[dependencies]
serde_json = { version = "1.0", optional = true }

[features]
json = ["dep:serde_json"]

However, as I mentioned, this is a non-Cargo project.

What would be the correct way of achieving this? Tried diggint through the docs, but can't figure out.


Solution

  • In Bazel you can not select features of library in the consumer of the library, features should be defined in the library itself.

    So in your case you have two options.

    1. Define different library for each feature set.

    You can just define library using the same sources, but with different dependencies.

    rust_library(
        name = "foo",
        srcs = glob("**/*.rs"),
        deps = [
            "@crates_io//:serde",
        ],
    )
    
    rust_library(
        name = "foo_json",
        crate_name = "foo",
        crate_features = ["foo_json"],
        srcs = glob("**/*.rs"),
        deps = [
            "@crates_io//:serde",
            "@crates_io//:serde_json",
        ],
    )
    

    Note, that the you need to pass crate_name if Bazel target name is not the same as the crate name. Note as well, that you may have to set your features on the library as well, since rustc should know about enabled features as well.

    This is the straight forward and reasonably robust way to work with features.

    2. Use configurable attributes

    This will allow to have your library defined just once. And turn features on and off at build time. The downside of it is that the features can only be enabled globally. All usages of the foo library will either use the version with or without json.

    load("@rules_rust//rust:defs.bzl", "rust_library")
    load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
    
    # Here we define a boolean flag, that can later use on command line
    bool_flag(
        name = "use_json",
        build_setting_default = False,
    )
    
    # The flag from above turn on and off the named config setting
    config_setting(
        name = "foo_json",
        flag_values = {":use_json": "true"},
    )
    
    # based on the config setting we can select features
    # we will pass to the compiler...
    foo_features = select({
        ":foo_json": ["json"],
        "//conditions:default": [],
    })
    
    # .. and the dependncies, that we need.
    foo_optional_deps = select({
        ":foo_json": [
            # "@crates_io//:serde_json",
        ],
        "//conditions:default": [],
    })
    
    # After we selected features and dependencies, we
    # define the library using them.
    rust_library(
        name = "foo",
        srcs = ["src/lib.rs"],
        crate_features = foo_features,
        deps = [
        ] + foo_optional_deps
    )
    

    E.g. if I have a tool that depends on the foo library,

    rust_binary(
        name = "tool",
        srcs = ["src/main.rs"],
        deps = [
            "//foo:foo",
        ],
    )
    

    This this example to turn on the json feature you can use command line:

    bazel run //tool:tool --//foo:use_json
    

    How to choose?

    If you want for different downstream project use different features of foo then the fist approach is way to go.

    If you build software with some functionalities globally toggled (like free version, with some functionalities disabled), the second approach may work better.