When adding or maintaining Envoy binary, library and test targets, it's
necessary to write or modify Bazel BUILD files. In general, each directory has
a BUILD file covering the source files contained immediately in the directory.
Some guidelines for defining new targets using the custom Envoy build rules are provided below. The Bazel BUILD Encyclopedia provides further details regarding the underlying rules.
The BUILD file style
guide is the
canonical style reference. The
buildifier tool automatically
enforces these guidelines. In addition, within the BUILD file, targets should
be sorted alphabetically by their name attribute.
All modules that make up the Envoy binary are statically linked at compile time.
Many of the modules within Envoy have a pure virtual interface living in
envoy, implementation sources in
source, mocks in test/mocks and
unit/integration tests in test. The relevant BUILD files will
require updating or to be added in these locations as you extend Envoy.
As an example, consider adding the following interface in envoy/foo/bar.h:
#pragma once
#include "envoy/buffer/buffer.h"
#include "envoy/foo/baz.h"
class Bar {
public:
virtual ~Bar() = default;
virtual void someThing() PURE;
...This would require the addition to envoy/foo/BUILD of the following target:
envoy_cc_library(
name = "bar_interface",
hdrs = ["bar.h"],
deps = [
":baz_interface",
"//envoy/buffer:buffer_interface",
],
)This declares a new target bar_interface, where the convention is that pure
virtual interfaces have their targets suffixed with _interface. The header
bar.h is exported to other targets that depend on
//envoy/foo:bar_interface. The interface target itself depends on
baz_interface (in the same directory, hence the relative Bazel label) and
buffer_interface.
In general, any header included via #include in a file belonging to the union
of the hdrs and srcs lists for a Bazel target X should appear directly in
the exported hdrs list for some target Y listed in the deps of X.
Continuing the above example, the implementation of Bar might take place in
source/common/foo/bar_impl.h, e.g.
#pragma once
#include "envoy/foo/bar.h"
class BarImpl : public Bar {
...and source/common/foo/bar_impl.cc:
#include "source/common/foo/bar_impl.h"
#include "source/common/buffer/buffer_impl.h"
#include "source/common/foo/bar_internal.h"
#include "source/common/foo/baz_impl.h"
...The corresponding target to be added to source/common/foo/BUILD would be:
envoy_cc_library(
name = "bar_lib",
srcs = [
"bar_impl.cc",
"bar_internal.h",
],
hdrs = ["bar_impl.h"],
deps = [
":baz_lib",
"//envoy/foo:bar_interface",
"//source/common/buffer:buffer_lib",
],
)By convention, Bazel targets for internal implementation libraries are suffixed
with _lib.
Similar to the above, a test mock target might be declared for test/mocks/foo/mocks.h in
test/mocks/foo/BUILD with:
envoy_cc_mock(
name = "foo_mocks",
srcs = ["mocks.cc"],
hdrs = ["mocks.h"],
deps = [
"//envoy/foo:bar_interface",
...
],
)Typically, mocks are provided for all interfaces in a directory in a single
mocks.{cc,h} and corresponding _mocks Bazel target. There are some
exceptions, such as test/mocks/upstream/BUILD,
where more granular mock targets are defined.
Unit tests for BarImpl would be written in test/common/foo/bar_impl_test.cc
and a target added to test/common/foo/BUILD:
envoy_cc_test(
name = "bar_impl_test",
srcs = ["bar_impl_test.cc"],
deps = [
"//test/mocks/buffer:buffer_mocks",
"//source/common/foo:bar_lib",
...
],
)New binary targets, for example tools that make use of some Envoy libraries, can be added
with the envoy_cc_binary rule, e.g. for a new tools/hello/world.cc that depends on
bar_lib, we might have in tools/hello/BUILD:
envoy_cc_binary(
name = "world",
srcs = ["world.cc"],
deps = [
"//source/common/foo:bar_lib",
],
)Filters are registered via static initializers at early runtime by modules in
source/extensions/filters. These require the alwayslink = 1 attribute to be set in the corresponding envoy_cc_library target to
ensure they are correctly linked. See
source/extensions/filters/http/BUILD for
examples.
Some tests depends on read-only data files. In general, these can be specified by adding a
data = ["some_file.csv", ...], attribute to the envoy_cc_test target, e.g.
envoy_cc_test(
name = "bar_impl_test",
srcs = ["bar_impl_test.cc"],
data = ["some_file.csv"],
deps = [
"//test/mocks/buffer:buffer_mocks",
"//source/common/foo:bar_lib",
...
],
)A glob
function is
available for simple pattern matching. Within a test, the read-only data dependencies
can be accessed via the
TestEnvironment::runfilesPath() method.
A writable path is provided for test temporary files by
TestEnvironment::temporaryDirectory().
Integration tests might rely on JSON files that require paths for writable
temporary files and paths for file-based Unix Domain Sockets to be specified in
the JSON. Jinja-style {{ test_tmpdir }} and {{ test_udsdir }} macros can be used as
placeholders, with the substituted JSON files made available in
TestEnvironment::temporaryDirectory() by
the envoy_cc_test_with_json rule, e.g.
envoy_cc_test_with_json(
name = "bar_integration_test",
srcs = ["bar_integration_test.cc"],
jsons = ["//test/config/integration:server.json"],
deps = [
"//source/server:server_lib",
...
],
)In general, the setup_cmds attribute can be used to declare a setup shell
script that executes in the test
environment
prior to the test, see bazel/envoy_build_system.bzl
for further details.