Skip to content

Commit

Permalink
Add convenience target for running in the current working directory (#29
Browse files Browse the repository at this point in the history
)

It's common for users of multitool to want to run tools in the current working directory. In #26, @alexeagle documented a technique we've used for a while with creating a script and symlinking to it. Our internal copy of this script is a bit more complicated to help avoid expensive calls to Bazel that simple `bazel run` calls don't really need. More refinements have been proposed in #27. All of these things are fundamentally workarounds for bazelbuild/bazel#3325.

To help simplify things, this PR adds a convenience wrapper that captures the execpath, switches to $BUILD_WORKING_DIRECTORY, and then runs the desired tool. The resulting shell script gets to use a very simple `bazel run`, should be compatible across any slew of Bazel options, and cache as well as any typical run target.
  • Loading branch information
mark-thm committed Apr 16, 2024
1 parent 9a0e53e commit 6d0487b
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 28 deletions.
13 changes: 9 additions & 4 deletions examples/module/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
exports_files(
["multitool.lock.json"],
)

sh_test(
name = "integration_test",
srcs = ["integration_test.sh"],
Expand All @@ -10,3 +6,12 @@ sh_test(
],
data = ["@multitool//tools/target-determinator"],
)

sh_test(
name = "integration_test_cwd",
srcs = ["integration_test.sh"],
args = [
"$(location @multitool//tools/target-determinator:cwd)",
],
data = ["@multitool//tools/target-determinator:cwd"],
)
9 changes: 9 additions & 0 deletions examples/workspace/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ sh_test(
],
data = ["@multitool//tools/target-determinator"],
)

sh_test(
name = "integration_test_cwd",
srcs = ["integration_test.sh"],
args = [
"$(location @multitool//tools/target-determinator:cwd)",
],
data = ["@multitool//tools/target-determinator:cwd"],
)
5 changes: 5 additions & 0 deletions multitool/cwd.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"multitool cwd execution rule"

load("@rules_multitool//multitool/private:cwd.bzl", _cwd = "cwd")

cwd = _cwd
4 changes: 4 additions & 0 deletions multitool/private/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

exports_files([
"cwd.template.sh",
])

bzl_library(
name = "multitool",
srcs = ["multitool.bzl"],
Expand Down
21 changes: 21 additions & 0 deletions multitool/private/cwd.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"cwd: a rule for executing an executable in the BUILD_WORKING_DIRECTORY"

def _cwd_impl(ctx):
output = ctx.actions.declare_file(ctx.label.name)
ctx.actions.expand_template(
template = ctx.file._template,
output = output,
substitutions = {
"{{tool}}": ctx.file.tool.short_path,
},
)
return [DefaultInfo(executable = output, runfiles = ctx.runfiles(files = [ctx.file.tool]))]

cwd = rule(
implementation = _cwd_impl,
attrs = {
"tool": attr.label(mandatory = True, allow_single_file = True, executable = True, cfg = "exec"),
"_template": attr.label(default = "//multitool/private:cwd.template.sh", allow_single_file = True),
},
executable = True,
)
8 changes: 8 additions & 0 deletions multitool/private/cwd.template.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

tool="{{tool}}"
execdir="$PWD"

pushd "$BUILD_WORKING_DIRECTORY" > /dev/null
"$execdir/$tool" "$@"
popd > /dev/null
7 changes: 7 additions & 0 deletions multitool/private/hub_repo_tool_template/BUILD.bazel.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# generated by multitool

load("@rules_multitool//multitool:cwd.bzl", "cwd")
load(":tool.bzl", "tool")

toolchain_type(
Expand All @@ -11,3 +12,9 @@ tool(
name = "{name}",
visibility = ["//visibility:public"],
)

cwd(
name = "cwd",
tool = ":{name}",
visibility = ["//visibility:public"],
)
35 changes: 11 additions & 24 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ The lockfile supports the following binary kinds:
- `file`: executable file within the archive
- `sha256`: the sha256 of the downloaded pkg archive

Save your lockfile and ensure the file is exported using `export_files` so that it's available to Bazel.

### Bazel Module Usage

Once your lockfile is defined, load the ruleset in your MODULE.bazel and create a hub that refers to your lockfile:
Expand All @@ -61,29 +59,18 @@ Tools may then be accessed using `@multitool//tools/tool-name`.

Instructions for using with WORKSPACE may be found in [release notes](https://github.com/theoremlp/rules_multitool/releases).

### Creating convenience scripts

When users need to execute tools directly, Bazel does not provide very good support for this,
because it sets the working directory to the root of the runfiles tree, which breaks the ability of the tool to resolve configuration files and other relative paths.

The typical workarounds are all bad:
### Running tools in the current working directory

1. Change all paths the tool interacts with to be absolute, so that the working directory is inconsequential.
2. Wrap the tool in a trivial `sh_binary` that can read `$BUILD_WORKING_DIRECTORY`.
3. Use `--run_under="cd $PWD &&` however this [discards the analysis cache] slowing down this and the subsequent build.

Instead, the authors recommend the following technique. Create a script like `tools/_multitool_run_under_cwd.sh` containing the following shell code:

```sh
#!/bin/bash
# Workaround https://github.com/bazelbuild/bazel/issues/3325
target="@multitool//tools/$(basename "$0")"
bazel build "$target" && exec $(bazel 2>/dev/null cquery --output=files "$target") "$@"
```
When running `@multitool//tools/tool-name`, Bazel will execute the tool at the root of the runfiles tree due to https://github.com/bazelbuild/bazel/issues/3325.

Now just create symlinks such as `tools/mytool -> ./_multitool_run_under_cwd.sh`.
This will build `@multitool//tools/mytool` and then execute the resulting binary in the current working directory.
To run a tool in the current working directory, use the convenience target `@multitool//tools/tool-name:cwd`.

Developers can now just naively run `./tools/mytool --arg my/file` and don't need to worry about where the tool comes from.
A common pattern we recommend to further simplify invoking tools for repository users it to:

[discards the analysis cache]: https://github.com/bazelbuild/bazel/issues/10782
1. Create a `tools/` directory
1. Create an executable shell script `tools/_run_multitool.sh` with the following code:
```sh
#!/usr/bin/env bash
bazel run "@multitool//tools/$( basename $0 ):cwd" -- "$@"
```
1. Create symlinks of `tools/tool-name` to `tools/_run_multitool.sh`

0 comments on commit 6d0487b

Please sign in to comment.