Система сборки bazel, изначально являвшаяся внутренним инструментом сборки Google, представляет собой проект с открытым исходным кодом, ориентированный на быструю, надежную и воспроизводимую разработку программного обеспечения для широкого спектра языков программирования и платформ.
Типичный проект Bazel представляет собой дерево каталогов с файлом с именем WORKSPACE
в корне дерева, который определяет внешние зависимости, и одним или несколькими файлами с именем BUILD
(или BUILD.bazel
), которые содержат объявления целей сборки. Больше информации и примеров можно найти здесь.
Недавно для проекта мне понадобился способ выбора между несколькими различными вариантами сборки на основе выбранного пользователем значения. Bazel поддерживает это с помощью пользовательских настроек сборки. Чтобы немного упростить ситуацию, мы будем использовать библиотеку bazel_skylib
, которая содержит некоторые правила для определения параметров сборки. Нам нужно объявить это в нашем файле WORKSPACE
:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "bazel_skylib", urls = [ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.2.0/bazel-skylib-1.2.0.tar.gz", "https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.0/bazel-skylib-1.2.0.tar.gz", ], )
При этом используется правило http_archive
из встроенной библиотеки bazel_tools
, которую мы используем для импорта репозитория bazel_skylib
.
Теперь мы можем добавить настройку сборки в наш проект в файле BUILD
:
load("@bazel_skylib//rules:common_settings.bzl", "string_flag") string_flag( name = "foo", build_setting_default = "42", )
В моем проекте мне нужен был способ использовать значение этого параметра сборки в некотором коде C++. Здесь все начинает становиться немного сложнее. Во-первых, добавим код программы:
// test.cpp #include <iostream> using namespace std; int main() { cout << FLAG << endl; return 0; }
В настоящее время это не скомпилируется, если мы не добавим что-то в командную строку компилятора C, чтобы определить значение для FLAG
. Мы могли бы сделать это в нашем файле BUILD
, используя атрибут defines
правила cc_binary
:
cc_binary( name = "test", srcs = ["test.cpp"], defines = ["FLAG=99"], )
Теперь нам нужно выяснить, как связать нашу настройку сборки со значением FLAG
в нашем коде. Для этого мы собираемся определить пользовательское правило, используя язык bazel Starlark. Добавляем новый файл defs.bzl
, содержащий реализацию правила:
# defs.bzl load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") def _define_from_flag_impl(ctx): return CcInfo( compilation_context = cc_common.create_compilation_context( defines = depset([ "FLAG=\"{}\"".format( ctx.attr.value[BuildSettingInfo].value, ), ]), ), ) define_from_flag = rule( implementation = _define_from_flag_impl, attrs = { "value": attr.label(), }, )
Разбивая вещи, мы определяем новое правило с именем define_from_flag
, которое имеет единственный атрибут с именем value
, который получает метку. Этот атрибут будет использоваться с меткой, ссылающейся на нашу настройку сборки. Для дальнейшего чтения взгляните на Учебник по правилам, в котором эта тема рассматривается более подробно.
В Starlark метки содержат набор поставщиков, которые описывают детали объекта с этой меткой. Для правила string_flag
для bazel_skylib
это включает BuildSettingInfo
, который содержит значение флага — мы извлекаем его из метки и используем для форматирования параметра определения для FLAG
. Последняя часть оборачивает это определение в поставщика CcInfo
, который ведет себя как пустой cc_library
, содержащий наше значение определения.
Теперь мы можем подключить это в нашем файле BUILD
:
load("@bazel_skylib//rules:common_settings.bzl", "string_flag") load("defs.bzl", "define_from_flag") cc_binary( name = "test", srcs = ["test.cpp"], deps = [":flag"], ) define_from_flag( name = "flag", value = ":foo", ) string_flag( name = "foo", build_setting_default = "42", )
Это добавляет экземпляр define_from_flag
в качестве зависимости от нашего cc_binary
. Поскольку определения распространяются от библиотек к целям в зависимости от этих библиотек, это автоматически добавит -DFLAG=”42"
в командную строку компилятора.
Наконец, нам нужно выяснить, как изменить значение параметра сборки. К счастью, это легко — значения флагов можно изменить с помощью командной строки bazel:
$ bazel run:test --//foo=hello ... hello
Мы можем сделать это немного более удобным для пользователя, определив псевдоним в .bazelrc
:
# .bazelrc build --flag_alias=foo=//:foo
Весь код этого примера находится на гитхабе здесь.