525 lines
18 KiB
Python
525 lines
18 KiB
Python
# Copyright 2022 The Pigweed Authors
|
|
#
|
|
# 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.
|
|
"""WORK IN PROGRESS!
|
|
|
|
This is intended to be a replacement for the proto codegen in proto.bzl, which
|
|
relies on the transitive proto compilation support removed from newer versions
|
|
of rules_proto_grpc. However, the version checked in here does not yet support,
|
|
|
|
1. Proto libraries with dependencies in external repositories.
|
|
2. Proto libraries with strip_import_prefix or import_prefix attributes.
|
|
|
|
In addition, nanopb proto files are not yet generated.
|
|
|
|
TODO(b/234873954): Close these gaps and start using this implementation.
|
|
|
|
# Overview of implementation
|
|
|
|
(If you just want to use the macros, see their docstrings; this section is
|
|
intended to orient future maintainers.)
|
|
|
|
Proto code generation is carried out by the _pw_proto_library,
|
|
_pw_raw_rpc_proto_library and _pw_nanopb_rpc_proto_library rules using aspects
|
|
(https://docs.bazel.build/versions/main/skylark/aspects.html). A
|
|
_pw_proto_library has a single proto_library as a dependency, but that
|
|
proto_library may depend on other proto_library targets; as a result, the
|
|
generated .pwpb.h file #include's .pwpb.h files generated from the dependency
|
|
proto_libraries. The aspect propagates along the proto_library dependency
|
|
graph, running the proto compiler on each proto_library in the original
|
|
target's transitive dependencies, ensuring that we're not missing any .pwpb.h
|
|
files at C++ compile time.
|
|
|
|
Although we have a separate rule for each protocol compiler plugin
|
|
(_pw_proto_library, _pw_raw_rpc_proto_library, _pw_nanopb_rpc_proto_library),
|
|
they actually share an implementation (_pw _impl_pw_proto_library) and use
|
|
similar aspects, all generated by _proto_compiler_aspect.
|
|
"""
|
|
|
|
load("//pw_build:pigweed.bzl", "pw_cc_library")
|
|
load("@rules_proto//proto:defs.bzl", "ProtoInfo")
|
|
load("//pw_protobuf_compiler:pw_nanopb_cc_library.bzl", "pw_nanopb_cc_library")
|
|
|
|
def pwpb_proto_library(name, deps, tags = None, visibility = None):
|
|
"""A C++ proto library generated using pw_protobuf.
|
|
|
|
Attributes:
|
|
deps: proto_library targets for which to generate this library.
|
|
"""
|
|
name_pb = name + ".pb"
|
|
|
|
_pw_proto_library(
|
|
name = name_pb,
|
|
deps = deps,
|
|
)
|
|
|
|
pw_cc_library(
|
|
name = name,
|
|
hdrs = [":" + name_pb],
|
|
deps = [
|
|
"//pw_assert:facade",
|
|
"//pw_containers:vector",
|
|
"//pw_preprocessor",
|
|
"//pw_protobuf",
|
|
"//pw_result",
|
|
"//pw_span",
|
|
"//pw_status",
|
|
"//pw_string:string",
|
|
],
|
|
linkstatic = 1,
|
|
tags = tags,
|
|
visibility = visibility,
|
|
)
|
|
|
|
def pwpb_rpc_proto_library(name, deps, pwpb_proto_library_deps, tags = None, visibility = None):
|
|
"""A pwpb_rpc proto library target.
|
|
|
|
Attributes:
|
|
deps: proto_library targets for which to generate this library.
|
|
pwpb_proto_library_deps: A pwpb_proto_library generated
|
|
from the same proto_library. Required.
|
|
"""
|
|
name_pb = name + ".pb"
|
|
|
|
_pw_pwpb_rpc_proto_library(
|
|
name = name_pb,
|
|
deps = deps,
|
|
)
|
|
|
|
pw_cc_library(
|
|
name = name,
|
|
hdrs = [":" + name_pb],
|
|
deps = [
|
|
"//pw_protobuf",
|
|
"//pw_rpc",
|
|
"//pw_rpc/pwpb:client_api",
|
|
"//pw_rpc/pwpb:server_api",
|
|
] + pwpb_proto_library_deps,
|
|
linkstatic = 1,
|
|
tags = tags,
|
|
visibility = visibility,
|
|
)
|
|
|
|
def raw_rpc_proto_library(name, deps, tags = None, visibility = None):
|
|
"""A raw C++ RPC proto library."""
|
|
name_pb = name + ".pb"
|
|
|
|
_pw_raw_rpc_proto_library(
|
|
name = name_pb,
|
|
deps = deps,
|
|
)
|
|
|
|
pw_cc_library(
|
|
name = name,
|
|
hdrs = [":" + name_pb],
|
|
deps = [
|
|
"//pw_rpc",
|
|
"//pw_rpc/raw:client_api",
|
|
"//pw_rpc/raw:server_api",
|
|
],
|
|
linkstatic = 1,
|
|
tags = tags,
|
|
visibility = visibility,
|
|
)
|
|
|
|
def nanopb_rpc_proto_library(name, deps, nanopb_proto_library_deps, tags = None, visibility = None):
|
|
"""A C++ RPC proto library using nanopb.
|
|
|
|
Attributes:
|
|
deps: proto_library targets for which to generate this library.
|
|
nanopb_proto_library_deps: A pw_nanopb_cc_library generated
|
|
from the same proto_library. Required.
|
|
"""
|
|
name_pb = name + ".pb"
|
|
|
|
_pw_nanopb_rpc_proto_library(
|
|
name = name_pb,
|
|
deps = deps,
|
|
)
|
|
|
|
pw_cc_library(
|
|
name = name,
|
|
hdrs = [":" + name_pb],
|
|
deps = [
|
|
"//pw_rpc",
|
|
"//pw_rpc/nanopb:client_api",
|
|
"//pw_rpc/nanopb:server_api",
|
|
] + nanopb_proto_library_deps,
|
|
linkstatic = 1,
|
|
tags = tags,
|
|
visibility = visibility,
|
|
)
|
|
|
|
def pw_proto_library(
|
|
name,
|
|
deps,
|
|
visibility = None,
|
|
tags = None,
|
|
nanopb_options = None,
|
|
enabled_targets = None):
|
|
"""Generate Pigweed proto C++ code.
|
|
|
|
DEPRECATED. This macro is deprecated and will be removed in a future
|
|
Pigweed version. Please use the single-target macros above.
|
|
|
|
Args:
|
|
name: The name of the target.
|
|
deps: proto_library targets from which to generate Pigweed C++.
|
|
visibility: The visibility of the target. See
|
|
https://bazel.build/concepts/visibility.
|
|
tags: Tags for the target. See
|
|
https://bazel.build/reference/be/common-definitions#common-attributes.
|
|
nanopb_options: path to file containing nanopb options, if any
|
|
(https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options).
|
|
enabled_targets: Specifies which libraries should be generated. Libraries
|
|
will only be generated as needed, but unnecessary outputs may conflict
|
|
with other build rules and thus cause build failures. This filter allows
|
|
manual selection of which libraries should be supported by this build
|
|
target in order to prevent such conflicts. The argument, if provided,
|
|
should be a subset of ["pwpb", "nanopb", "raw_rpc", "nanopb_rpc"]. All
|
|
are enabled by default. Note that "nanopb_rpc" relies on "nanopb".
|
|
|
|
Example usage:
|
|
|
|
proto_library(
|
|
name = "benchmark_proto",
|
|
srcs = [
|
|
"benchmark.proto",
|
|
],
|
|
)
|
|
|
|
pw_proto_library(
|
|
name = "benchmark_pw_proto",
|
|
deps = [":benchmark_proto"],
|
|
)
|
|
|
|
pw_cc_binary(
|
|
name = "proto_user",
|
|
srcs = ["proto_user.cc"],
|
|
deps = [":benchmark_pw_proto.pwpb"],
|
|
)
|
|
|
|
The pw_proto_library generates the following targets in this example:
|
|
|
|
"benchmark_pw_proto.pwpb": C++ library exposing the "benchmark.pwpb.h" header.
|
|
"benchmark_pw_proto.pwpb_rpc": C++ library exposing the
|
|
"benchmark.rpc.pwpb.h" header.
|
|
"benchmark_pw_proto.raw_rpc": C++ library exposing the "benchmark.raw_rpc.h"
|
|
header.
|
|
"benchmark_pw_proto.nanopb": C++ library exposing the "benchmark.pb.h"
|
|
header.
|
|
"benchmark_pw_proto.nanopb_rpc": C++ library exposing the
|
|
"benchmark.rpc.pb.h" header.
|
|
"""
|
|
|
|
def is_plugin_enabled(plugin):
|
|
return (enabled_targets == None or plugin in enabled_targets)
|
|
|
|
if is_plugin_enabled("nanopb"):
|
|
# Use nanopb to generate the pb.h and pb.c files, and the target
|
|
# exposing them.
|
|
pw_nanopb_cc_library(
|
|
name = name + ".nanopb",
|
|
deps = deps,
|
|
visibility = visibility,
|
|
tags = tags,
|
|
options = nanopb_options,
|
|
)
|
|
|
|
if is_plugin_enabled("pwpb"):
|
|
pwpb_proto_library(
|
|
name = name + ".pwpb",
|
|
deps = deps,
|
|
tags = tags,
|
|
visibility = visibility,
|
|
)
|
|
|
|
if is_plugin_enabled("pwpb_rpc"):
|
|
pwpb_rpc_proto_library(
|
|
name = name + ".pwpb_rpc",
|
|
deps = deps,
|
|
pwpb_proto_library_deps = [":" + name + ".pwpb"],
|
|
tags = tags,
|
|
visibility = visibility,
|
|
)
|
|
|
|
if is_plugin_enabled("raw_rpc"):
|
|
raw_rpc_proto_library(
|
|
name = name + ".raw_rpc",
|
|
deps = deps,
|
|
tags = tags,
|
|
visibility = visibility,
|
|
)
|
|
|
|
if is_plugin_enabled("nanopb_rpc"):
|
|
nanopb_rpc_proto_library(
|
|
name = name + ".nanopb_rpc",
|
|
deps = deps,
|
|
nanopb_proto_library_deps = [":" + name + ".nanopb"],
|
|
tags = tags,
|
|
visibility = visibility,
|
|
)
|
|
|
|
PwProtoInfo = provider(
|
|
"Returned by PW proto compilation aspect",
|
|
fields = {
|
|
"genfiles": "generated C++ header files",
|
|
},
|
|
)
|
|
|
|
PwProtoOptionsInfo = provider(
|
|
"Allows `pw_proto_filegroup` targets to pass along `.options` files " +
|
|
"without polluting the `DefaultInfo` provider, which means they can " +
|
|
"still be used in the `srcs` of `proto_library` targets.",
|
|
fields = {
|
|
"options_files": (".options file(s) associated with a proto_library " +
|
|
"for Pigweed codegen."),
|
|
},
|
|
)
|
|
|
|
def _get_short_path(source):
|
|
return source.short_path
|
|
|
|
def _get_path(file):
|
|
return file.path
|
|
|
|
def _proto_compiler_aspect_impl(target, ctx):
|
|
# List the files we will generate for this proto_library target.
|
|
genfiles = []
|
|
|
|
for src in target[ProtoInfo].direct_sources:
|
|
path = src.basename[:-len("proto")] + ctx.attr._extension
|
|
genfiles.append(ctx.actions.declare_file(path, sibling = src))
|
|
|
|
# List the `.options` files from any `pw_proto_filegroup` targets listed
|
|
# under this target's `srcs`.
|
|
options_files = [
|
|
options_file
|
|
for src in ctx.rule.attr.srcs
|
|
if PwProtoOptionsInfo in src
|
|
for options_file in src[PwProtoOptionsInfo].options_files.to_list()
|
|
]
|
|
|
|
# Convert include paths to a depset and back to deduplicate entries.
|
|
# Note that this will probably evaluate to either [] or ["."] in most cases.
|
|
options_file_include_paths = depset([
|
|
"." if options_file.root.path == "" else options_file.root.path
|
|
for options_file in options_files
|
|
]).to_list()
|
|
|
|
args = ctx.actions.args()
|
|
args.add("--plugin=protoc-gen-pwpb={}".format(ctx.executable._protoc_plugin.path))
|
|
for options_file_include_path in options_file_include_paths:
|
|
args.add("--pwpb_opt=-I{}".format(options_file_include_path))
|
|
args.add("--pwpb_opt=--no-legacy-namespace")
|
|
args.add("--pwpb_out={}".format(ctx.bin_dir.path))
|
|
args.add_joined(
|
|
"--descriptor_set_in",
|
|
target[ProtoInfo].transitive_descriptor_sets,
|
|
join_with = ctx.configuration.host_path_separator,
|
|
map_each = _get_path,
|
|
)
|
|
|
|
args.add_all(target[ProtoInfo].direct_sources, map_each = _get_short_path)
|
|
|
|
ctx.actions.run(
|
|
inputs = depset(
|
|
target[ProtoInfo].transitive_sources.to_list() + options_files,
|
|
transitive = [target[ProtoInfo].transitive_descriptor_sets],
|
|
),
|
|
progress_message = "Generating %s C++ files for %s" % (ctx.attr._extension, ctx.label.name),
|
|
tools = [ctx.executable._protoc_plugin],
|
|
outputs = genfiles,
|
|
executable = ctx.executable._protoc,
|
|
arguments = [args],
|
|
)
|
|
|
|
transitive_genfiles = genfiles
|
|
for dep in ctx.rule.attr.deps:
|
|
transitive_genfiles += dep[PwProtoInfo].genfiles
|
|
return [PwProtoInfo(genfiles = transitive_genfiles)]
|
|
|
|
def _proto_compiler_aspect(extension, protoc_plugin):
|
|
"""Returns an aspect that runs the proto compiler.
|
|
|
|
The aspect propagates through the deps of proto_library targets, running
|
|
the proto compiler with the specified plugin for each of their source
|
|
files. The proto compiler is assumed to produce one output file per input
|
|
.proto file. That file is placed under bazel-bin at the same path as the
|
|
input file, but with the specified extension (i.e., with _extension =
|
|
.pwpb.h, the aspect converts pw_log/log.proto into
|
|
bazel-bin/pw_log/log.pwpb.h).
|
|
|
|
The aspect returns a provider exposing all the File objects generated from
|
|
the dependency graph.
|
|
"""
|
|
return aspect(
|
|
attr_aspects = ["deps"],
|
|
attrs = {
|
|
"_extension": attr.string(default = extension),
|
|
"_protoc": attr.label(
|
|
default = Label("@com_google_protobuf//:protoc"),
|
|
executable = True,
|
|
cfg = "exec",
|
|
),
|
|
"_protoc_plugin": attr.label(
|
|
default = Label(protoc_plugin),
|
|
executable = True,
|
|
cfg = "exec",
|
|
),
|
|
},
|
|
implementation = _proto_compiler_aspect_impl,
|
|
provides = [PwProtoInfo],
|
|
)
|
|
|
|
def _impl_pw_proto_library(ctx):
|
|
"""Implementation of the proto codegen rule.
|
|
|
|
The work of actually generating the code is done by the aspect, so here we
|
|
just gather up all the generated files and return them.
|
|
"""
|
|
|
|
# Note that we don't distinguish between the files generated from the
|
|
# target, and the files generated from its dependencies. We return all of
|
|
# them together, and in pw_proto_library expose all of them as hdrs.
|
|
# Pigweed's plugins happen to only generate .h files, so this works, but
|
|
# strictly speaking we should expose only the files generated from the
|
|
# target itself in hdrs, and place the headers generated from dependencies
|
|
# in srcs. We don't perform layering_check in Pigweed, so this is not a big
|
|
# deal.
|
|
#
|
|
# TODO(b/234873954): Tidy this up.
|
|
all_genfiles = []
|
|
for dep in ctx.attr.deps:
|
|
for f in dep[PwProtoInfo].genfiles:
|
|
all_genfiles.append(f)
|
|
|
|
return [DefaultInfo(files = depset(all_genfiles))]
|
|
|
|
# Instantiate the aspects and rules for generating code using specific plugins.
|
|
_pw_proto_compiler_aspect = _proto_compiler_aspect("pwpb.h", "//pw_protobuf/py:plugin")
|
|
|
|
_pw_proto_library = rule(
|
|
implementation = _impl_pw_proto_library,
|
|
attrs = {
|
|
"deps": attr.label_list(
|
|
providers = [ProtoInfo],
|
|
aspects = [_pw_proto_compiler_aspect],
|
|
),
|
|
},
|
|
)
|
|
|
|
_pw_pwpb_rpc_proto_compiler_aspect = _proto_compiler_aspect("rpc.pwpb.h", "//pw_rpc/py:plugin_pwpb")
|
|
|
|
_pw_pwpb_rpc_proto_library = rule(
|
|
implementation = _impl_pw_proto_library,
|
|
attrs = {
|
|
"deps": attr.label_list(
|
|
providers = [ProtoInfo],
|
|
aspects = [_pw_pwpb_rpc_proto_compiler_aspect],
|
|
),
|
|
},
|
|
)
|
|
|
|
_pw_raw_rpc_proto_compiler_aspect = _proto_compiler_aspect("raw_rpc.pb.h", "//pw_rpc/py:plugin_raw")
|
|
|
|
_pw_raw_rpc_proto_library = rule(
|
|
implementation = _impl_pw_proto_library,
|
|
attrs = {
|
|
"deps": attr.label_list(
|
|
providers = [ProtoInfo],
|
|
aspects = [_pw_raw_rpc_proto_compiler_aspect],
|
|
),
|
|
},
|
|
)
|
|
|
|
_pw_nanopb_rpc_proto_compiler_aspect = _proto_compiler_aspect("rpc.pb.h", "//pw_rpc/py:plugin_nanopb")
|
|
|
|
_pw_nanopb_rpc_proto_library = rule(
|
|
implementation = _impl_pw_proto_library,
|
|
attrs = {
|
|
"deps": attr.label_list(
|
|
providers = [ProtoInfo],
|
|
aspects = [_pw_nanopb_rpc_proto_compiler_aspect],
|
|
),
|
|
},
|
|
)
|
|
|
|
def _pw_proto_filegroup_impl(ctx):
|
|
source_files = list()
|
|
options_files = list()
|
|
|
|
for src in ctx.attr.srcs:
|
|
source_files += src.files.to_list()
|
|
|
|
for options_src in ctx.attr.options_files:
|
|
for file in options_src.files.to_list():
|
|
if file.extension == "options":
|
|
options_files.append(file)
|
|
else:
|
|
fail((
|
|
"Files provided as `options_files` to a " +
|
|
"`pw_proto_filegroup` must have the `.options` " +
|
|
"extension; the file `{}` was provided."
|
|
).format(file.basename))
|
|
|
|
return [
|
|
DefaultInfo(files = depset(source_files)),
|
|
PwProtoOptionsInfo(options_files = depset(options_files)),
|
|
]
|
|
|
|
pw_proto_filegroup = rule(
|
|
doc = (
|
|
"Acts like a `filegroup`, but with an additional `options_files` " +
|
|
"attribute that accepts a list of `.options` files. These `.options` " +
|
|
"files should typically correspond to `.proto` files provided under " +
|
|
"the `srcs` attribute." +
|
|
"\n\n" +
|
|
"A `pw_proto_filegroup` is intended to be passed into the `srcs` of " +
|
|
"a `proto_library` target as if it were a normal `filegroup` " +
|
|
"containing only `.proto` files. For the purposes of the " +
|
|
"`proto_library` itself, the `pw_proto_filegroup` does indeed act " +
|
|
"just like a normal `filegroup`; the `options_files` attribute is " +
|
|
"ignored. However, if that `proto_library` target is then passed " +
|
|
"(directly or transitively) into the `deps` of a `pw_proto_library` " +
|
|
"for code generation, the `pw_proto_library` target will have access " +
|
|
"to the provided `.options` files and will pass them to the code " +
|
|
"generator." +
|
|
"\n\n" +
|
|
"Note that, in order for a `pw_proto_filegroup` to be a valid `srcs` " +
|
|
"entry for a `proto_library`, it must meet the same conditions " +
|
|
"required of a standard `filegroup` in that context. Namely, its " +
|
|
"`srcs` must provide at least one `.proto` (or `.protodevel`) file. " +
|
|
"Put simply, a `pw_proto_filegroup` cannot be used as a vector for " +
|
|
"injecting solely `.options` files; it must contain at least one " +
|
|
"proto as well (generally one associated with an included `.options` " +
|
|
"file in the interest of clarity)." +
|
|
"\n\n" +
|
|
"Regarding the somewhat unusual usage, this feature's design was " +
|
|
"mostly preordained by the combination of Bazel's strict access " +
|
|
"controls, the restrictions imposed on inputs to the `proto_library` " +
|
|
"rule, and the need to support `.options` files from transitive " +
|
|
"dependencies."
|
|
),
|
|
implementation = _pw_proto_filegroup_impl,
|
|
attrs = {
|
|
"srcs": attr.label_list(
|
|
allow_files = True,
|
|
),
|
|
"options_files": attr.label_list(
|
|
allow_files = True,
|
|
),
|
|
},
|
|
provides = [PwProtoOptionsInfo],
|
|
)
|