Add pw_protobuf_compiler module

This change adds a module responsible for invoking the protobuf compiler
to generate code from .proto definition files. It provides a GN template
which performs the codegen operation on a list of input .proto files.
The template currently only supports C++.

Change-Id: I5558fd5b4282e0d6f09f8fafebda972738dc1e83
diff --git a/pw_protobuf_compiler/proto.gni b/pw_protobuf_compiler/proto.gni
new file mode 100644
index 0000000..d20679a
--- /dev/null
+++ b/pw_protobuf_compiler/proto.gni
@@ -0,0 +1,140 @@
+# Copyright 2019 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.
+
+import("$dir_pw_build/python_script.gni")
+
+# Python script that invokes protoc.
+_gen_script_path =
+    "$dir_pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py"
+
+# Generates C++ code for proto files, creating a source_set of the generated
+# files. This is internal and should not be used outside of this file. Use
+# pw_proto_library instead.
+#
+# Args:
+#  protos: List of input .proto files.
+template("_pw_cc_proto_library") {
+  _proto_gen_dir = "$root_gen_dir/protos"
+  _outputs = process_file_template(
+          invoker.protos,
+          "$root_gen_dir/protos/{{source_root_relative_dir}}/{{source_name_part}}.pb.h")
+
+  _gen_target = "${target_name}_gen"
+  pw_python_script(_gen_target) {
+    script = _gen_script_path
+    args = [
+             "--language",
+             "cc",
+             "--module-path",
+             "//",
+             "--out-dir",
+             _proto_gen_dir,
+           ] + get_path_info(invoker.protos, "abspath")
+    inputs = invoker.protos
+    outputs = _outputs
+  }
+
+  # For C++ proto files, the generated proto directory is added as an include
+  # path for the code. This requires using "all_dependent_configs" to force the
+  # include on any code that transitively depends on the generated protos.
+  _include_root = rebase_path(get_path_info(".", "abspath"), "//")
+  _include_config_target = "${target_name}_includes"
+  config(_include_config_target) {
+    include_dirs = [ "$_proto_gen_dir/$_include_root" ]
+  }
+
+  # Create a library with the generated source files.
+  # TODO(frolv): This currently only supports pw_protobuf, which is header-only.
+  # Figure out how to support .cc files.
+  source_set(target_name) {
+    all_dependent_configs = [ ":$_include_config_target" ]
+    deps = [ ":$_gen_target" ] + invoker.deps
+    sources = get_target_outputs(":$_gen_target")
+  }
+}
+
+# Generates protobuf code from .proto definitions for various languages.
+#
+# The languages to generate are defined in the pw_protobuf_langs build variable.
+# Each listed language creates a generated code target called
+#
+#   <target_name>_<language>
+#
+# For example, with the following definitions:
+#
+#   pw_protobuf_langs = [ "cc", "py" ]
+#
+#   pw_proto_library("my_protos") {
+#     sources = [ "foo.proto" ]
+#   }
+#
+# Two build targets will be created for the declared "my_protos" target.
+#
+#   "my_protos_cc"  <-- C++ source_set containing generated proto code
+#   "my_protos_py"  <-- Python module containing generated proto code
+#
+# Args:
+#  sources: List of input .proto files.
+#  deps: List of other pw_proto_library dependencies.
+#
+# TODO(frolv): Provide a way to set the protoc plugin for different languages.
+template("pw_proto_library") {
+  assert(defined(invoker.sources) && invoker.sources != [],
+         "pw_proto_codegen requires .proto source files")
+
+  foreach(lang, pw_protobuf_langs) {
+    if (defined(invoker.deps)) {
+      _lang_deps = process_file_template(invoker.deps, "{{source}}_${lang}")
+    } else {
+      _lang_deps = []
+    }
+    _lang_target = "${target_name}_${lang}"
+
+    if (lang == "cc") {
+      _pw_cc_proto_library(_lang_target) {
+        protos = invoker.sources
+        deps = _lang_deps
+      }
+    } else {
+      assert(false,
+             string_join(
+                 " ",
+                 [
+                   "pw_proto_codegen doesn't know how to generate code for",
+                   "language '$lang'. Please add support if you require it.",
+                 ]))
+    }
+  }
+
+  # If the user attempts to use the target directly instead of one of the
+  # language targets, run a script which prints a nice error message.
+  pw_python_script(target_name) {
+    script = string_join("/",
+                         [
+                           dir_pw_protobuf_compiler,
+                           "py",
+                           "pw_protobuf_compiler",
+                           "proto_target_invalid.py",
+                         ])
+    args = [
+             "--target",
+             target_name,
+             "--dir",
+             get_path_info(".", "abspath"),
+             "--root",
+             "//",
+           ] + pw_protobuf_langs
+    stamp = true
+  }
+}