Make binary rules run from workspace (#162)
Previously these just ran with normal `bazel run` locations. This is
do-able with bazel's `--run_under` or the new `--run_in_cwd` but I think
it makes sense to just make it work for these tools.
Fixes https://github.com/keith/buildifier-prebuilt/issues/9
diff --git a/BUILD b/BUILD
index b124b8b..be09df6 100644
--- a/BUILD
+++ b/BUILD
@@ -2,6 +2,8 @@
exports_files(
[
+ "binary_runner.bash.template",
+ "binary_runner.bat.template",
"runner.bash.template",
"runner.bat.template",
"WORKSPACE",
@@ -74,6 +76,8 @@
"BUILD",
"MODULE.bazel",
"WORKSPACE",
+ "binary_runner.bash.template",
+ "binary_runner.bat.template",
"runner.bash.template",
"runner.bat.template",
"//buildifier:all_files",
diff --git a/binary_runner.bash.template b/binary_runner.bash.template
new file mode 100644
index 0000000..a4ff078
--- /dev/null
+++ b/binary_runner.bash.template
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+TOOL_NAME='{TOOL_NAME}'
+TOOL_SHORT_PATH='{TOOL_SHORT_PATH}'
+
+function resolve_runfile() {
+ local path=$1
+ local runfiles_path=${path#../}
+ local candidate
+ local manifest
+ local line
+
+ if [[ -e "$path" ]]; then
+ realpath "$path"
+ return 0
+ fi
+
+ if [[ -n "${RUNFILES_DIR:-}" ]]; then
+ candidate="${RUNFILES_DIR}/${runfiles_path}"
+ if [[ -e "$candidate" ]]; then
+ realpath "$candidate"
+ return 0
+ fi
+ fi
+
+ for manifest in \
+ "${RUNFILES_MANIFEST_FILE:-}" \
+ "$0.runfiles_manifest" \
+ "$0.exe.runfiles_manifest" \
+ "$0.runfiles/MANIFEST"; do
+ [[ -n "$manifest" && -f "$manifest" ]] || continue
+ while IFS= read -r line; do
+ if [[ "$line" == "${runfiles_path} "* ]]; then
+ printf '%s\n' "${line#* }"
+ return 0
+ elif [[ "$line" == "$runfiles_path" ]]; then
+ printf '%s\n' "$runfiles_path"
+ return 0
+ fi
+ done < "$manifest"
+ done
+
+ return 1
+}
+
+if ! tool_path=$(resolve_runfile "$TOOL_SHORT_PATH"); then
+ echo "Unable to locate $TOOL_NAME runfile: $TOOL_SHORT_PATH" >&2
+ exit 1
+fi
+
+working_directory=${BUILD_WORKING_DIRECTORY:-${BUILD_WORKSPACE_DIRECTORY:-}}
+if [[ -n "$working_directory" ]]; then
+ if ! cd "$working_directory"; then
+ echo "Unable to change working directory: $working_directory" >&2
+ exit 1
+ fi
+fi
+
+exec "$tool_path" "$@"
diff --git a/binary_runner.bat.template b/binary_runner.bat.template
new file mode 100644
index 0000000..0642fb5
--- /dev/null
+++ b/binary_runner.bat.template
@@ -0,0 +1,44 @@
+@echo off
+setlocal EnableDelayedExpansion
+
+set "TOOL_NAME={TOOL_NAME}"
+set "TOOL_FILENAME={TOOL_FILENAME}"
+set "TOOL_SHORT_PATH={TOOL_SHORT_PATH}"
+
+REM When runfiles are enabled, the tool is available through its short path.
+set "tool_path=%TOOL_SHORT_PATH:/=\%"
+for %%I in ("!tool_path!") do set "tool_path=%%~fI"
+
+REM Otherwise, locate the tool through the runfiles manifest.
+if not exist "!tool_path!" (
+ set "manifest_path=%RUNFILES_MANIFEST_FILE%"
+ if defined manifest_path if not exist "!manifest_path!" set "manifest_path="
+ if not defined manifest_path if exist "%~f0.runfiles_manifest" set "manifest_path=%~f0.runfiles_manifest"
+ if not defined manifest_path if exist "%~f0.exe.runfiles_manifest" set "manifest_path=%~f0.exe.runfiles_manifest"
+ if not defined manifest_path if exist "MANIFEST" set "manifest_path=MANIFEST"
+ if not defined manifest_path if exist "..\MANIFEST" set "manifest_path=..\MANIFEST"
+
+ if defined manifest_path (
+ for /F "tokens=1,* delims= " %%F IN ('findstr /l /c:"!TOOL_FILENAME!" "!manifest_path!"') DO (
+ set "tool_path=%%G"
+ )
+ )
+)
+
+if not exist "!tool_path!" (
+ echo Unable to locate !TOOL_NAME! runfile: !TOOL_SHORT_PATH! 1>&2
+ exit /b 1
+)
+
+set "working_directory=%BUILD_WORKSPACE_DIRECTORY%"
+if defined BUILD_WORKING_DIRECTORY set "working_directory=%BUILD_WORKING_DIRECTORY%"
+if defined working_directory (
+ cd /d "!working_directory!"
+ if errorlevel 1 (
+ echo Unable to change working directory: !working_directory! 1>&2
+ exit /b 1
+ )
+)
+
+"!tool_path!" %*
+exit /b !ERRORLEVEL!
diff --git a/buildifier/buildifier_binary.bzl b/buildifier/buildifier_binary.bzl
index 8213df8..753dc93 100644
--- a/buildifier/buildifier_binary.bzl
+++ b/buildifier/buildifier_binary.bzl
@@ -6,11 +6,19 @@
load("@platforms//host:constraints.bzl", "HOST_CONSTRAINTS")
def _buildifier_binary(ctx):
- buildifier = ctx.toolchains["@buildifier_prebuilt//buildifier:toolchain"]._tool
- script = ctx.actions.declare_file("buildifier")
- ctx.actions.symlink(
+ toolchain = ctx.toolchains["@buildifier_prebuilt//buildifier:toolchain"]
+ buildifier = toolchain._tool
+ runner = toolchain._binary_runner
+ out_ext = ".bash" if runner.label.name.endswith(".bash.template") else ".bat"
+ script = ctx.actions.declare_file(ctx.label.name + out_ext)
+ ctx.actions.expand_template(
+ template = runner.files.to_list()[0],
output = script,
- target_file = buildifier,
+ substitutions = {
+ "{TOOL_NAME}": "buildifier",
+ "{TOOL_FILENAME}": buildifier.basename,
+ "{TOOL_SHORT_PATH}": buildifier.short_path,
+ },
is_executable = True,
)
diff --git a/buildozer/buildozer_binary.bzl b/buildozer/buildozer_binary.bzl
index 25c56e6..87ff031 100644
--- a/buildozer/buildozer_binary.bzl
+++ b/buildozer/buildozer_binary.bzl
@@ -6,11 +6,19 @@
load("@platforms//host:constraints.bzl", "HOST_CONSTRAINTS")
def _buildozer_binary(ctx):
- buildozer = ctx.toolchains["@buildifier_prebuilt//buildozer:toolchain"]._tool
- script = ctx.actions.declare_file("buildozer")
- ctx.actions.symlink(
+ toolchain = ctx.toolchains["@buildifier_prebuilt//buildozer:toolchain"]
+ buildozer = toolchain._tool
+ runner = toolchain._binary_runner
+ out_ext = ".bash" if runner.label.name.endswith(".bash.template") else ".bat"
+ script = ctx.actions.declare_file(ctx.label.name + out_ext)
+ ctx.actions.expand_template(
+ template = runner.files.to_list()[0],
output = script,
- target_file = buildozer,
+ substitutions = {
+ "{TOOL_NAME}": "buildozer",
+ "{TOOL_FILENAME}": buildozer.basename,
+ "{TOOL_SHORT_PATH}": buildozer.short_path,
+ },
is_executable = True,
)
diff --git a/examples/modify_example_test_runner.sh b/examples/modify_example_test_runner.sh
index 568a1c7..be2e711 100755
--- a/examples/modify_example_test_runner.sh
+++ b/examples/modify_example_test_runner.sh
@@ -45,10 +45,12 @@
# Run the buildifier rule check.
"${bazel}" run //:buildifier.check || fail "Expected //:buildifier.check to succeed."
-# # Execute buildozer command
-# "${bazel}" run -- @buildifier_prebuilt//:buildozer \
-# 'comment Comment\ added\ by\ test.' //:buildifier.check || \
-# fail "Expected buildozer command to succeed."
+# Execute buildozer and verify that it modifies the source workspace.
+"${bazel}" run -- @buildifier_prebuilt//:buildozer \
+ 'comment Comment\ added\ by\ test.' //:buildifier.check || \
+ fail "Expected buildozer command to succeed."
+grep -Fq '# Comment added by test.' BUILD || \
+ fail "Expected buildozer to modify the workspace BUILD file."
# Execute buildifier directly
"${bazel}" run -- @buildifier_prebuilt//:buildifier -r "${PWD}" || \
@@ -57,4 +59,3 @@
# Ensure that the workspace works properly
"${bazel}" test //... || fail "Expected tests to succeed in the scratch directory."
-
diff --git a/tests/binary_runner/BUILD b/tests/binary_runner/BUILD
new file mode 100644
index 0000000..75cef31
--- /dev/null
+++ b/tests/binary_runner/BUILD
@@ -0,0 +1,14 @@
+load("@rules_shell//shell:sh_test.bzl", "sh_test")
+
+sh_test(
+ name = "binary_runner_test",
+ srcs = ["binary_runner_test.sh"],
+ args = [
+ "$(location //:buildifier)",
+ "$(location //:buildozer)",
+ ],
+ data = [
+ "//:buildifier",
+ "//:buildozer",
+ ],
+)
diff --git a/tests/binary_runner/binary_runner_test.sh b/tests/binary_runner/binary_runner_test.sh
new file mode 100755
index 0000000..770d652
--- /dev/null
+++ b/tests/binary_runner/binary_runner_test.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+buildifier=$(realpath "$1")
+buildozer=$(realpath "$2")
+workspace="${TEST_TMPDIR}/workspace"
+working_directory="${workspace}/pkg"
+decoy="${TEST_TMPDIR}/decoy"
+
+mkdir -p "$working_directory" "$decoy"
+
+cat >"${workspace}/MODULE.bazel" <<'EOF'
+module(name = "binary_runner_test")
+EOF
+
+cat >"${workspace}/BUILD" <<'EOF'
+filegroup(
+ name = "workspace_target",
+)
+EOF
+
+cat >"${working_directory}/BUILD" <<'EOF'
+filegroup(
+ name = "working_target",
+)
+EOF
+
+cat >"${working_directory}/working_only.bzl" <<'EOF'
+WORKING_ONLY = True
+EOF
+
+# A workspace marker here makes a raw buildtools binary treat this directory as
+# the workspace root. The runner must instead use the Bazel working-directory
+# environment variables.
+touch "${decoy}/MODULE.bazel"
+cd "$decoy"
+
+case "$(uname -s)" in
+CYGWIN* | MINGW32* | MSYS* | MINGW*)
+ BUILD_WORKSPACE_DIRECTORY=$(cygpath -w "$workspace")
+ BUILD_WORKING_DIRECTORY=$(cygpath -w "$working_directory")
+ ;;
+*)
+ BUILD_WORKSPACE_DIRECTORY=$workspace
+ BUILD_WORKING_DIRECTORY=$working_directory
+ ;;
+esac
+export BUILD_WORKING_DIRECTORY BUILD_WORKSPACE_DIRECTORY
+
+# BUILD_WORKING_DIRECTORY takes precedence over BUILD_WORKSPACE_DIRECTORY.
+"$buildifier" -mode=check working_only.bzl
+
+output=$("$buildozer" 'print name' :all)
+if [[ "$output" != "working_target" ]]; then
+ echo "Unexpected buildozer output from working directory: $output" >&2
+ exit 1
+fi
+
+# BUILD_WORKSPACE_DIRECTORY is used when BUILD_WORKING_DIRECTORY is absent.
+unset BUILD_WORKING_DIRECTORY
+"$buildifier" -mode=check BUILD
+
+output=$("$buildozer" 'print name' //:all)
+if [[ "$output" != "workspace_target" ]]; then
+ echo "Unexpected buildozer output from workspace directory: $output" >&2
+ exit 1
+fi
diff --git a/toolchain.bzl b/toolchain.bzl
index ec73ad2..0cc37aa 100644
--- a/toolchain.bzl
+++ b/toolchain.bzl
@@ -7,6 +7,7 @@
def _buildifier_toolchain(ctx):
return [
platform_common.ToolchainInfo(
+ _binary_runner = ctx.attr.binary_runner,
_tool = ctx.executable.tool,
_runner = ctx.attr.runner,
),
@@ -15,6 +16,11 @@
prebuilt_toolchain = rule(
_buildifier_toolchain,
attrs = {
+ "binary_runner": attr.label(
+ allow_single_file = True,
+ mandatory = True,
+ doc = "The templated runner script for invoking the tool directly",
+ ),
"tool": attr.label(
allow_single_file = True,
mandatory = True,
@@ -42,10 +48,12 @@
"""
name = buildtools.create_unique_name(name = tool_name, platform = os, arch = arch)
+ binary_runner = "@buildifier_prebuilt//:binary_runner.bat.template" if os == "windows" else "@buildifier_prebuilt//:binary_runner.bash.template"
buildifier_runner = "@buildifier_prebuilt//:runner.bat.template" if os == "windows" else "@buildifier_prebuilt//:runner.bash.template"
prebuilt_toolchain(
name = name,
+ binary_runner = binary_runner,
tool = tool,
runner = buildifier_runner if tool_name == "buildifier" else None,
)