rules_aip
Bazel rules for Google AIP (aip.dev) proto linting — shared api-linter toolchain + aip_proto_lint test rule + aip_proto_library deps macro.
| Latest | 0.3.0 |
|---|---|
| Versions | 5 |
| Category | Bazel rules |
| Compat level | 1 |
| Maintainers | Matt Marshall |
| Registry | https://registry.tbzl.dev/modules/rules_aip/ |
| Source | github.com/tomato-bazel/rules_aip |
bazel_dep(name = "rules_aip", version = "0.3.0")
View source & releases on GitHub ↗
Bazel rules for Google AIP proto linting — a shared toolkit
so repos stop recreating the api-linter setup by hand.
Published to fastverk/bazel-registry;
consume via bazel_dep(name = "rules_aip", version = "0.2.0").
What it replaces
Three repos (hrcrawl, pinax, agora) each carried:
- a copy of
tools/api-linter/(go.mod+go.sum+ aBUILDalias to the Go binary), and - a hand-rolled
aip_lintgenruleinlined perBUILD, re-doing the googleapis--proto-pathplumbing and--disable-ruleflags every time, plus - repeated
@googleapis//google/api:*dep lists on everyproto_library.
rules_aip lifts all three into one versioned module.
API
load("@rules_aip//aip:defs.bzl", "aip_proto_library", "aip_proto_lint", "AIP_LRO_DEPS")
# proto_library + the standard AIP googleapis deps (field_behavior, resource,
# annotations, client). Add the rest via deps / the AIP_* groups.
aip_proto_library(
name = "library_proto",
srcs = ["library/v1/library.proto"],
deps = AIP_LRO_DEPS, # long-running operations, when needed
strip_import_prefix = "/proto", # make the import path match the package (AIP-191)
)
# A `bazel test` target that AIP-lints it.
aip_proto_lint(
name = "library_aip_lint",
proto = ":library_proto",
disable_rules = ["core::0191::proto-package"], # only if your layout needs it
)
aip_proto_library—proto_librarywrapper bundlingAIP_COMMON_DEPS. Dep-group constants exported:AIP_COMMON_DEPS,AIP_LRO_DEPS,AIP_WKT_DEPS.aip_proto_lint— runs api-linter as abuild_test. Hermetic: it lints off the proto_library’s transitiveFileDescriptorSet(--descriptor-set-in), so you never pass--proto-pathor pull googleapis sources in by hand. Target files are named by their import path, so AIP-191 passes when the layout is idiomatic (see thestrip_import_prefixnote).
Requirements
Proto toolchain resolution — default in Bazel 9; on Bazel 8 or older pass
--incompatible_enable_proto_toolchain_resolution.
aip_proto_lint regenerates a source-info descriptor set (Bazel’s own are built
without comments, so AIP-0192 would fire on every symbol), which means it runs
protoc. It takes that from the proto toolchain, so
--@protobuf//bazel/toolchains:prefer_prebuilt_protoc gets you the prebuilt
binary and no C++ toolchain is needed at all. Naming @protobuf//:protoc
directly — as this rule did before 0.3.0 — forces the from-source build and drags
a compiler into repos that have no C++ in them.
Toolchain
The linter is the upstream prebuilt api-linter release binary (v2.3.1),
fetched per-platform as a sha256-pinned http_archive (no Go toolchain /
build-from-source) and re-exposed as @rules_aip//tools/api-linter:api-linter.
It lands in Bazel’s downloads cache, so lint runs are offline after the first
fetch. Platforms: darwin/{amd64,arm64}, linux/amd64, windows/amd64.
Example
//examples:bookstore_aip_lint lints the canonical AIP bookstore service
(examples/proto/bookstore/v1/bookstore.proto) — a clean copy-paste template for
new services. bazel test //examples/....
Scope
v0.1 is the AIP toolkit: linter toolchain + lint rule + deps macro. The name is
deliberately broad — protovalidate, buf breaking-change detection, and
AIP→OpenAPI/gateway generation can join later without a rename. Multi-language
stub generation is intentionally out of scope (use rules_proto_grpc).
Verify with
bazel test //examples/...to confirm api-linter flag behavior in your Bazel/toolchain combo. The linter binary itself is a pinned prebuilt, so no toolchain/source build is involved.
Usage#
Real usage, taken from the module’s examples/.
examples/BUILD.bazel
load("//aip:defs.bzl", "aip_proto_library", "aip_proto_lint")
# Idiomatic AIP service: proto_library + the standard googleapis AIP deps.
aip_proto_library(
name = "bookstore_proto",
srcs = ["proto/bookstore/v1/bookstore.proto"],
# Make the import path `bookstore/v1/bookstore.proto` match `package bookstore.v1`.
strip_import_prefix = "/examples/proto",
visibility = ["//visibility:public"],
)
# `bazel test //examples:bookstore_aip_lint` runs the AIP linter over it.
aip_proto_lint(
name = "bookstore_aip_lint",
proto = ":bookstore_proto",
)Conformance#
No gate findings. 8 contested atoms. See how gating works or the full report.
Contested atoms
Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.
| Atom | Resolved here | Elsewhere |
|---|---|---|
apple_support | 1.24.2 | 2.2.0 ×1 |
bazel_skylib | 1.8.2 | 1.9.0 ×2 |
nlohmann_json | 3.6.1 | 3.12.0.bcr.1 ×1 |
protobuf | 33.4 | 34.0.bcr.1 ×2 |
rules_jvm_external | 6.7 | 6.8 ×4 |
rules_python | 1.7.0 | 2.0.1 ×1 |
rules_swift | 3.1.2 | 3.6.1 ×1 |
upb | 0.0.0-20220923-a547704 | 0.0.0-20230516-61a97ef ×1 |
Dependencies#
Depends on
Used by (3 in the registry)
Versions#
5 published versions, newest first. Each resolves to an immutable, integrity-checked archive.
| Version | Integrity (sha256) | Source archive |
|---|---|---|
0.3.0 latest | pSv1XPfmDFsWGFiB… | tag archive ↗ |
0.2.2 | 3JRF1A1woKK50yYY… | tag archive ↗ |
0.2.1 | AlKUIidRlq2+BG2e… | tag archive ↗ |
0.2.0 | 0A9+03Zs2x8TYEDS… | tag archive ↗ |
0.1.0 | 7NpUGwwKayEWLiCO… | tag archive ↗ |
Changelog#
All notable changes to this module are documented here; this project adheres to Keep a Changelog and SemVer.
[Unreleased]
[0.3.0] — 2026-07-27
Changed
-
aip_proto_lintresolvesprotocfrom the proto toolchain instead of naming@protobuf//:protoc. That label is the from-sourcecc_binary, so every consumer previously had to fetch and run a C++ compiler to lint protos — in repos that typically contain no C++ — and--@protobuf//bazel/toolchains:prefer_prebuilt_protocwas inert, because it redirects the toolchain the rule never consulted.The cost was concrete:
aion/e2ecarried atoolchains_llvmblock whose only purpose was to satisfy this, and alternated between LLVM 18.1.8 and 20.1.3 — 18 linkslibtinfo.so.5and dies at exec on workers shipping.so.6; 20 loads but its tarball fills constrained runners. One root cause, two symptoms, no pin correct everywhere.The private
_protocattribute is removed rather than kept as a fallback. Anattr.labeldefault is a dependency edge whether or not the rule reads it, so keeping it would still pull the from-sourceprotocthrough analysis — which is exactly what requires the C++ toolchain. Verified bycquery:cc_binary @protobuf//:protocandprotoc_lib_stage1are no longer indeps(), and a consumer builds green on macOS with no cc toolchain registered.
Requires
- Proto toolchain resolution, which Bazel 9 enables by default. On Bazel 8 or
older, pass
--incompatible_enable_proto_toolchain_resolution; the rule now fails with that instruction rather than a bare toolchain error.