pw_protobuf: Add Bazel plugin targets and deps

Adds bazel target for the python protoc plugin and includes
com_google_protobuf as a WORKSPACE dependency.

Change-Id: I346474fe8ddf7fec16b7e433596426bba50ba2ff
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/15321
Reviewed-by: Akira Baruah <akirabaruah@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Akira Baruah <akirabaruah@google.com>
diff --git a/.bazelrc b/.bazelrc
index 68e57cd..bd879cb 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,4 +1,25 @@
+# Copyright 2021 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.
+
+# Required for new toolchain resolution API.
 build --incompatible_enable_cc_toolchain_resolution
 
+# Required for combined code coverage reports.
 coverage --experimental_generate_llvm_lcov
-coverage --combined_report=lcov
\ No newline at end of file
+coverage --combined_report=lcov
+
+# Enforces consistent action environment variables. This is important to
+# address Protobuf's rebuild sensitivity on changes to the environment
+# variables.
+build --incompatible_strict_action_env
\ No newline at end of file
diff --git a/WORKSPACE b/WORKSPACE
index 6aa2053..9c25067 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -53,6 +53,22 @@
 
 protobuf_deps()
 
+# Setup tools to build custom grpc rules.
+# Regquired by: pigweed.
+# Used in modules: //pw_protobuf
+http_archive(
+    name = "rules_proto_grpc",
+    sha256 = "5f0f2fc0199810c65a2de148a52ba0aff14d631d4e8202f41aff6a9d590a471b",
+    strip_prefix = "rules_proto_grpc-1.0.2",
+    urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/1.0.2.tar.gz"],
+)
+
+load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains")
+
+rules_proto_grpc_toolchains()
+
+rules_proto_grpc_repos()
+
 # Set up build_bazel_rules_nodejs.
 # Required by: pigweed.
 # Used in modules: //pw_web_ui.
@@ -88,11 +104,14 @@
 
 npm_bazel_karma_dependencies()
 
-load(
-    "@io_bazel_rules_webtesting//web:repositories.bzl",
-    "web_test_repositories",
+http_archive(
+    name = "io_bazel_rules_webtesting",
+    sha256 = "9bb461d5ef08e850025480bab185fd269242d4e533bca75bfb748001ceb343c3",
+    urls = ["https://github.com/bazelbuild/rules_webtesting/releases/download/0.3.3/rules_webtesting.tar.gz"],
 )
 
+load("@io_bazel_rules_webtesting//web:repositories.bzl", "web_test_repositories")
+
 web_test_repositories()
 
 load(
diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
index 18a3843..6f8f3cb 100755
--- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
@@ -221,6 +221,7 @@
     "//pw_allocator/...",
     "//pw_assert/...",
     '//pw_assert_basic/...',
+    '//pw_protobuf/...',
     '//pw_base64/...',
     '//pw_build/...',
     '//pw_chrono/...',
diff --git a/pw_protobuf/BUILD b/pw_protobuf/BUILD
index 40edf6d..d3772ad 100644
--- a/pw_protobuf/BUILD
+++ b/pw_protobuf/BUILD
@@ -1,4 +1,4 @@
-# Copyright 2020 The Pigweed Authors
+# Copyright 2021 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
@@ -12,11 +12,15 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+load("@rules_proto//proto:defs.bzl", "proto_library")
 load(
     "//pw_build:pigweed.bzl",
     "pw_cc_library",
     "pw_cc_test",
 )
+load("@rules_python//python:defs.bzl", "py_binary")
+load("@rules_proto_grpc//:plugin.bzl", "proto_plugin")
+load("//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -46,6 +50,8 @@
     includes = ["public"],
     deps = [
         ":config",
+        "//pw_bytes",
+        "//pw_result",
         "//pw_span",
         "//pw_status",
         "//pw_varint",
@@ -80,12 +86,36 @@
     ],
 )
 
-# TODO(frolv): Figure out how to integrate pw_protobuf codegen into Bazel.
-filegroup(
+proto_library(
+    name = "codegen_test_proto",
+    srcs = [
+        "pw_protobuf_protos/common.proto",
+        "pw_protobuf_test_protos/full_test.proto",
+        "pw_protobuf_test_protos/imported.proto",
+        "pw_protobuf_test_protos/importer.proto",
+        "pw_protobuf_test_protos/non_pw_package.proto",
+        "pw_protobuf_test_protos/proto2.proto",
+        "pw_protobuf_test_protos/repeated.proto",
+    ],
+    strip_import_prefix = "//pw_protobuf",
+)
+
+pw_proto_library(
+    name = "codegen_test_protos_pwpb",
+    deps = [":codegen_test_proto"],
+)
+
+pw_cc_test(
     name = "codegen_test",
     srcs = [
         "codegen_test.cc",
     ],
+    deps = [
+        ":codegen_test_protos_pwpb",
+        ":pw_protobuf",
+        "//pw_span",
+        "//pw_unit_test",
+    ],
 )
 
 # TODO(frolv): Figure out how to add facade tests to Bazel.
@@ -103,3 +133,22 @@
         "size_report/*.cc",
     ]),
 )
+
+py_binary(
+    name = "plugin",
+    srcs = glob(["py/pw_protobuf/*.py"]),
+    imports = ["py"],
+    main = "py/pw_protobuf/plugin.py",
+    python_version = "PY3",
+    deps = ["@com_google_protobuf//:protobuf_python"],
+)
+
+proto_plugin(
+    name = "pw_cc_plugin",
+    outputs = [
+        "{protopath}.pwpb.h",
+    ],
+    protoc_plugin_name = "pwpb",
+    tool = "@pigweed//pw_protobuf:plugin",
+    visibility = ["//visibility:public"],
+)
diff --git a/pw_protobuf_compiler/deps.bzl b/pw_protobuf_compiler/deps.bzl
new file mode 100644
index 0000000..ed6492a
--- /dev/null
+++ b/pw_protobuf_compiler/deps.bzl
@@ -0,0 +1,32 @@
+# Copyright 2021 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.
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+def pw_protobuf_dependencies():
+    """ Fetches workspace dependencies required for //pw_protobuf """
+    if "com_google_protobuf" not in native.existing_rules():
+        http_archive(
+            name = "com_google_protobuf",
+            sha256 = "71030a04aedf9f612d2991c1c552317038c3c5a2b578ac4745267a45e7037c29",
+            strip_prefix = "protobuf-3.12.3",
+            url = "https://github.com/protocolbuffers/protobuf/archive/v3.12.3.tar.gz",
+        )
+    if "rules_proto_grpc" not in native.existing_rules():
+        http_archive(
+            name = "rules_proto_grpc",
+            sha256 = "5f0f2fc0199810c65a2de148a52ba0aff14d631d4e8202f41aff6a9d590a471b",
+            strip_prefix = "rules_proto_grpc-1.0.2",
+            urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/1.0.2.tar.gz"],
+        )
diff --git a/pw_protobuf_compiler/docs.rst b/pw_protobuf_compiler/docs.rst
index eeb1620..fc9ed7b 100644
--- a/pw_protobuf_compiler/docs.rst
+++ b/pw_protobuf_compiler/docs.rst
@@ -302,3 +302,58 @@
 .. code-block:: cpp
 
   #include "my_other_protos/baz.pwpb.h"
+
+Bazel
+=====
+Bazel provides a ``pw_proto_library`` rule with similar features as the
+GN template. The Bazel build only supports building firmware code, so
+``pw_proto_library`` does not generate a Python package. The Bazel rules differ
+slightly compared to the GN build to be more in line with what would be
+considered idiomatic in Bazel.
+
+To use Pigweeds Protobuf rules you must first pull in the required dependencies
+into your Bazel WORKSPACE file. e.g.
+
+.. code-block:: python
+
+  # WORKSPACE ...
+  load("@pigweed//pw_protobuf_compiler:deps.bzl", "pw_protobuf_dependencies")
+  pw_protobuf_dependencies()
+
+Bazel uses a different set of rules to manage proto files than it does to
+compile them. e.g.
+
+.. code-block:: python
+
+  # BUILD ...
+  load("@rules_proto//proto:defs.bzl", "proto_library")
+  load("@pigweed//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
+
+  # Manages proto sources and dependencies.
+  proto_library(
+    name = "my_proto",
+    srcs = [
+      "my_protos/foo.proto",
+      "my_protos/bar.proto",
+    ]
+  )
+
+  # Compiles dependant protos to C++.
+  pw_proto_library(
+    name = "my_cc_proto",
+    deps = [":my_proto"],
+  )
+
+  # Library that depends on generated proto targets.
+  pw_cc_library(
+    name = "my_lib",
+    srcs = ["my/lib.cc"],
+    deps = [":my_cc_proto"],
+  )
+
+From ``my/lib.cc`` you can now include the generated headers.
+e.g.
+
+.. code:: cpp
+
+  #include "my_protos/bar.pwpb.h"
\ No newline at end of file
diff --git a/pw_protobuf_compiler/proto.bzl b/pw_protobuf_compiler/proto.bzl
new file mode 100644
index 0000000..5273c46
--- /dev/null
+++ b/pw_protobuf_compiler/proto.bzl
@@ -0,0 +1,100 @@
+# Copyright 2021 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.
+load("//pw_build:pigweed.bzl", "pw_cc_library")
+load("@rules_proto//proto:defs.bzl", "ProtoInfo")
+load("@rules_proto_grpc//:plugin.bzl", "ProtoPluginInfo")
+load(
+    "@rules_proto_grpc//:aspect.bzl",
+    "ProtoLibraryAspectNodeInfo",
+    "proto_compile_aspect_attrs",
+    "proto_compile_aspect_impl",
+    "proto_compile_attrs",
+    "proto_compile_impl",
+)
+
+# Create aspect for cc_proto_compile
+cc_proto_compile_aspect = aspect(
+    implementation = proto_compile_aspect_impl,
+    provides = [ProtoLibraryAspectNodeInfo],
+    attr_aspects = ["deps"],
+    attrs = dict(
+        proto_compile_aspect_attrs,
+        _plugins = attr.label_list(
+            doc = "List of protoc plugins to apply",
+            providers = [ProtoPluginInfo],
+            default = [
+                Label("@pigweed//pw_protobuf:pw_cc_plugin"),
+            ],
+        ),
+        _prefix = attr.string(
+            doc = "String used to disambiguate aspects when generating outputs",
+            default = "cc_proto_compile_aspect",
+        ),
+    ),
+    toolchains = [str(Label("@rules_proto_grpc//protobuf:toolchain_type"))],
+)
+
+# Create compile rule to apply aspect
+_rule = rule(
+    implementation = proto_compile_impl,
+    attrs = dict(
+        proto_compile_attrs,
+        deps = attr.label_list(
+            mandatory = True,
+            providers = [ProtoInfo, ProtoLibraryAspectNodeInfo],
+            aspects = [cc_proto_compile_aspect],
+        ),
+    ),
+)
+
+# Create macro for converting attrs and passing to compile
+def _cc_proto_compile(**kwargs):
+    _rule(
+        verbose_string = "{}".format(kwargs.get("verbose", 0)),
+        merge_directories = True,
+        **{k: v for k, v in kwargs.items() if k != "merge_directories"}
+    )
+
+def pw_proto_library(**kwargs):
+    """ Embedded friendly replacement for native.cc_proto_library
+
+    This Protobuf implementation is designed to run on embedded
+    computers. Because of this the cc API differs from the standard
+    Protobuf cc plugin. The generated headers in this library are not a drop in
+    replacement for the standard cc_proto_library.
+
+    Args:
+        **kwargs: Equivalent inputs to cc_proto_library
+    """
+
+    # Compile protos
+    name_pb = kwargs.get("name") + "_pb"
+    _cc_proto_compile(
+        name = name_pb,
+        # Forward deps and verbose tags to implementation
+        **{k: v for (k, v) in kwargs.items() if k in ("deps", "verbose")}
+    )
+
+    # Create cc_library
+    pw_cc_library(
+        name = kwargs.get("name"),
+        srcs = [name_pb],
+        deps = [
+            "@pigweed//pw_protobuf",
+        ],
+        includes = [name_pb],
+        strip_include_prefix = ".",
+        visibility = kwargs.get("visibility"),
+        linkstatic = 1,
+    )