Option to make all ctx.actions.run_shell actions in this run to have a hermetic PATH
- Dominant language
- Java
- Stars
- 25.8k
- Forks
- 4.6k
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 72
Description
### Description of the feature request:
Allow the user to create a hermetic environment for **ALL** ctx.actions.run_shell in the current build run.
### Which category does this issue belong to?
Core, Local Execution, Rules API
### What underlying problem are you trying to solve with this feature?
# Problem statement
Suppose we have this simple rule:
```
def _impl(ctx):
f = ctx.actions.declare_file(ctx.label.name)
ctx.actions.run_shell(
inputs = [],
outputs = [f],
command = """
echo $PATH >&2
touch {}
""".format(f.path)
)
return DefaultInfo(files = depset([f]))
foo = rule(
implementation = _impl,
)
```
Building a target of this rule with `--incompatible_strict_action_env` will yield the following result (Bazel 8.2.1, on Linux)
```
/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:.
```
This means any commands executed by `ctx.actions.run_shell()` will by default use non-hermetic tools in PATH. In the above example, `touch` is `/usr/bin/touch` from the host machine. This is the case even with `--incompatible_strict_action_env`; the flag limits the list in PATH, but it is still non-hermetic.
The developer of the `foo()` rule could change all `ctx.actions.run_shell()` in their rulesets to set up a `PATH` before executing the command. However, that does not include dependencies. For example, bazel_skylib's copy_file() rule is also not hermetic:
https://github.com/bazelbuild/bazel-skylib/blob/223e4e945801dfbc0bfa31d0900196f5bb54b0fc/rules/private/copy_file_private.bzl#L56
As a user of bazel_skylib, I can't control what `cp` program that `copy_file()` is using. (There are some workarounds; see below)
Another example: if a module dependency has a `genrule()` in it , it is also likely that the `genrule()` will be built with tools from the host.
# Workaround
Workaround 1: One could use third-party sandboxes (e.g. docker) to control which tools would be provided to Bazel actions.
Workaround 2: One could use `--action_env=PATH=...` to control the list of tools that any action uses. For example, one could:
1. Set up a `/tmp/contained_path` directory with symlinks to a list of tools, e.g. `cp` in the above example
2. Then `bazel build --action_env=PATH=/tmp/contained_path [targets]`
Both workarounds are not only hacky, non-bazellic, but also can't recognize tools that are built from sources. For example, if I don't have the prebuilt `cp` program, but a source file and a `cc_binary()` rule that builds it, then I'll need to:
1. `bazel build //:cp`
2. Create symlink `/tmp/contained_path/cp` -> `bazel-bin/cp`
3. Execute the real build for my targets with `--action_env=PATH=/tmp/contained_path`.
# Proposed feature
The user should be able to register a toolchain that contains a set of tools. These tools will be in PATH for **all** ctx.actions.run_shell(); all other PATH's in ctx.actions.run_shell() should be eliminated.
A more concrete example in pseudocode is below. I use `sh_toolchain()` here because I think it is natrual that this is combined with rules_shell, but it doesn't have to be; it can be a separate toolchain.
```
cc_binary(name = "cp", ...) # binaries built from sources
native_binary(name = "lz4", ...) # prebuilt binaries
sh_toolchain(
name = "my_hermetic_shell_toolchain",
path = ...,
tools = [":cp", ":lz4", ...],
)
toolchain(
name = "my_hermetic_shell_toolchain_toolchain",
toolchain = ":my_hermetic_shell_toolchain",
toolchain_type = "@rules_shell//shell:toolchain_type",
)
```
Then, with `my_hermetic_shell_toolchain_toolchain` in `register_toolchains()`, any `ctx.actions.run_shell()` should have a sanitized `PATH` that does not point to any tools in the host machine. `copy_file()` above will use `cp` built from this `cc_binary()` rule.
In the above example, since sh_toolchain() contains the path to a shell, ctx.actions.run_shell() should probably use the same shell.
# Backwards Compatibility
If this feature were to be built based on `sh_toolchain()` then it may need to be hidden behind an `--incompatible_run_shell_action_uses_sh_toolchain`, for example.
If this feature were to be built on top of a new toolchain type, then I don't think any flag would be necessary; Bazel can just fallback to its old behavior when the new toolchain type is not registered.
### Which operating system are you running Bazel on?
Linux
### What is the output of `bazel info release`?
release 8.2.1
### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.
_No response_
### What's the output of `git remote get-url origin; git rev-parse HEAD` ?
```text
```
### Have you found anything relevant by searching the web?
https://github.com/bazelbuild/bazel/issues/5265 ("shell toolchain, design: including common bintools") is related. The bug is closed, but comments like
https://github.com/bazelbuild/bazel/issues/5265#issuecomment-1381126111
https://github.com/bazelbuild/bazel/issues/5265#issuecomment-1384783692
are still relevant, and I think these comments would be addressed if `ctx.actions.run_shell` respects `@bazel_tools//tools/sh:toolchain_type` or `@rules_shell//shell:toolchain_type` or some toolchain.
### Any other information, logs, or outputs that you want to share?
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.