| # Fuchsia Platform Bazel Build Support |
| |
| This directory contains support files for using Bazel with the Fuchsia platform |
| build. |
| |
| ## Goal |
| |
| The goal is to allow defining and building Bazel build targets in BUILD.bazel |
| files in the platform source tree. |
| |
| ## Requirements |
| |
| - Check out full Fuchsia platform sources with Jiri |
| - Build on Linux host |
| |
| ## Bazel workspace |
| |
| Bazel requires a workspace to run build commands. A Bazel workspace is |
| identified by a `MODULE.bazel` file. |
| |
| We create a synthesized Bazel workspace inside build output directory to execute |
| Bazel build commands. The Fuchsia platform checkout itself is intentionally |
| __NOT__ a Bazel workspace yet. |
| |
| ### Synthesized Bazel workspace layout |
| |
| The path to the synthesized Bazel workspace is determined by the content of |
| `//build/bazel/config/bazel_top_dir`, referred to as `${BAZEL_TOPDIR}` below. |
| More specifically: |
| |
| - The `${BAZEL_TOPDIR}` directory under the Ninja output directory is used |
| to store all files related to the Bazel part of the build. By default this is |
| `out/default/gen/build/bazel` from your Fuchsia source root. |
| |
| - The `${BAZEL_TOPDIR}/workspace` directory is used as the Bazel workspace to |
| invoke all bazel commands. |
| |
| - 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. It's a symlink to |
| `//build/bazel/wrapper.bazel.sh` |
| |
| ### Synthesized Bazel workspace generation |
| |
| - The above mentioned contents in `${BAZEL_TOPDIR}` are generated by running |
| `fx set` or `fx gen`. The workspace subdirectory has a specific layout that |
| mirrors the Fuchsia source tree with a few exceptions: |
| |
| - A top-level `BUILD.bazel` file, which is by default a 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 `MODULE.bazel` file, which are by default a symlink to |
| `//build/bazel/toplevel.MODULE.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` as 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](#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 in |
| `MODULE.bazel`, 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 `//build/bazel_sdk/bazel_rules_fuchsia`, since this allows |
| experimenting with C++ toolchain definitions, build variants and PIE-optimized |
| machine code generation without conflicts with sdk-integration development. |
| |
| ### Clang toolchain repository |
| |
| 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][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). |
| |
| ### Quick access to Bazel workspace and artifacts |
| |
| > IMPORTANT: This feature is *purely* for developer convenience during local |
| > development and *nothing* should depend on these links in our build rules |
| > or scripts. In particular, these directories never exist on infra builders. |
| |
| Several convenience symlinks in your Fuchsia source tree are created |
| by `fx set` to access the Bazel workspace, external repositories and |
| artifacts more easily. Namely: |
| |
| - `bazel-workspace` points to the Bazel workspace directory. |
| |
| - `bazel-bin` and `bazel-out` have the same role as with regular Bazel, but |
| will only point to directories corresponding to the *latest* Bazel build |
| invocation. Keep in mind that a single `fx build <target>` command might |
| end up invoking several different Bazel build commands. |
| |
| - `bazel-repos` points to the directory where all external repositories |
| are stored. This is mostly useful to the build team to inspect that |
| repository rules generated the correct output. |
| |
| These symlinks targets will be updated on each `fx set`, `fx gen`, `fx use` or |
| `fx change-build-dir` command. |
| |
| ### Bazel workspace no-SDK configuration |
| |
| The Bazel workspace provides by default a `@fuchsia_sdk` repository that |
| exposes Fuchsia SDK atoms from the source tree to the Bazel graph. However, |
| this workspace cannot be used, except for queries, unless the in-tree IDK |
| has been built with Ninja. Building the in-tree IDK is a lengthy operation that |
| can easily take several minutes on powerful workstations, and even longer on |
| less capable machines. |
| |
| It is possible to avoid this dependency, by not populating `@fuchsia_sdk` with |
| Ninja artifacts. This allows building host Bazel targets (e.g. host tools) |
| directly, just after `fx set` or `fx gen` is called. |
| |
| There are three ways to use this mode: |
| |
| - In GN `bazel_action()` target definitions by setting `bazel_config = "host"` |
| to indicate that the bazel targets are for the host and thus do not require |
| Fuchsia SDK dependencies at all. |
| |
| - When invoker `fx build` to build Bazel targets, specify `--host`, for example |
| `fx build --host @//build/tools/formatjson5`. |
| |
| - When invoking `fx bazel` directly, specify the host platform using |
| `--config=host`, for example |
| `fx bazel build --config=host //build/tools/formatjson5`. |
| |
| It is possible to safely switch between normal and no-sdk variants operations. |
| For example: |
| |
| ```shell |
| # Clean build directory, and set a new build configuration. |
| fx set minimal.x64 --with //build/bazel:tests |
| fx clean |
| |
| # Build and run a series of host tests directly from Bazel |
| # This does not invoke Ninja at all. |
| fx bazel test --config=host //build/bazel/host_tests/... |
| |
| # Build a host hello_world program with Bazel, invoked from |
| # a Ninja action. Because this target definition uses |
| # `bazel_config = "fuchsia_platform"`, it is fast, as it doesn't require |
| # building the IDK. |
| fx build //build/bazel/examples/hello_no_sdk |
| |
| # Build a similar program for Fuchsia, this will take several |
| # minutes because it requires building the IDK first, even though |
| # the program does not depend on any SDK atom. :-/ |
| fx build //build/bazel/examples/hello_world |
| ``` |
| |
| ### Why is Fuchsia source root not a Bazel workspace? |
| |
| The reasons why there are not top-level `MODULE.bazel` or `BUILD.bazel` |
| files provided in `${FUCHSIA_DIR}` are that: |
| |
| - Some top-level Bazel files (e.g. `.bazelrc`) contain values that always |
| must be auto-generated or adjusted on each Bazel invocation. This can |
| only be performed through wrapper scripts, not direct invocation of the |
| tool. |
| |
| - 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][Platforms] |
| feature, which impacts how toolchain selection happens inside a given |
| build configuration. See `//build/bazel/platforms/BUILD.bazel` for more |
| details. |
| |
| ## Building Bazel targets |
| |
| ### `bazel_build_action.gni` |
| |
| 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 later 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/examples/hello_test/BUILD.gn` 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. |
| |
| ### fx build |
| |
| For host targets defined in Bazel, you can build them directly with `fx build`, |
| for example `fx build --host @//build/tools/formatjson5`. Build outputs will be |
| available in `bazel-bin` and `bazel-out` |
| |
| ### fx bazel |
| |
| > IMPORTANT: `fx bazel` is for __debugging purposes only__, there is no |
| > guarantee that your Bazel workspace is setup properly to run the Bazel command |
| > you specified, for example it can be missing contents of the SDK, or outputs |
| > from Ninja that your Bazel command need to consume as inputs. |
| |
| 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. |
| |
| ## Ninja outputs as Bazel inputs |
| |
| GN target outputs (i.e. Ninja build artifacts) can be exposed to the Bazel |
| graph as inputs through `bazel_input_file()` and `bazel_input_directory()`, |
| which expose the outputs of other GN targets as filegroups in the special |
| `@gn_targets` external repository. |
| |
| A GN `bazel_action()` target must be defined to invoke Bazel, through a script, |
| and must list the `bazel_input_xxx()` in its dependencies to ensure that the |
| right Ninja outputs are exposed to the Bazel graph before the Bazel command runs. |
| |
| ### Ninja outputs in the `@gn_targets` repository |
| |
| #### Example |
| |
| Here's a simple example that uses the first scheme. It allows a Bazel target to |
| process the output of a GN action, and copy the result back to the Ninja build |
| directory through the `//src/lib:process_foo` GN target. |
| |
| On the GN side: |
| |
| ```py |
| # From //src/lib/BUILD.gn, evaluated in the default toolchain context: |
| |
| # An action that generates one or more output files. |
| action("foo") { |
| outputs = [ ... ] |
| } |
| |
| # A target that exposes the outputs of :foo as |
| # as the @gn_targets//src/lib:foo filegroup() |
| bazel_input_file("foo.bazel_input") { |
| generator = ":foo" |
| } |
| |
| # A target that invokes a Bazel target to process the |
| # foo outputs. The result is copied to $BUILD_DIR/obj/src/lib/foo.final |
| bazel_action("process_foo") { |
| command = "build" |
| deps = [ ":foo.bazel_input" ] |
| bazel_targets = [ ":foo_processor" ] |
| copy_outputs = { |
| bazel = "{{BAZEL_TARGET_OUT_DIR}}/foo.processed_by_bazel" |
| ninja = "foo.final" |
| } |
| } |
| ``` |
| |
| And on the Bazel side. |
| |
| ```py |
| # From //src/lib/BUILD.bazel |
| genrule( |
| name = "foo_processor", |
| srcs = [ "@gn_targets//src/lib:foo" ], |
| outs = [ "foo.processed_by_bazel" ], |
| command = "process.sh $< $@", |
| ) |
| ``` |
| |
| Notice the `//src/lib:foo.bazel_input` GN target definition. This does not build |
| anything, but records information about the outputs of the *generator* target |
| `//src/lib:foo`. |
| |
| The `//src/lib:process_foo` GN target depends on it, which will force the content |
| of the special `@gn_targets` repository to be *automatically updated before |
| invoking Bazel* to reflect the target's dependencies. |
| |
| In this case, because `//src/lib:process_foo` depends on `//src/lib:foo.bazel_input` |
| in the GN graph, the Bazel `@gn_targets//src/lib:foo` filegroup will be defined, |
| grouping the outputs of the GN `//src/lib:foo` target, built or updated by Ninja |
| before Bazel is invoked. |
| |
| Note that all Ninja outputs are accessed in Bazel through filegroups named from the GN |
| target label that exposes it. I.e. one cannot access files using a Ninja artifact |
| path such as `@gn_targets//obj/src/lib/foo.out`. |
| |
| #### `bazel_input_file()` filegroup naming |
| |
| The `bazel_input_file()` template requires a `generator` argument that must point |
| to a GN target that generates Ninja output files. |
| |
| The corresponding Bazel filegroup will be defined as `@gn_targets//{package_name}:{bazel_name}` |
| where: |
| |
| - `{bazel_name}` matches the name of the `generator` target itself, but this value |
| can be overridden using the optional `gn_targets_name` argument. |
| |
| - `{bazel_package}` matches the directory of the `generator` target itself, if it is |
| defined in the default GN toolchain context. Otherwise, it will include |
| a `toolchain_{toolchain_name}/` prefix as well. |
| |
| Note that the directory and names of the `bazel_input_file()` target itself does not impact |
| the content of `@gn_targets` at all. |
| |
| Hence the following examples: |
| |
| ```py |
| # From //src/lib/BUILD.gn |
| action("foo") { |
| ... |
| } |
| |
| # This creates `@gn_targets//src/lib:foo` |
| bazel_input_file("foo_outputs") { |
| generator = ":foo" |
| } |
| |
| # This creates `@gn_targets//src/lib:foo_alt`, by overriding the |
| # name explicitly. |
| bazel_input_file("foo_outputs_alt") { |
| generator = ":foo" |
| gn_targets_name = "foo_alt" |
| } |
| |
| if (current_toolchain == default_toolchain) { |
| # This creates @gn_targets//toolchain_host_x64/src/lib:foo |
| bazel_input_file("foo_host_outputs") { |
| generator = ":foo($host_toolchain)" |
| |
| # Because the generator is in a different toolchain context, |
| # its `outputs` argument must be provided (see next section). |
| outputs = [ get_label_info(generator, "target_out_dir") + "/foo.out" ] |
| } |
| } |
| ``` |
| |
| If one defines several `bazel_input_file()` with the same filegroup package and name, |
| Bazel will complain about multiply defined targets in the corresponding `BUILD.bazel` |
| file (which can be inspected, as comments indicate which exact GN target defined them). |
| |
| #### `bazel_input_file()` outputs selection |
| |
| By default, `bazel_input_file()` exposes all outputs of the generator target, |
| but this can only work if the following conditions apply: |
| |
| - The generator target is a GN `action()` (i.e. not a `group()` or an `executable()`). |
| - The target and the generator are defined in the same `BUILD.gn` file. |
| - The target and the generator are evaluated in the same toolchain context. |
| |
| Otherwise, specifying the list of outputs using the `outputs` argument is required. |
| GN will try to print a user-friendly error message explaining the situation. An |
| example of explicit outputs: |
| |
| ```py |
| # From //src/lib/BUILD.gn |
| action("foo") { |
| ... |
| } |
| |
| # From //src/lib/bar/BUILD.gn |
| |
| # Only expose the (first) output of //src/lib:foo as @gn_targets//src/lib:foo |
| # The fact that this target is defined under //src/lib/bar/ does not matter. |
| bazel_input_file("foo.bazel_input") { |
| generator = "//src/lib:foo" |
| _foo_output_dir = get_label_info(generator, "target_out_dir") |
| outputs = [ |
| "${_foo_output_dir}/output", |
| ] |
| } |
| ``` |
| |
| Note that this generates the filegroup as `@gn_targets//src/lib:foo`, and not |
| as `@gn_targets//src/lib/bar:foo`. |
| |
| This is why defining `bazel_input_file()` targets *in the same BUILD.gn file as the |
| generator target* is recommended, though not required. |
| |
| #### `bazel_input_directory()` |
| |
| It is an error to list a directory as an output in a `bazel_input_file()`, as this |
| will may result in incremental build errors. There is no easy way to detect this from |
| GN / Ninja, but one can use the `bazel_input_directory()` GN template to expose |
| directory Ninja outputs to Bazel. |
| |
| This creates a Bazel filegroup that uses a `glob()` statement, under the hood, to |
| ensure that all files from the directory are visible from the Bazel sandbox / command |
| execution environment, and carry dependency information properly across the GN / Bazel |
| graph boundaries. |
| |
| ### `bazel_input_file()` and `bazel_input_directory()` license information. |
| |
| Each `bazel_input_xxx()` target also generates an SPDX JSON file containing licensing |
| information related to the Ninja outputs it covers. This information is automatically |
| associated with each `@gn_targets` filegroup() target in the Bazel graph. |
| |
| In case accessing the licensing information is needed, it is available as a |
| `license()` target with the same name as the filegroup, plus a `.license` suffix |
| |
| For example, `@gn_targets//src:foo.license` is the `license()` target that the |
| `@gn_targets//src:foo` filegroup definition uses for its own `applicable_licenses` |
| value. And `@gn_targets//src/BUILD.gn` looks like: |
| |
| |
| ```py |
| license( |
| name = "foo.license", |
| package_name = "Legacy Ninja Build Outputs", |
| license_text = "_files/src/foo.bazel_inputs.license_spdx.json", |
| ) |
| |
| filegroup( |
| name = "foo", |
| applicable_licenses = [ ":foo.license" ], |
| srcs = [ ... ] |
| ) |
| ``` |
| |
| Note that due to how the Bazel licensing pipelines work, simply depending on |
| a `@gn_targets` filegroup() is not always sufficient to avoid the license checker |
| to complain about targets without a proper license. In particular defining a |
| `fuchsia_prebuilt_package()` target requires an explicit `applicable_licenses` |
| value pointing to the archive's license, as in: |
| |
| ```py |
| fuchsia_prebuilt_package( |
| name = "msd-arm-mali", |
| applicable_licenses = ["@gn_targets//src/graphics/drivers/msd-arm-mali:msd-arm-mali.license"], |
| archive = "@gn_targets//src/graphics/drivers/msd-arm-mali:msd-arm-mali", |
| drivers = [ ... ] |
| ) |
| ``` |
| |
| The same is true for a number of other Bazel rules pointing to `@gn_targets` inputs, as in: |
| |
| ```py |
| fuchsia_platform_artifacts( |
| name = "platform_user", |
| applicable_licenses = [ "@gn_targets//bundles/assembly:user.bazel_inputs.license" ], |
| directory = "@gn_targets//bundles/assembly:user.bazel_inputs.directory", |
| files = "@gn_targets//bundles/assembly:user.bazel_inputs", |
| ) |
| ``` |
| |
| ## Configurations |
| |
| Bazel command-line configurations are defined in |
| `//build/bazel/templates/template.bazelrc`, which produces |
| `${BAZEL_TOPDIR}/.bazelrc`. Configurations are invoked as `--config=NAME`. |
| |
| ### Publish Build and Test Results |
| |
| 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 |
| |
| Remote building can speed up builds by 1) offloading work remotely and 2) |
| caching. |
| |
| - `--config=remote` enables remote build for many actions using RBE. |
| |
| ## Testing |
| |
| 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: |
| |
| 1) Invoke `build/bazel/scripts/prepare-fuchsia-checkout.py`, unless |
| the `--skip-prepare` option is used. |
| |
| 2) Invoke `fx clean` unless `--skip-clean` is used. |
| |
| 3) 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). |
| |
| ## Debugging tips |
| |
| ### Debugging build sandbox |
| |
| Most non-remote bazel build actions are launched in a sandbox, whose |
| content disappears after the build, even in case of failure. It is |
| possible to set `FUCHSIA_DEBUG_BAZEL_SANDBOX=1` in the environment |
| before invoking `fx build` to tell Bazel to preserve the sandboxes. |
| |
| Note that setting this variable makes Bazel much more chatty, and too |
| many stale sandboxes can become a problem, so this is best used when |
| encountering a sandboxing-related issue, which most of the time come |
| from inputs missing from the sandbox due to missing dependency. |
| |
| Also note that *this does not affect remote actions* (i.e. if you have |
| RBE enabled). It is however possible to force a specific Bazel target |
| to run in a local sandbox run by using adding the `no-remote` string |
| to its `tags` attribute, as in: |
| |
| ```gn |
| cc_library( |
| name = "bin", |
| srcs = [ ... ], |
| deps = [ ... ], |
| tags = [ "no-remote" ], |
| ) |
| ``` |
| |
| ### Debugging hermeticity issues in repository rules |
| |
| 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. |
| |
| [Platforms]: https://bazel.build/concepts/platforms-intro |