Experimental Bazel Support in Everest¶
Introduction¶
EVerest offers support for Bazel <https://bazel.build/>. With Bazel, you can efficiently build, test and deploy software projects of any size.
For EVerest developers, Bazel offers a couple advantages:
While developing features, that span multiple modules, Bazel can swiftly rebuild only the necessary parts of the project.
If you have already setup Bazel in your project, EVerest framework can be integrated with it.
This tutorial will guide you through the process of setting up and using Bazel in your Everest projects.
Getting Started¶
To install Bazel, it’s recommended to use bazelisk, which is a tool that downloads and runs the correct version of Bazel for your project. You can install bazelisk by following the instructions on the official GitHub repository <https://github.com/bazelbuild/bazelisk?tab=readme-ov-file#installation>.
Note
Bazelisk provides a bazel command, and the rest of this tutorial will refer to it as bazel.
C/C++ Compilers: At the moment, Bazel will use the default C/C++ compilers on your system. If it is not the desired compiler to build EVerest, you can set the environment variables CC and CXX to the desired compiler.
All other dependencies are fetched by Bazel as needed.
Using Bazel Commands¶
Once Bazel is configured, you can use various Bazel commands to build, test and run.
Most useful commands are:
bazel build //… - Build all targets in the project.
bazel test //… - Run all tests in the project.
Dependency Management¶
There are a few different ways of managing dependencies in EVerest.
Dependencies that CMake takes from the system (e.g. boost). These dependencies are configured in the third_party/bazel/repos.bzl file.
Dependencies that are described in the ./dependencies.yaml file. These dependencies are pulled automatically by Bazel with help of edm tool.
Rust dependencies are managed by cargo, and are described in the Cargo.toml file. Cargo dependencies are automatically picked up by Bazel.
Defining C++ EVerest Modules¶
Let’s assume, you have a module named Example with a single interface implementation named example. This module depends on the sigslot library. For a more realistic scenario, the module has two extra files utils.cpp and utils.hpp.
modules/
└── Example
├── BUILD.bazel
├── CMakeLists.txt
├── Example.cpp
├── Example.hpp
├── manifest.yaml
├── utils.cpp
├── utils.hpp
└── example/
├── exampleImpl.cpp
└── exampleImpl.hpp
The manifest.yaml file for the module looks like this:
description: Simple example module written in C++
provides:
example:
interface: example
description: This implements an example interface that uses multiple framework features
...
requires:
kvs:
interface: kvs
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Example Authors
To build this module with Bazel, you need to create a BUILD.bazel file in the module directory, next to the manifest.yaml file.
In the BUILD.bazel, use predefined macros to define the module:
load("//modules:module.bzl", "cc_everest_module")
cc_everest_module(
# Name of the module, must be the same as the directory name.
name = "Example",
deps = [
# List of libraries, that module depends on.
# In CMakeLists.txt these are typically added as `target_link_libraries`.
# The should be listed in the `third_party/bazel/repos.bzl` file.
# Note that header-only libraries should be added here as well.
"@sigslot//:sigslot",
],
impls = [
# List of implementations in the module.
# This should correspond to the list of keys in the
# `provides` section of the manifest.yaml file.
"example",
],
# List of additional source files of the module.
#
# Here you only have to list the files that are not autogenerated.
# The mandatory module files are added automatically.
srcs = [
"utils.cpp",
"utils.hpp",
],
# Alternatively, you can use `glob` function to list files.
# srcs = glob(
# [
# "*.cpp",
# "*.hpp",
# ],
# ),
)
Defining Rust EVerest Modules¶
To define a Rust module in EVerest, you need to create a BUILD.bazel file in the module directory. Generic rust_binary and rust_test are used at the moment.
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
load("@everest_core_crate_index//:defs.bzl", "all_crate_deps")
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
# cargo_build_script describes to Bazel how to run autogeneration of the code.
# This should be pretty-much the same for every module
cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
edition="2021",
build_script_env = {
# This is the path relative to the module directory.
"EVEREST_CORE_ROOT": "../..",
},
data = [
"manifest.yaml",
"@everest-core//interfaces",
"@everest-core//types",
],
deps = all_crate_deps(build = True),
)
# The module is described as a rust_binary at the moment.
rust_binary(
# Name of the module, must be the same as the directory name.
name = "RsIskraMeterBinary",
# List of source files of the module.
# In most cases this glob should be enough.
srcs = glob(["src/*.rs"]),
# Rust language edition, used in this module.
edition="2021",
# Bazel makes distinctions between dependencies needed on different
# stages of the build. This is the list of proc_macro dependencies.
# In most cases this is enough
proc_macro_deps = all_crate_deps(proc_macro = True),
visibility = ["//visibility:public"],
# List of "normal" dependencies.
# all_crate_deps will add all the dependencies from the Cargo.toml file.
# We need as well to add framework, bridge, and the result of the build_script.
# Typically this is enough.
deps = all_crate_deps(normal = True) + [
":build_script",
"@everest-framework//everestrs/everestrs:everestrs_sys",
"@everest-framework//everestrs/everestrs:everestrs_bridge",
],
)
Authors: Evgeny Petrov