| # Copyright 2026 Google LLC | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # https://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| """Starlark build rules for CBOR code generation.""" | |
| load("@protobuf//bazel/common:proto_info.bzl", "ProtoInfo") | |
| load("@rules_cc//cc:defs.bzl", "cc_library") | |
| _PROTOC = "@protobuf//:protoc" | |
| _PLUGIN = "//cbor:cbor_generator_main" | |
| _CLANG_FORMAT = "@protobuf//:protoc" | |
| def cc_cbor_proto_library( | |
| *, | |
| name, | |
| srcs, | |
| deps = [], | |
| **kwargs): | |
| """Generates C++ CBOR code from a proto_library. | |
| Args: | |
| name: name of the generated cc_library | |
| srcs: proto_library rules that wrap the *.proto files; must contain exactly one entry | |
| deps: cc_proto_library rules that wrap the proto_library above | |
| **kwargs: extra attributes for the generated cc_library | |
| """ | |
| if len(srcs) != 1: | |
| fail("srcs must contain exactly one entry", attr = "srcs") | |
| proto_rule_name = srcs[0] | |
| if proto_rule_name.startswith(":"): | |
| proto_rule_name = proto_rule_name[1:] | |
| proto_rule = native.existing_rule(proto_rule_name) | |
| if not proto_rule: | |
| fail("'%s' not found. Define it before cbor_proto_library." % proto_rule_name) | |
| if proto_rule["kind"] != "proto_library": | |
| fail("src '%s' has kind '%s', expecting 'proto_library'." % (proto_rule_name, proto_rule["kind"])) | |
| proto_srcs = list(proto_rule["srcs"]) | |
| generated_srcs = [src.removesuffix(".proto") + ".cbor.cc" for src in proto_srcs] | |
| generated_hdrs = [src.removesuffix(".proto") + ".cbor.h" for src in proto_srcs] | |
| # Automatically infer corresponding `_cbor_proto` dependencies for the `proto_library` dependencies | |
| # within the same package. For example, if the proto_library depends on :foo_proto, we check if | |
| # :foo_cbor_proto exists in the same package and add it to ensure transitive header resolution. | |
| auto_deps = [] | |
| current_pkg = "//" + native.package_name() + ":" | |
| for dep in proto_rule.get("deps", []): | |
| target_name = None | |
| if dep.startswith(current_pkg): | |
| target_name = dep.removeprefix(current_pkg) | |
| elif dep.startswith(":"): | |
| target_name = dep[1:] | |
| if target_name and target_name.endswith("_proto"): | |
| cbor_name = target_name.removesuffix("_proto") + "_cbor_proto" | |
| if native.existing_rule(cbor_name): | |
| auto_deps.append(":" + cbor_name) | |
| generated_target_name = name + "_gen" | |
| _generate_cbor_code( | |
| name = generated_target_name, | |
| srcs = srcs, | |
| generated_files = generated_srcs + generated_hdrs, | |
| visibility = ["//visibility:private"], | |
| ) | |
| cc_library( | |
| name = name, | |
| srcs = generated_srcs, | |
| hdrs = generated_hdrs, | |
| deps = deps + auto_deps + [ | |
| "//cbor", | |
| "@abseil-cpp//absl/status", | |
| "@abseil-cpp//absl/status:status_macros", | |
| "@abseil-cpp//absl/status:statusor", | |
| "@libcppbor", | |
| ], | |
| **kwargs | |
| ) | |
| def _generate_cbor_code_impl(ctx): | |
| proto_sources = depset( | |
| [f for dep in ctx.attr.srcs for f in dep[ProtoInfo].direct_sources], | |
| ) | |
| proto_imports = depset( | |
| transitive = [dep[ProtoInfo].transitive_sources for dep in ctx.attr.srcs], | |
| ) | |
| unique_roots = {} | |
| all_files = depset(transitive = [proto_imports, proto_sources]).to_list() | |
| for f in all_files: | |
| idx = f.path.rfind("google/protobuf/") | |
| if idx != -1: | |
| root = f.path[:idx] | |
| if root: | |
| unique_roots[root] = True | |
| protoc_paths = ["-I."] + ["-I" + r for r in unique_roots] + ["-I" + f.short_path + "=" + f.path for f in all_files] | |
| args = ctx.actions.args() | |
| args.add("--plugin=protoc-gen-cbor=" + ctx.executable._plugin.path) | |
| args.add_all(protoc_paths) | |
| args.add("--cbor_out", ctx.bin_dir.path) | |
| args.add_all(proto_sources) | |
| args.set_param_file_format("multiline") | |
| args.use_param_file(param_file_arg = "@%s") | |
| output_paths = [f.path for f in ctx.outputs.generated_files] | |
| outputs_str = " ".join(output_paths) | |
| command = "protoc_path=$1; clang_format_path=$2; shift 2; \"$protoc_path\" \"$@\"" | |
| ctx.actions.run_shell( | |
| arguments = [ctx.executable._proto_compiler.path, ctx.executable._clang_format.path, args], | |
| command = command, | |
| inputs = depset(transitive = [proto_sources, proto_imports]), | |
| mnemonic = "GenerateCborCode", | |
| outputs = ctx.outputs.generated_files, | |
| progress_message = "Generating and formatting CBOR code for %{label}", | |
| tools = depset([ | |
| ctx.executable._proto_compiler, | |
| ctx.executable._plugin, | |
| ctx.executable._clang_format, | |
| ]), | |
| ) | |
| return [DefaultInfo(files = depset(ctx.outputs.generated_files))] | |
| _generate_cbor_code = rule( | |
| attrs = { | |
| "srcs": attr.label_list( | |
| allow_empty = False, | |
| mandatory = True, | |
| providers = [ProtoInfo], | |
| ), | |
| "_proto_compiler": attr.label( | |
| default = Label(_PROTOC), | |
| executable = True, | |
| cfg = "exec", | |
| ), | |
| "_plugin": attr.label( | |
| default = Label(_PLUGIN), | |
| executable = True, | |
| cfg = "exec", | |
| ), | |
| "_clang_format": attr.label( | |
| default = Label(_CLANG_FORMAT), | |
| executable = True, | |
| cfg = "exec", | |
| ), | |
| "generated_files": attr.output_list(), | |
| }, | |
| implementation = _generate_cbor_code_impl, | |
| ) |