diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..c1b5199 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,23 @@ +"Bazel module definition for bzlmod" +module( + name = "rules_buf", + version = "0.0.0", # Replaced when publishing + compatibility_level = 1, +) + +bazel_dep(name = "platforms", version = "0.0.4") +# Only needed because rules_proto doesn't provide the protoc toolchain yet. +# TODO: remove in the future +bazel_dep(name = "protobuf", version = "21.7", repo_name = "com_google_protobuf") +bazel_dep(name = "rules_proto", version = "5.3.0-21.7") + +# We depend on gazelle at runtime to generate our proto_library rules +bazel_dep(name = "gazelle", version = "0.33.0") + +# ... and then reach inside to get the gazelle binary from this repository +non_module_deps = use_extension("@gazelle//internal/bzlmod:non_module_deps.bzl", "non_module_deps") +use_repo(non_module_deps, "bazel_gazelle_go_repository_tools") + +ext = use_extension("//buf:extensions.bzl", "buf") +use_repo(ext, "rules_buf_toolchains") +register_toolchains("@rules_buf_toolchains//:all") diff --git a/buf/extensions.bzl b/buf/extensions.bzl new file mode 100644 index 0000000..95d120c --- /dev/null +++ b/buf/extensions.bzl @@ -0,0 +1,78 @@ +"""Define module extensions for using rules_buf with bzlmod. +See https://bazel.build/docs/bzlmod#extension-definition +""" + +load("//buf/internal:toolchain.bzl", "buf_download_releases") +load("//buf/internal:repo.bzl", "buf_dependencies") + +# NB: this should be updated periodically +_DEFAULT_VERSION = "v1.27.0" +_DEFAULT_TOOLCHAIN_NAME = "rules_buf_toolchains" +_DEFAULT_DEPS = "buf_deps" + +dependency = tag_class(attrs = { + "name": attr.string(doc = "name of resulting deps repo", default = _DEFAULT_DEPS), + "module": attr.string(doc = "A module name from the Buf Schema Registry, see https://buf.build/docs/bsr/module/manage"), +}) + +toolchains = tag_class(attrs = { + "name": attr.string(doc = "name of resulting buf toolchains repo", default = _DEFAULT_TOOLCHAIN_NAME), + "version": attr.string(doc = "Version of the buf tool, see https://github.com/bufbuild/buf/releases"), +}) + +def _extension_impl(module_ctx): + registrations = {} + dependencies = {} + + # Iterate over the global modules registered either directly by the user + # or transitively by some other bazel module they use. + for mod in module_ctx.modules: + + # collect all buf.dependency tags, group by name of resulting buf_dependencies repo + for dependency in mod.tags.dependency: + if dependency.name not in dependencies.keys(): + dependencies[dependency.name] = [] + dependencies[dependency.name].append(dependency.module) + + # collect all toolchain versions, group by name of toolchain repo + for toolchains in mod.tags.toolchains: + if toolchains.name != _DEFAULT_TOOLCHAIN_NAME and not mod.is_root: + fail("""\ + Only the root module may override the default name for the buf toolchains. + This prevents conflicting registrations in the global namespace of external repos. + """) + if toolchains.name not in registrations.keys(): + registrations[toolchains.name] = [] + registrations[toolchains.name].append(toolchains.version) + + # Don't require that the user manually registers a toolchain + if len(registrations) == 0: + registrations = {_DEFAULT_TOOLCHAIN_NAME: [_DEFAULT_VERSION]} + + for name, versions in registrations.items(): + if len(versions) > 1: + # TODO: should be semver-aware, using MVS + selected = sorted(versions, reverse = True)[0] + + # buildifier: disable=print + print("NOTE: buf toolchains {} has multiple versions {}, selected {}".format(name, versions, selected)) + else: + selected = versions[0] + buf_download_releases( + name = name, + version = selected, + ) + + for name, modules in dependencies.items(): + buf_dependencies( + name = name, + modules = modules, + ) + +buf = module_extension( + implementation = _extension_impl, + tag_classes = { + "dependency": dependency, + "toolchains": toolchains, + }, +) diff --git a/buf/internal/toolchain.bzl b/buf/internal/toolchain.bzl index a4eae8c..af233ac 100644 --- a/buf/internal/toolchain.bzl +++ b/buf/internal/toolchain.bzl @@ -63,7 +63,7 @@ def declare_buf_toolchains(os, cpu, rules_buf_repo_name): native.toolchain( name = cmd + "_toolchain", toolchain = ":" + toolchain_impl, - toolchain_type = "@{}//tools/{}:toolchain_type".format(rules_buf_repo_name, cmd), + toolchain_type = "@@{}//tools/{}:toolchain_type".format(rules_buf_repo_name, cmd), exec_compatible_with = [ "@platforms//os:" + os, "@platforms//cpu:" + cpu, @@ -202,7 +202,7 @@ def _buf_download_releases_impl(ctx): ) return update_attrs(ctx.attr, ["version", "sha256"], {"version": version, "sha256": sha256}) -_buf_download_releases = repository_rule( +buf_download_releases = repository_rule( implementation = _buf_download_releases_impl, attrs = { "version": attr.string( @@ -229,7 +229,7 @@ def rules_buf_toolchains(name = _TOOLCHAINS_REPO, version = None, sha256 = None, repository_url: The repository url base used for downloads. Defaults to "https://github.com/bufbuild/buf/releases/download" """ - _buf_download_releases(name = name, version = version, sha256 = sha256, repository_url = repository_url) + buf_download_releases(name = name, version = version, sha256 = sha256, repository_url = repository_url) _register_toolchains(name, "buf") _register_toolchains(name, "protoc-gen-buf-breaking") diff --git a/examples/bzlmod/.bazelrc b/examples/bzlmod/.bazelrc new file mode 100644 index 0000000..3ce91d2 --- /dev/null +++ b/examples/bzlmod/.bazelrc @@ -0,0 +1 @@ +common --enable_bzlmod diff --git a/examples/bzlmod/BUILD.bazel b/examples/bzlmod/BUILD.bazel new file mode 100644 index 0000000..4bb7b41 --- /dev/null +++ b/examples/bzlmod/BUILD.bazel @@ -0,0 +1,25 @@ +load("@rules_buf//buf:defs.bzl", "buf_lint_test") +load("@rules_proto//proto:defs.bzl", "proto_library") + +exports_files(["buf.yaml"], visibility = ["//visibility:public"]) + +proto_library( + name = "unused", + srcs = ["unused.proto"], +) + +proto_library( + name = "foo_proto", + srcs = ["file.proto"], + deps = [ + # imports "validate/validate.proto" + "@buf_deps//validate:validate_proto", + ":unused", + ], +) + +buf_lint_test( + name = "foo_proto_lint", + targets = [":foo_proto"], + config = "buf.yaml", +) diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel new file mode 100644 index 0000000..9cf71d1 --- /dev/null +++ b/examples/bzlmod/MODULE.bazel @@ -0,0 +1,19 @@ +"Bazel dependencies" +bazel_dep(name = "rules_buf", dev_dependency = True, version = "0.0.0") + +local_path_override( + module_name = "rules_buf", + path = "../..", +) + +buf = use_extension("@rules_buf//buf:extensions.bzl", "buf") + +# Override the default version of buf +buf.toolchains(version = "v1.26.0") + +# See https://buf.build/docs/build-systems/bazel#buf-dependencies +buf.dependency(module = "buf.build/envoyproxy/protoc-gen-validate:eac44469a7af47e7839a7f1f3d7ac004") +buf.dependency(module = "buf.build/acme/petapis:7abdb7802c8f4737a1a23a35ca8266ef") + +# Allow references to labels under @buf_deps +use_repo(buf, "buf_deps") diff --git a/examples/bzlmod/WORKSPACE b/examples/bzlmod/WORKSPACE new file mode 100644 index 0000000..9f0c4f0 --- /dev/null +++ b/examples/bzlmod/WORKSPACE @@ -0,0 +1 @@ +# Marker that this folder is the root of a Bazel workspace diff --git a/examples/bzlmod/buf.yaml b/examples/bzlmod/buf.yaml new file mode 100644 index 0000000..29fd418 --- /dev/null +++ b/examples/bzlmod/buf.yaml @@ -0,0 +1,4 @@ +version: v1 +lint: + use: + - IMPORT_USED diff --git a/examples/bzlmod/file.proto b/examples/bzlmod/file.proto new file mode 100644 index 0000000..1ce71de --- /dev/null +++ b/examples/bzlmod/file.proto @@ -0,0 +1,4 @@ +syntax = "proto3"; + +import "unused.proto"; +import "validate/validate.proto"; diff --git a/examples/bzlmod/unused.proto b/examples/bzlmod/unused.proto new file mode 100644 index 0000000..d95660e --- /dev/null +++ b/examples/bzlmod/unused.proto @@ -0,0 +1 @@ +syntax = "proto3"; \ No newline at end of file