|
2 | 2 |
|
3 | 3 | # bazel-aquery-differ |
4 | 4 |
|
5 | | -This is a port of |
6 | | -<https://github.com/bazelbuild/bazel/blob/master/tools/aquery_differ/aquery_differ.py> |
7 | | -to golang. |
| 5 | +A tool to compare Bazel action query outputs with an interactive HTML report. |
| 6 | +This is a re-imagination of the [Bazel |
| 7 | +aquery_differ.py](https://github.com/bazelbuild/bazel/blob/master/tools/aquery_differ/aquery_differ.py) |
| 8 | +in Go with enhanced visualization features. |
| 9 | + |
| 10 | +## Features |
| 11 | + |
| 12 | +- Compare Bazel action graphs between builds or git commits |
| 13 | +- Interactive HTML reports with GitHub-style diff visualization |
| 14 | +- Syntax-highlighted diffs (unified diff and go-cmp formats) |
| 15 | +- Built-in web server with auto-open browser support |
| 16 | +- Native Bazel rules for integration into your build |
8 | 17 |
|
9 | 18 | ## Installation |
10 | 19 |
|
11 | | -Download and unzip a release artifact, or clone and `bazel build //cmd/aquerydiff`. |
| 20 | +### As a Bazel Module |
12 | 21 |
|
13 | | -## Usage |
| 22 | +Add to your `MODULE.bazel`: |
| 23 | + |
| 24 | +```starlark |
| 25 | +bazel_dep(name = "bazel-aquery-differ", version = "0.0.0") |
| 26 | +``` |
| 27 | + |
| 28 | +> **Note**: This module is not yet published to the Bazel Central Registry. For |
| 29 | +> now, use an `archive_override` or `git_override` pointing to this repository. |
| 30 | +
|
| 31 | +### As a Standalone Binary |
| 32 | + |
| 33 | +Download a release artifact, or build from source: |
14 | 34 |
|
15 | 35 | ```bash |
16 | | -aquerydiff --before <BEFORE_FILE> --after <AFTER_FILE> --report_dir <REPORT_DIR> |
| 36 | +git clone https://github.com/stackb/bazel-aquery-differ.git |
| 37 | +cd bazel-aquery-differ |
| 38 | +bazel build //cmd/aquerydiff |
| 39 | +``` |
| 40 | + |
| 41 | +## Usage |
| 42 | + |
| 43 | +### Using Bazel Rules |
| 44 | + |
| 45 | +Load the rules in your `BUILD.bazel` file: |
| 46 | + |
| 47 | +```starlark |
| 48 | +load("@bazel-aquery-differ//rules:defs.bzl", "aquery_diff", "aquery_git_diff") |
17 | 49 | ``` |
18 | 50 |
|
19 | | -You can generate the `<BEFORE_FILE>` (and `<AFTER_FILE>`) using: |
| 51 | +#### Rule: `aquery_diff` |
| 52 | + |
| 53 | +Compare two aquery output files: |
| 54 | + |
| 55 | +```starlark |
| 56 | +aquery_diff( |
| 57 | + name = "compare_actions", |
| 58 | + before = "before.pb", |
| 59 | + after = "after.pb", |
| 60 | +) |
| 61 | +``` |
| 62 | + |
| 63 | +**Attributes:** |
| 64 | + |
| 65 | +| Attribute | Type | Default | Description | |
| 66 | +|-----------|----------|------------------|------------------------------------------------------------------------------| |
| 67 | +| `before` | `label` | **required** | Baseline aquery file (`.pb`, `.proto`, `.textproto`, `.json`, `.jsonproto`) | |
| 68 | +| `after` | `label` | **required** | Comparison aquery file (same format options) | |
| 69 | +| `match` | `string` | `"output_files"` | Strategy to match before and after actions: `"output_files"` or `"mnemonic"` | |
| 70 | +| `serve` | `bool` | `True` | Start web server to view report | |
| 71 | +| `open` | `bool` | `True` | Automatically open browser to report | |
| 72 | +| `unidiff` | `bool` | `False` | Generate unified diffs (can be slow for large actions) | |
| 73 | +| `cmpdiff` | `bool` | `True` | Generate go-cmp diffs (fast, structural comparison) | |
| 74 | + |
| 75 | +Run the comparison: |
20 | 76 |
|
21 | 77 | ```bash |
22 | | -bazel aquery //pkg:target-name --output jsonproto > before.json |
23 | | -bazel aquery //pkg:target-name --output textproto > before.textproto |
24 | | -bazel aquery //pkg:target-name --output proto > before.pb |
| 78 | +bazel run //path/to:compare_actions |
| 79 | +``` |
| 80 | + |
| 81 | +> **Performance Note**: The `unidiff` attribute defaults to `False` because generating unified diffs can be prohibitively slow for large actions with many inputs/outputs. The `cmpdiff` format (enabled by default) is much faster and provides good structural comparison for most use cases. Only enable `unidiff` if you need the traditional unified diff format and are willing to wait for the additional processing time. |
| 82 | +
|
| 83 | +**Choosing a Match Strategy:** |
| 84 | + |
| 85 | +The `match` attribute determines how actions are paired between the before and |
| 86 | +after builds: |
| 87 | + |
| 88 | +- **`output_files`** (default): Actions are matched by their output file paths. |
| 89 | + Use this when comparing the same target across different commits or |
| 90 | + configurations. This is the most common use case and ensures you're comparing |
| 91 | + the exact same action that produces the same outputs. |
| 92 | + |
| 93 | +- **`mnemonic`**: Actions are matched by their mnemonic (action type, e.g., |
| 94 | + "GoCompile", "CppCompile"). Use this when comparing different targets that use |
| 95 | + similar build rules. For example, comparing `//old/pkg:binary` vs |
| 96 | + `//new/pkg:binary` where both are `go_binary` targets but produce different |
| 97 | + output paths. This helps identify how the same type of action differs between |
| 98 | + targets. |
| 99 | + |
| 100 | +Example using mnemonic matching: |
| 101 | + |
| 102 | +```starlark |
| 103 | +aquery_diff( |
| 104 | + name = "compare_go_binaries", |
| 105 | + before = "old_binary.pb", |
| 106 | + after = "new_binary.pb", |
| 107 | + match = "mnemonic", # Compare by action type instead of output path |
| 108 | +) |
25 | 109 | ``` |
26 | 110 |
|
27 | | -> The file extensions are relevant; the proto decoder will be `protojson` if |
28 | | -`.json`, `prototext` if `.textproto` and `proto` otherwise. |
| 111 | +#### Rule: `aquery_git_diff` |
| 112 | + |
| 113 | +Compare aquery outputs between git commits: |
| 114 | + |
| 115 | +```starlark |
| 116 | +aquery_git_diff( |
| 117 | + name = "git_compare", |
| 118 | + before = "main", |
| 119 | + after = "feature-branch", |
| 120 | + target = "//my/package:target", |
| 121 | +) |
| 122 | +``` |
| 123 | + |
| 124 | +**Attributes:** |
| 125 | + |
| 126 | +Same as `aquery_diff`, plus: |
| 127 | + |
| 128 | +| Attribute | Type | Default | Description | |
| 129 | +|-----------|----------|--------------|------------------------------------------------------------| |
| 130 | +| `target` | `string` | **required** | Bazel target to aquery (e.g., `//pkg:binary`, `deps(...)`) | |
| 131 | +| `bazel` | `string` | `"bazel"` | Path to bazel executable | |
| 132 | +| `before` | `string` | **required** | Git commit/branch/tag for baseline | |
| 133 | +| `after` | `string` | **required** | Git commit/branch/tag for comparison | |
29 | 134 |
|
| 135 | +This rule will: |
| 136 | +1. Check for uncommitted changes (fails if found) |
| 137 | +2. Checkout `before` commit and run `bazel aquery` |
| 138 | +3. Checkout `after` commit and run `bazel aquery` |
| 139 | +4. Restore original commit |
| 140 | +5. Generate comparison report |
30 | 141 |
|
31 | | -An HTML report and accessory files will be written to the given `--report_dir`, |
32 | | -which you could serve as follows: |
| 142 | +### Using the CLI |
33 | 143 |
|
| 144 | +Generate aquery files using Bazel: |
| 145 | + |
| 146 | +```bash |
| 147 | +# Binary proto format (recommended for large graphs) |
| 148 | +bazel aquery //pkg:target --output=proto > before.pb |
| 149 | + |
| 150 | +# Text proto format (human-readable) |
| 151 | +bazel aquery //pkg:target --output=textproto > before.textproto |
| 152 | + |
| 153 | +# JSON proto format |
| 154 | +bazel aquery //pkg:target --output=jsonproto > before.json |
34 | 155 | ``` |
35 | | -(cd <REPORT_DIR> && python3 -m http.server 8000) & |
| 156 | + |
| 157 | +> **Supported formats**: The tool automatically detects format based on file extension: |
| 158 | +> - Binary: `.pb`, `.proto` |
| 159 | +> - Text: `.textproto` |
| 160 | +> - JSON: `.json`, `.jsonproto` |
| 161 | +
|
| 162 | +Run the comparison: |
| 163 | + |
| 164 | +```bash |
| 165 | +aquerydiff \ |
| 166 | + --before before.pb \ |
| 167 | + --after after.pb \ |
| 168 | + --report_dir ./output \ |
| 169 | + --serve \ |
| 170 | + --open |
36 | 171 | ``` |
37 | 172 |
|
38 | | -> Report will look something like: |
| 173 | +**CLI Flags:** |
| 174 | + |
| 175 | +- `--before` - Path to baseline aquery file |
| 176 | +- `--after` - Path to comparison aquery file |
| 177 | +- `--report_dir` - Directory to write HTML report |
| 178 | +- `--match` - Matching strategy: `output_files` (default) or `mnemonic` |
| 179 | +- `--serve` - Start web server (default: true) |
| 180 | +- `--open` - Open browser automatically (default: true) |
| 181 | +- `--unidiff` - Generate unified diffs (default: false) |
| 182 | +- `--cmpdiff` - Generate go-cmp diffs (default: true) |
| 183 | + |
| 184 | +> **Note**: The report title is automatically derived from the most common target in the action graph. |
| 185 | +
|
| 186 | +### Report Output |
| 187 | + |
| 188 | +The HTML report shows: |
| 189 | + |
| 190 | +- **Actions only in before** - Removed actions |
| 191 | +- **Actions only in after** - New actions |
| 192 | +- **Non-equal actions** - Actions with changes |
| 193 | +- **Equal actions** - Unchanged actions |
| 194 | + |
| 195 | +Each action displays: |
| 196 | +- Mnemonic (action type) |
| 197 | +- Output files |
| 198 | +- Links to before/after JSON/textproto representations |
| 199 | +- Colorized diffs (unified and/or go-cmp format) |
| 200 | + |
| 201 | +<img width="934" alt="Example report showing action comparison" src="https://user-images.githubusercontent.com/50580/209453563-064db4dd-4068-4d2f-8bb3-35c425bfb8b5.png"> |
| 202 | + |
| 203 | +## Example |
| 204 | + |
| 205 | +See the [examples/simple](examples/simple) directory for working examples using both rules. |
| 206 | + |
| 207 | +## Contributing |
| 208 | + |
| 209 | +Contributions welcome! Please open an issue or pull request. |
| 210 | + |
| 211 | +## License |
39 | 212 |
|
40 | | -<img width="934" alt="image" src="https://user-images.githubusercontent.com/50580/209453563-064db4dd-4068-4d2f-8bb3-35c425bfb8b5.png"> |
| 213 | +Apache 2.0 |
0 commit comments