blob: 25d7c135a05a4cbb7d0040dbe0ab73e6e5086b4e [file] [log] [blame] [view]
# Ensuring Hermeticity
[TOC]
Hermetic builds are a key goal of Kleaf. Hermeticity means that all tools,
toolchain, inputs, etc. come from the source tree, not outside of the source
tree from the host machine.
All rules provided by Kleaf are as hermetic as possible (see
[Known violations](#known-violations)). However, this does not guarantee
hermeticity unless you also set up the build targets properly.
Below are some tips to ensure hermeticity for your builds.
## Enable --incompatible_hermetic_actions
This is a [Bazel wrapper](../bazel.py) flag that does the following:
- Modifies `PATH` to an auto-generated directory that contains a
limited list of tools:
- [_ACTION_HERMETIC_TOOLS](../bazel.py)
- [DEFAULT_HOST_TOOLS](../impl/default_host_tools.scl)
- Sets `--action_env=PATH` so this `PATH` is used instead of the one
determined by Bazel (e.g. with
[--incompatible_strict_action_env](https://bazel.build/reference/command-line-reference#flag--incompatible_strict_action_env)).
When this flag is enabled, hermeticity is enforced on
actions agnostic to Bazel (e.g. those from bazel_skylib and
rules_python).
Even with the flag set, if your actions are built with Kleaf tooling, you are
encouraged to use the hermetic toolchain. See [Custom rules](#custom-rules).
## Use hermetic\_genrule
The command of the native
[`genrule`](https://bazel.build/reference/be/general#genrule)
can access the passthrough `PATH`, allowing the `genrule`
to use any tools from the host machine. See
[Genrule Environment](https://bazel.build/reference/be/general#genrule)
for details.
Kleaf provides the `hermetic_genrule` via
`//build/kernel:hermetic_tools.bzl` as a drop-in replacement for `genrule`.
The `hermetic_genrule` sets PATH to the registered hermetic toolchain.
Avoid using absolute paths (e.g. `/bin/ls`) in your `genrule`s or
`hermetic_genrule`s, since this will use tools and resources from your host
machine.
Example:
```python
load("//build/kernel/kleaf:hermetic_tools.bzl", "hermetic_genrule")
hermetic_genrule(
name = "generated_source",
srcs = ["in.template"],
outs = ["generated.c"],
# cat and grep is from hermetic toolchain
script = "cat $(location in.template) | grep x y > $@",
)
```
To make the change more transparent, you may use an alias in the `load`
statement:
```python
load("//build/kernel/kleaf:hermetic_tools.bzl", genrule = "hermetic_genrule")
genrule(
name = "generated_source",
...
)
```
### Accessing CC toolchain
Setting `use_cc_toolchain` to `True` in `hermetic_genrule` makes C/C++ tools
and binaries available. For example:
```python
hermetic_genrule(
name = "readelf_version",
outs = ["version.txt"],
# llvm-readelf comes from the resolved CC toolchain.
cmd = "llvm-readelf --version > $@",
use_cc_toolchain = True,
)
```
**NOTE**: This is recommended for very simple use cases, for complex ones,
prefer to use [custom rules](#custom-rules).
## Use hermetic\_exec and hermetic\_exec\_test
Kleaf provides the `hermetic_exec` and `hermetic_exec_test` via
`//build/kernel:hermetic_tools.bzl`.
Avoid using absolute paths (e.g. `/bin/ls`) in your
`hermetic_exec`s, or `hermetic_exec_test`s, since this will use tools and
resources from your host machine.
## sh\_* rules
If you use `sh_binary`, `sh_library`, `sh_test` etc. from Bazel, the shell
executable is defined by the shebangs (e.g. `#!/bin/bash`). If you want to
execute these binaries in an hermetic environment, please file a bug or send an
email to [kernel-team@android.com](mailto:kernel-team@android.com).
There are several other dependencies on `/bin/bash` and `/bin/sh` (see
[Known violations](#known-violations)). Besides them, avoid using other
shell executables in `sh_*` rules.
## Custom rules
If you have custom `rule()`s, make sure to use the hermetic toolchain.
- Add `hermetic_toolchain.type` to `toolchains` of `rule()`.
- Add `hermetic_tools = hermetic_toolchain.get(ctx)` to your rule
implementation. `hermetic_tools` is a struct with two fields:
`setup` and `deps`.
- Ensure the following to `ctx.actions.run_shell`:
- The `command` should start with `hermetic_tools.setup`
- The `tools` should include the depset `hermetic_tools.deps`.
If there are other `tools`, chain the `depset`s using the
[`transitive`](https://bazel.build/rules/lib/globals/bzl.html#depset)
argument.
If you are using `ctx.actions.run`, usually there are no actions needed, since
Bazel will execute that binary directly without instantiating a shell
environment.
Example:
```python
load("//build/kernel:hermetic_tools.bzl", "hermetic_toolchain")
def _rename_impl(ctx):
dst = ctx.actions.declare_file("{}/{}".format(ctx.attr.name, ctx.attr.dst))
# Retrieve the toolchain
hermetic_tools = hermetic_toolchain.get(ctx)
# Set up environment (PATH)
command = hermetic_tools.setup
command += """
cp -L {src} {dst}
""".format(
src = ctx.file.src.path,
dst = dst.path,
)
ctx.actions.run_shell(
inputs = [ctx.file.src],
outputs = [dst],
# Add hermetic tools to the dependencies of the action.
tools = hermetic_tools.deps,
command = command,
)
return DefaultInfo(files = depset([dst]))
rename = rule(
implementation = _rename_impl,
attrs = {
"src": attr.label(allow_single_file = True),
"dst": attr.string(),
},
# Declare the list of toolchains that the rule uses.
toolchains = [
hermetic_toolchain.type,
],
)
```
### Accessing CC toolchain
Accessing the CC tools is possible for custom rules too. The following shows an
example on how to access the `strip` tool from the resolved toolchain.
```python
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
load("//build/kernel/kleaf:hermetic_tools.bzl", "hermetic_toolchain")
def _strip_version_impl(ctx):
version = ctx.actions.declare_file("{}/strip_version.txt".format(ctx.attr.name))
# Retrieve the tools toolchain.
hermetic_tools = hermetic_toolchain.get(ctx)
# Set up environment (PATH)
command = hermetic_tools.setup
# Retrieve default resolved CC toolchain.
cc_toolchain = find_cpp_toolchain(ctx, mandatory = False)
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
requested_features = ctx.features,
)
# Customize this according to the tool needed.
strip_path = cc_common.get_tool_for_action(
feature_configuration = feature_configuration,
action_name = ACTION_NAMES.strip,
)
command += """
{strip} --version > {version}
""".format(
version = version.path,
strip = strip_path,
)
ctx.actions.run_shell(
tools = [cc_toolchain.all_files, hermetic_tools.deps],
outputs = [version],
command = command,
)
return DefaultInfo(files = depset([version]))
strip_version = rule(
implementation = _strip_version_impl,
attrs = {
"_cc_toolchain": attr.label(default = "//build/kernel/kleaf/impl:kernel_toolchains"),
},
toolchains = [hermetic_toolchain.type] + use_cpp_toolchain(mandatory = False),
fragments = ["cpp"],
)
```
For more action names mapping to tool names, refer to
`prebuilts/clang/host/linux-x86/kleaf/common.bzl`.
## Known violations
The hermetic toolchain provided by `//build/kernel:hermetic-tools`
still uses a few binaries from the host machine. For the up-to-date list,
see `host_tools` of the target. In particular, `bash` and `sh` are in the list
at the time of this writing.
For bootstraping, some scripts still uses `/bin/bash`. This
includes:
* `tools/bazel` that points to `build/kernel/kleaf/bazel.sh`
* `build/kernel/kleaf/workspace_status.sh`, which uses `git` from the
host machine.
* The script may also use `printf` etc. from the host machine if
`--nokleaf_localversion`. See `scripts/setlocalversion`.
`build/kernel/kleaf/bazel.sh` uses `readlink` from host for bootstrapping to
determine its own path.
All `ctx.actions.run_shell` uses a shell defined by Bazel, which is usually
`/bin/bash`.
When configuring a kernel via `tools/bazel run //path/to:foo_config`, the
script is not hermetic in order to use `ncurses` from the host machine
for `menuconfig`.
When running a `checkpatch()` target, the execution is not fully hermetic
in order to use `git` from the host machine.
The kernel build may also read from absolute paths outside of the source tree,
e.g. to draw randomness from `/dev/urandom` to create key pairs for signing.
Updating the ABI definition uses the host executables in order to use `git`.
If `--workaround_btrfs_b292212788` is set, `find` comes from the host machine.
[See internal bug b/292212788](http://b/292212788), or
[Bug 217681 - gen_kheaders.sh gets stuck in an infinite loop](https://bugzilla.kernel.org/show_bug.cgi?id=217681)
If [`--incompatible_hermetic_actions`](#enable---incompatible_hermetic_actions)
is not set:
- `copy_file()` uses `cp`
- `rules_python` uses `uname` during toolchain resolution
- Host `python3` is needed to run any `py_binary` (
[reference](https://github.com/bazelbuild/bazel/issues/19355))
- [workspace_status.sh](../workspace_status.sh) uses readlink.