From 9e3d34c46dae90ce4d8cbac8d77c4af43595f394 Mon Sep 17 00:00:00 2001
From: Mike Lundy
Date: Fri, 14 Nov 2025 12:25:43 -0800
Subject: [PATCH] Add a 'tools' attribute to the rules, similar to genrule
It's common for shell scripts to use external binaries (things from coreutils being
a common example). This provides a way to declare those dependencies
explicitly, and ensure they are in the correct configuration to be run
as part of the script.
---
shell/private/sh_executable.bzl | 17 +++++++++++++++--
shell/private/sh_library.bzl | 13 ++++++++++++-
tests/bcr/BUILD | 21 +++++++++++++++++++++
tests/bcr/bin.sh | 2 ++
tests/bcr/greeting_tool.txt | 1 +
tests/bcr/is_exec_configuration.bzl | 12 ++++++++++++
tests/bcr/lib.sh | 5 +++++
tests/bcr/test.sh | 9 +++++++--
8 files changed, 75 insertions(+), 5 deletions(-)
create mode 100644 tests/bcr/greeting_tool.txt
create mode 100644 tests/bcr/is_exec_configuration.bzl
diff --git a/shell/private/sh_executable.bzl b/shell/private/sh_executable.bzl
index 105484e..e55f5a5 100644
--- a/shell/private/sh_executable.bzl
+++ b/shell/private/sh_executable.bzl
@@ -32,6 +32,8 @@ def _sh_executable_impl(ctx):
direct_files = [src]
transitive_files = []
runfiles = ctx.runfiles(collect_default = True)
+ for target in ctx.attr.tools:
+ runfiles = runfiles.merge(target[DefaultInfo].default_runfiles)
entrypoint = ctx.actions.declare_file(ctx.label.name)
if ctx.attr.use_bash_launcher:
@@ -89,14 +91,16 @@ exec "$(rlocation "{src}")" "$@"
instrumented_files_info = coverage_common.instrumented_files_info(
ctx,
source_attributes = ["srcs"],
- dependency_attributes = ["deps", "_runfiles_dep", "data"],
+ dependency_attributes = ["deps", "_runfiles_dep", "data", "tools"],
)
+ locations = ctx.attr.data + ctx.attr.tools
+
run_environment_info = RunEnvironmentInfo(
environment = {
key: ctx.expand_make_variables(
"env",
- ctx.expand_location(value, ctx.attr.data, short_paths = True),
+ ctx.expand_location(value, locations, short_paths = True),
{},
)
for key, value in ctx.attr.env.items()
@@ -209,6 +213,15 @@ most build rules.
),
"env": attr.string_dict(),
"env_inherit": attr.string_list(),
+ "tools": attr.label_list(
+ cfg = config.exec(),
+ allow_files = True,
+ doc = """
+The list of tool dependencies, similar to genrule's equivalent. You can use
+this to ensure that any helper binaries your scripts need are built in the exec
+configuration.
+""",
+ ),
"use_bash_launcher": attr.bool(
doc = "Use a bash launcher initializing the runfiles library",
),
diff --git a/shell/private/sh_library.bzl b/shell/private/sh_library.bzl
index 2f3613c..49d3161 100644
--- a/shell/private/sh_library.bzl
+++ b/shell/private/sh_library.bzl
@@ -25,6 +25,8 @@ def _sh_library_impl(ctx):
transitive_files.append(target[DefaultInfo].files)
for target in ctx.attr.data:
transitive_files.append(target[DefaultInfo].files)
+ for target in ctx.attr.tools:
+ transitive_files.append(target[DefaultInfo].files)
files = depset(transitive = transitive_files)
runfiles = ctx.runfiles(transitive_files = files, collect_default = True)
@@ -32,7 +34,7 @@ def _sh_library_impl(ctx):
instrumented_files_info = coverage_common.instrumented_files_info(
ctx,
source_attributes = ["srcs"],
- dependency_attributes = ["deps", "data"],
+ dependency_attributes = ["deps", "data", "tools"],
)
return [
@@ -112,6 +114,15 @@ most build rules.
interpreted program source code depended on by the code in srcs. The files
provided by these rules will be present among the runfiles of this target.
+""",
+ ),
+ "tools": attr.label_list(
+ cfg = config.exec(),
+ allow_files = True,
+ doc = """
+The list of tool dependencies, similar to genrule's equivalent. You can use
+this to ensure that any helper binaries your scripts need are built in the exec
+configuration.
""",
),
},
diff --git a/tests/bcr/BUILD b/tests/bcr/BUILD
index fb884dd..6504acf 100644
--- a/tests/bcr/BUILD
+++ b/tests/bcr/BUILD
@@ -1,11 +1,32 @@
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
load("@rules_shell//shell:sh_library.bzl", "sh_library")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
+load(":is_exec_configuration.bzl", "is_exec_configuration")
+
+is_exec_configuration(
+ name = "_is_exec_configuration",
+ visibility = ["//visibility:private"],
+)
+
+config_setting(
+ name = "config_exec",
+ flag_values = {":_is_exec_configuration": "yes"},
+ visibility = ["//visibility:private"],
+)
+
+alias(
+ name = "greetings",
+ actual = select({
+ ":config_exec": "greeting_tool.txt",
+ "//conditions:default": "greeting.txt",
+ }),
+)
sh_library(
name = "lib",
srcs = ["lib.sh"],
data = ["greeting.txt"],
+ tools = ["greetings"],
deps = ["@rules_shell//shell/runfiles"],
)
diff --git a/tests/bcr/bin.sh b/tests/bcr/bin.sh
index f76d4a0..e366174 100755
--- a/tests/bcr/bin.sh
+++ b/tests/bcr/bin.sh
@@ -28,3 +28,5 @@ source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(rlocation "rules_shell_tests/lib.sh")"
get_greeting
+
+get_tool_greeting
diff --git a/tests/bcr/greeting_tool.txt b/tests/bcr/greeting_tool.txt
new file mode 100644
index 0000000..65158d2
--- /dev/null
+++ b/tests/bcr/greeting_tool.txt
@@ -0,0 +1 @@
+and the tools
diff --git a/tests/bcr/is_exec_configuration.bzl b/tests/bcr/is_exec_configuration.bzl
new file mode 100644
index 0000000..c197a4d
--- /dev/null
+++ b/tests/bcr/is_exec_configuration.bzl
@@ -0,0 +1,12 @@
+def _is_exec_configuration(ctx):
+ # https://github.com/bazelbuild/bazel/issues/14444
+ return config_common.FeatureFlagInfo(
+ # feature flags must be strings, so we're avoiding True and False in
+ # favor of "yes" and "no" to avoid confusion between "True" and True
+ value = "yes" if "-exec" in ctx.bin_dir.path else "no",
+ )
+
+is_exec_configuration = rule(
+ doc = "identify if the current target is in exec configuration",
+ implementation = _is_exec_configuration,
+)
diff --git a/tests/bcr/lib.sh b/tests/bcr/lib.sh
index 16fa810..9a1f422 100755
--- a/tests/bcr/lib.sh
+++ b/tests/bcr/lib.sh
@@ -29,3 +29,8 @@ function get_greeting() {
greeting_path=$(rlocation "rules_shell_tests/greeting.txt")
cat "${greeting_path}"
}
+
+function get_tool_greeting() {
+ greeting_path=$(rlocation "rules_shell_tests/greeting_tool.txt")
+ cat "${greeting_path}"
+}
diff --git a/tests/bcr/test.sh b/tests/bcr/test.sh
index ce37244..d006c5b 100755
--- a/tests/bcr/test.sh
+++ b/tests/bcr/test.sh
@@ -33,8 +33,13 @@ fi
runfiles_export_envvars
+read -r -d '' expected <