blob: f9a30484654f3839337a407d01653405a131d644 [file] [log] [blame]
Ted Pudlikdf015c62022-02-02 22:32:26 +00001# Copyright 2022 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7# https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""WORK IN PROGRESS!
15
16This is intended to be a replacement for the proto codegen in proto.bzl, which
17relies on the transitive proto compilation support removed from newer versions
18of rules_proto_grpc. However, the version checked in here does not yet support,
19
201. Proto libraries with dependencies in external repositories.
212. Proto libraries with strip_import_prefix or import_prefix attributes.
22
23In addition, nanopb proto files are not yet generated.
24
25TODO(pwbug/621): Close these gaps and start using this implementation.
26
27# Overview of implementation
28
29(If you just want to use pw_proto_library, see its docstring; this section is
30intended to orient future maintainers.)
31
32Proto code generation is carried out by the _pw_proto_library,
33_pw_raw_rpc_proto_library and _pw_nanopb_rpc_proto_library rules using aspects
34(https://docs.bazel.build/versions/main/skylark/aspects.html). A
35_pw_proto_library has a single proto_library as a dependency, but that
36proto_library may depend on other proto_library targets; as a result, the
37generated .pwpb.h file #include's .pwpb.h files generated from the dependency
38proto_libraries. The aspect propagates along the proto_library dependency
39graph, running the proto compiler on each proto_library in the original
40target's transitive dependencies, ensuring that we're not missing any .pwpb.h
41files at C++ compile time.
42
43Although we have a separate rule for each protocol compiler plugin
44(_pw_proto_library, _pw_raw_rpc_proto_library, _pw_nanopb_rpc_proto_library),
45they actually share an implementation (_pw _impl_pw_proto_library) and use
46similar aspects, all generated by _proto_compiler_aspect. The only difference
47between the rules are captured in the PIGWEED_PLUGIN dictonary and the aspect
48instantiations (_pw_proto_compiler_aspect, etc).
49
50"""
51
52load("//pw_build:pigweed.bzl", "pw_cc_library")
53load("@rules_proto//proto:defs.bzl", "ProtoInfo")
54load("//pw_protobuf_compiler:pw_nanopb_cc_library", "pw_nanopb_cc_library")
55
56def pw_proto_library(name = "", deps = [], nanopb_options = None):
57 """Generate Pigweed proto C++ code.
58
59 This is the only public symbol in this file: everything else is
60 implementation details.
61
62 Args:
63 name: The name of the target.
64 deps: proto_library targets from which to generate Pigweed C++.
65 nanopb_options: path to file containing nanopb options, if any
66 (https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options).
67
68 Example usage:
69
70 proto_library(
71 name = "benchmark_proto",
72 srcs = [
73 "benchmark.proto",
74 ],
75 )
76
77 pw_proto_library(
78 name = "benchmark_pw_proto",
79 deps = [":benchmark_proto"],
80 )
81
82 pw_cc_binary(
83 name = "proto_user",
84 srcs = ["proto_user.cc"],
85 deps = [":benchmark_pw_proto.pwpb"],
86 )
87
88 The pw_proto_library generates the following targets in this example:
89
90 "benchmark_pw_proto.pwpb": C++ library exposing the "benchmark.pwpb.h" header.
91 "benchmark_pw_proto.raw_rpc": C++ library exposing the "benchmark.raw_rpc.h"
92 header.
93 "benchmark_pw_proto.nanopb": C++ library exposing the "benchmark.pb.h"
94 header.
95 "benchmark_pw_proto.nanopb_rpc": C++ library exposing the
96 "benchmark.rpc.pb.h" header.
97 """
98
99 # Use nanopb to generate the pb.h and pb.c files, and the target exposing
100 # them.
101 pw_nanopb_cc_library(name + ".nanopb", deps, options = nanopb_options)
102
103 # Use Pigweed proto plugins to generate the remaining files and targets.
104 for plugin_name, info in PIGWEED_PLUGIN.items():
105 name_pb = name + "_pb." + plugin_name
106 info["compiler"](
107 name = name_pb,
108 deps = deps,
109 )
110
111 # The rpc.pb.h header depends on the generated nanopb code.
112 if info["include_nanopb_dep"]:
113 lib_deps = info["deps"] + [":" + name + ".nanopb"]
114 else:
115 lib_deps = info["deps"]
116
117 pw_cc_library(
118 name = name + "." + plugin_name,
119 hdrs = [name_pb],
120 deps = lib_deps,
121 linkstatic = 1,
122 )
123
124PwProtoInfo = provider(
125 "Returned by PW proto compilation aspect",
126 fields = {
127 "genfiles": "generated C++ header files",
128 },
129)
130
131def _get_short_path(source):
132 return source.short_path
133
134def _get_path(file):
135 return file.path
136
137def _proto_compiler_aspect_impl(target, ctx):
138 # List the files we will generate for this proto_library target.
139 genfiles = []
140 for src in target[ProtoInfo].direct_sources:
141 path = src.basename[:-len("proto")] + ctx.attr._extension
142 genfiles.append(ctx.actions.declare_file(path, sibling = src))
143
144 args = ctx.actions.args()
145 args.add("--plugin=protoc-gen-pwpb={}".format(ctx.executable._protoc_plugin.path))
146 args.add("--pwpb_out={}".format(ctx.bin_dir.path))
147 args.add_joined(
148 "--descriptor_set_in",
149 target[ProtoInfo].transitive_descriptor_sets,
150 join_with = ctx.host_configuration.host_path_separator,
151 map_each = _get_path,
152 )
153 args.add_all(target[ProtoInfo].direct_sources, map_each = _get_short_path)
154
155 ctx.actions.run(
156 inputs = depset(target[ProtoInfo].transitive_sources.to_list(), transitive = [target[ProtoInfo].transitive_descriptor_sets]),
157 progress_message = "Generating %s C++ files for %s" % (ctx.attr._extension, ctx.label.name),
158 tools = [ctx.executable._protoc_plugin],
159 outputs = genfiles,
160 executable = ctx.executable._protoc,
161 arguments = [args],
162 )
163
164 transitive_genfiles = genfiles
165 for dep in ctx.rule.attr.deps:
166 transitive_genfiles += dep[PwProtoInfo].genfiles
167 return [PwProtoInfo(genfiles = transitive_genfiles)]
168
169def _proto_compiler_aspect(extension, protoc_plugin):
170 """Returns an aspect that runs the proto compiler.
171
172 The aspect propagates through the deps of proto_library targets, running
173 the proto compiler with the specified plugin for each of their source
174 files. The proto compiler is assumed to produce one output file per input
175 .proto file. That file is placed under bazel-bin at the same path as the
176 input file, but with the specified extension (i.e., with _extension =
177 .pwpb.h, the aspect converts pw_log/log.proto into
178 bazel-bin/pw_log/log.pwpb.h).
179
180 The aspect returns a provider exposing all the File objects generated from
181 the dependency graph.
182 """
183 return aspect(
184 attr_aspects = ["deps"],
185 attrs = {
186 "_extension": attr.string(default = extension),
187 "_protoc": attr.label(
188 default = Label("@com_google_protobuf//:protoc"),
189 executable = True,
190 cfg = "host",
191 ),
192 "_protoc_plugin": attr.label(
193 default = Label(protoc_plugin),
194 executable = True,
195 cfg = "host",
196 ),
197 },
198 implementation = _proto_compiler_aspect_impl,
199 )
200
201def _impl_pw_proto_library(ctx):
202 """Implementation of the proto codegen rule.
203
204 The work of actually generating the code is done by the aspect, so here we
205 just gather up all the generated files and return them.
206 """
207
208 # Note that we don't distinguish between the files generated from the
209 # target, and the files generated from its dependencies. We return all of
210 # them together, and in pw_proto_library expose all of them as hdrs.
211 # Pigweed's plugins happen to only generate .h files, so this works, but
212 # strictly speaking we should expose only the files generated from the
213 # target itself in hdrs, and place the headers generated from dependencies
214 # in srcs. We don't perform layering_check in Pigweed, so this is not a big
215 # deal.
216 #
217 # TODO(pwbug/621): Tidy this up.
218 all_genfiles = []
219 for dep in ctx.attr.deps:
220 for f in dep[PwProtoInfo].genfiles:
221 all_genfiles.append(f)
222
223 return [DefaultInfo(files = depset(all_genfiles))]
224
225# Instantiate the aspects and rules for generating code using specific plugins.
226_pw_proto_compiler_aspect = _proto_compiler_aspect("pwpb.h", "//pw_protobuf/py:plugin")
227
228_pw_proto_library = rule(
229 implementation = _impl_pw_proto_library,
230 attrs = {
231 "deps": attr.label_list(
232 providers = [ProtoInfo],
233 aspects = [_pw_proto_compiler_aspect],
234 ),
235 },
236)
237
Ted Pudlikc0dc8e02022-02-08 23:10:32 +0000238_pw_raw_rpc_proto_compiler_aspect = _proto_compiler_aspect("raw_rpc.pb.h", "//pw_rpc/py:plugin_raw")
Ted Pudlikdf015c62022-02-02 22:32:26 +0000239
240_pw_raw_rpc_proto_library = rule(
241 implementation = _impl_pw_proto_library,
242 attrs = {
243 "deps": attr.label_list(
244 providers = [ProtoInfo],
245 aspects = [_pw_raw_rpc_proto_compiler_aspect],
246 ),
247 },
248)
249
Ted Pudlikc0dc8e02022-02-08 23:10:32 +0000250_pw_nanopb_rpc_proto_compiler_aspect = _proto_compiler_aspect("rpc.pb.h", "//pw_rpc/py:plugin_nanopb")
Ted Pudlikdf015c62022-02-02 22:32:26 +0000251
252_pw_nanopb_rpc_proto_library = rule(
253 implementation = _impl_pw_proto_library,
254 attrs = {
255 "deps": attr.label_list(
256 providers = [ProtoInfo],
257 aspects = [_pw_nanopb_rpc_proto_compiler_aspect],
258 ),
259 },
260)
261
262PIGWEED_PLUGIN = {
263 "pwpb": {
264 "compiler": _pw_proto_library,
265 "deps": [
266 "//pw_span",
267 "//pw_protobuf:pw_protobuf",
268 ],
269 "include_nanopb_dep": False,
270 },
271 "raw_rpc": {
272 "compiler": _pw_raw_rpc_proto_library,
273 "deps": [
274 "//pw_rpc",
275 "//pw_rpc/raw:client_api",
276 "//pw_rpc/raw:server_api",
277 ],
278 "include_nanopb_dep": False,
279 },
280 "nanopb_rpc": {
281 "compiler": _pw_nanopb_rpc_proto_library,
282 "deps": [
283 "//pw_rpc",
Ted Pudlikc0dc8e02022-02-08 23:10:32 +0000284 "//pw_rpc/nanopb:client_api",
285 "//pw_rpc/nanopb:server_api",
Ted Pudlikdf015c62022-02-02 22:32:26 +0000286 ],
287 "include_nanopb_dep": True,
288 },
289}