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.
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.