pw_build: Support pw_python_requirements targets

- pw_python_requirements targets express Python dependencies as in a
  requirements.txt, independently of a package or script.
- Move requirements from the Pigweed requirements.txt to the pw_docgen
  Python package.

Requires: pigweed-internal:8940
Change-Id: I8e9c977c03fcac6df54568e426dae7dd6478e012
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/29488
Reviewed-by: Rob Mohr <mohrr@google.com>
Commit-Queue: Wyatt Hepler <hepler@google.com>
diff --git a/pw_build/python.gni b/pw_build/python.gni
index a15edf0..7c5a58a 100644
--- a/pw_build/python.gni
+++ b/pw_build/python.gni
@@ -307,6 +307,15 @@
   }
 }
 
+_python_subtargets = [
+  "tests",
+  "lint",
+  "lint.mypy",
+  "lint.pylint",
+  "install",
+  "wheel",
+]
+
 # Declares a group of Python packages or other Python groups. pw_python_groups
 # expose the same set of subtargets as pw_python_package (e.g.
 # "$group_name.lint" and "$group_name.tests"), but these apply to all packages
@@ -322,16 +331,7 @@
     deps = _python_deps
   }
 
-  _subtargets = [
-    "tests",
-    "lint",
-    "lint.mypy",
-    "lint.pylint",
-    "install",
-    "wheel",
-  ]
-
-  foreach(subtarget, _subtargets) {
+  foreach(subtarget, _python_subtargets) {
     group("$target_name.$subtarget") {
       deps = []
       foreach(dep, _python_deps) {
@@ -368,3 +368,57 @@
     forward_variables_from(invoker, _supported_variables)
   }
 }
+
+# Represents a list of Python requirements, as in a requirements.txt.
+#
+# Args:
+#  files: One or more requirements.txt files.
+#  requirements: A list of requirements.txt-style requirements.
+template("pw_python_requirements") {
+  assert(defined(invoker.files) || defined(invoker.requirements),
+         "pw_python_requirements requires a list of requirements.txt files " +
+             "in the 'files' arg or requirements in 'requirements'")
+
+  _requirements_files = []
+
+  if (defined(invoker.files)) {
+    _requirements_files += invoker.files
+  }
+
+  if (defined(invoker.requirements)) {
+    _requirements_file = "$target_gen_dir/$target_name.requirements.txt"
+    write_file(_requirements_file, invoker.requirements)
+    _requirements_files += [ _requirements_file ]
+  }
+
+  # The default target represents the requirements themselves.
+  pw_input_group(target_name) {
+    inputs = _requirements_files
+  }
+
+  # Use the same subtargets as pw_python_package so these targets can be listed
+  # as python_deps of pw_python_packages.
+  pw_python_action("$target_name.install") {
+    inputs = _requirements_files
+
+    module = "pip"
+    args = [ "install" ]
+
+    foreach(_requirements_file, inputs) {
+      args += [
+        "--requirement",
+        rebase_path(_requirements_file),
+      ]
+    }
+
+    pool = "$dir_pw_build:pip_pool"
+    stamp = true
+  }
+
+  # Create stubs for the unused subtargets so that pw_python_requirements can be
+  # used as python_deps.
+  foreach(subtarget, _python_subtargets - [ "install" ]) {
+    group("$target_name.$subtarget") {
+    }
+  }
+}
diff --git a/pw_docgen/py/setup.py b/pw_docgen/py/setup.py
index 6953c15..e14a315 100644
--- a/pw_docgen/py/setup.py
+++ b/pw_docgen/py/setup.py
@@ -24,4 +24,15 @@
     packages=setuptools.find_packages(),
     package_data={'pw_docgen': ['py.typed']},
     zip_safe=False,
+    install_requires=[
+        'sphinx>=2, <3',  # Restrict version for compatibility with m2r plugin
+        'sphinx-rtd-theme',
+        # Markdown to REST for documentation.
+        'm2r',
+        # Diagram generation modules.
+        'sphinxcontrib-actdiag',
+        'sphinxcontrib-blockdiag',
+        'sphinxcontrib-nwdiag',
+        'sphinxcontrib-seqdiag',
+    ],
 )
diff --git a/pw_env_setup/py/pw_env_setup/env_setup.py b/pw_env_setup/py/pw_env_setup/env_setup.py
index 4a217f2..30113e7 100755
--- a/pw_env_setup/py/pw_env_setup/env_setup.py
+++ b/pw_env_setup/py/pw_env_setup/env_setup.py
@@ -218,9 +218,6 @@
                 os.path.join(setup_root, 'cipd_setup', 'pigweed.json'))
             self._cipd_package_file.append(
                 os.path.join(setup_root, 'cipd_setup', 'luci.json'))
-            self._virtualenv_requirements.append(
-                os.path.join(setup_root, 'virtualenv_setup',
-                             'requirements.txt'))
             self._virtualenv_gn_targets.append(
                 virtualenv_setup.GnTarget(
                     '{}#:python.install'.format(pw_root)))
diff --git a/pw_env_setup/py/pw_env_setup/virtualenv_setup/requirements.in b/pw_env_setup/py/pw_env_setup/virtualenv_setup/requirements.in
deleted file mode 100644
index 9cced8d..0000000
--- a/pw_env_setup/py/pw_env_setup/virtualenv_setup/requirements.in
+++ /dev/null
@@ -1,34 +0,0 @@
-# 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.
-
-# This is the list of packages we want installed. When we want to update them
-# or install additional packages update this file and run
-# `pip-compile requirements.in` and it will update requirements.txt.
-
-# To help manage these files.
-pip-tools
-
-# The below modules are all for documentation generation.
-# TODO(pwbug/198): Create a setup.py for pw_docgen and remove these.
-sphinx
-sphinx-rtd-theme
-
-# Markdown to REST for documentation.
-m2r
-
-# Diagram generation modules.
-sphinxcontrib-actdiag
-sphinxcontrib-blockdiag
-sphinxcontrib-nwdiag
-sphinxcontrib-seqdiag
diff --git a/pw_env_setup/py/pw_env_setup/virtualenv_setup/requirements.txt b/pw_env_setup/py/pw_env_setup/virtualenv_setup/requirements.txt
index 7e18bfe..e69de29 100644
--- a/pw_env_setup/py/pw_env_setup/virtualenv_setup/requirements.txt
+++ b/pw_env_setup/py/pw_env_setup/virtualenv_setup/requirements.txt
@@ -1,49 +0,0 @@
-#
-# This file is autogenerated by pip-compile
-# To update, run:
-#
-#    pip-compile requirements.in
-#
-actdiag==2.0.0            # via sphinxcontrib-actdiag
-alabaster==0.7.12         # via sphinx
-babel==2.7.0              # via sphinx
-blockdiag==2.0.1          # via actdiag, nwdiag, seqdiag, sphinxcontrib-actdiag, sphinxcontrib-blockdiag, sphinxcontrib-nwdiag, sphinxcontrib-seqdiag
-certifi==2019.9.11        # via requests
-chardet==3.0.4            # via requests
-click==7.0                # via pip-tools
-docutils==0.15.2          # via m2r, sphinx
-funcparserlib==0.3.6      # via blockdiag
-idna==2.8                 # via requests
-imagesize==1.1.0          # via sphinx
-jinja2==2.10.3            # via sphinx
-m2r==0.2.1                # via -r requirements.in
-markupsafe==1.1.1         # via jinja2
-mistune==0.8.4            # via m2r
-nwdiag==2.0.0             # via sphinxcontrib-nwdiag
-packaging==19.2           # via sphinx
-pillow==7.1.2             # via blockdiag
-pip-tools==5.1.2          # via -r requirements.in
-pygments==2.4.2           # via sphinx
-pyparsing==2.4.5          # via packaging
-pytz==2019.3              # via babel
-requests==2.22.0          # via sphinx
-seqdiag==2.0.0            # via sphinxcontrib-seqdiag
-six==1.13.0               # via packaging, pip-tools
-snowballstemmer==2.0.0    # via sphinx
-sphinx-rtd-theme==0.4.3   # via -r requirements.in
-sphinx==2.2.1             # via -r requirements.in, sphinx-rtd-theme, sphinxcontrib-actdiag, sphinxcontrib-blockdiag, sphinxcontrib-nwdiag, sphinxcontrib-seqdiag
-sphinxcontrib-actdiag==2.0.0  # via -r requirements.in
-sphinxcontrib-applehelp==1.0.1  # via sphinx
-sphinxcontrib-blockdiag==2.0.0  # via -r requirements.in
-sphinxcontrib-devhelp==1.0.1  # via sphinx
-sphinxcontrib-htmlhelp==1.0.2  # via sphinx
-sphinxcontrib-jsmath==1.0.1  # via sphinx
-sphinxcontrib-nwdiag==2.0.0  # via -r requirements.in
-sphinxcontrib-qthelp==1.0.2  # via sphinx
-sphinxcontrib-seqdiag==2.0.0  # via -r requirements.in
-sphinxcontrib-serializinghtml==1.1.3  # via sphinx
-urllib3==1.25.7           # via requests
-webcolors==1.11.1         # via blockdiag
-
-# The following packages are considered to be unsafe in a requirements file:
-# setuptools