Bazel — это инструмент для сборки и тестирования с открытым исходным кодом, разработанный Google. Он предназначен для быстрого, надежного и эффективного создания программного обеспечения на разных платформах и языках. Он поддерживает многие языки программирования, включая C++, Java, Python, и использует уникальную систему сборки, использующую граф зависимостей для сборки и тестирования программных проектов, что помогает гарантировать, что сборки являются воспроизводимыми, инкрементальными и надежными. оптимизирован для параллельного выполнения.
Для меня ключевым преимуществом здесь является воспроизводимость слова, поскольку Bazel можно использовать для автоматической компиляции и использования зависимостей вашего проекта без необходимости их предварительной установки/настройки на вашем компьютере.
В этой статье будут рассмотрены основы использования Bazel для сборки и тестирования проектов C++. Далее последуют другие статьи, объясняющие более сложные сценарии с внешними зависимостями, которые мы также предварительно скомпилируем в Bazel, что избавит от необходимости их предварительной установки в вашей ОС.
Установка Базеля
Первым шагом к началу работы с Bazel является его установка в вашей системе. Bazel поддерживает различные платформы, включая Windows, macOS и Linux. С этого момента я предполагаю, что вы работаете в Linux (или WSL).
Веб-сайт Bazel предоставляет инструкции по установке для нескольких систем. Здесь я буду использовать Bazelisk и npm, так как считаю их самым простым методом. Сначала установите npm через apt, а затем bazel с помощью npm:
sudo apt update && sudo apt install npm sudo npm install -g @bazel/bazelisk
Среда разработки
Чтобы использовать Bazel, вам нужно настроить среду разработки, которая включает два файла: WORKSPACE
и BUILD
, оба написаны на Starlark, специальном языке программирования, разработанном для Bazel, который очень похож на Python. В целях обучения наш репозиторий кодов будет выглядеть следующим образом, также доступен на GitHub:
bazel_tutorial/ cc/ my_lib/ my_lib.cpp my_lib.hpp main.cpp BUILD WORKSPACE
Исходный код С++
Давайте приступим к созданию некоторых необходимых файлов C++, которые мы будем использовать для тестирования Bazel. Мы напишем простой класс (my_lib.hpp
и my_lib.cpp
) и main.cpp
, который его использует.
//main.cpp #include <iostream> #include "cc/my_lib/my_lib.hpp" int main() { MyClass obj; obj.setValue(5); std::cout << "Value: " << obj.getValue() << std::endl; return 0; } //my_lib.hpp #ifndef MY_LIB_H #define MY_LIB_H class MyClass { public: MyClass(); void setValue(int val); int getValue(); private: int value; }; #endif //my_lib.cpp #include "cc/my_lib/my_lib.hpp" MyClass::MyClass() { value = 0; } void MyClass::setValue(int val) { value = val; } int MyClass::getValue() { return value; }
Базель файлы
Файл WORKSPACE
устанавливает среду и должен быть помещен в корень вашего проекта, среди прочего, он сообщает Bazel, где найти внешние зависимости и как с ними обращаться. Он также делает доступными правила сборки, своего рода API-интерфейсы компилятора, вам понадобятся некоторые правила для сборки C++, другие для Rust, еще некоторые для создания пакета DEB с вашей программой и т. д. Базовый файл WORKSPACE
выглядит следующим образом:
workspace(name = "main") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # Setup rules_foreign_cc (CMake integration) http_archive( name = "rules_foreign_cc", strip_prefix = "rules_foreign_cc-8d540605805fb69e24c6bf5dc885b0403d74746a", # 0.9.0 url = "https://github.com/bazelbuild/rules_foreign_cc/archive/8d540605805fb69e24c6bf5dc885b0403d74746a.tar.gz", ) load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies") rules_foreign_cc_dependencies()
Файлы BUILD
используются для указания того, как Bazel должен создавать и тестировать ваш проект. Они содержат информацию о целях (например, исполняемых файлах, библиотеках и тестах), которые вы хотите создать, а также о необходимых зависимостях и правилах. Для каждой цели в файле BUILD
указаны исходные файлы, параметры компилятора и любая другая важная информация, необходимая Bazel для сборки программы.
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") cc_library( name = "my_lib", srcs = ["cc/my_lib/my_lib.cpp"], hdrs = ["cc/my_lib/my_lib.hpp"], visibility = ["//visibility:public"], ) cc_binary( name = "main", srcs = ["cc/main.cpp"], deps = [ "//:my_lib", ], visibility = ["//visibility:public"], )
Сборка и тестирование
С приведенной выше настройкой (WORKSPACE
+ BUILD
+ Sources) вы можете собрать свой проект, выполнив следующую инструкцию в корневой папке:
bazel build //<relative-path-to-target>:<target>
<relative-path-to-target>
— это путь к файлу BUILD
, в котором определена ваша цель, относительно того, где находится файл WORKSPACE
. В нашем примере один файл BUILD
находится в той же папке, что и файл WORSPACE; поэтому <relative-path-to-target>
пусто.
<target>
— это имя цели (а не исходных файлов .h/.cpp), как определено в файле BUILD
. В настоящее время у нашего проекта есть две цели: main
и my_lib
.
Если мы хотим скомпилировать нашу цель main
, мы просто запускаем следующее:
bazel build //:main
Поскольку my_lib
является зависимостью от main
, он был автоматически создан и связан. Это сгенерирует несколько папок и, по сути, скомпилирует программу. Если все пойдет хорошо, вы можете запустить его со следующим:
bazel run //:main
Все файлы в рабочем состоянии доступны в этой папке GitHub.
Относительные пути
Теперь давайте внесем простое изменение в нашу структуру папок, чтобы продемонстрировать использование <relative-path-to-target>
. Теперь я помещаю файл BUILD
в папку cc
, где находятся мои исходники:
bazel_tutorial/ cc/ my_lib/ my_lib.cpp my_lib.hpp main.cpp BUILD WORKSPACE
Мы также должны обновить содержимое файла BUILD
, чтобы отразить новые пути:
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") cc_library( name = "my_lib", srcs = ["my_lib/my_lib.cpp"], hdrs = ["my_lib/my_lib.hpp"], visibility = ["//visibility:public"], ) cc_binary( name = "main", srcs = ["main.cpp"], deps = [ "//cc:my_lib", ], visibility = ["//visibility:public"], )
Обратите внимание, что я сделал два изменения:
- При определении библиотеки я удалил
cc
из путей, так как теперь файлы библиотеки находятся всего в одной папке от того места, где находится файлBUILD
. - Я добавил
cc
в раздел deps вcc_binary
, так как теперь файлBUILD
, определяющий цельmy_lib
, находится внутри папкиcc
относительно файлаWORKSPACE
.
Если мы выполним сборку bazel //:main, она завершится ошибкой, так как не сможет найти цель. Вот где появляется относительный путь, новые и правильные инструкции:
bazel build //cc:main bazel run //cc:main
Вы можете проверить файловую структуру в этой папке GitHub.
Построение зависимостей
Как мы видели, наш проект имеет 2 цели: main
и my_lib
, обе определены в нашем единственном файле BUILD
. Запустив bazel build //cc:main, мы автоматически создадим my_lib
, так как он зависит от main
. Однако, возможно, в некоторых сценариях нас интересует только создание зависимости или библиотеки. Мы также можем сделать это, вызвав его:
bazel build //cc:my_lib
И последнее замечание: со следующей опцией вы можете построить все в рабочей области, даже если цели не зависят друг от друга:
bazel build ...
Сборка с несколькими файлами BUILD
Сделаем последний поворот. Так как у нашей библиотеки есть своя папка, давайте зададим ей и свой файл BUILD
. Наша структура папок теперь будет выглядеть следующим образом:
bazel_tutorial/ cc/ my_lib/ my_lib.cpp my_lib.hpp BUILD main.cpp BUILD WORKSPACE
Новый файл BUILD
будет выглядеть следующим образом:
load("@rules_cc//cc:defs.bzl", "cc_library") cc_library( name = "my_lib", srcs = ["my_lib.cpp"], hdrs = ["my_lib.hpp"], visibility = ["//visibility:public"], )
Нам нужно только один раз определить цель my_lib
, поэтому давайте удалим ее из исходного файла BUILD
, который теперь выглядит следующим образом:
load("@rules_cc//cc:defs.bzl", "cc_binary") cc_binary( name = "main", srcs = ["main.cpp"], deps = [ "//cc/my_lib:my_lib", ], visibility = ["//visibility:public"], )
Опять же, обратите внимание, как я также добавил относительный путь к библиотеке (где находится ее файл BUILD
) в разделе «deps».
Теперь мы можем скомпилировать каждый из компонентов независимо или вместе со следующими инструкциями (с обновленными относительными путями):
bazel build //cc/my_lib:my_lib bazel build //cc:main
Хотя сейчас добавление дополнительных файлов BUILD
может показаться ненужным, оно станет полезным, когда ваши программы станут больше. При выполнении проекта с внесением изменений легче понять небольшие и автономные файлы BUILD
, чем просто пройтись по одному файлу BUILD
из 1000 строк.
Обновленные файлы в этой окончательной конфигурации также доступны на GitHub.
Заключение
Bazel — это мощный и гибкий инструмент, который хорошо подходит для проектов C++ любого размера. Выполнив шаги, описанные в этой статье, вы сможете приступить к работе с ним и приступить к созданию своих проектов C++. Независимо от того, работаете ли вы над небольшим проектом или обширным сложным программным обеспечением, Bazel может помочь вам в достижении ваших целей.
Это был всего лишь простой пример для закладки основ. Я намерен опираться на это, используя bazel для следующего:
- Внешние зависимости в проекте Bazel: protobuf (скомпилированный с помощью Bazel) и ArrayFire (скомпилированный с помощью CMake в Bazel).
- Пример использования расширенных внешних зависимостей: сборка OpenCV с поддержкой Contrib и VTK.
- Создавайте выпуски: пакеты ZIP, TAR и DEB.
- Расширение информации о процессе сборки и ценные инструкции по отладке.
- Скомпилируйте программы на Rust с помощью Bazel.
- Многоязычная поддержка: C++ с использованием библиотеки Rust.
- Создайте расширения базы данных (PostgreSQL) из Bazel.