Skip to content

Latest commit

 

History

History
224 lines (181 loc) · 6.72 KB

DEVELOPER.md

File metadata and controls

224 lines (181 loc) · 6.72 KB

Developer guide for writing Envoy Bazel rules

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.

Style guide

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.

Adding files to the Envoy build

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",
        ...
    ],
)

Binary targets

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",
    ],
)

Filter linking

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.

Tests with environment dependencies

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.