diff --git a/README.md b/README.md index 750e744..24a0dd5 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,10 @@ Installation instructions are included on each release: - [run_binary](docs/run_binary.md) Like skylib's run_binary but adds directory output support. - [stamping](docs/stamping.md) Support version stamping in custom rules. +## Running programs + +- [wrap_binary](docs/wrap_binary.md) Compatibility shims which wrap binaries to adapt to the Bazel execution environment. + ## Generating documentation - [docs](docs/docs.md) Rules for generating docs and stamping tests to ensure they are up to date. diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index 4a3ccb7..3aff3c2 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -73,6 +73,11 @@ stardoc_with_diff_test( bzl_library_target = "//lib:run_binary", ) +stardoc_with_diff_test( + name = "wrap_binary", + bzl_library_target = "//lib:wrap_binary", +) + stardoc_with_diff_test( name = "transitions", bzl_library_target = "//lib:transitions", diff --git a/docs/paths.md b/docs/paths.md index ab2d6bc..869e585 100644 --- a/docs/paths.md +++ b/docs/paths.md @@ -2,27 +2,6 @@ Public API - - -## chdir_binary - -
-chdir_binary(name, binary, chdir, kwargs)
-
- -Wrap a *_binary to be executed under a given directory. - -**PARAMETERS** - - -| Name | Description | Default Value | -| :------------- | :------------- | :------------- | -| name | Name of the rule. | none | -| binary | Label of an executable target to wrap. | none | -| chdir | Argument for the cd command, the default is commonly used with bazel run to run the program in the root of the Bazel workspace, in the source tree. | "$BUILD_WORKSPACE_DIRECTORY" | -| kwargs | Additional named arguments for the resulting sh_binary rule. | none | - - ## relative_file diff --git a/docs/wrap_binary.md b/docs/wrap_binary.md new file mode 100644 index 0000000..e648f99 --- /dev/null +++ b/docs/wrap_binary.md @@ -0,0 +1,40 @@ + + +Wraps binary rules to make them more compatible with Bazel. + +Currently supports only Bash as the wrapper language, not cmd.exe. + +Future additions might include: +- wrap a binary such that it sees a tty on stdin +- manipulate arguments or environment variables +- redirect stdout/stderr, e.g. to silence buildspam on success +- intercept exit code, e.g. to make an "expect_fail" +- change user, e.g. to deal with containerized build running as root, but tool requires non-root +- intercept signals, e.g. to make a tool behave as a Bazel persistent worker + + + + +## chdir_binary + +
+chdir_binary(name, binary, chdir, kwargs)
+
+ +Wrap a *_binary to be executed under a given working directory. + +Note: under `bazel run`, this is similar to the `--run_under "cd $PWD &&"` trick, but is hidden +from the user so they don't need to know about that flag. + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| name | Name of the rule. | none | +| binary | Label of an executable target to wrap. | none | +| chdir | Argument for the cd command, the default is commonly used with bazel run to run the program in the root of the Bazel workspace, in the source tree. | "$BUILD_WORKSPACE_DIRECTORY" | +| kwargs | Additional named arguments for the resulting sh_binary rule. | none | + + diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel index 7a55d8a..fe74388 100644 --- a/lib/BUILD.bazel +++ b/lib/BUILD.bazel @@ -48,10 +48,7 @@ bzl_library( bzl_library( name = "paths", srcs = ["paths.bzl"], - deps = [ - "//lib/private:paths", - "@bazel_skylib//rules:write_file", - ], + deps = ["//lib/private:paths"], ) bzl_library( @@ -126,6 +123,16 @@ bzl_library( deps = ["//lib/private:run_binary"], ) +bzl_library( + name = "wrap_binary", + srcs = ["wrap_binary.bzl"], + deps = [ + ":paths", + "//lib/private:run_binary", + "@bazel_skylib//rules:write_file", + ], +) + bzl_library( name = "repo_utils", srcs = ["repo_utils.bzl"], diff --git a/lib/paths.bzl b/lib/paths.bzl index 76932a4..c8e1601 100644 --- a/lib/paths.bzl +++ b/lib/paths.bzl @@ -1,6 +1,5 @@ "Public API" -load("@bazel_skylib//rules:write_file.bzl", "write_file") load("//lib/private:paths.bzl", "paths") relative_file = paths.relative_file @@ -23,44 +22,3 @@ source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/ { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e # --- end runfiles.bash initialization v2 --- """ - -def chdir_binary(name, binary, chdir = "$BUILD_WORKSPACE_DIRECTORY", **kwargs): - """Wrap a *_binary to be executed under a given directory. - - Args: - name: Name of the rule. - binary: Label of an executable target to wrap. - chdir: Argument for the `cd` command, the default is commonly used with `bazel run` - to run the program in the root of the Bazel workspace, in the source tree. - **kwargs: Additional named arguments for the resulting sh_binary rule. - """ - - script = "_{}_chdir.sh".format(name) - - # It's 2022 and java_binary still cannot be told to cd to the source directory under bazel run. - write_file( - name = "_{}_wrap".format(name), - out = script, - content = [ - "#!/usr/bin/env bash", - BASH_RLOCATION_FUNCTION, - # Remove external/ prefix that is included in $(rootpath) but not supported by $(rlocation) - "bin=$(rlocation ${1#external/})", - # Consume the first argument - "shift", - # Fix the working directory - "cd " + chdir, - # Replace the current process - "exec $bin $@", - ], - is_executable = True, - ) - - native.sh_binary( - name = name, - srcs = [script], - args = ["$(rootpath {})".format(binary)] + kwargs.pop("args", []), - data = [binary], - deps = ["@bazel_tools//tools/bash/runfiles"], - **kwargs - ) diff --git a/lib/tests/wrap_binary/BUILD.bazel b/lib/tests/wrap_binary/BUILD.bazel new file mode 100644 index 0000000..eb5bfe2 --- /dev/null +++ b/lib/tests/wrap_binary/BUILD.bazel @@ -0,0 +1,25 @@ +load("@bazel_skylib//rules:run_binary.bzl", "run_binary") +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("@aspect_bazel_lib//lib:wrap_binary.bzl", "chdir_binary") + +sh_binary( + name = "fixture", + srcs = ["needs-working-dir.sh"], +) + +chdir_binary( + name = "wrap", + binary = "fixture", + chdir = package_name(), +) + +run_binary( + name = "assert", + outs = ["thing"], + tool = "wrap", +) + +build_test( + name = "test", + targets = ["assert"], +) diff --git a/lib/tests/wrap_binary/needs-working-dir.sh b/lib/tests/wrap_binary/needs-working-dir.sh new file mode 100755 index 0000000..6f1d0f0 --- /dev/null +++ b/lib/tests/wrap_binary/needs-working-dir.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -o errexit -o nounset -o pipefail + +if [ "$(dirname $(pwd))" != "wrap_binary" ]; then + echo >&2 "this program must be run with the working directory inside the package, but was $(pwd)" + exit 1 +fi \ No newline at end of file diff --git a/lib/wrap_binary.bzl b/lib/wrap_binary.bzl new file mode 100644 index 0000000..ee75d4a --- /dev/null +++ b/lib/wrap_binary.bzl @@ -0,0 +1,62 @@ +"""Wraps binary rules to make them more compatible with Bazel. + +Currently supports only Bash as the wrapper language, not cmd.exe. + +Future additions might include: +- wrap a binary such that it sees a tty on stdin +- manipulate arguments or environment variables +- redirect stdout/stderr, e.g. to silence buildspam on success +- intercept exit code, e.g. to make an "expect_fail" +- change user, e.g. to deal with containerized build running as root, but tool requires non-root +- intercept signals, e.g. to make a tool behave as a Bazel persistent worker +""" + +load(":paths.bzl", "BASH_RLOCATION_FUNCTION") +load(":utils.bzl", "to_label") +load("@bazel_skylib//rules:write_file.bzl", "write_file") + +def chdir_binary(name, binary, chdir = "$BUILD_WORKSPACE_DIRECTORY", **kwargs): + """Wrap a *_binary to be executed under a given working directory. + + Note: under `bazel run`, this is similar to the `--run_under "cd $PWD &&"` trick, but is hidden + from the user so they don't need to know about that flag. + + Args: + name: Name of the rule. + binary: Label of an executable target to wrap. + chdir: Argument for the `cd` command. + By default, supports using the binary under `bazel run` by running program in the + root of the Bazel workspace, in the source tree. + **kwargs: Additional named arguments for the resulting sh_binary rule. + """ + + script = "_{}_chdir.sh".format(name) + binary = to_label(binary) + + # It's 2022 and java_binary still cannot be told to cd to the source directory under bazel run. + write_file( + name = "_{}_wrap".format(name), + out = script, + content = [ + "#!/usr/bin/env bash", + BASH_RLOCATION_FUNCTION, + # Remove external/ prefix that is included in $(rootpath) but not supported by $(rlocation) + "bin=$(rlocation ${1#external/})", + # Consume the first argument + "shift", + # Fix the working directory + "cd " + chdir, + # Replace the current process + "exec $bin $@", + ], + is_executable = True, + ) + + native.sh_binary( + name = name, + srcs = [script], + args = ["$(rootpath {})".format(binary)] + kwargs.pop("args", []), + data = [binary], + deps = ["@bazel_tools//tools/bash/runfiles"], + **kwargs + )