Python: Add remaining packages to the build

- Restructure pw_bloat and pw_docgen as Python packages and add them
  to the build.
- Add pw_package and target-specific packages to the build.

Change-Id: I62e857d5c8b069e1923f68fe5f82ef49aa88a326
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/22302
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Alexei Frolov <frolv@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 2ea1172..c72a386 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -118,12 +118,15 @@
     # Python packages
     "$dir_pw_allocator/py",
     "$dir_pw_arduino_build/py",
+    "$dir_pw_bloat/py",
     "$dir_pw_build/py",
     "$dir_pw_cli/py",
+    "$dir_pw_docgen/py",
     "$dir_pw_doctor/py",
     "$dir_pw_env_setup/py",
     "$dir_pw_hdlc_lite/py",
     "$dir_pw_module/py",
+    "$dir_pw_package/py",
     "$dir_pw_presubmit/py",
     "$dir_pw_protobuf_compiler/py",
     "$dir_pw_protobuf/py",
@@ -137,20 +140,24 @@
 
     # Standalone scripts
     "$dir_pw_hdlc_lite/rpc_example:example_script",
-
-    # TODO(pwbug/239): Structure pw_docgen as Python packages.
-    # "$dir_pw_bloat/py",
-    # "$dir_pw_docgen/py",
   ]
 
-  _toolchain = "$_default_toolchain_prefix$pw_default_optimization_level"
+  _py_toolchain = "$_default_toolchain_prefix$pw_default_optimization_level"
 
   python_deps = []
   foreach(dep, _python_gn_targets) {
-    python_deps += [ "$dep($_toolchain)" ]
+    python_deps += [ "$dep($_py_toolchain)" ]
   }
 }
 
+# Python packages for supporting specific targets.
+pw_python_group("target_support_packages") {
+  python_deps = [
+    "$dir_pigweed/targets/lm3s6965evb-qemu/py($dir_pigweed/targets/lm3s6965evb-qemu:lm3s6965evb_qemu_$pw_default_optimization_level)",
+    "$dir_pigweed/targets/stm32f429i-disc1/py($dir_pigweed/targets/stm32f429i-disc1:stm32f429i_disc1_$pw_default_optimization_level)",
+  ]
+}
+
 # By default, Pigweed will build this target when invoking ninja.
 group("pigweed_default") {
   deps = []
@@ -164,6 +171,8 @@
         ":apps",
         ":python.lint",
         ":python.tests",
+        ":target_support_packages.lint",
+        ":target_support_packages.tests",
       ]
       if (pw_unit_test_AUTOMATIC_RUNNER == "") {
         # Without a test runner defined, build the tests but don't run them.
diff --git a/pw_bloat/bloat.gni b/pw_bloat/bloat.gni
index 7302bae..9acfd05 100644
--- a/pw_bloat/bloat.gni
+++ b/pw_bloat/bloat.gni
@@ -160,7 +160,7 @@
         metadata = {
           pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
         }
-        script = "$dir_pw_bloat/py/no_bloaty.py"
+        script = "$dir_pw_bloat/py/pw_bloat/no_bloaty.py"
         args = [ rebase_path(_doc_rst_output) ]
         outputs = [ _doc_rst_output ]
       }
@@ -175,16 +175,13 @@
         metadata = {
           pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
         }
-        script = "$dir_pw_bloat/py/bloat.py"
-        inputs = [
-                   "$dir_pw_bloat/py/binary_diff.py",
-                   "$dir_pw_bloat/py/bloat_output.py",
-                 ] + _bloaty_configs
+        script = "$dir_pw_bloat/py/pw_bloat/bloat.py"
+        inputs = _bloaty_configs
         outputs = [
           "$target_gen_dir/${target_name}.txt",
           _doc_rst_output,
         ]
-        deps = _all_target_dependencies
+        deps = _all_target_dependencies + [ "$dir_pw_bloat/py" ]
         args = _bloat_script_args + _binary_paths
 
         # Print size reports to stdout when they are generated.
@@ -323,7 +320,7 @@
       metadata = {
         pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
       }
-      script = "$dir_pw_bloat/py/no_toolchains.py"
+      script = "$dir_pw_bloat/py/pw_bloat/no_toolchains.py"
       args = [ rebase_path(_doc_rst_output) ]
       outputs = [ _doc_rst_output ]
     }
diff --git a/pw_bloat/py/BUILD.gn b/pw_bloat/py/BUILD.gn
new file mode 100644
index 0000000..74f574f
--- /dev/null
+++ b/pw_bloat/py/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2020 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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/python.gni")
+
+pw_python_package("py") {
+  setup = [ "setup.py" ]
+  sources = [
+    "pw_bloat/__init__.py",
+    "pw_bloat/binary_diff.py",
+    "pw_bloat/bloat.py",
+    "pw_bloat/bloat_output.py",
+    "pw_bloat/no_bloaty.py",
+    "pw_bloat/no_toolchains.py",
+  ]
+}
diff --git a/pw_bloat/py/pw_bloat/__init__.py b/pw_bloat/py/pw_bloat/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pw_bloat/py/pw_bloat/__init__.py
diff --git a/pw_bloat/py/binary_diff.py b/pw_bloat/py/pw_bloat/binary_diff.py
similarity index 100%
rename from pw_bloat/py/binary_diff.py
rename to pw_bloat/py/pw_bloat/binary_diff.py
diff --git a/pw_bloat/py/bloat.py b/pw_bloat/py/pw_bloat/bloat.py
similarity index 98%
rename from pw_bloat/py/bloat.py
rename to pw_bloat/py/pw_bloat/bloat.py
index 8302fcf..c3e145b 100755
--- a/pw_bloat/py/bloat.py
+++ b/pw_bloat/py/pw_bloat/bloat.py
@@ -23,8 +23,8 @@
 
 from typing import List, Iterable, Optional
 
-from binary_diff import BinaryDiff
-import bloat_output
+from pw_bloat.binary_diff import BinaryDiff
+from pw_bloat import bloat_output
 
 import pw_cli.log
 
diff --git a/pw_bloat/py/bloat_output.py b/pw_bloat/py/pw_bloat/bloat_output.py
similarity index 97%
rename from pw_bloat/py/bloat_output.py
rename to pw_bloat/py/pw_bloat/bloat_output.py
index 569cb1f..d203717 100644
--- a/pw_bloat/py/bloat_output.py
+++ b/pw_bloat/py/pw_bloat/bloat_output.py
@@ -15,10 +15,10 @@
 
 import abc
 import enum
-from typing import Callable, Collection, Dict, List, Optional, Tuple, Type
-from typing import Union
+from typing import (Callable, Collection, Dict, List, Optional, Tuple, Type,
+                    Union)
 
-from binary_diff import BinaryDiff, FormattedDiff
+from pw_bloat.binary_diff import BinaryDiff, FormattedDiff
 
 
 class Output(abc.ABC):
diff --git a/pw_bloat/py/no_bloaty.py b/pw_bloat/py/pw_bloat/no_bloaty.py
similarity index 100%
rename from pw_bloat/py/no_bloaty.py
rename to pw_bloat/py/pw_bloat/no_bloaty.py
diff --git a/pw_bloat/py/no_toolchains.py b/pw_bloat/py/pw_bloat/no_toolchains.py
similarity index 100%
rename from pw_bloat/py/no_toolchains.py
rename to pw_bloat/py/pw_bloat/no_toolchains.py
diff --git a/pw_docgen/docs.gni b/pw_docgen/docs.gni
index 2dda55c..a6354bb 100644
--- a/pw_docgen/docs.gni
+++ b/pw_docgen/docs.gni
@@ -108,7 +108,7 @@
 
   if (pw_docgen_BUILD_DOCS) {
     pw_python_action(target_name) {
-      script = "$dir_pw_docgen/py/docgen.py"
+      script = "$dir_pw_docgen/py/pw_docgen/docgen.py"
       args = _script_args
       deps = [ ":$_metadata_file_target" ]
       inputs = [ invoker.conf ]
diff --git a/pw_docgen/py/BUILD.gn b/pw_docgen/py/BUILD.gn
new file mode 100644
index 0000000..28d4734
--- /dev/null
+++ b/pw_docgen/py/BUILD.gn
@@ -0,0 +1,25 @@
+# Copyright 2020 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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/python.gni")
+
+pw_python_package("py") {
+  setup = [ "setup.py" ]
+  sources = [
+    "pw_docgen/__init__.py",
+    "pw_docgen/docgen.py",
+  ]
+}
diff --git a/pw_docgen/py/pw_docgen/__init__.py b/pw_docgen/py/pw_docgen/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pw_docgen/py/pw_docgen/__init__.py
diff --git a/pw_docgen/py/docgen.py b/pw_docgen/py/pw_docgen/docgen.py
similarity index 100%
rename from pw_docgen/py/docgen.py
rename to pw_docgen/py/pw_docgen/docgen.py
diff --git a/pw_docgen/py/pw_docgen/py.typed b/pw_docgen/py/pw_docgen/py.typed
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pw_docgen/py/pw_docgen/py.typed
diff --git a/pw_docgen/py/setup.py b/pw_docgen/py/setup.py
new file mode 100644
index 0000000..6953c15
--- /dev/null
+++ b/pw_docgen/py/setup.py
@@ -0,0 +1,27 @@
+# Copyright 2020 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.
+"""pw_docgen"""
+
+import setuptools  # type: ignore
+
+setuptools.setup(
+    name='pw_docgen',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Generate Sphinx documentation',
+    packages=setuptools.find_packages(),
+    package_data={'pw_docgen': ['py.typed']},
+    zip_safe=False,
+)
diff --git a/pw_package/py/BUILD.gn b/pw_package/py/BUILD.gn
new file mode 100644
index 0000000..6388bc0
--- /dev/null
+++ b/pw_package/py/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2020 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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/python.gni")
+
+pw_python_package("py") {
+  setup = [ "setup.py" ]
+  sources = [
+    "pw_package/__init__.py",
+    "pw_package/git_repo.py",
+    "pw_package/package_manager.py",
+    "pw_package/packages/__init__.py",
+    "pw_package/packages/nanopb.py",
+    "pw_package/pigweed_packages.py",
+  ]
+}
diff --git a/pw_package/py/pw_package/__init__.py b/pw_package/py/pw_package/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pw_package/py/pw_package/__init__.py
diff --git a/pw_package/py/setup.py b/pw_package/py/setup.py
index 682e013..eb574be 100644
--- a/pw_package/py/setup.py
+++ b/pw_package/py/setup.py
@@ -13,7 +13,7 @@
 # the License.
 """The pw_package package."""
 
-import setuptools
+import setuptools  # type: ignore
 
 setuptools.setup(
     name='pw_package',
@@ -21,6 +21,8 @@
     author='Pigweed Authors',
     author_email='pigweed-developers@googlegroups.com',
     description='Tools for installing optional packages',
-    install_requires=[],
     packages=setuptools.find_packages(),
+    package_data={'pw_package': ['py.typed']},
+    zip_safe=False,
+    install_requires=[],
 )
diff --git a/targets/lm3s6965evb-qemu/py/BUILD.gn b/targets/lm3s6965evb-qemu/py/BUILD.gn
new file mode 100644
index 0000000..8962902
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/py/BUILD.gn
@@ -0,0 +1,25 @@
+# Copyright 2020 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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/python.gni")
+
+pw_python_package("py") {
+  setup = [ "setup.py" ]
+  sources = [
+    "lm3s6965evb_qemu_utils/__init__.py",
+    "lm3s6965evb_qemu_utils/unit_test_runner.py",
+  ]
+}
diff --git a/targets/stm32f429i-disc1/py/BUILD.gn b/targets/stm32f429i-disc1/py/BUILD.gn
new file mode 100644
index 0000000..b93104b
--- /dev/null
+++ b/targets/stm32f429i-disc1/py/BUILD.gn
@@ -0,0 +1,28 @@
+# Copyright 2020 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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/python.gni")
+
+pw_python_package("py") {
+  setup = [ "setup.py" ]
+  sources = [
+    "stm32f429i_disc1_utils/__init__.py",
+    "stm32f429i_disc1_utils/stm32f429i_detector.py",
+    "stm32f429i_disc1_utils/unit_test_client.py",
+    "stm32f429i_disc1_utils/unit_test_runner.py",
+    "stm32f429i_disc1_utils/unit_test_server.py",
+  ]
+}
diff --git a/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/stm32f429i_detector.py b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/stm32f429i_detector.py
index 56b98de..8b7a0e4 100644
--- a/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/stm32f429i_detector.py
+++ b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/stm32f429i_detector.py
@@ -17,8 +17,8 @@
 import logging
 import typing
 
-import coloredlogs
-import serial.tools.list_ports
+import coloredlogs  # type: ignore
+import serial.tools.list_ports  # type: ignore
 
 # Vendor/device ID to search for in USB devices.
 _ST_VENDOR_ID = 0x0483
diff --git a/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_runner.py b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_runner.py
index d290198..328def7 100755
--- a/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_runner.py
+++ b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_runner.py
@@ -22,8 +22,8 @@
 import threading
 from typing import List
 
-import coloredlogs
-import serial
+import coloredlogs  # type: ignore
+import serial  # type: ignore
 from stm32f429i_disc1_utils import stm32f429i_detector
 
 # Path used to access non-python resources in this python module.