Skip to content

Commit

Permalink
docs: Add README for rules_task (#650)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvgijssel authored Feb 26, 2024
1 parent 82937e5 commit a193f71
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/rules_task-rude-doors-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rules_task": patch
---

docs: Add docs for rules_task
281 changes: 281 additions & 0 deletions rules/rules_task/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
# rules_task

`rules_task` is a Bazel ruleset for creating and running programs called tasks. It aims to be a simpler alternative than writing `sh_binary` targets and look similar to scripts you would write for CI/CD systems like GitHub and GitLab. It achieves this by using a basic rule definition and by exposing AST nodes for more complex tasks.

# Installation

Follow instructions from the release you wish to use: [rules_task releases](https://github.com/vgijssel/setup/releases?q=%22name+%3D+%5C%22rules_task%5C%22%22&expanded=true).

# Getting Started

1. Load the `task` rule in a `BUILD.bazel` file

```bazel
load("@rules_task//task:def.bzl", "task")
```

2. Create a task with the `task` rule

```bazel
task(
name = "hello",
cmds = [
"echo Hello, world",
]
)
```

3. Run the task with `bazel run`

```bash
bazel run :hello
```

# Use cases

### Multiple commands in a single target

```bazel
task(
name = "multiple_commands",
cmds = [
"echo hello",
"echo world",
]
)
```

### Command with environment variables

```bazel
task(
name = "env",
cmds = [
"echo $MY_ENV_VAR",
],
env = {
"MY_ENV_VAR": "Hello, world",
}
)
```

Note that these environment variables are persisted inside the task binary, as opposed to the args / env arguments of `py_binary` and `sh_binary` rules. This means if this task target is called by another target, the environments will still be present.

### Runnning a command at exit

```bazel
task(
name = "defer",
cmds = [
"echo 1",
"echo 2",
{ "defer": "echo 3" },
"echo 4",
"exit 1",
"echo 5",
],
)
```

This will print

```bash
1
2
4
3 # note this is executed after 4
```

### Passing cli arguments

A special environment variable `$CLI_ARGS` is available to all tasks, which contains the arguments passed to the task.

```bazel
task(
name = "cli_args",
cmds = [
"echo $CLI_ARGS",
],
)
```

```bash
bazel run :cli_args -- "Hello, world"
```

### Setting a current working directory

This will set the current working directory to the root of the Bazel workspace.

```bazel
task(
name = "cwd",
cmds = [
"pwd",
],
cwd = "$BUILD_WORKSPACE_DIRECTORY",
)
```

### Using executable targets

You can use the `cmd.executable` AST node to reference other **executable** targets. This does some magic behind the scenes to make sure the target is tracked as a runfile dependency and the absolute path is resolved. This makes it easy to use in combination with a changed working directory. No need to add `$(location ...)` or `$(execpath ...)` statements or to explicitly add the target as a dependency in the `data` attribute.

```bazel
task(
name = "executable",
cmds = [
"$my_executable",
],
env = {
"tool": cmd.executable("my_executable"),
}
)
```

### Referencing outputs of other targets

You can use the `cmd.file` and `cmd.files` AST nodes to reference other targets. This does some magic behind the scenes to make sure the target is tracked as a runfile dependency and the absolute path is resolved.

```bazel
task(
name = "file",
cmds = [
"echo $my_file",
"cat $my_file_group",
],
env = {
"file": cmd.file("my_file"),
"files": cmd.files(":my_file_group"),
}
)
```

### More examples

For more examples, see the [tests](tests/BUILD.bazel).

# AST nodes

Using the AST nodes directly allows for more advanced use cases. The current AST visitor implementation does not allow for nested nodes of the same type because Starlark does not support recursion. This means that you cannot use `cmd.defer` inside a `cmd.defer` or `cmd.file` inside a `cmd.file`. This is a limitation of the current implementation and may be resolved in the future.

Load the `cmd` rule in a `BUILD.bazel` file

```bazel
load("@rules_task//task:def.bzl", "cmd")
```

### `cmd.root`

Is used by the `task` rule to define the root of the AST. It is not meant to be used directly currently, due to the recursion limitation of the current visitor.

### `cmd.env`

This allows you to set environment variables using a dict in any place of the AST.

```bazel
task(
name = "env_ast",
cmds = [
"echo $MY_ENV_VAR",
cmd.env({
"MY_ENV_VAR": "bar",
}),
"echo $MY_ENV_VAR",
],
env = {
"MY_ENV_VAR": "foo",
}
)
```

This will print

```bash
foo
bar
```

### `cmd.defer`

Previously example `{ "defer": "echo 3" }` is syntactic sugar for `cmd.defer("echo 3")`.

### `cmd.shell`

The `cmd.shell` is the default node for each of the arguments of the `cmd.root` node. For example

```bazel
task(
name = "hello",
cmds = [
"echo Hello, world",
]
)
```

Can also be written as

```bazel
task(
name = "shell",
cmds = [
cmd.shell("echo Hello, world"),
]
)
```

or

```bazel
task(
name = "shell_args",
cmds = [
cmd.shell("echo", "Hello", "world"),
]
)
```

which is useful for passing arguments to a command. This also allows passing a `cmd.executable` without relying on `cmd.env`

```bazel
task(
name = "shell_and_executable",
cmds = [
cmd.shell(cmd.executable(":my_executable"), "some", "args"),
]
)
```

### `cmd.file`

See [Referencing outputs of other targets](#referencing-outputs-of-other-targets)

### `cmd.files`

See [Referencing outputs of other targets](#referencing-outputs-of-other-targets)

### `cmd.executable`

See [Using executable targets](#using-executable-targets)

### `cmd.string`

This is used for most of the leaf nodes, at the end of the AST. For example

```bazel
cmd.shell("echo", "hello", world)
```

can also be written as

```bazel
cmd.shell(cmd.string("echo"), cmd.string("hello"), cmd.string("world"))
```

but is arguably less readable.

# Inspiration/Alternatives

- [task](https://github.com/go-task/task) - rules_task is heavily inspired by the task tool, taking some of the best ideals like deferred execution.
- [multirun](https://github.com/atlassian/bazel-tools/blob/master/multirun/README.md) - Initial inspiration for rules_task, making it easy to run multiple commands directly from a Bazel file
- [rules_multirun](https://github.com/keith/rules_multirun) - Modern and maintained version of the Atlassian multirun tool.

0 comments on commit a193f71

Please sign in to comment.