Bazel Cross Compilation
Basics of Bazel
- Target:
- The basic unit of the Bazel build process, representing an individual item that can be built.
- Label:
- A unique identifier used to reference a specific target.
- Rule:
- A definition that describes build actions.
- Package:
- A directory containing a BUILD file, acting as a logical unit that groups source files and build rules.
- Workspace:
- The top-level directory of a Bazel build, serving as the starting point of the project.
- Starlark:
- An embedded scripting language used to write Bazel’s build and configuration files.
- It has a syntax similar to Python but is optimized for build systems and designed to operate deterministically.
Getting Started with Cross Compilation in Bazel
Preparing the Cross Compiler
Preparing the appropriate toolchain for the target platform.
- What is a Toolchain?
- A toolchain refers to a collection of tools used to generate executable files for a target platform (e.g., CPU architecture or OS) that differs from the host platform where development is occurring.
Platform
In Bazel, a Platform defines the hardware and software requirements matching a specific build environment. By defining platforms, Bazel can manage build actions across multiple environments more efficiently and automatically select the appropriate toolchain for each platform.
A Platform in Bazel is defined using two basic elements:
- Constraint Setting (constraint_setting)
- Defines the type of constraints usable on a Platform. For example, OS type, CPU architecture, C++ ABI, etc.
- Constraint Value (constraint_value)
- Specifies the concrete values for each constraint setting. For example, values like linux, windows, or macos for the OS constraint.
constraint_value(
name = "bare_metal",
constraint_setting = "@platforms//os",
visibility = ["//visibility:public"],
)
platform(
name = "riscv64_bare_metal",
constraint_values = [
":bare_metal",
"@platforms//cpu:riscv64",
],
)
Toolchain
To perform cross-compilation in Bazel, you must configure a toolchain that matches the compilation target.
Since this depends on the CPU architecture or operating system, it is typically configured using the CROSSTOOL file or .bazelrc.
You can use Bazel’s default toolchain as is, or specify a new toolchain.
- Defining a toolchain while writing a new custom rule.
- Defining a toolchain by reusing existing rules.
Here, we describe how to define a toolchain by reusing existing rules.
Defining the Toolchain Config
A toolchain_config is a configuration file in Bazel that configures and sets up the behavior of a specific toolchain. This configuration handles details such as which flags the compiler and linker should use when performing specific actions, and what paths to search for executables.
load("@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "feature", "tool_path", ... )
toolchain_tools = ["gcc", "ld", "ar", "cpp", "gcov", "nm", "objdump", "strip"]
tool_paths = [tool_path(name = tool, path = "/usr/bin/riscv64-linux-gnu-{}".format(tool)) for tool in toolchain_tools]
What is a feature?
- In Bazel, a feature rule defines specific compiler/build configurations to control the flags or behavior needed during the build process.
# rv64_bare_metal_toolchain.bzl
load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
ASSEMBLE_ACTIONS = [
ACTION_NAMES.assemble,
ACTION_NAMES.preprocess_assemble,
]
COMPILE_ACTIONS = [
ACTION_NAMES.cpp_compile,
ACTION_NAMES.c_compile
]
ASSEMBLE_AND_COMPILE_ACTIONS = ASSEMBLE_ACTIONS + COMPILE_ACTIONS
arch_abi = feature(
name = "arch_and_abi",
enabled = True,
flag_sets = [
flag_set(
actions = ASSEMBLE_AND_COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = ["-march=rv64i", "-mabi=lp64"],
),
],
)
],
)
...
features = [arch_abi, link_static_no_stdlib, no_build_id]
...
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
toolchain_identifier = "local",
host_system_name = "local",
target_system_name = "local",
target_cpu = "riscv64",
target_libc = "unknown",
compiler = "clang",
abi_version = "unknown",
abi_libc_version = "unknown",
tool_paths = tool_paths,
cxx_builtin_include_directories = [
"/usr/riscv64-linux-gnu/include/",
"/usr/lib/gcc-cross/riscv64-linux-gnu/10/include/",
],
features = features,
)
What is CcToolchainConfigInfo?
- CcToolchainConfigInfo is a provider representing the toolchain configuration in Bazel’s C/C++ builds.
- This provider is used by Bazel to pass and manage detailed configuration information to the toolchain to satisfy C/C++ build requirements.
rv64_bare_metal_toolchain_config = rule(
implementation = _impl,
attrs = {},
provides = [CcToolchainConfigInfo],
)
load(":rv64_bare_metal_toolchain.bzl", "rv64_bare_metal_toolchain_config")
rv64_bare_metal_toolchain_config(
name = "rv64_bare_metal_config"
);
Defining the Toolchain
load(":rv64_bare_metal_toolchain.bzl", "rv64_bare_metal_toolchain_config")
rv64_bare_metal_toolchain_config(
name = "rv64_bare_metal_config"
);
cc_toolchain(
name = "rv64_bare_metal_toolchain",
toolchain_identifier = "local",
toolchain_config = ":rv64_bare_metal_config",
...
)
toolchain(
name = "riscv64_bare_metal_toolchain_from_linux_x86_64",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"//platform:bare_metal",
"@platforms//cpu:riscv64",
],
toolchain = ":rv64_bare_metal_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
)
Using the Toolchain
Now you can write standard cc_library and cc_binary targets!
BARE_METAL_RISCV64_CONSTRAINTS = [
"//platform:bare_metal",
"@platforms//cpu:riscv64",
]
cc_binary(
name = "program",
srcs = [
"program.c",
"boot.S",
],
additional_linker_inputs = [
"link_script.ld",
],
linkopts = ["-Wl,-T $(location :link_script.ld)"],
target_compatible_with = BARE_METAL_RISCV64_CONSTRAINTS,
)
Build Command
When running cross-compilation, use the --platforms flag to specify the target platform. For example:
bazel build -s --platforms=//platform:riscv64_bare_metal //program
Leave a comment