pw_protobuf_compiler: Support standalone external protos
Projects may want to use externally defined proto files that are not
organized for Python packaging as required by pw_proto_library. This
change makes it possible to support standalone, externally defined
protobufs, such as nanopb.proto.
Change-Id: I4ae053a950c664878150d911cea9e9de031bb20d
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/31800
Reviewed-by: Alexei Frolov <frolv@google.com>
diff --git a/pw_protobuf_compiler/proto.gni b/pw_protobuf_compiler/proto.gni
index 1478a64..ce297c6 100644
--- a/pw_protobuf_compiler/proto.gni
+++ b/pw_protobuf_compiler/proto.gni
@@ -54,8 +54,8 @@
args = [
"--language",
invoker.language,
- "--module-path",
- rebase_path("."),
+ "--include-path",
+ rebase_path(invoker.include_path),
"--include-file",
_output[0],
"--out-dir",
@@ -69,17 +69,11 @@
args += [ "--plugin-path=" + rebase_path(invoker.plugin) ]
}
- if (defined(invoker.include_paths)) {
- args += [
- "--include-paths",
- string_join(";", rebase_path(invoker.include_paths)),
- ]
- }
-
outputs = []
foreach(extension, invoker.output_extensions) {
foreach(proto,
- rebase_path(invoker.sources, get_path_info(".", "abspath"))) {
+ rebase_path(invoker.sources,
+ get_path_info(invoker.include_path, "abspath"))) {
_output = string_replace(proto, ".proto", extension)
outputs += [ "${invoker.gen_dir}/$_output" ]
}
@@ -127,7 +121,6 @@
language = "nanopb_rpc"
plugin = "$dir_pw_rpc/py/pw_rpc/plugin_nanopb.py"
python_deps = [ "$dir_pw_rpc/py" ]
- include_paths = [ "$dir_pw_third_party_nanopb/generator/proto" ]
output_extensions = [ ".rpc.pb.h" ]
}
@@ -156,7 +149,6 @@
forward_variables_from(invoker, "*", _forwarded_vars)
language = "nanopb"
plugin = "$dir_pw_third_party_nanopb/generator/protoc-gen-nanopb"
- include_paths = [ "$dir_pw_third_party_nanopb/generator/proto" ]
output_extensions = [
".pb.h",
".pb.c",
@@ -230,12 +222,24 @@
# file. Use pw_proto_library instead.
template("_pw_python_proto_library") {
_target = target_name
- _package_dir = invoker.package_dir
+
+ # For standalone protos (e.g. `import "nanopb.proto"`), nest the proto file in
+ # a directory with the same name for Python packaging purposes.
+ if (invoker.standalone_proto) {
+ _source_name = get_path_info(invoker.sources, "name")
+ _proto_gen_dir = "${invoker.gen_dir}/${_source_name[0]}_pb2"
+ } else {
+ _proto_gen_dir = invoker.gen_dir
+ }
_pw_invoke_protoc(target_name) {
forward_variables_from(invoker, "*", _forwarded_vars)
+ gen_dir = _proto_gen_dir
language = "python"
- output_extensions = [ "_pb2.py" ]
+ output_extensions = [
+ "_pb2.py",
+ "_pb2.pyi",
+ ]
deps += [ "$dir_pw_protobuf_compiler:protobuf_requirements.install" ]
}
@@ -249,9 +253,13 @@
"--setup",
rebase_path(_setup_py),
"--package",
- _package_dir,
+ invoker._package_dir,
] + rebase_path(_generated_files, invoker.gen_dir)
+ if (invoker.standalone_proto) {
+ args += [ "--standalone" ]
+ }
+
public_deps = [ ":$_target._gen" ]
outputs = [ _setup_py ]
}
@@ -276,18 +284,52 @@
# sources: List of input .proto files.
# deps: List of other pw_proto_library dependencies.
# inputs: Other files on which the protos depend (e.g. nanopb .options files).
+# include_path: Sets the proto include path. The default is ".". It is not
+# recommended to set this, unless pulling in an externally defined proto.
#
template("pw_proto_library") {
assert(defined(invoker.sources) && invoker.sources != [],
"pw_proto_library requires .proto source files")
+ _common = {
+ base_target = target_name
+ gen_dir = "$target_gen_dir/$target_name"
+ sources = invoker.sources
+
+ if (defined(invoker.include_path)) {
+ include_path = invoker.include_path
+ } else {
+ include_path = "."
+ }
+ }
+
+ _rebased_sources = rebase_path(invoker.sources, _common.include_path)
+
+ # The pw_proto_library GN target requires protos to be nested under the
+ # include directory unless three conditions are met:
+ #
+ # 1. There is only one .proto file.
+ # 2. The file is in a different directory (an externally defined proto).
+ # 3. The include path is the .proto's include directory. Since there are no
+ # nested directories, the proto cannot be packaged properly in Python.
+ #
+ # When these conditions are met, the proto library is allowed, even though the
+ # proto file is not nested. The Python package for it uses the Python module's
+ # name. This is a special exception to the typical pattern to allow for
+ # working with single, external, standalone protobuf not set up for Python
+ # packaging (such as nanopb.proto).
+ _standalone_proto =
+ _rebased_sources == [ _rebased_sources[0] ] &&
+ _common.include_path != "." &&
+ string_split(_rebased_sources[0], "/") == [ _rebased_sources[0] ]
+
_package_dir = ""
- foreach(_rebased_proto_path, rebase_path(invoker.sources, ".")) {
+ foreach(_rebased_source, _rebased_sources) {
_path_components = []
- _path_components = string_split(_rebased_proto_path, "/")
+ _path_components = string_split(_rebased_source, "/")
- assert(_path_components != [ _rebased_proto_path ] &&
+ assert((_standalone_proto || _path_components != [ _rebased_source ]) &&
_path_components[0] != "..",
"Sources in a pw_proto_library must live in subdirectories " +
"of where it is defined")
@@ -308,12 +350,6 @@
visibility = []
}
- _common = {
- base_target = target_name
- gen_dir = "$target_gen_dir/$target_name"
- sources = invoker.sources
- }
-
if (defined(invoker.deps)) {
_deps = invoker.deps
} else {
@@ -331,7 +367,7 @@
# Indicate this library's base directory for its dependents.
metadata = {
- protoc_includes = [ rebase_path(".") ]
+ protoc_includes = [ rebase_path(_common.include_path) ]
}
}
@@ -368,10 +404,19 @@
deps = process_file_template(_deps, "{{source}}.nanopb_rpc")
}
- _pw_nanopb_proto_library("$target_name.nanopb") {
- forward_variables_from(invoker, _forwarded_vars)
- forward_variables_from(_common, "*")
- deps = process_file_template(_deps, "{{source}}.nanopb")
+ # When compiling with the Nanopb plugin, the nanopb.proto file is already
+ # compiled internally, so skip recompiling it here.
+ if (invoker.sources ==
+ [ "$dir_pw_third_party_nanopb/generator/proto/nanopb.proto" ]) {
+ pw_input_group("$target_name.nanopb") {
+ sources = invoker.sources
+ }
+ } else {
+ _pw_nanopb_proto_library("$target_name.nanopb") {
+ forward_variables_from(invoker, _forwarded_vars)
+ forward_variables_from(_common, "*")
+ deps = process_file_template(_deps, "{{source}}.nanopb")
+ }
}
} else {
pw_error("$target_name.nanopb_rpc") {
@@ -395,6 +440,7 @@
sources = invoker.sources
deps = process_file_template(_deps, "{{source}}.go")
base_target = _common.base_target
+ include_path = _common.include_path
}
_pw_python_proto_library("$target_name.python") {
@@ -402,7 +448,7 @@
forward_variables_from(_common, "*")
deps = process_file_template(_deps, "{{source}}.python")
base_target = _common.base_target
- package_dir = _package_dir
+ standalone_proto = _standalone_proto
}
# All supported pw_protobuf generators.