This directory contains experimental support files for using Bazel with the Fuchsia platform build. Nothing here is final and may break at any time for now.
Have a full Fuchsia platform checkout from Jiri, and run the following on a Linux build machine (MacOS is not supported at the moment!)
Now that you‘ve been warned, here’s how this is supposed to work:
The BAZEL_TOPDIR
directory under the Ninja output directory is used to store all files related to the Bazel part of the build. Its default value is out/default/gen/build/bazel
.
The ${BAZEL_TOPDIR}/workspace
directory is used as the Bazel workspace for all bazel commands invoked from Ninja.
The ${BAZEL_TOPDIR}/output_base
directory is used to place all Bazel outputs (including external repositories).
The ${BAZEL_TOPDIR}/bazel
wrapper script is used to launch a known prebuilt Bazel binary with the right options to support this setup, and other requirements of the platform build.
All four are generated by running fx build bazel_workspace
(which ends up calling targets in //build/bazel/BUILD.gn
). The workspace content has a specific layout that mirrors the Fuchsia source tree with a few exceptions:
A top-level BUILD.bazel
symlink to //build/bazel/toplevel.BUILD.bazel
in the source tree. This is the file you should modify to add new top-level targets in the Bazel build graph.
A top-level WORKSPACE.bazel
generated from //build/bazel/templates/template.WORKSPACE.bazel
in the source tree. This is the file you should modify to add new external repositories to the Bazel project.
A top-level auto-generated .bazelrc
file to configure Bazel. Note that this does not support --config=fuchsia_x64
and --config=fuchsia_arm64
are per the Fuchsia Bazel SDK. Also note that the user's own bazel configuration file (e.g. $HOME/.bazelrc
) will always be ignored. See Configurations for more details.
Symlinks to all top-level entries in the Fuchsia source tree, except for out
. This way, any source or configuration file will appear at the same exact location during the Bazel and Ninja builds.
In other words, if you add a file at $FUCHSIA_DIR/src/foo/BUILD.bazel
, it will be visible and will define a Bazel package at //src/foo:
.
The out
directory is not symlinked intentionally to ensure that Ninja outputs are not visible by default. Instead, these are exposed to Bazel on a case-by-case basis using special bazel_input_xxx()
template calls in BUILD.gn
files. For more details, see the corresponding section below.
Other Bazel external repositories are generated through repository rules, for example the @prebuilt_clang
repository will provide C++ toolchain definitions that use the prebuilt Clang toolchain available from the Fuchsia checkout.
This is intentionally different from the @fuchsia_clang
repository generated by third_party/sdk-integration
, since this allows experimenting with C++ toolchain definitions, build variants and PIE-optimized machine code generation without conflicts with sdk-integration development.
The GN bazel_build_action()
template can be used to invoke a bazel build ...
command from GN, and copy its outputs to a GN-compatible location. See //build/bazel/bazel_build_action.gni
for details.
The //build/bazel/tests/build_action/
directory contains a working example, which invokes a Bazel build action from GN, then verifies the output.
For debugging only, the fx bazel
wrapper tool is provided to launch a Bazel command inside the Fuchsia workspace file. This will always update the Bazel workspace if needed.
For example, use fx bazel version
to print information about the version number, of fx bazel info workspace
to print the absolute path to the workspace.
And fx bazel build ...
or fx bazel query ...
commands will work as expected.
The reasons why there are not top-level WORKSPACE.bazel
or BUILD.bazel
files provided in $FUCHSIA_DIR
are that:
The content of WORKSPACE.bazel contain multiple values that depend on the host machine type, and soon on the actual Jiri manifest or git supermodule hierarchy, and thus must always be auto-generated.
Developers cannot call Bazel directly from the Fuchsia source tree (which otherwise would likely fail with very confusing error messages).
Bazel's traditional command line interface to configure Bazel is not compatible with the requirements of the Fuchsia platform build. In particular, the Fuchsia Bazel SDK samples use --config=fuchsia_arm64
to specify the target device architecture, but this will not work here.
Finally, Bazel support in the Fuchsia platform build is still very experimental, and is best considered an implementation detail, hidden from developer workflows.
Similarly, the reason why Bazel build artifacts are placed under ${BAZEL_TOPDIR}/output_base
, instead of using the standard location under the user's home directory (e.g. $HOME/.cache/bazel/
) are:
This allows fx clean
to properly remove previous build artifacts.
This increases the chance of using hard-links when copying Bazel build outputs to the Ninja output directory, since in many setups, $HOME
lives in a different partition / mount point than the Fuchsia checkout.
This prevents filling up the user-specific directory with hundreds of GiBs of build artifacts that can be hard to clean up properly (e.g. when deleting a Fuchsia checkout directory manually with rm -rf
).
In particular, a Bazel module extension is used to generate a repository named @prebuilt_clang
that provides C++ toolchain instances and configurations for the build.
The Fuchsia platform build also relies on the new Platforms feature, which impacts how toolchain selection happens inside a given build configuration. See //build/bazel/platforms/BUILD.bazel
for more details.
The @legacy_ninja_build_outputs
external repository is used by the Bazel workspace to expose Ninja build outputs as Bazel input files. This works in the following way:
Fuchsia platform BUILD.gn
files must use bazel_input_xxx()
templates (see //build/bazel/bazel_inputs.gni
for documentation) to expose Ninja output files as Bazel top-level filegroup()
targets in the @legacy_ninja_build_outputs
repository (note that this name is long to discourage its use out of specific requirements).
For example, the following GN target definition:
# An action that generates two files. action("generator") { ... outputs = [ "$target_gen_dir/foo", "$target_gen_dir/bar/zoo", ] } # A target that exposes these outputs files to Bazel # as @legacy_ninja_build_outputs//:generated_files. bazel_inputs_resource("bazel_inputs") { name = "generated_files" sources = get_target_outputs(":generated") outputs = [ "generated/{{source_file_part}}" ] }
Will end up generating the following Bazel target declaration that will appear in the auto-generated @legacy_ninja_build_outputs//BUILD.bazel
file:
# From //....../...:bazel_inputs filegroup( name = "generated_files", srcs = [ "generated/foo", "generated/zoo", ] )
Note that the bar
path segment has disappeared due to the way {{source_file_part}}
works in GN.
All bazel_input_xxx()
GN targets MUST be reachable from the special GN target //build/bazel:legacy_ninja_build_outputs
. More specifically from the transitive dependencies of its inputs_deps
list.
If not, these targets will not generate a filegroup()
in the generated repository BUILD.bazel
file, and the corresponding Ninja outputs will be invisible to the Bazel workspace.
The bazel_build_action()
template defines a GN action that invokes a Bazel build command (passing a list of Bazel target patterns). Some of these commands do not depend on Ninja outputs and can be run directly.
Those that depend on Ninja outputs should depend on the corresponding bazel_input_xxx()
target, and ensure they invoke a Bazel target that references them through the appropriate label (as in @legacy_ninja_build_outputs//:<name>
).
See //build/bazel/tests/build_action
for a concrete example of how this works.
It is possible to perform Bazel queries directly after setting up the Bazel workspace (i.e. after running fx build bazel_workspace
), without the need to generate all Ninja outputs before that. This can be handy to inspect changes during development.
Technically, the content of the legacy repository is mostly symlinks, which points to locations in the Ninja output path, the repository rule ensures that empty files or directories are created on demand if the link target paths do not exist yet (otherwise Bazel would complain).
Since Bazel queries are not concerned by the content of such files, only by their presence, they can return adequate results. The empty files will otherwise be over-written by Ninja build commands if needed.
The ‘testonly’ property is not preserved across the Ninja / Bazel boundary, but a non-testonly bazel_build_action()
cannot depend on a testonly bazel_input_xxx()
target, as per usual with GN.
The bazel_build_action()
template, defined in //build/bazel/bazel_build_action.gni
is used to create a GN action that will run a bazel build <targets>
command in the Bazel workspace.
If such a Bazel target depends on a Ninja-generated output file, this must be expressed by a dependency on a bazel_input_xxx()
target, as explained in the previous section,
After the Bazel build command completes, its outputs are copied from the non-deterministic location in the Bazel output base into a stable location under target_out_dir
or target_gen_dir
(as required by GN for all actions).
For a concrete example, see //build/bazel/tests/build_action/
that contains a working target that invokes a Bazel build command (that simply copies an input file into a Bazel output), then verify that it worked properly.
The Bazel build generates an external repository named @prebuilt_clang
that mirrors the content of the host Clang toolchain, and augments it with Bazel-specific files. These are needed to define C++ toolchain instances and configurations.
C++ Toolchain selection is performed using the new Bazel Platforms toolchain, which is very different to the traditional use of --crosstool_top
, which is why using this option will not work.
Note that this is distinct from sdk-integration's own @fuchsia_clang
external repository, which defines a C++ toolchain that generates Fuchsia binaries (while @prebuilt_clang
is used to generate host binaries and experiment with build variants and PIE-optimized executables, without conflicts).
Bazel command-line configurations are defined in //build/bazel/templates/template.bazelrc
, which produces ${BAZEL_TOPDIR}/.bazelrc
. Configurations are invoked as --config=NAME
.
Sharing build results can be helpful for triaging and reproducing issues. Use one of:
--config=sponge
streams build event data to the Sponge service.--config=resultstore
streams build event data to the ResultStore service.Remote building can speed up builds by 1) offloading work remotely and 2) caching.
--config=remote
enables remote build for many actions using RBE.--config=gcertauth
uses your LOAS credentials to authenticate for using the aforementioned build services.You can invoke build/bazel/scripts/test-all.py
to verify that everything works properly. This is used to verify regressions during development of Bazel support in the platform build, what is does is:
Invoke build/bazel/scripts/prepare-fuchsia-checkout.py
, unless the --skip-prepare
option is used.
Invoke fx clean
unless --skip-clean
is used.
Setup the Bazel workspace, then run a series of tests to verify that things work properly.
Always try to add new tests when introducing new features under //build/bazel/, and run this script when changing its implementation (for now this is all manual, but will likely be automated in CQ in the future).
To help debug hermeticity issues that happen within repository rules, the Bazel workspace is configured to save a log of repository-related events to a file located under $BAZEL_TOPDIR/logs/workspace-events.log
, this is a binary proto file (see https://bazel.build/remote/workspace) that can be converted to text automatically with //build/bazel/scripts/parse-workspace-event-log.py
which can be invoked directly from your Fuchsia directory.
The script will automatically find the log for the latest command and dump its content to text on stdout, or to a file if the --output=FILE
option is used.
The log files are rotated, up to 3 older revisions are stored in the $BAZEL_TOPDIR/logs
directory so you can compare them if possible.