tomato·bazeldocs v0 · latest
Docs/Reference/Modules/rules_aip

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.

Latest0.3.0
Versions5
CategoryBazel rules
Compat level1
MaintainersMatt Marshall
Registryhttps://registry.tbzl.dev/modules/rules_aip/
Sourcegithub.com/tomato-bazel/rules_aip
MODULE.bazelstarlark
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 + a BUILD alias to the Go binary), and
  • a hand-rolled aip_lint genrule inlined per BUILD, re-doing the googleapis --proto-path plumbing and --disable-rule flags every time, plus
  • repeated @googleapis//google/api:* dep lists on every proto_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_libraryproto_library wrapper bundling AIP_COMMON_DEPS. Dep-group constants exported: AIP_COMMON_DEPS, AIP_LRO_DEPS, AIP_WKT_DEPS.
  • aip_proto_lint — runs api-linter as a build_test. Hermetic: it lints off the proto_library’s transitive FileDescriptorSet (--descriptor-set-in), so you never pass --proto-path or pull googleapis sources in by hand. Target files are named by their import path, so AIP-191 passes when the layout is idiomatic (see the strip_import_prefix note).

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.

AtomResolved hereElsewhere
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#

rules_aip in the registry graph — what it depends on (left) and what depends on it (right).

Depends on

bazel_skylib1.8.2protobuf33.4googleapis0.0.0-20260422-20ac242aplatforms1.0.0

Used by (3 in the registry)

Versions#

5 published versions, newest first. Each resolves to an immutable, integrity-checked archive.

VersionIntegrity (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_lint resolves protoc from the proto toolchain instead of naming @protobuf//:protoc. That label is the from-source cc_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_protoc was inert, because it redirects the toolchain the rule never consulted.

    The cost was concrete: aion/e2e carried a toolchains_llvm block whose only purpose was to satisfy this, and alternated between LLVM 18.1.8 and 20.1.3 — 18 links libtinfo.so.5 and 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 _protoc attribute is removed rather than kept as a fallback. An attr.label default is a dependency edge whether or not the rule reads it, so keeping it would still pull the from-source protoc through analysis — which is exactly what requires the C++ toolchain. Verified by cquery: cc_binary @protobuf//:protoc and protoc_lib_stage1 are no longer in deps(), 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.

← All modules