pw_build: python_test_deps for Python packages

Split python_test_deps from python_deps. This makes the true
dependencies clearer and prevents circular dependencies in some cases
(pw_protobuf_compiler's tests depend on compiled protobufs).

Change-Id: I97dcba908995ef281850105d0cecafd0d87522e8
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/30561
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Wyatt Hepler <hepler@google.com>
diff --git a/pw_build/python.gni b/pw_build/python.gni
index 5af65c7..5bb412c 100644
--- a/pw_build/python.gni
+++ b/pw_build/python.gni
@@ -50,6 +50,7 @@
 #   sources: Python sources files in the package.
 #   tests: Test files for this Python package.
 #   python_deps: Dependencies on other pw_python_packages in the GN build.
+#   python_test_deps: Test-only pw_python_package dependencies.
 #   other_deps: Dependencies on GN targets that are not pw_python_packages.
 #   inputs: Other files to track, such as package_data.
 #   lint: If true (default), applies mypy and pylint to the package. If false,
@@ -128,6 +129,21 @@
     }
   }
 
+  # All dependencies needed for the package and its tests.
+  _python_test_deps = _python_deps
+  if (defined(invoker.python_test_deps)) {
+    foreach(test_dep, invoker.python_test_deps) {
+      _python_test_deps += [ get_label_info(test_dep, "label_no_toolchain") ]
+    }
+  }
+
+  if (_test_sources == []) {
+    assert(!defined(invoker.python_test_deps),
+           "python_test_deps was provided, but there are no tests in " +
+               get_label_info(":$target_name", "label_no_toolchain"))
+    not_needed(_python_test_deps)
+  }
+
   _internal_target = "$target_name._internal"
 
   # Create groups with the public target names ($target_name, $target_name.lint,
@@ -225,6 +241,14 @@
     ]
   }
 
+  if (_should_lint || _test_sources != []) {
+    # Packages that must be installed to use the package or run its tests.
+    _test_install_deps = []
+    foreach(dep, _python_test_deps) {
+      _test_install_deps += [ "$dep.install" ]
+    }
+  }
+
   # For packages that are not generated, create targets to run mypy and pylint.
   # Linting is not performed on generated packages.
   if (_should_lint) {
@@ -262,8 +286,9 @@
       directory = _lint_directory
       stamp = true
 
-      deps = [ ":$_internal_target.install" ]
-      foreach(dep, _python_deps) {
+      deps = _test_install_deps
+
+      foreach(dep, _python_test_deps) {
         deps += [ "$dep.lint.mypy" ]
       }
     }
@@ -293,8 +318,9 @@
       directory = _lint_directory
       stamp = "$target_gen_dir/{{source_target_relative}}.pylint.passed"
 
-      deps = [ ":$_internal_target.install" ]
-      foreach(dep, _python_deps) {
+      deps = _test_install_deps
+
+      foreach(dep, _python_test_deps) {
         deps += [ "$dep.lint.pylint" ]
       }
     }
@@ -322,8 +348,9 @@
       script = test
       stamp = true
 
-      deps = [ ":$_internal_target.install" ]
-      foreach(dep, _python_deps) {
+      deps = _test_install_deps
+
+      foreach(dep, _python_test_deps) {
         deps += [ "$dep.tests" ]
       }
     }