Skip to content

Commit

Permalink
Keep tools up-to-date when run
Browse files Browse the repository at this point in the history
By default, tools invoke `bazel build` in a way that minimizes latency before being run to ensure that they are up-to-date.
The new `update_when_run` attribute can be used to tweak this behavior.
  • Loading branch information
fmeum committed Jun 10, 2024
1 parent a966556 commit f02c44f
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 3 deletions.
21 changes: 20 additions & 1 deletion bazel_env.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def _tool_impl(ctx):
substitutions = {
"{{bazel_env_label}}": str(ctx.label).removeprefix("@@").removesuffix("/bin/" + name),
"{{rlocation_path}}": rlocation_path,
"{{update_when_run}}": ctx.attr.update_when_run,
},
)

Expand Down Expand Up @@ -188,6 +189,9 @@ _tool = rule(
cfg = _flip_output_dir,
allow_files = True,
),
"update_when_run": attr.string(
values = ["auto", "yes", "no"],
),
"_launcher": attr.label(
allow_single_file = True,
cfg = _flip_output_dir,
Expand Down Expand Up @@ -339,7 +343,7 @@ _bazel_env_rule = rule(

_FORBIDDEN_TOOL_NAMES = ["direnv", "bazel", "bazelisk"]

def bazel_env(*, name, tools = {}, toolchains = {}, **kwargs):
def bazel_env(*, name, tools = {}, toolchains = {}, update_when_run = "auto", **kwargs):
# type: (string, dict[string, string | Label], dict[string, string | Label]) -> None
"""Makes Bazel-managed tools and toolchains available under stable paths.
Expand Down Expand Up @@ -369,6 +373,20 @@ def bazel_env(*, name, tools = {}, toolchains = {}, **kwargs):
basename of the toolchain directory in the `toolchains` directory. The directory is
a symlink to the repository root of the (single) repository containing the toolchain.
update_when_run: Whether to always update the tools via `bazel build` before running them.
This is a trade-off between performance (startup latency of a tool) and correctness.
The supported values are:
<ul>
<li>"auto" (default): Always try to update the tool, but skip the update with a warning if
it would result in excessive latency (e.g. because there is a concurrent Bazel
invocation).
<li>"yes": Always try to update the tool and fail with an error if it would result in
excessive latency.
<li>"no": Use the tool in the state of the last build of the `bazel_env` target. This is
the fastest option as it never invokes Bazel, but it may run the an old version of a
tool if the `bazel_env` target or the tool itself has changed since the last build.
</ul>
**kwargs: Additional arguments to pass to the main `bazel_env` target. It is usually not
necessary to provide any and the target should have private visibility.
"""
Expand Down Expand Up @@ -429,6 +447,7 @@ def bazel_env(*, name, tools = {}, toolchains = {}, **kwargs):
name = tool_target_name,
raw_tool = str(tool),
toolchain_targets = reversed_toolchains,
update_when_run = update_when_run,
visibility = ["//visibility:private"],
tags = ["manual"],
**tool_kwargs
Expand Down
3 changes: 2 additions & 1 deletion docs-gen/bazel_env.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
## bazel_env

<pre>
bazel_env(<a href="#bazel_env-name">name</a>, <a href="#bazel_env-tools">tools</a>, <a href="#bazel_env-toolchains">toolchains</a>, <a href="#bazel_env-kwargs">kwargs</a>)
bazel_env(<a href="#bazel_env-name">name</a>, <a href="#bazel_env-tools">tools</a>, <a href="#bazel_env-toolchains">toolchains</a>, <a href="#bazel_env-update_when_run">update_when_run</a>, <a href="#bazel_env-kwargs">kwargs</a>)
</pre>

Makes Bazel-managed tools and toolchains available under stable paths.
Expand All @@ -31,6 +31,7 @@ well as cleans up stale tools.
| <a id="bazel_env-name"></a>name | The name of the rule. | none |
| <a id="bazel_env-tools"></a>tools | A dictionary mapping tool names to their targets or paths. The name is used as the basename of the tool in the `bin` directory and will be available on `PATH`.<br><br>If a target is provided, the corresponding executable is staged in the `bin` directory together with its runfiles.<br><br>If a path is provided, Make variables provided by `toolchains` are expanded in it and all the files of referenced toolchains are staged as runfiles. | `{}` |
| <a id="bazel_env-toolchains"></a>toolchains | A dictionary mapping toolchain names to their targets. The name is used as the basename of the toolchain directory in the `toolchains` directory. The directory is a symlink to the repository root of the (single) repository containing the toolchain. | `{}` |
| <a id="bazel_env-update_when_run"></a>update_when_run | Whether to always update the tools via `bazel build` before running them. This is a trade-off between performance (startup latency of a tool) and correctness. The supported values are: <ul> <li>"auto" (default): Always try to update the tool, but skip the update with a warning if it would result in excessive latency (e.g. because there is a concurrent Bazel invocation). <li>"yes": Always try to update the tool and fail with an error if it would result in excessive latency. <li>"no": Use the tool in the state of the last build of the `bazel_env` target. This is the fastest option as it never invokes Bazel, but it may run the an old version of a tool if the `bazel_env` target or the tool itself has changed since the last build. </ul> | `"auto"` |
| <a id="bazel_env-kwargs"></a>kwargs | Additional arguments to pass to the main `bazel_env` target. It is usually not necessary to provide any and the target should have private visibility. | none |


1 change: 1 addition & 0 deletions examples/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ bazel_env(
"python": "$(PYTHON3)",
"python_tool": ":python_tool",
},
update_when_run = "yes",
)

alias(
Expand Down
59 changes: 58 additions & 1 deletion launcher.sh.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,74 @@ _bazel__get_workspace_path() {
echo "$workspace"
}

log_colored() {
if [[ -t 2 ]]; then
local -r colored="$1"
local -r normal="\e[m"
else
local -r colored=""
local -r normal=""
fi
printf "${colored}%s${normal}\n" "$2" >&2
}

error_color="\e[38;5;1m"
warning_color="\e[38;5;3m"

log_error() {
log_colored "$error_color" "ERROR[bazel_env.bzl]: $1"
}

log_warning() {
log_colored "$warning_color" "WARNING[bazel_env.bzl]: $1"
}

case "${BASH_SOURCE[0]}" in
/*) own_path="${BASH_SOURCE[0]}" ;;
*) own_path="$PWD/${BASH_SOURCE[0]}" ;;
esac
own_dir="$(dirname "$own_path")"
own_name="$(basename "$own_path")"
if ! grep -q -F "$own_name" "$own_dir/_all_tools.txt"; then
echo "ERROR: $own_name has been removed from bazel_env, run 'bazel run {{bazel_env_label}}' to remove it from PATH." >&2
log_error "'$own_name' has been removed from '{{bazel_env_label}}', run 'bazel run {{bazel_env_label}}' to remove it from PATH."
exit 1
fi

if [[ {{update_when_run}} != no && "${BAZEL_ENV_INTERNAL_EXEC:-False}" != True ]]; then
if [[ -t 2 ]]; then
color=yes
warning_prefix="\e[38;5;3m>\e[m "
else
color=no
warning_prefix="> "
fi
# Minimize latency (this tool may be run by another tool or even an IDE) by
# not waiting for concurrent Bazel commands and also avoid thrashing the
# analysis cache, which could silently slow down subsequent Bazel commands.
if ! bazel_output=$(\
"${BAZEL:-bazel}" \
--noblock_for_lock \
build \
--color=$color \
--noallow_analysis_cache_discard \
{{bazel_env_label}} 2>&1); then
msg="Failed to keep '$own_name' up-to-date with 'bazel build {{bazel_env_label}}':"
if [[ {{update_when_run}} == auto ]]; then
log_warning "$msg"
else
log_error "$msg"
fi
echo "$bazel_output" | while IFS= read -r line; do printf "$warning_prefix%s\n" "$line" >&2; done
if [[ {{update_when_run}} == yes ]]; then
# Use an abnormal exit code (SIGABRT) that can't be confused with a
# legitimate exit code of the wrapped tool.
exit 134
fi
fi
# Re-exec the script, which might have been replaced by Bazel.
BAZEL_ENV_INTERNAL_EXEC=True exec "$own_path" "$@"
fi

# Set up an environment similar to 'bazel run' to support tools designed to be
# run with it.
# Since tools may cd into BUILD_WORKSPACE_DIRECTORY, ensure that RUNFILES_DIR
Expand Down

0 comments on commit f02c44f

Please sign in to comment.