Я начинаю с проекта Bazel на Rust, без Cargo. Я пытаюсь настроить необязательную зависимость с функцией контроля функций.
У меня есть настройка crates_vendor
:
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"],
)
Теперь моя библиотека Rust определяется как:
rust_library(
name = "foo",
srcs = glob("**/*.rs"),
deps = [
"@crates_io//:serde",
"@crates_io//:serde_json",
],
)
Это @crates_io//:serde_json
, что я хочу показать.
Код Cargo.toml
для этого будет:
[dependencies]
serde_json = { version = "1.0", optional = true }
[features]
json = ["dep:serde_json"]
Однако, как я уже говорил, это проект, не связанный с Cargo.
Каким будет правильный способ добиться этого? Пытался покопаться в документации, но не могу понять.
В Bazel нельзя выбирать функции библиотеки в потребителе библиотеки, функции должны быть определены в самой библиотеке.
Итак, в вашем случае есть два варианта.
1. Определите отдельную библиотеку для каждого набора функций.
Вы можете просто определить библиотеку, используя те же источники, но с разными зависимостями.
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",
],
)
Обратите внимание, что вам нужно пройти crate_name
, если имя цели Bazel не совпадает с именем ящика.
Также обратите внимание, что вам, возможно, придется установить свои функции и в библиотеке, поскольку rustc
также должен знать о включенных функциях.
Это простой и достаточно надежный способ работы с функциями.
2. Используйте настраиваемые атрибуты
Это позволит определить вашу библиотеку только один раз. И включать и выключать функции во время сборки.
Обратной стороной является то, что эти функции можно включить только глобально. При любом использовании библиотеки foo
будет использоваться версия с 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
)
Например. если у меня есть инструмент, зависящий от библиотеки foo
,
rust_binary(
name = "tool",
srcs = ["src/main.rs"],
deps = [
"//foo:foo",
],
)
В этом примере, чтобы включить функцию json
, вы можете использовать командную строку:
bazel run //tool:tool --//foo:use_json
Как выбрать?
Если вы хотите для разных последующих проектов использовать разные функции foo
, то лучше всего подойдет первый подход.
Если вы создаете программное обеспечение с глобально включенными некоторыми функциями (например, бесплатная версия с отключенными некоторыми функциями), второй подход может работать лучше.
Уже было что-то с двумя rust_library
, но crate_name
— очень приятное дополнение: мне не придется менять импорт в Rust, если я решу использовать другой набор функций. Тоже пытался поиграться с флагами, но не получилось. Очень хорошо. Оба варианта нравятся!
Возможно, стоит заметить, что второй подход более точно имитирует Cargo, который выполняет унификацию функций для определения единого унифицированного набора функций для каждого ящика в графе зависимостей; это, в свою очередь, является причиной того, почему функции должны быть только аддитивными, чтобы они не нарушали работу тех функций, которые, как вы не ожидали, будут включены по требованию в другом месте графика.