Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@ patch; breaking Starlark API changes bump the minor.

## [Unreleased]

## [0.3.4] - 2026-10-12

### Fixed

- `bazel_utils_buf`: on a Windows ARM64 exec platform the proto toolchain
matched no prebuilt `protoc` (protobuf publishes `win32` and `win64` only),
so Bazel fell back to `@protobuf//:protoc` and compiled protoc, abseil, and
upb from source. The module now registers a toolchain for Windows ARM64
that uses the x64 `protoc-36.2-win64.zip` (the same release and sha256 as
the `protobuf` module's win64 pin; Windows 11 on ARM emulates it).
- `protoc_prefix` fails with a clear message when the resolved `protoc` is
compiled from source instead of staging it silently.
- `protoc_prefix` staged the prefix with the host bash (`run_shell`), which
does not exist on a Windows runner without Git Bash. It now runs through
`run_shell_action`, like the other rules (busybox on Windows), and also
stages `bin/protoc.exe` next to `bin/protoc` when the compiler is an `.exe`.

### Added

- `//tests/buf:protoc_prefix` builds `protoc_prefix` on every CI platform.

### Changed

- Raised every language module and the aggregator to `0.3.4` (lockstep).

## [0.3.3] - 2026-10-12

### Added
Expand Down Expand Up @@ -377,7 +402,8 @@ Initial tagged release. Language modules for Bazel workspaces:
Pin modules with `git_override` at tag `v0.1.0`. `bazel_utils_core` is a
transitive dependency, not a consumer API.

[unreleased]: https://github.com/sonalect/bazel_utils/compare/v0.3.3...HEAD
[unreleased]: https://github.com/sonalect/bazel_utils/compare/v0.3.4...HEAD
[0.3.4]: https://github.com/sonalect/bazel_utils/compare/v0.3.3...v0.3.4
[0.3.3]: https://github.com/sonalect/bazel_utils/compare/v0.3.2...v0.3.3
[0.3.2]: https://github.com/sonalect/bazel_utils/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/sonalect/bazel_utils/compare/v0.3.0...v0.3.1
Expand Down
27 changes: 14 additions & 13 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,20 @@ aggregator that pulls them in with `local_path_override`; consumers use

Where pins live:

| Tool | File |
| -------------- | ------------------------------------------------------------------------------ |
| Buf CLI | `buf/registry.bzl` (+ fallback in `buf/MODULE.bazel`) |
| protoc plugins | `protoc/plugins/<name>/registry.bzl` (+ fallback pin in `protoc/MODULE.bazel`) |
| buildifier | `bazel/buildifier.MODULE.bazel` |
| golangci-lint | `go/golangci.MODULE.bazel` |
| govulncheck | `go/go.mod` |
| ruff | `python/ruff.MODULE.bazel` |
| uv | `python/uv.MODULE.bazel` |
| cargo-audit | `rust/cargo_audit.MODULE.bazel` |
| Node, pnpm | `markdown/MODULE.bazel` + `markdown/package.json` `packageManager` |
| busybox-w32 | `core/MODULE.bazel` |
| bazelisk (CI) | `.github/workflows/ci.yml` matrix |
| Tool | File |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Buf CLI | `buf/registry.bzl` (+ fallback in `buf/MODULE.bazel`) |
| protoc plugins | `protoc/plugins/<name>/registry.bzl` (+ fallback pin in `protoc/MODULE.bazel`) |
| protoc (Windows ARM64 only) | `buf/MODULE.bazel` `protoc_windows_arm64`: the `win64` zip of the same release as the `protobuf` `bazel_dep`; sha256 from protobuf's `tool_integrity.bzl` |
| buildifier | `bazel/buildifier.MODULE.bazel` |
| golangci-lint | `go/golangci.MODULE.bazel` |
| govulncheck | `go/go.mod` |
| ruff | `python/ruff.MODULE.bazel` |
| uv | `python/uv.MODULE.bazel` |
| cargo-audit | `rust/cargo_audit.MODULE.bazel` |
| Node, pnpm | `markdown/MODULE.bazel` + `markdown/package.json` `packageManager` |
| busybox-w32 | `core/MODULE.bazel` |
| bazelisk (CI) | `.github/workflows/ci.yml` matrix |

When a version is newer than a rule set's built-in catalog, pin it ourselves:
Node uses `node_repositories` with the sha256s from `SHASUMS256.txt`, and pnpm
Expand Down
23 changes: 12 additions & 11 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@

module(
name = "bazel_utils",
version = "0.3.3",
)

bazel_dep(name = "bazel_utils_bazel", version = "0.3.3")
bazel_dep(name = "bazel_utils_buf", version = "0.3.3")
bazel_dep(name = "bazel_utils_core", version = "0.3.3")
bazel_dep(name = "bazel_utils_go", version = "0.3.3")
bazel_dep(name = "bazel_utils_md", version = "0.3.3")
bazel_dep(name = "bazel_utils_protoc", version = "0.3.3")
bazel_dep(name = "bazel_utils_python", version = "0.3.3")
bazel_dep(name = "bazel_utils_rust", version = "0.3.3")
version = "0.3.4",
)

bazel_dep(name = "bazel_utils_bazel", version = "0.3.4")
bazel_dep(name = "bazel_utils_buf", version = "0.3.4")
bazel_dep(name = "bazel_utils_core", version = "0.3.4")
bazel_dep(name = "bazel_utils_go", version = "0.3.4")
bazel_dep(name = "bazel_utils_md", version = "0.3.4")
bazel_dep(name = "bazel_utils_protoc", version = "0.3.4")
bazel_dep(name = "bazel_utils_python", version = "0.3.4")
bazel_dep(name = "bazel_utils_rust", version = "0.3.4")
bazel_dep(name = "protobuf", version = "36.2")

local_path_override(
module_name = "bazel_utils_bazel",
Expand Down
59 changes: 41 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Starlark helpers for Bazel workspaces. Each language is a **separate Bazel modul

`bazel_utils_core` is pulled in transitively. The root module `bazel_utils` in this repository is an aggregator for development, not a consumer dependency.

Current module version: **0.3.3**. See [CHANGELOG.md](CHANGELOG.md) for release notes.
Current module version: **0.3.4**. See [CHANGELOG.md](CHANGELOG.md) for release notes.

| Module | Load | Public API |
| ------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------- |
Expand All @@ -22,16 +22,16 @@ Pass `buildifier` / `golangci` / `govulncheck` / `buf` / `ruff` / `uv` / `cargo_

Windows needs no host bash (Git Bash, MSYS2) or Python. `bazel_utils_core` downloads [busybox-w32](https://frippery.org/busybox/) (amd64, arm64): the `*_test` and `*_format` wrappers run through a `.bat` launcher with `busybox sh`, and `buf_module` / `buf_generate` actions run with it too. Linux and macOS use the system bash. Prebuilt binaries have a `.exe` output on Windows. `markdownlint_test` on Windows needs `build:windows --enable_runfiles` (with `common --enable_platform_specific_config`): rules_js's `js_binary` needs a runfiles tree. `protoc.plugin` zip releases are extracted at build time with the hermetic bsdtar from [tar.bzl](https://registry.bazel.build/modules/tar.bzl); no host Python or unzip is used.

Modules live in subdirectories of [sonalect/bazel_utils](https://github.com/sonalect/bazel_utils.git). Pin each language module with `git_override` at tag `v0.3.3` and `strip_prefix` matching that directory. Language modules depend on `bazel_utils_core` (no public macros, not on the Bazel Central Registry), so add this once:
Modules live in subdirectories of [sonalect/bazel_utils](https://github.com/sonalect/bazel_utils.git). Pin each language module with `git_override` at tag `v0.3.4` and `strip_prefix` matching that directory. Language modules depend on `bazel_utils_core` (no public macros, not on the Bazel Central Registry), so add this once:

```starlark
bazel_dep(name = "bazel_utils_core", version = "0.3.3")
bazel_dep(name = "bazel_utils_core", version = "0.3.4")

git_override(
module_name = "bazel_utils_core",
remote = "https://github.com/sonalect/bazel_utils.git",
strip_prefix = "core",
tag = "v0.3.3",
tag = "v0.3.4",
)
```

Expand All @@ -45,13 +45,13 @@ Prebuilt [buildifier](https://github.com/bazelbuild/buildtools) (GitHub release,

```starlark
# MODULE.bazel
bazel_dep(name = "bazel_utils_bazel", version = "0.3.3")
bazel_dep(name = "bazel_utils_bazel", version = "0.3.4")

git_override(
module_name = "bazel_utils_bazel",
remote = "https://github.com/sonalect/bazel_utils.git",
strip_prefix = "bazel",
tag = "v0.3.3",
tag = "v0.3.4",
)
```

Expand Down Expand Up @@ -128,13 +128,13 @@ The action sandbox does not inherit the user shell. Without this, buf runs anony

```starlark
# MODULE.bazel
bazel_dep(name = "bazel_utils_buf", version = "0.3.3")
bazel_dep(name = "bazel_utils_buf", version = "0.3.4")

git_override(
module_name = "bazel_utils_buf",
remote = "https://github.com/sonalect/bazel_utils.git",
strip_prefix = "buf",
tag = "v0.3.3",
tag = "v0.3.4",
)

buf = use_extension("@bazel_utils_buf//:extensions.bzl", "buf")
Expand Down Expand Up @@ -281,6 +281,29 @@ plugins:
out: rust/api/gen/my_thing
```

### `protoc_prefix`

Stages the **prebuilt** `protoc` of the resolved proto toolchain (`@protobuf//bazel/private:proto_toolchain_type`) with the well-known-type `.proto` files as a Unix-layout directory: `bin/protoc` and `include/google/protobuf/…`. Use it where a tool needs a `protoc` path, such as a Cargo `build.rs` (`PROTOC=$(execpath :protoc_prefix)/bin/protoc`). Never depend on `@protobuf//:protoc`: that target compiles protoc from source, and the rule fails if the toolchain resolves to such a build.

protobuf publishes no Windows ARM64 `protoc`. `bazel_utils_buf` registers a toolchain for that exec platform that uses the x64 `protoc-<version>-win64.zip` of the same release as the `protobuf` module (Windows 11 on ARM emulates it), so Windows ARM64 never compiles protoc.

| Name | Type | Required | Default | Description |
| ------ | ------------ | -------- | ------- | --------------------------------------------------------------- |
| `name` | `string` | yes | — | Target name; the output directory has the same name. |
| `wkts` | `label_list` | yes | — | `.proto` filegroups, e.g. `@protobuf//:well_known_type_protos`. |

```starlark
load("@bazel_utils_buf//:protoc_prefix.bzl", "protoc_prefix")

protoc_prefix(
name = "protoc_prefix",
wkts = [
"@protobuf//:descriptor_proto_srcs",
"@protobuf//:well_known_type_protos",
],
)
```

### `buf_lint_test`

`bazel test`: `buf lint` on a staged `buf_module`. Fetches `buf.yaml` `deps` from the BSR.
Expand Down Expand Up @@ -333,13 +356,13 @@ Canonical labels: `@bazel_utils_protoc//plugins/…`. [`bazel_utils_buf`](#bazel

```starlark
# MODULE.bazel
bazel_dep(name = "bazel_utils_protoc", version = "0.3.3")
bazel_dep(name = "bazel_utils_protoc", version = "0.3.4")

git_override(
module_name = "bazel_utils_protoc",
remote = "https://github.com/sonalect/bazel_utils.git",
strip_prefix = "protoc",
tag = "v0.3.3",
tag = "v0.3.4",
)

protoc = use_extension("@bazel_utils_protoc//:extensions.bzl", "protoc")
Expand Down Expand Up @@ -416,13 +439,13 @@ Both tools run in the `manifest` (`go.mod`) directory, so `go.mod` may be below

```starlark
# MODULE.bazel
bazel_dep(name = "bazel_utils_go", version = "0.3.3")
bazel_dep(name = "bazel_utils_go", version = "0.3.4")

git_override(
module_name = "bazel_utils_go",
remote = "https://github.com/sonalect/bazel_utils.git",
strip_prefix = "go",
tag = "v0.3.3",
tag = "v0.3.4",
)
```

Expand Down Expand Up @@ -490,13 +513,13 @@ Prebuilt [ruff](https://github.com/astral-sh/ruff) and [uv](https://github.com/a

```starlark
# MODULE.bazel
bazel_dep(name = "bazel_utils_python", version = "0.3.3")
bazel_dep(name = "bazel_utils_python", version = "0.3.4")

git_override(
module_name = "bazel_utils_python",
remote = "https://github.com/sonalect/bazel_utils.git",
strip_prefix = "python",
tag = "v0.3.3",
tag = "v0.3.4",
)
```

Expand Down Expand Up @@ -580,13 +603,13 @@ Prebuilt [cargo-audit](https://github.com/rustsec/rustsec/tree/main/cargo-audit)

```starlark
# MODULE.bazel
bazel_dep(name = "bazel_utils_rust", version = "0.3.3")
bazel_dep(name = "bazel_utils_rust", version = "0.3.4")

git_override(
module_name = "bazel_utils_rust",
remote = "https://github.com/sonalect/bazel_utils.git",
strip_prefix = "rust",
tag = "v0.3.3",
tag = "v0.3.4",
)
```

Expand Down Expand Up @@ -627,13 +650,13 @@ cargo_audit_test(

```starlark
# MODULE.bazel
bazel_dep(name = "bazel_utils_md", version = "0.3.3")
bazel_dep(name = "bazel_utils_md", version = "0.3.4")

git_override(
module_name = "bazel_utils_md",
remote = "https://github.com/sonalect/bazel_utils.git",
strip_prefix = "markdown",
tag = "v0.3.3",
tag = "v0.3.4",
)
```

Expand Down
4 changes: 2 additions & 2 deletions bazel/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

module(
name = "bazel_utils_bazel",
version = "0.3.3",
version = "0.3.4",
)

bazel_dep(name = "bazel_skylib", version = "1.9.2")
bazel_dep(name = "bazel_utils_core", version = "0.3.3")
bazel_dep(name = "bazel_utils_core", version = "0.3.4")

include("//:buildifier.MODULE.bazel")
11 changes: 11 additions & 0 deletions buf/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ load(":generate_path_test.bzl", "dep_import_path_test", "repo_rel_from_short_pat

package(default_visibility = ["//visibility:public"])

# The x64 protoc for a Windows ARM64 exec platform (see MODULE.bazel).
toolchain(
name = "protoc_windows_arm64_toolchain",
exec_compatible_with = [
"@platforms//cpu:aarch64",
"@platforms//os:windows",
],
toolchain = "@protoc_windows_arm64//:toolchain",
toolchain_type = "@protobuf//bazel/private:proto_toolchain_type",
)

bzl_library(
name = "plugin_bzl",
srcs = ["plugin.bzl"],
Expand Down
23 changes: 20 additions & 3 deletions buf/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

module(
name = "bazel_utils_buf",
version = "0.3.3",
version = "0.3.4",
)

bazel_dep(name = "bazel_skylib", version = "1.9.2")
bazel_dep(name = "bazel_utils_core", version = "0.3.3")
bazel_dep(name = "bazel_utils_protoc", version = "0.3.3")
bazel_dep(name = "bazel_utils_core", version = "0.3.4")
bazel_dep(name = "bazel_utils_protoc", version = "0.3.4")
bazel_dep(name = "platforms", version = "1.1.0")
bazel_dep(name = "protobuf", version = "36.2")

Expand All @@ -20,3 +20,20 @@ use_repo(buf, "buf")
# Root-module `protoc.plugin` labels for buf_generate PATH (`ROOT_PLUGIN_LABELS`).
protoc = use_extension("@bazel_utils_protoc//:extensions.bzl", "protoc")
use_repo(protoc, "protoc_root_plugins")

# protobuf publishes no Windows ARM64 protoc, so its prebuilt toolchains match
# no Windows ARM64 exec platform and Bazel falls back to compiling protoc from
# source (a large C++ build). Windows 11 on ARM runs the x64 build, so register
# that one for this exec platform. Same release and sha256 as the protobuf
# module's own win64 pin (`bazel/private/oss/toolchains/prebuilt/
# tool_integrity.bzl`); move it with the `protobuf` bazel_dep.
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
name = "protoc_windows_arm64",
build_file = "//:protoc_windows_arm64.BUILD.bazel",
sha256 = "f0c128dc0d8492eceece83bb459a4c0e316764b929ffbf1aa416357fd644edd3",
url = "https://github.com/protocolbuffers/protobuf/releases/download/v36.2/protoc-36.2-win64.zip",
)

register_toolchains("//:protoc_windows_arm64_toolchain")
18 changes: 13 additions & 5 deletions buf/protoc_prefix.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
Never depend on @protobuf//:protoc — that cc_binary compiles protoc from source.
Protobuf 33.4+ registers a GitHub prebuilt on
@protobuf//bazel/private:proto_toolchain_type when prefer_prebuilt_protoc is
true (the default).
true (the default). protobuf publishes no Windows ARM64 protoc; for that exec
platform this module registers the x64 build (`MODULE.bazel`), which Windows 11
on ARM emulates.
"""

load("@bazel_utils_core//internal:launcher.bzl", "SHELL_ACTION_ATTRS", "run_shell_action")

_PROTO_TOOLCHAIN_TYPE = "@protobuf//bazel/private:proto_toolchain_type"

def _protoc_prefix_impl(ctx):
Expand All @@ -16,23 +20,27 @@ def _protoc_prefix_impl(ctx):
protoc = compiler.executable
if not protoc:
fail("{}: proto toolchain has no proto_compiler".format(ctx.label))
if not protoc.is_source:
fail(("{}: the proto toolchain resolved to a protoc compiled from source ({}). " +
"No prebuilt protoc matches this exec platform.").format(ctx.label, protoc.path))

out = ctx.actions.declare_directory(ctx.label.name)
copies = [
'cp "{}" "$OUT/include/google/protobuf/{}"'.format(src.path, src.basename)
for src in ctx.files.wkts
]
ctx.actions.run_shell(
run_shell_action(
ctx,
outputs = [out],
inputs = depset(direct = [protoc] + ctx.files.wkts),
tools = [compiler],
command = "\n".join([
"set -euo pipefail",
"set -eu",
'OUT="{}"'.format(out.path),
'mkdir -p "$OUT/bin" "$OUT/include/google/protobuf"',
'cp "{}" "$OUT/bin/protoc"'.format(protoc.path),
"chmod +x \"$OUT/bin/protoc\"",
] + copies),
] + (['cp "{}" "$OUT/bin/protoc.exe"'.format(protoc.path)] if protoc.extension == "exe" else []) + copies),
mnemonic = "ProtocPrefix",
progress_message = "Staging prebuilt protoc prefix %{label}",
)
Expand All @@ -46,6 +54,6 @@ protoc_prefix = rule(
mandatory = True,
doc = "google.protobuf WKT .proto files (filegroups, not compiled C++).",
),
},
} | SHELL_ACTION_ATTRS,
toolchains = [_PROTO_TOOLCHAIN_TYPE],
)
Loading
Loading