Bazel workshop
Welcome!
Who are we?
Plan
A whirlwind tour of:
Expectations
We expect that you:
Outline
1.1�Intro and basic concepts
When to use Bazel?
When not to use Bazel?
Migration costs
Modeling the build
Build system recap
Build system recap
Source file
Executable
Compile action
main.c
main
gcc main.c -o main
Dockerfile
Docker image
docker build -t … .
Build system recap
Source file
Executable
(Compile action)
depends on
Source file
Executable
Compile action
main.c
main
gcc main.c -o main
Build system recap
main.h
main.c
main
gcc main.c -o main
#include “main.h”
main.h
Build system recap
main.c
main
gcc main.c -o main
main.h
#include “main.h”
GCC toolchain
Build system recap
Build system recap
Build system recap
⇒ Speed!
What breaks hermeticity + reproducibility?
Including any of this in the build output:
What happens when we break hermeticity?
First steps to using Bazel
Bazel concepts
# BUILD.bazel
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "main",
srcs = ["main.c"],
)
Target
Rule
To build: bazel build //:main
main.c
main
gcc main.c -o main
Bazel concepts
# BUILD.bazel
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "main",
srcs = ["main.c"],
)
To build: bazel build //:main
Label
Root of current repository
BUILD.bazel file at top level
main target
main.c
main
gcc main.c -o main
Bazel concepts
# WORKSPACE
workspace(name = "cookie_machine")
load("@bazel_tools//…/repo:http.bzl", "http_archive")
http_archive(
name = "rules_cc",
urls = ["https://…/rules_cc-0.0.10.tar.gz"],
sha256 = "65b…e49",
strip_prefix = "rules_cc-0.0.10",
)
# BUILD.bazel
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "main",
srcs = ["main.c"],
)
Workspace declaration
Official docs: https://bazel.build/concepts/build-ref
Bazel concepts
# WORKSPACE
workspace(name = "cookie_machine")
load("@bazel_tools//…/repo:http.bzl", "http_archive")
http_archive(
name = "rules_cc",
urls = ["https://…/rules_cc-0.0.10.tar.gz"],
sha256 = "65b…e49",
strip_prefix = "rules_cc-0.0.10",
)
# BUILD.bazel
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "main",
srcs = ["main.c"],
)
Official docs: https://bazel.build/rules/language
Bazel’s phases
Tip: For visualizing the complete target + actions graph that Bazel builds, check out Skyscope:�https://github.com/tweag/skyscope and https://www.tweag.io/blog/2023-05-04-announcing-skyscope/
Official docs: https://bazel.build/extending/concepts#evaluation-model
Getting help
Questions?
Try it yourself!
Exercise #1 The Cookie Machine
Exercise #1 List target dependencies
Exercise #1 Building with Bazel
Exercise #1 Subcommands
Exercise #1 Building and testing everything
Exercise #1 Reproducible and hermetic?
1.2�Bazel concepts deep dive
Labels
@cookie_machine//cpp-server:cpp-server
Loads
# BUILD.bazel / WORKSPACE / .bzl
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_binary")
Target visibility
# BUILD.bazel
cc_binary(
name = "c-client",
srcs = ["main.c",],
linkopts = ["-lcurl"],
visibility = ["//visibility:public"],
)
Target visibility: default visibility in a package
# BUILD.bazel
package(default_visibility = ["public"])
Rules
# BUILD.bazel
cc_library(
name = "crow",
hdrs = glob(
["crow/**/*.h", "crow/**/*.hpp"]),
strip_include_prefix = "crow/",
)
# cc_library.bzl
cc_library = rule(
implementation = _cc_library_impl,
attrs = {
"srcs": attr.label_list(
allow_files = True,
),
"hdrs": attr.label_list(
allow_files = True,
),
"deps": attr.label_list(
providers = [CcInfo],
),
"linkstatic": attr.bool(default = False),
"includes": attr.string_list(),
"strip_include_prefix": attr.string(),
"copts": attr.string_list(),
...
},
...,
provides = [CcInfo],
)
Providers
# my_provider.bzl
CcInfo = provider(
doc = "A provider for cc rules.",
fields = {
"headers": "Headers of this target and all transitive dependencies",
"includes": "Directories to pass via -I when compiling",
"linker_inputs": "A collection of files to pass to the linker.",
...
},
)
Rules: a simple implementation function
# cc_library.bzl
def _cc_library_impl(ctx):
args = ctx.actions.args()
lib_path = ctx.attr.name + ".so"
transitive_headers = ctx.attr.hdrs
for dep in ctx.attr.deps:
transitive_headers += dep[CcInfo].headers
args.add_all(["-I" + include for include
in ctx.attr.includes])
args.add_all(ctx.attr.srcs)
...
lib = ctx.actions.declare_file(lib_path)
ctx.actions.run(
inputs = ctx.attr.srcs + MORE STUFF,
outputs = [lib],
arguments = args,
executable = my_compiler,
)
return
[CcInfo(headers = transitive_headers,
linker_inputs = [lib],
...)]
Rules: declare_directory
Rule implementation and build actions
Rules: Q&A
Filegroups
# BUILD.bazel
filegroup(
name = "exported_testdata",
srcs = glob([
"testdata/*.dat",
"testdata/logs/**/*.log",
]),
)
cc_library(
name = "lib_b",
...,
data = [
"//my_package:exported_testdata",
],
visibility = ["//visibility:public"],
)
Filegroups: implementation + DefaultInfo
# filegroup.bzl
filegroup = rule(
implementation = _filegroup_impl,
attrs = {
"srcs": attr.label_list(
allow_files = True,
),
},
provides = [DefaultInfo],
)
def _filegroup_impl(ctx):
return [DefaultInfo(files = ctx.attr.srcs)]
Genrule
# BUILD.bazel
genrule(
name = "concat_all_files",
srcs = [
"//some:files", # a filegroup with multiple files in it ==> $(locations)
"//other:gen", # a genrule with a single output ==> $(location)
],
outs = ["concatenated.txt"],
cmd = "cat $(locations //some:files) $(location //other:gen) > $@",
)
Macros: templating around rule invocations
# my_cc_library.bzl
def my_cc_library(**attrs):
if "enable_foo" in attrs.keys():
attrs["copts"] = attrs.get("copts", default = [])
+ ["-std=c++14", "-Wstack-usage=10000"]
attrs.pop("enable_foo", None)
native.cc_library(**attrs)
Macros: templating around rule invocations
# my_app.bzl
def my_app(name, srcs, deps):
app_name = name + "_app"
native.cc_binary(
name = app_name,
srcs = srcs,
deps = deps,
)
native.genrule(
name = name + "_config",
srcs = [":" + app_name],
outs = [name + ".json"],
cmd = "$(location //:make_config) $(location {}) > $@".format(app_name),
tools = ["//:make_config"],
)
Repository rules
# http_archive.bzl
http_archive = repository_rule(
implementation = _impl,
attrs = {
"urls": attr.string_list(mandatory=True),
"sha256": attr.string(mandatory=True),
"strip_prefix": attr.string(),
}
)
Repository rules
# WORKSPACE
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "rules_cc",
urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.10/rules_cc-0.0.10.tar.gz"],
sha256 = "65b67b81c6da378f136cc7e7e14ee08d5b9375973427eceb8c773a4f69fa7e49",
strip_prefix = "rules_cc-0.0.10",
)
Repository rules
# http_archive.bzl
def _impl(repository_ctx):
url = repository_ctx.attr.urls[0]
sha256 = repository_ctx.attr.sha256
repository_ctx.download_and_extract(url, sha256=sha256)
There are a few ways to trigger a fetch:
$ bazel sync --only=rules_cc
$ bazel fetch @rules_cc//:*
To check what an external repository looks like:
$ ls -la $(bazel info output_base)/external/rules_cc
Repository rules: the repository_ctx object
Common repository rules
new_* rules typically allow for creation of BUILD files, although http_archive does not need a new_* version and in recent versions of Bazel git_repository and new_git_repository are essentially the same thing.
WORKSPACE files in external repositories have no significance.
Repository rules
Prefer http_archive to git_repository. The reasons are:
Questions?
Exercise #2: Write a macro
2.1�Toolchains, platforms, selects
Toolchains & platforms
Platforms
Toolchains
Toolchains & platforms
Platforms
Toolchains
Constraints
Constraints
# BUILD.bazel
constraint_setting(
name = "compiler",
default_constraint_value = ":gcc",
)
constraint_value(
name = "qcc",
constraint_setting = ":compiler",
)
constraint_value(
name = "gcc",
constraint_setting = ":compiler",
)
Platforms
# BUILD.bazel
platform(
name = "x86_64_linux",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
)
Platforms
# BUILD.bazel
platform(
name = "x86_64_linux",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
)
$ bazel build --platforms=//:x86_64_linux //...
Toolchains: toolchain type
# bar_tools/BUILD.bazel
# By convention, toolchain_type targets are named "toolchain_type" and
# distinguished by their package path. So the full path for this would be
# //bar_tools:toolchain_type.
toolchain_type(name = "toolchain_type")
Toolchain definition
# BUILD.bazel
toolchain(
name = "qnx_x86_64_toolchain",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:qnx",
"@platforms//cpu:x86_64",
"//platform:gcc",
],
toolchain = "@qnx_sdp//:x86_64-pc-nto-qnx7.1.0",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
)
Toolchain registration
# WORKSPACE
register_toolchains(
"//bazel/toolchains/qnx_compiler:qnx_x86_64_toolchain",
"//bazel/toolchains/qnx_compiler:qcc_qnx_x86_64_toolchain",
"//bazel/toolchains/qnx_compiler:qnx_aarch64_toolchain",
"//bazel/toolchains/qnx_compiler:qcc_qnx_aarch64_toolchain",
)
Toolchain resolution
How rule definitions specify toolchain type
# cc_library.bzl
cc_library = rule(
implementation = _cc_library_impl,
attrs = {
"srcs": attr.label_list(
allow_files = True,
),
"deps": attr.label_list(
providers = [CcInfo],
),
"copts": attr.string_list(),
...
},
...,
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
provides = [CcInfo],
)
def _cc_library_impl(ctx):
...
cc_toolchain = ctx.toolchains["@bazel_tools//tools/cpp:toolchain_type"]
...
Configurable attributes
# BUILD.bazel
load("@bazel_skylib//lib:selects.bzl", "selects")
selects.config_setting_group(
name = "x86_64_linux",
match_all = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
)
cc_library(
name = "clock_util",
srcs = select({
":x86_64_linux": [
"clock_util_linux.cpp",
],
"//conditions:default": [
"clock_util_other.cpp",
],
}) + [
"clock_util.hpp",
],
visibility = ["//visibility:public"],
)
Questions?
2.2�Sandbox,�Remote cache + execution
Sandboxing
The Bazel sandbox
How can Bazel help enforce hermeticity in build actions?
The Bazel sandbox
processwrapper-sandbox
Works on any POSIX system, does not require “advanced” features
linux-sandbox
Uses Linux Namespaces to isolate the build action from the underlying system:
darwin-sandbox
Uses Apple’s sandbox-exec to achieve roughly the same as linux-sandbox:
Windows?
Sorry, no official sandboxing support available
local (no sandbox)
Executes the action from the root of your workspace.
Useful for debugging hermeticity:
The Bazel sandbox
Remote cache
Bazel’s (local) action cache
actionKey
Hash function
digestKey
Hash function
bazel dump --action_cache
Bazel’s remote cache
What if we could share this cache with our colleagues (and with CI)?
Bazel’s remote cache
Setting up a remote cache
A “local” remote cache?
Remote execution
Remote execution
Remote execution
Setting up remote execution
Build without the Bytes
By default (before Bazel 7), for each build action that needs to be executed remotely:
This is expensive when all you want is the final build result, i.e. the output of the last build action
Enter “Build without the Bytes”:
2.3�Bazel rule sets
Bazel rule sets
Official docs: https://bazel.build/rules
Example: rules_go and rules_rust vs. rules_cc
Example: rules_foreign_cc
Example: rules_foreign_cc
Questions?
Exercise #3: Import and build a 3rd-party dependency
Exercise #3: Import and build a 3rd-party dependency
# WORKSPACE
http_archive(
name = "curl",
...
)
# BUILD
cmake(
name = "curl",
...
)
@curl
//c-client:main.c
//c-client:c-client
@curl//:curl
Exercise #3: Import and build a 3rd-party dependency
# WORKSPACE
http_archive(
name = "openssl",
...
)
# BUILD
configure_make(
name = "openssl",
...
)
@curl
//c-client:main.c
//c-client:c-client
@curl//:curl
@openssl//:openssl
Exercise #3: Import and build a 3rd-party dependency
Exercise #3: Import and build a 3rd-party dependency
Exercise #4: Hermetic toolchain
Exercises recap
3.1�Bazel and CI
Repository cache
When is a repository rule re-fetched?
In contrast to regular targets, repos are not necessarily re-fetched when something changes that would cause the repo to be different. This is because there are things that Bazel either cannot detect changes to or it would cause too much overhead on every build (for example, things that are fetched from the network). Therefore, repos are re-fetched only if one of the following things changes:
Bazel’s client/server architecture
Analysis cache
Overview of Bazel caches
In memory
On local disk
Remote
Skyframe
Analysis cache
Repository cache
Under output_base
Action cache
Output tree
Disk cache
Action cache
CAS
Remote cache
Action cache
CAS
Target determination
The relationship between Bazel and CI
3.2�Organizational points
Build system maintenance
Debugging and improving the build system issues requires:
Who maintains the build system?
Everybody!
Build system maintenance
The build system is never finished:
Build system maintenance
Build system champions!
3.3�Extra topics
.bazelrc: Storing common command line options
# .bazelrc
# Remote cache/execution configuration
common --remote_cache=
common --remote_executor=grpcs://my_RE_cluster_URL
# Don't let env vars like $PATH sneak into the build
build --incompatible_strict_action_env
# When running tests, only build necessary test targets
test --build_tests_only
.bazelrc: Configuring named sets of related options
# .bazelrc
# Enable with --config=leak_sanitizer
build:leak_sanitizer --copt="-fsanitize=leak"
build:leak_sanitizer --linkopt="-fsanitize=leak"
build:leak_sanitizer --copt="-U_FORTIFY_SOURCE"
# Enable with --config=address_sanitizer
build:address_sanitizer --copt="-fsanitize=address"
build:address_sanitizer --linkopt="-fsanitize=address"
build:address_sanitizer --copt="-U_FORTIFY_SOURCE"
.bazelrc: Spread across multiple files
# .bazelrc
# Import common
import %workspace%/remote.bazelrc
# Import user-specific options (if any)
try-import %workspace%/user.bazelrc
Bzlmod, aka. Bazel modules
Controlling provenance of 3rd-party dependencies
Downloader config that limits access only to Artifactory
allow mycompany.com
block *
rewrite (.*)(api.github.com/.*) https://mycompany.com/artifactory/$2
Exercise #5: Consume ASIO hermetically
3.4
Wrap up!
Resources
Thanks for listening to us!
We hope you have:
Questions?