xref: /aosp_15_r20/external/pigweed/pw_build/python.gni (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker# Copyright 2021 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker#
3*61c4878aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker# use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker# the License at
6*61c4878aSAndroid Build Coastguard Worker#
7*61c4878aSAndroid Build Coastguard Worker#     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker#
9*61c4878aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker# License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker# the License.
14*61c4878aSAndroid Build Coastguard Worker
15*61c4878aSAndroid Build Coastguard Workerimport("//build_overrides/pigweed.gni")
16*61c4878aSAndroid Build Coastguard Worker
17*61c4878aSAndroid Build Coastguard Workerimport("$dir_pw_build/input_group.gni")
18*61c4878aSAndroid Build Coastguard Workerimport("$dir_pw_build/mirror_tree.gni")
19*61c4878aSAndroid Build Coastguard Workerimport("$dir_pw_build/python_action.gni")
20*61c4878aSAndroid Build Coastguard Workerimport("$dir_pw_build/python_gn_args.gni")
21*61c4878aSAndroid Build Coastguard Workerimport("$dir_pw_protobuf_compiler/toolchain.gni")
22*61c4878aSAndroid Build Coastguard Worker
23*61c4878aSAndroid Build Coastguard Workerdeclare_args() {
24*61c4878aSAndroid Build Coastguard Worker  # Constraints file selection (arguments to pip install --constraint).
25*61c4878aSAndroid Build Coastguard Worker  # See pip help install.
26*61c4878aSAndroid Build Coastguard Worker  pw_build_PIP_CONSTRAINTS =
27*61c4878aSAndroid Build Coastguard Worker      [ "$dir_pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list" ]
28*61c4878aSAndroid Build Coastguard Worker
29*61c4878aSAndroid Build Coastguard Worker  # Default pip requirements file for all Pigweed based projects.
30*61c4878aSAndroid Build Coastguard Worker  pw_build_PIP_REQUIREMENTS = []
31*61c4878aSAndroid Build Coastguard Worker
32*61c4878aSAndroid Build Coastguard Worker  # DOCSTAG: [python-static-analysis-tools]
33*61c4878aSAndroid Build Coastguard Worker  # Default set of Python static alaysis tools to run for pw_python_package targets.
34*61c4878aSAndroid Build Coastguard Worker  pw_build_PYTHON_STATIC_ANALYSIS_TOOLS = [
35*61c4878aSAndroid Build Coastguard Worker    "pylint",
36*61c4878aSAndroid Build Coastguard Worker    "mypy",
37*61c4878aSAndroid Build Coastguard Worker  ]
38*61c4878aSAndroid Build Coastguard Worker
39*61c4878aSAndroid Build Coastguard Worker  # DOCSTAG: [python-static-analysis-tools]
40*61c4878aSAndroid Build Coastguard Worker
41*61c4878aSAndroid Build Coastguard Worker  # If true, GN will run each Python test using the coverage command. A separate
42*61c4878aSAndroid Build Coastguard Worker  # coverage data file for each test will be saved. To generate reports from
43*61c4878aSAndroid Build Coastguard Worker  # this information run: pw presubmit --step gn_python_test_coverage
44*61c4878aSAndroid Build Coastguard Worker  pw_build_PYTHON_TEST_COVERAGE = false
45*61c4878aSAndroid Build Coastguard Worker
46*61c4878aSAndroid Build Coastguard Worker  # Output format for pylint. Options include "text" and "colorized".
47*61c4878aSAndroid Build Coastguard Worker  pw_build_PYLINT_OUTPUT_FORMAT = "colorized"
48*61c4878aSAndroid Build Coastguard Worker
49*61c4878aSAndroid Build Coastguard Worker  # Whether or not to lint/test transitive deps of pw_python_package targets.
50*61c4878aSAndroid Build Coastguard Worker  #
51*61c4878aSAndroid Build Coastguard Worker  # For example: if lib_a depends on lib_b, lib_a.tests will run after first
52*61c4878aSAndroid Build Coastguard Worker  # running lib_b.tests if pw_build_TEST_TRANSITIVE_PYTHON_DEPS is true.
53*61c4878aSAndroid Build Coastguard Worker  #
54*61c4878aSAndroid Build Coastguard Worker  # If pw_build_TEST_TRANSITIVE_PYTHON_DEPS is false, tests for a
55*61c4878aSAndroid Build Coastguard Worker  # pw_python_package will run if you directly build the target (e.g.
56*61c4878aSAndroid Build Coastguard Worker  # lib_b.tests) OR if the pw_python_package is placed in a pw_python_group AND
57*61c4878aSAndroid Build Coastguard Worker  # you build the group.tests target.
58*61c4878aSAndroid Build Coastguard Worker  #
59*61c4878aSAndroid Build Coastguard Worker  # This applies to mypy, pylint, ruff and tests.
60*61c4878aSAndroid Build Coastguard Worker  #
61*61c4878aSAndroid Build Coastguard Worker  # While this defaults to true for compatibility reasons, it's strongly
62*61c4878aSAndroid Build Coastguard Worker  # recommended to turn this off so you're not linting and testing all of your
63*61c4878aSAndroid Build Coastguard Worker  # external dependencies.
64*61c4878aSAndroid Build Coastguard Worker  pw_build_TEST_TRANSITIVE_PYTHON_DEPS = true
65*61c4878aSAndroid Build Coastguard Worker}
66*61c4878aSAndroid Build Coastguard Worker
67*61c4878aSAndroid Build Coastguard Worker# Python packages provide the following targets as $target_name.$subtarget.
68*61c4878aSAndroid Build Coastguard Workerpw_python_package_subtargets = [
69*61c4878aSAndroid Build Coastguard Worker  "tests",
70*61c4878aSAndroid Build Coastguard Worker  "lint",
71*61c4878aSAndroid Build Coastguard Worker  "lint.mypy",
72*61c4878aSAndroid Build Coastguard Worker  "lint.pylint",
73*61c4878aSAndroid Build Coastguard Worker  "lint.ruff",
74*61c4878aSAndroid Build Coastguard Worker  "install",
75*61c4878aSAndroid Build Coastguard Worker  "wheel",
76*61c4878aSAndroid Build Coastguard Worker
77*61c4878aSAndroid Build Coastguard Worker  # Internal targets that directly depend on one another.
78*61c4878aSAndroid Build Coastguard Worker  "_build_wheel",
79*61c4878aSAndroid Build Coastguard Worker]
80*61c4878aSAndroid Build Coastguard Worker
81*61c4878aSAndroid Build Coastguard Worker# Create aliases for subsargets when the target name matches the directory name.
82*61c4878aSAndroid Build Coastguard Worker# This allows //foo:foo.tests to be accessed as //foo:tests, for example.
83*61c4878aSAndroid Build Coastguard Workertemplate("_pw_create_aliases_if_name_matches_directory") {
84*61c4878aSAndroid Build Coastguard Worker  not_needed([ "invoker" ])
85*61c4878aSAndroid Build Coastguard Worker
86*61c4878aSAndroid Build Coastguard Worker  if (get_label_info(":$target_name", "name") ==
87*61c4878aSAndroid Build Coastguard Worker      get_path_info(get_label_info(":$target_name", "dir"), "name")) {
88*61c4878aSAndroid Build Coastguard Worker    foreach(subtarget, pw_python_package_subtargets) {
89*61c4878aSAndroid Build Coastguard Worker      group(subtarget) {
90*61c4878aSAndroid Build Coastguard Worker        public_deps = [ ":${invoker.target_name}.$subtarget" ]
91*61c4878aSAndroid Build Coastguard Worker      }
92*61c4878aSAndroid Build Coastguard Worker    }
93*61c4878aSAndroid Build Coastguard Worker  }
94*61c4878aSAndroid Build Coastguard Worker}
95*61c4878aSAndroid Build Coastguard Worker
96*61c4878aSAndroid Build Coastguard Worker# Internal template that runs Mypy.
97*61c4878aSAndroid Build Coastguard Workertemplate("_pw_python_static_analysis_mypy") {
98*61c4878aSAndroid Build Coastguard Worker  pw_python_action(target_name) {
99*61c4878aSAndroid Build Coastguard Worker    module = "mypy"
100*61c4878aSAndroid Build Coastguard Worker
101*61c4878aSAndroid Build Coastguard Worker    # DOCSTAG: [default-mypy-args]
102*61c4878aSAndroid Build Coastguard Worker    args = [
103*61c4878aSAndroid Build Coastguard Worker      "--pretty",
104*61c4878aSAndroid Build Coastguard Worker      "--show-error-codes",
105*61c4878aSAndroid Build Coastguard Worker
106*61c4878aSAndroid Build Coastguard Worker      # Use a mypy cache dir for this target only to avoid cache conflicts in
107*61c4878aSAndroid Build Coastguard Worker      # parallel mypy invocations.
108*61c4878aSAndroid Build Coastguard Worker      "--cache-dir",
109*61c4878aSAndroid Build Coastguard Worker      rebase_path(target_out_dir, root_build_dir) + "/.mypy_cache",
110*61c4878aSAndroid Build Coastguard Worker    ]
111*61c4878aSAndroid Build Coastguard Worker
112*61c4878aSAndroid Build Coastguard Worker    # Use this environment variable to force mypy to colorize output.
113*61c4878aSAndroid Build Coastguard Worker    # See https://github.com/python/mypy/issues/7771
114*61c4878aSAndroid Build Coastguard Worker    environment = [ "MYPY_FORCE_COLOR=1" ]
115*61c4878aSAndroid Build Coastguard Worker
116*61c4878aSAndroid Build Coastguard Worker    # DOCSTAG: [default-mypy-args]
117*61c4878aSAndroid Build Coastguard Worker
118*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.mypy_ini)) {
119*61c4878aSAndroid Build Coastguard Worker      args +=
120*61c4878aSAndroid Build Coastguard Worker          [ "--config-file=" + rebase_path(invoker.mypy_ini, root_build_dir) ]
121*61c4878aSAndroid Build Coastguard Worker      inputs = [ invoker.mypy_ini ]
122*61c4878aSAndroid Build Coastguard Worker    }
123*61c4878aSAndroid Build Coastguard Worker
124*61c4878aSAndroid Build Coastguard Worker    args += rebase_path(invoker.sources, root_build_dir)
125*61c4878aSAndroid Build Coastguard Worker
126*61c4878aSAndroid Build Coastguard Worker    stamp = true
127*61c4878aSAndroid Build Coastguard Worker
128*61c4878aSAndroid Build Coastguard Worker    deps = invoker.deps
129*61c4878aSAndroid Build Coastguard Worker
130*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.python_deps)) {
131*61c4878aSAndroid Build Coastguard Worker      python_deps = invoker.python_deps
132*61c4878aSAndroid Build Coastguard Worker      if (pw_build_TEST_TRANSITIVE_PYTHON_DEPS) {
133*61c4878aSAndroid Build Coastguard Worker        foreach(dep, invoker.python_deps) {
134*61c4878aSAndroid Build Coastguard Worker          deps += [ string_replace(dep, "(", ".lint.mypy(") ]
135*61c4878aSAndroid Build Coastguard Worker        }
136*61c4878aSAndroid Build Coastguard Worker      }
137*61c4878aSAndroid Build Coastguard Worker    }
138*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.python_metadata_deps)) {
139*61c4878aSAndroid Build Coastguard Worker      python_metadata_deps = invoker.python_metadata_deps
140*61c4878aSAndroid Build Coastguard Worker    }
141*61c4878aSAndroid Build Coastguard Worker  }
142*61c4878aSAndroid Build Coastguard Worker}
143*61c4878aSAndroid Build Coastguard Worker
144*61c4878aSAndroid Build Coastguard Worker# Internal template that runs Pylint.
145*61c4878aSAndroid Build Coastguard Workertemplate("_pw_python_static_analysis_pylint") {
146*61c4878aSAndroid Build Coastguard Worker  # Create a target to run pylint on each of the Python files in this
147*61c4878aSAndroid Build Coastguard Worker  # package and its dependencies.
148*61c4878aSAndroid Build Coastguard Worker  pw_python_action_foreach(target_name) {
149*61c4878aSAndroid Build Coastguard Worker    module = "pylint"
150*61c4878aSAndroid Build Coastguard Worker    args = [
151*61c4878aSAndroid Build Coastguard Worker      rebase_path(".", root_build_dir) + "/{{source_target_relative}}",
152*61c4878aSAndroid Build Coastguard Worker      "--jobs=1",
153*61c4878aSAndroid Build Coastguard Worker      "--output-format=$pw_build_PYLINT_OUTPUT_FORMAT",
154*61c4878aSAndroid Build Coastguard Worker    ]
155*61c4878aSAndroid Build Coastguard Worker
156*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.pylintrc)) {
157*61c4878aSAndroid Build Coastguard Worker      args += [ "--rcfile=" + rebase_path(invoker.pylintrc, root_build_dir) ]
158*61c4878aSAndroid Build Coastguard Worker      inputs = [ invoker.pylintrc ]
159*61c4878aSAndroid Build Coastguard Worker    }
160*61c4878aSAndroid Build Coastguard Worker
161*61c4878aSAndroid Build Coastguard Worker    if (host_os == "win") {
162*61c4878aSAndroid Build Coastguard Worker      # Allow CRLF on Windows, in case Git is set to switch line endings.
163*61c4878aSAndroid Build Coastguard Worker      args += [ "--disable=unexpected-line-ending-format" ]
164*61c4878aSAndroid Build Coastguard Worker    }
165*61c4878aSAndroid Build Coastguard Worker
166*61c4878aSAndroid Build Coastguard Worker    sources = invoker.sources
167*61c4878aSAndroid Build Coastguard Worker
168*61c4878aSAndroid Build Coastguard Worker    stamp = "$target_gen_dir/{{source_target_relative}}.pylint.passed"
169*61c4878aSAndroid Build Coastguard Worker
170*61c4878aSAndroid Build Coastguard Worker    public_deps = invoker.deps
171*61c4878aSAndroid Build Coastguard Worker
172*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.python_deps)) {
173*61c4878aSAndroid Build Coastguard Worker      python_deps = invoker.python_deps
174*61c4878aSAndroid Build Coastguard Worker      if (pw_build_TEST_TRANSITIVE_PYTHON_DEPS) {
175*61c4878aSAndroid Build Coastguard Worker        foreach(dep, invoker.python_deps) {
176*61c4878aSAndroid Build Coastguard Worker          public_deps += [ string_replace(dep, "(", ".lint.pylint(") ]
177*61c4878aSAndroid Build Coastguard Worker        }
178*61c4878aSAndroid Build Coastguard Worker      }
179*61c4878aSAndroid Build Coastguard Worker    }
180*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.python_metadata_deps)) {
181*61c4878aSAndroid Build Coastguard Worker      python_metadata_deps = invoker.python_metadata_deps
182*61c4878aSAndroid Build Coastguard Worker    }
183*61c4878aSAndroid Build Coastguard Worker  }
184*61c4878aSAndroid Build Coastguard Worker}
185*61c4878aSAndroid Build Coastguard Worker
186*61c4878aSAndroid Build Coastguard Workertemplate("_pw_python_static_analysis_ruff") {
187*61c4878aSAndroid Build Coastguard Worker  # Create a target to run ruff on each of the Python files in this
188*61c4878aSAndroid Build Coastguard Worker  # package and its dependencies.
189*61c4878aSAndroid Build Coastguard Worker  pw_python_action_foreach(target_name) {
190*61c4878aSAndroid Build Coastguard Worker    script = "$dir_pw_build/py/pw_build/exec.py"
191*61c4878aSAndroid Build Coastguard Worker    args = [
192*61c4878aSAndroid Build Coastguard Worker      "--",
193*61c4878aSAndroid Build Coastguard Worker      "ruff",
194*61c4878aSAndroid Build Coastguard Worker      "check",
195*61c4878aSAndroid Build Coastguard Worker      rebase_path(".", root_build_dir) + "/{{source_target_relative}}",
196*61c4878aSAndroid Build Coastguard Worker    ]
197*61c4878aSAndroid Build Coastguard Worker
198*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.ruff_toml)) {
199*61c4878aSAndroid Build Coastguard Worker      args += [
200*61c4878aSAndroid Build Coastguard Worker        "--config",
201*61c4878aSAndroid Build Coastguard Worker        rebase_path(invoker.ruff_toml, root_build_dir),
202*61c4878aSAndroid Build Coastguard Worker      ]
203*61c4878aSAndroid Build Coastguard Worker      inputs = [ invoker.ruff_toml ]
204*61c4878aSAndroid Build Coastguard Worker    }
205*61c4878aSAndroid Build Coastguard Worker
206*61c4878aSAndroid Build Coastguard Worker    environment = [ "CLICOLOR_FORCE=1" ]
207*61c4878aSAndroid Build Coastguard Worker
208*61c4878aSAndroid Build Coastguard Worker    sources = invoker.sources
209*61c4878aSAndroid Build Coastguard Worker
210*61c4878aSAndroid Build Coastguard Worker    stamp = "$target_gen_dir/{{source_target_relative}}.ruff.passed"
211*61c4878aSAndroid Build Coastguard Worker
212*61c4878aSAndroid Build Coastguard Worker    public_deps = invoker.deps
213*61c4878aSAndroid Build Coastguard Worker
214*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.python_deps)) {
215*61c4878aSAndroid Build Coastguard Worker      python_deps = []
216*61c4878aSAndroid Build Coastguard Worker      foreach(dep, invoker.python_deps) {
217*61c4878aSAndroid Build Coastguard Worker        public_deps += [ string_replace(dep, "(", ".lint.ruff(") ]
218*61c4878aSAndroid Build Coastguard Worker        python_deps += [ dep ]
219*61c4878aSAndroid Build Coastguard Worker      }
220*61c4878aSAndroid Build Coastguard Worker    }
221*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.python_metadata_deps)) {
222*61c4878aSAndroid Build Coastguard Worker      python_metadata_deps = invoker.python_metadata_deps
223*61c4878aSAndroid Build Coastguard Worker    }
224*61c4878aSAndroid Build Coastguard Worker  }
225*61c4878aSAndroid Build Coastguard Worker}
226*61c4878aSAndroid Build Coastguard Worker
227*61c4878aSAndroid Build Coastguard Worker# Defines a Python package. GN Python packages contain several GN targets:
228*61c4878aSAndroid Build Coastguard Worker#
229*61c4878aSAndroid Build Coastguard Worker#   - $name - Provides the Python files in the build, but does not take any
230*61c4878aSAndroid Build Coastguard Worker#         actions. All subtargets depend on this target.
231*61c4878aSAndroid Build Coastguard Worker#   - $name.lint - Runs static analyis tools on the Python code. This is a group
232*61c4878aSAndroid Build Coastguard Worker#     of two subtargets:
233*61c4878aSAndroid Build Coastguard Worker#     - $name.lint.mypy - Runs mypy (if enabled).
234*61c4878aSAndroid Build Coastguard Worker#     - $name.lint.pylint - Runs pylint (if enabled).
235*61c4878aSAndroid Build Coastguard Worker#     - $name.lint.ruff - Runs ruff (if enabled).
236*61c4878aSAndroid Build Coastguard Worker#   - $name.tests - Runs all tests for this package.
237*61c4878aSAndroid Build Coastguard Worker#   - $name.install - Installs the package in a venv.
238*61c4878aSAndroid Build Coastguard Worker#   - $name.wheel - Builds a Python wheel for the package.
239*61c4878aSAndroid Build Coastguard Worker#
240*61c4878aSAndroid Build Coastguard Worker# All Python packages are instantiated with in pw_build_PYTHON_TOOLCHAIN,
241*61c4878aSAndroid Build Coastguard Worker# regardless of the current toolchain. This prevents Python-specific work, like
242*61c4878aSAndroid Build Coastguard Worker# running Pylint, from occurring multiple times in a build.
243*61c4878aSAndroid Build Coastguard Worker#
244*61c4878aSAndroid Build Coastguard Worker# Args:
245*61c4878aSAndroid Build Coastguard Worker#   setup: List of setup file paths (setup.py or pyproject.toml & setup.cfg),
246*61c4878aSAndroid Build Coastguard Worker#       which must all be in the same directory.
247*61c4878aSAndroid Build Coastguard Worker#   generate_setup: As an alternative to 'setup', generate setup files with the
248*61c4878aSAndroid Build Coastguard Worker#       keywords in this scope. 'name' is required.
249*61c4878aSAndroid Build Coastguard Worker#   sources: Python sources files in the package.
250*61c4878aSAndroid Build Coastguard Worker#   tests: Test files for this Python package.
251*61c4878aSAndroid Build Coastguard Worker#   python_deps: Dependencies on other pw_python_packages in the GN build.
252*61c4878aSAndroid Build Coastguard Worker#   python_test_deps: Test-only pw_python_package dependencies.
253*61c4878aSAndroid Build Coastguard Worker#   other_deps: Dependencies on GN targets that are not pw_python_packages.
254*61c4878aSAndroid Build Coastguard Worker#   inputs: Other files to track, such as package_data.
255*61c4878aSAndroid Build Coastguard Worker#   proto_library: A pw_proto_library target to embed in this Python package.
256*61c4878aSAndroid Build Coastguard Worker#       generate_setup is required in place of setup if proto_library is used.
257*61c4878aSAndroid Build Coastguard Worker#   static_analysis: List of static analysis tools to run; "*" (default) runs
258*61c4878aSAndroid Build Coastguard Worker#       all tools. The supported tools are "mypy", "pylint" and "ruff".
259*61c4878aSAndroid Build Coastguard Worker#   pylintrc: Path to a pylintrc configuration file to use. If not
260*61c4878aSAndroid Build Coastguard Worker#       provided, Pylint's default rcfile search is used. As this may
261*61c4878aSAndroid Build Coastguard Worker#       use the the local user's configuration file, it is highly
262*61c4878aSAndroid Build Coastguard Worker#       recommended to pass this option to specify the rcfile explicitly.
263*61c4878aSAndroid Build Coastguard Worker#   mypy_ini: Optional path to a mypy configuration file to use. If not
264*61c4878aSAndroid Build Coastguard Worker#       provided, mypy's default configuration file search is used. mypy is
265*61c4878aSAndroid Build Coastguard Worker#       executed from the package's setup directory, so mypy.ini files in that
266*61c4878aSAndroid Build Coastguard Worker#       directory will take precedence over others.
267*61c4878aSAndroid Build Coastguard Worker#   ruff_toml: Path to a ruff.toml configuration file to use.
268*61c4878aSAndroid Build Coastguard Worker#
269*61c4878aSAndroid Build Coastguard Workertemplate("pw_python_package") {
270*61c4878aSAndroid Build Coastguard Worker  # The Python targets are always instantiated in pw_build_PYTHON_TOOLCHAIN. Use
271*61c4878aSAndroid Build Coastguard Worker  # fully qualified labels so that the toolchain is not lost.
272*61c4878aSAndroid Build Coastguard Worker  _other_deps = []
273*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.other_deps)) {
274*61c4878aSAndroid Build Coastguard Worker    foreach(dep, invoker.other_deps) {
275*61c4878aSAndroid Build Coastguard Worker      _other_deps += [ get_label_info(dep, "label_with_toolchain") ]
276*61c4878aSAndroid Build Coastguard Worker    }
277*61c4878aSAndroid Build Coastguard Worker  }
278*61c4878aSAndroid Build Coastguard Worker
279*61c4878aSAndroid Build Coastguard Worker  _python_deps = []
280*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.python_deps)) {
281*61c4878aSAndroid Build Coastguard Worker    foreach(dep, invoker.python_deps) {
282*61c4878aSAndroid Build Coastguard Worker      _python_deps += [ get_label_info(dep, "label_with_toolchain") ]
283*61c4878aSAndroid Build Coastguard Worker    }
284*61c4878aSAndroid Build Coastguard Worker  }
285*61c4878aSAndroid Build Coastguard Worker
286*61c4878aSAndroid Build Coastguard Worker  # pw_python_script uses pw_python_package, but with a limited set of features.
287*61c4878aSAndroid Build Coastguard Worker  # _pw_standalone signals that this target is actually a pw_python_script.
288*61c4878aSAndroid Build Coastguard Worker  _is_package = !(defined(invoker._pw_standalone) && invoker._pw_standalone)
289*61c4878aSAndroid Build Coastguard Worker
290*61c4878aSAndroid Build Coastguard Worker  _generate_package = false
291*61c4878aSAndroid Build Coastguard Worker
292*61c4878aSAndroid Build Coastguard Worker  _pydeplabel = get_label_info(":$target_name", "label_with_toolchain")
293*61c4878aSAndroid Build Coastguard Worker
294*61c4878aSAndroid Build Coastguard Worker  # If a package does not run static analysis or if it does but doesn't have
295*61c4878aSAndroid Build Coastguard Worker  # any tests then this variable is not used.
296*61c4878aSAndroid Build Coastguard Worker  not_needed([ "_pydeplabel" ])
297*61c4878aSAndroid Build Coastguard Worker
298*61c4878aSAndroid Build Coastguard Worker  # Check the generate_setup and import_protos args to determine if this package
299*61c4878aSAndroid Build Coastguard Worker  # is generated.
300*61c4878aSAndroid Build Coastguard Worker  if (_is_package) {
301*61c4878aSAndroid Build Coastguard Worker    assert(defined(invoker.generate_setup) != defined(invoker.setup),
302*61c4878aSAndroid Build Coastguard Worker           "Either 'setup' or 'generate_setup' (but not both) must provided")
303*61c4878aSAndroid Build Coastguard Worker
304*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.proto_library)) {
305*61c4878aSAndroid Build Coastguard Worker      assert(invoker.proto_library != "", "'proto_library' cannot be empty")
306*61c4878aSAndroid Build Coastguard Worker      assert(defined(invoker.generate_setup),
307*61c4878aSAndroid Build Coastguard Worker             "Python packages that import protos with 'proto_library' must " +
308*61c4878aSAndroid Build Coastguard Worker                 "use 'generate_setup' instead of 'setup'")
309*61c4878aSAndroid Build Coastguard Worker
310*61c4878aSAndroid Build Coastguard Worker      _import_protos = [ invoker.proto_library ]
311*61c4878aSAndroid Build Coastguard Worker
312*61c4878aSAndroid Build Coastguard Worker      # Depend on the dependencies of the proto library.
313*61c4878aSAndroid Build Coastguard Worker      _proto = get_label_info(invoker.proto_library, "label_no_toolchain")
314*61c4878aSAndroid Build Coastguard Worker      _toolchain = get_label_info(invoker.proto_library, "toolchain")
315*61c4878aSAndroid Build Coastguard Worker      _python_deps += [ "$_proto.python._deps($_toolchain)" ]
316*61c4878aSAndroid Build Coastguard Worker    } else if (defined(invoker.generate_setup)) {
317*61c4878aSAndroid Build Coastguard Worker      _import_protos = []
318*61c4878aSAndroid Build Coastguard Worker    }
319*61c4878aSAndroid Build Coastguard Worker
320*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.generate_setup)) {
321*61c4878aSAndroid Build Coastguard Worker      _generate_package = true
322*61c4878aSAndroid Build Coastguard Worker      _setup_dir = "$target_gen_dir/$target_name.generated_python_package"
323*61c4878aSAndroid Build Coastguard Worker
324*61c4878aSAndroid Build Coastguard Worker      if (defined(invoker.strip_prefix)) {
325*61c4878aSAndroid Build Coastguard Worker        _source_root = invoker.strip_prefix
326*61c4878aSAndroid Build Coastguard Worker      } else {
327*61c4878aSAndroid Build Coastguard Worker        _source_root = "."
328*61c4878aSAndroid Build Coastguard Worker      }
329*61c4878aSAndroid Build Coastguard Worker    } else {
330*61c4878aSAndroid Build Coastguard Worker      # Non-generated packages with sources provided need an __init__.py.
331*61c4878aSAndroid Build Coastguard Worker      assert(!defined(invoker.sources) || invoker.sources == [] ||
332*61c4878aSAndroid Build Coastguard Worker                 filter_include(invoker.sources, [ "*\b__init__.py" ]) != [],
333*61c4878aSAndroid Build Coastguard Worker             "Python packages must have at least one __init__.py file")
334*61c4878aSAndroid Build Coastguard Worker
335*61c4878aSAndroid Build Coastguard Worker      # Get the directories of the setup files. All must be in the same dir.
336*61c4878aSAndroid Build Coastguard Worker      _setup_dirs = get_path_info(invoker.setup, "dir")
337*61c4878aSAndroid Build Coastguard Worker      _setup_dir = _setup_dirs[0]
338*61c4878aSAndroid Build Coastguard Worker
339*61c4878aSAndroid Build Coastguard Worker      foreach(dir, _setup_dirs) {
340*61c4878aSAndroid Build Coastguard Worker        assert(dir == _setup_dir,
341*61c4878aSAndroid Build Coastguard Worker               "All files in 'setup' must be in the same directory")
342*61c4878aSAndroid Build Coastguard Worker      }
343*61c4878aSAndroid Build Coastguard Worker
344*61c4878aSAndroid Build Coastguard Worker      assert(!defined(invoker.strip_prefix),
345*61c4878aSAndroid Build Coastguard Worker             "'strip_prefix' may only be given if 'generate_setup' is provided")
346*61c4878aSAndroid Build Coastguard Worker    }
347*61c4878aSAndroid Build Coastguard Worker  }
348*61c4878aSAndroid Build Coastguard Worker
349*61c4878aSAndroid Build Coastguard Worker  # Process arguments defaults and set defaults.
350*61c4878aSAndroid Build Coastguard Worker
351*61c4878aSAndroid Build Coastguard Worker  _supported_static_analysis_tools = [
352*61c4878aSAndroid Build Coastguard Worker    "mypy",
353*61c4878aSAndroid Build Coastguard Worker    "pylint",
354*61c4878aSAndroid Build Coastguard Worker    "ruff",
355*61c4878aSAndroid Build Coastguard Worker  ]
356*61c4878aSAndroid Build Coastguard Worker  not_needed([ "_supported_static_analysis_tools" ])
357*61c4878aSAndroid Build Coastguard Worker
358*61c4878aSAndroid Build Coastguard Worker  # Argument: static_analysis (list of tool names or "*"); default = "*" (all)
359*61c4878aSAndroid Build Coastguard Worker  if (!defined(invoker.static_analysis) || invoker.static_analysis == "*") {
360*61c4878aSAndroid Build Coastguard Worker    _static_analysis = pw_build_PYTHON_STATIC_ANALYSIS_TOOLS
361*61c4878aSAndroid Build Coastguard Worker  } else {
362*61c4878aSAndroid Build Coastguard Worker    _static_analysis = invoker.static_analysis
363*61c4878aSAndroid Build Coastguard Worker  }
364*61c4878aSAndroid Build Coastguard Worker
365*61c4878aSAndroid Build Coastguard Worker  foreach(_tool, _static_analysis) {
366*61c4878aSAndroid Build Coastguard Worker    assert(_supported_static_analysis_tools + [ _tool ] - [ _tool ] !=
367*61c4878aSAndroid Build Coastguard Worker               _supported_static_analysis_tools,
368*61c4878aSAndroid Build Coastguard Worker           "'$_tool' is not a supported static analysis tool")
369*61c4878aSAndroid Build Coastguard Worker  }
370*61c4878aSAndroid Build Coastguard Worker
371*61c4878aSAndroid Build Coastguard Worker  # Argument: sources (list)
372*61c4878aSAndroid Build Coastguard Worker  _sources = []
373*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.sources)) {
374*61c4878aSAndroid Build Coastguard Worker    if (_generate_package) {
375*61c4878aSAndroid Build Coastguard Worker      foreach(source, rebase_path(invoker.sources, _source_root)) {
376*61c4878aSAndroid Build Coastguard Worker        _sources += [ "$_setup_dir/$source" ]
377*61c4878aSAndroid Build Coastguard Worker      }
378*61c4878aSAndroid Build Coastguard Worker    } else {
379*61c4878aSAndroid Build Coastguard Worker      _sources += invoker.sources
380*61c4878aSAndroid Build Coastguard Worker    }
381*61c4878aSAndroid Build Coastguard Worker  }
382*61c4878aSAndroid Build Coastguard Worker
383*61c4878aSAndroid Build Coastguard Worker  # Argument: tests (list)
384*61c4878aSAndroid Build Coastguard Worker  _test_sources = []
385*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.tests)) {
386*61c4878aSAndroid Build Coastguard Worker    if (_generate_package) {
387*61c4878aSAndroid Build Coastguard Worker      foreach(source, rebase_path(invoker.tests, _source_root)) {
388*61c4878aSAndroid Build Coastguard Worker        _test_sources += [ "$_setup_dir/$source" ]
389*61c4878aSAndroid Build Coastguard Worker      }
390*61c4878aSAndroid Build Coastguard Worker    } else {
391*61c4878aSAndroid Build Coastguard Worker      _test_sources += invoker.tests
392*61c4878aSAndroid Build Coastguard Worker    }
393*61c4878aSAndroid Build Coastguard Worker  }
394*61c4878aSAndroid Build Coastguard Worker
395*61c4878aSAndroid Build Coastguard Worker  # Argument: setup (list)
396*61c4878aSAndroid Build Coastguard Worker  _setup_sources = []
397*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.setup)) {
398*61c4878aSAndroid Build Coastguard Worker    _setup_sources = invoker.setup
399*61c4878aSAndroid Build Coastguard Worker  } else if (_generate_package) {
400*61c4878aSAndroid Build Coastguard Worker    _setup_sources = [
401*61c4878aSAndroid Build Coastguard Worker      "$_setup_dir/pyproject.toml",
402*61c4878aSAndroid Build Coastguard Worker      "$_setup_dir/setup.cfg",
403*61c4878aSAndroid Build Coastguard Worker    ]
404*61c4878aSAndroid Build Coastguard Worker  }
405*61c4878aSAndroid Build Coastguard Worker
406*61c4878aSAndroid Build Coastguard Worker  # Argument: python_test_deps (list)
407*61c4878aSAndroid Build Coastguard Worker  _python_test_deps = _python_deps  # include all deps in test deps
408*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.python_test_deps)) {
409*61c4878aSAndroid Build Coastguard Worker    foreach(dep, invoker.python_test_deps) {
410*61c4878aSAndroid Build Coastguard Worker      _python_test_deps += [ get_label_info(dep, "label_with_toolchain") ]
411*61c4878aSAndroid Build Coastguard Worker    }
412*61c4878aSAndroid Build Coastguard Worker  }
413*61c4878aSAndroid Build Coastguard Worker
414*61c4878aSAndroid Build Coastguard Worker  if (_test_sources == []) {
415*61c4878aSAndroid Build Coastguard Worker    assert(!defined(invoker.python_test_deps),
416*61c4878aSAndroid Build Coastguard Worker           "python_test_deps was provided, but there are no tests in " +
417*61c4878aSAndroid Build Coastguard Worker               get_label_info(":$target_name", "label_no_toolchain"))
418*61c4878aSAndroid Build Coastguard Worker    not_needed([ "_python_test_deps" ])
419*61c4878aSAndroid Build Coastguard Worker  }
420*61c4878aSAndroid Build Coastguard Worker
421*61c4878aSAndroid Build Coastguard Worker  _all_py_files =
422*61c4878aSAndroid Build Coastguard Worker      _sources + _test_sources + filter_include(_setup_sources, [ "*.py" ])
423*61c4878aSAndroid Build Coastguard Worker
424*61c4878aSAndroid Build Coastguard Worker  # The pw_python_package subtargets are only instantiated in
425*61c4878aSAndroid Build Coastguard Worker  # pw_build_PYTHON_TOOLCHAIN. Targets in other toolchains just refer to the
426*61c4878aSAndroid Build Coastguard Worker  # targets in this toolchain.
427*61c4878aSAndroid Build Coastguard Worker  if (current_toolchain == pw_build_PYTHON_TOOLCHAIN) {
428*61c4878aSAndroid Build Coastguard Worker    # Create the package_metadata.json file. This is used by the
429*61c4878aSAndroid Build Coastguard Worker    # pw_python_distribution template.
430*61c4878aSAndroid Build Coastguard Worker    _package_metadata_json_file =
431*61c4878aSAndroid Build Coastguard Worker        "$target_gen_dir/$target_name/package_metadata.json"
432*61c4878aSAndroid Build Coastguard Worker
433*61c4878aSAndroid Build Coastguard Worker    # Get Python package metadata and write to disk as JSON.
434*61c4878aSAndroid Build Coastguard Worker    _package_metadata = {
435*61c4878aSAndroid Build Coastguard Worker      gn_target_name =
436*61c4878aSAndroid Build Coastguard Worker          get_label_info(":${invoker.target_name}", "label_no_toolchain")
437*61c4878aSAndroid Build Coastguard Worker
438*61c4878aSAndroid Build Coastguard Worker      # Get package source files
439*61c4878aSAndroid Build Coastguard Worker      sources = rebase_path(_sources, root_build_dir)
440*61c4878aSAndroid Build Coastguard Worker
441*61c4878aSAndroid Build Coastguard Worker      # Get setup.cfg, pyproject.toml, or setup.py file
442*61c4878aSAndroid Build Coastguard Worker      setup_sources = rebase_path(_setup_sources, root_build_dir)
443*61c4878aSAndroid Build Coastguard Worker
444*61c4878aSAndroid Build Coastguard Worker      # Get test source files
445*61c4878aSAndroid Build Coastguard Worker      tests = rebase_path(_test_sources, root_build_dir)
446*61c4878aSAndroid Build Coastguard Worker
447*61c4878aSAndroid Build Coastguard Worker      # Get package input files (package data)
448*61c4878aSAndroid Build Coastguard Worker      inputs = []
449*61c4878aSAndroid Build Coastguard Worker      if (defined(invoker.inputs)) {
450*61c4878aSAndroid Build Coastguard Worker        inputs = rebase_path(invoker.inputs, root_build_dir)
451*61c4878aSAndroid Build Coastguard Worker      }
452*61c4878aSAndroid Build Coastguard Worker
453*61c4878aSAndroid Build Coastguard Worker      # Get generate_setup
454*61c4878aSAndroid Build Coastguard Worker      if (defined(invoker.generate_setup)) {
455*61c4878aSAndroid Build Coastguard Worker        generate_setup = invoker.generate_setup
456*61c4878aSAndroid Build Coastguard Worker      }
457*61c4878aSAndroid Build Coastguard Worker    }
458*61c4878aSAndroid Build Coastguard Worker
459*61c4878aSAndroid Build Coastguard Worker    # Finally, write out the json
460*61c4878aSAndroid Build Coastguard Worker    write_file(_package_metadata_json_file, _package_metadata, "json")
461*61c4878aSAndroid Build Coastguard Worker
462*61c4878aSAndroid Build Coastguard Worker    # Create a target group for the Python package metadata only. This is a
463*61c4878aSAndroid Build Coastguard Worker    # python_action so the setup sources can be included as inputs.
464*61c4878aSAndroid Build Coastguard Worker    pw_python_action("$target_name._package_metadata") {
465*61c4878aSAndroid Build Coastguard Worker      metadata = {
466*61c4878aSAndroid Build Coastguard Worker        pw_python_package_metadata_json = [ _package_metadata_json_file ]
467*61c4878aSAndroid Build Coastguard Worker      }
468*61c4878aSAndroid Build Coastguard Worker
469*61c4878aSAndroid Build Coastguard Worker      script = "$dir_pw_build/py/pw_build/nop.py"
470*61c4878aSAndroid Build Coastguard Worker
471*61c4878aSAndroid Build Coastguard Worker      if (_generate_package) {
472*61c4878aSAndroid Build Coastguard Worker        inputs = [ "$_setup_dir/setup.json" ]
473*61c4878aSAndroid Build Coastguard Worker      } else {
474*61c4878aSAndroid Build Coastguard Worker        inputs = _setup_sources
475*61c4878aSAndroid Build Coastguard Worker      }
476*61c4878aSAndroid Build Coastguard Worker
477*61c4878aSAndroid Build Coastguard Worker      _pw_internal_run_in_venv = false
478*61c4878aSAndroid Build Coastguard Worker      stamp = true
479*61c4878aSAndroid Build Coastguard Worker
480*61c4878aSAndroid Build Coastguard Worker      # Forward the package_metadata subtarget for all python_deps.
481*61c4878aSAndroid Build Coastguard Worker      public_deps = []
482*61c4878aSAndroid Build Coastguard Worker      foreach(dep, _python_deps) {
483*61c4878aSAndroid Build Coastguard Worker        public_deps += [ get_label_info(dep, "label_no_toolchain") +
484*61c4878aSAndroid Build Coastguard Worker                         "._package_metadata($pw_build_PYTHON_TOOLCHAIN)" ]
485*61c4878aSAndroid Build Coastguard Worker      }
486*61c4878aSAndroid Build Coastguard Worker    }
487*61c4878aSAndroid Build Coastguard Worker
488*61c4878aSAndroid Build Coastguard Worker    # Declare the main Python package group. This represents the Python files,
489*61c4878aSAndroid Build Coastguard Worker    # but does not take any actions. GN targets can depend on the package name
490*61c4878aSAndroid Build Coastguard Worker    # to run when any files in the package change.
491*61c4878aSAndroid Build Coastguard Worker    if (_generate_package) {
492*61c4878aSAndroid Build Coastguard Worker      # If this package is generated, mirror the sources to the final directory.
493*61c4878aSAndroid Build Coastguard Worker      pw_mirror_tree("$target_name._mirror_sources_to_out_dir") {
494*61c4878aSAndroid Build Coastguard Worker        directory = _setup_dir
495*61c4878aSAndroid Build Coastguard Worker
496*61c4878aSAndroid Build Coastguard Worker        sources = []
497*61c4878aSAndroid Build Coastguard Worker        if (defined(invoker.sources)) {
498*61c4878aSAndroid Build Coastguard Worker          sources += invoker.sources
499*61c4878aSAndroid Build Coastguard Worker        }
500*61c4878aSAndroid Build Coastguard Worker        if (defined(invoker.tests)) {
501*61c4878aSAndroid Build Coastguard Worker          sources += invoker.tests
502*61c4878aSAndroid Build Coastguard Worker        }
503*61c4878aSAndroid Build Coastguard Worker        if (defined(invoker.inputs)) {
504*61c4878aSAndroid Build Coastguard Worker          sources += invoker.inputs
505*61c4878aSAndroid Build Coastguard Worker        }
506*61c4878aSAndroid Build Coastguard Worker
507*61c4878aSAndroid Build Coastguard Worker        source_root = _source_root
508*61c4878aSAndroid Build Coastguard Worker        public_deps = _python_deps + _other_deps
509*61c4878aSAndroid Build Coastguard Worker      }
510*61c4878aSAndroid Build Coastguard Worker
511*61c4878aSAndroid Build Coastguard Worker      # Get generated_setup scope and write it to disk as JSON.
512*61c4878aSAndroid Build Coastguard Worker
513*61c4878aSAndroid Build Coastguard Worker      # Expected setup.cfg structure:
514*61c4878aSAndroid Build Coastguard Worker      # https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html
515*61c4878aSAndroid Build Coastguard Worker      _gen_setup = invoker.generate_setup
516*61c4878aSAndroid Build Coastguard Worker      assert(defined(_gen_setup.metadata),
517*61c4878aSAndroid Build Coastguard Worker             "'metadata = {}' is required in generate_package")
518*61c4878aSAndroid Build Coastguard Worker
519*61c4878aSAndroid Build Coastguard Worker      # Get metadata which should contain at least name.
520*61c4878aSAndroid Build Coastguard Worker      _gen_metadata = {
521*61c4878aSAndroid Build Coastguard Worker      }
522*61c4878aSAndroid Build Coastguard Worker      _gen_metadata = _gen_setup.metadata
523*61c4878aSAndroid Build Coastguard Worker      assert(
524*61c4878aSAndroid Build Coastguard Worker          defined(_gen_metadata.name),
525*61c4878aSAndroid Build Coastguard Worker          "metadata = { name = 'package_name' } is required in generate_package")
526*61c4878aSAndroid Build Coastguard Worker
527*61c4878aSAndroid Build Coastguard Worker      # Get options which should not have packages or package_data.
528*61c4878aSAndroid Build Coastguard Worker      if (defined(_gen_setup.options)) {
529*61c4878aSAndroid Build Coastguard Worker        _gen_options = {
530*61c4878aSAndroid Build Coastguard Worker        }
531*61c4878aSAndroid Build Coastguard Worker        _gen_options = _gen_setup.options
532*61c4878aSAndroid Build Coastguard Worker        assert(!defined(_gen_options.packages) &&
533*61c4878aSAndroid Build Coastguard Worker                   !defined(_gen_options.package_data),
534*61c4878aSAndroid Build Coastguard Worker               "'packages' and 'package_data' may not be provided " +
535*61c4878aSAndroid Build Coastguard Worker                   "in 'generate_package' options.")
536*61c4878aSAndroid Build Coastguard Worker      }
537*61c4878aSAndroid Build Coastguard Worker
538*61c4878aSAndroid Build Coastguard Worker      write_file("$_setup_dir/setup.json", _gen_setup, "json")
539*61c4878aSAndroid Build Coastguard Worker
540*61c4878aSAndroid Build Coastguard Worker      # Generate the setup.py, py.typed, and __init__.py files as needed.
541*61c4878aSAndroid Build Coastguard Worker      action(target_name) {
542*61c4878aSAndroid Build Coastguard Worker        metadata = {
543*61c4878aSAndroid Build Coastguard Worker          pw_python_package_metadata_json = [ _package_metadata_json_file ]
544*61c4878aSAndroid Build Coastguard Worker        }
545*61c4878aSAndroid Build Coastguard Worker
546*61c4878aSAndroid Build Coastguard Worker        script = "$dir_pw_build/py/pw_build/generate_python_package.py"
547*61c4878aSAndroid Build Coastguard Worker        args = [
548*61c4878aSAndroid Build Coastguard Worker                 "--label",
549*61c4878aSAndroid Build Coastguard Worker                 get_label_info(":$target_name", "label_no_toolchain"),
550*61c4878aSAndroid Build Coastguard Worker                 "--generated-root",
551*61c4878aSAndroid Build Coastguard Worker                 rebase_path(_setup_dir, root_build_dir),
552*61c4878aSAndroid Build Coastguard Worker                 "--setup-json",
553*61c4878aSAndroid Build Coastguard Worker                 rebase_path("$_setup_dir/setup.json", root_build_dir),
554*61c4878aSAndroid Build Coastguard Worker               ] + rebase_path(_sources, root_build_dir)
555*61c4878aSAndroid Build Coastguard Worker
556*61c4878aSAndroid Build Coastguard Worker        # Pass in the .json information files for the imported proto libraries.
557*61c4878aSAndroid Build Coastguard Worker        foreach(proto, _import_protos) {
558*61c4878aSAndroid Build Coastguard Worker          _label = get_label_info(proto, "label_no_toolchain") +
559*61c4878aSAndroid Build Coastguard Worker                   ".python($pw_protobuf_compiler_TOOLCHAIN)"
560*61c4878aSAndroid Build Coastguard Worker          _file = get_label_info(_label, "target_gen_dir") + "/" +
561*61c4878aSAndroid Build Coastguard Worker                  get_label_info(_label, "name") + ".json"
562*61c4878aSAndroid Build Coastguard Worker          args += [
563*61c4878aSAndroid Build Coastguard Worker            "--proto-library",
564*61c4878aSAndroid Build Coastguard Worker            rebase_path(_file, root_build_dir),
565*61c4878aSAndroid Build Coastguard Worker          ]
566*61c4878aSAndroid Build Coastguard Worker        }
567*61c4878aSAndroid Build Coastguard Worker
568*61c4878aSAndroid Build Coastguard Worker        if (defined(invoker._pw_module_as_package) &&
569*61c4878aSAndroid Build Coastguard Worker            invoker._pw_module_as_package) {
570*61c4878aSAndroid Build Coastguard Worker          args += [ "--module-as-package" ]
571*61c4878aSAndroid Build Coastguard Worker        }
572*61c4878aSAndroid Build Coastguard Worker
573*61c4878aSAndroid Build Coastguard Worker        inputs = [ "$_setup_dir/setup.json" ]
574*61c4878aSAndroid Build Coastguard Worker
575*61c4878aSAndroid Build Coastguard Worker        public_deps = [ ":$target_name._mirror_sources_to_out_dir" ]
576*61c4878aSAndroid Build Coastguard Worker
577*61c4878aSAndroid Build Coastguard Worker        outputs = _setup_sources
578*61c4878aSAndroid Build Coastguard Worker      }
579*61c4878aSAndroid Build Coastguard Worker    } else {
580*61c4878aSAndroid Build Coastguard Worker      # If the package is not generated, use an input group for the sources.
581*61c4878aSAndroid Build Coastguard Worker      pw_input_group(target_name) {
582*61c4878aSAndroid Build Coastguard Worker        metadata = {
583*61c4878aSAndroid Build Coastguard Worker          pw_python_package_metadata_json = [ _package_metadata_json_file ]
584*61c4878aSAndroid Build Coastguard Worker        }
585*61c4878aSAndroid Build Coastguard Worker        inputs = _all_py_files
586*61c4878aSAndroid Build Coastguard Worker        if (defined(invoker.inputs)) {
587*61c4878aSAndroid Build Coastguard Worker          inputs += invoker.inputs
588*61c4878aSAndroid Build Coastguard Worker        }
589*61c4878aSAndroid Build Coastguard Worker
590*61c4878aSAndroid Build Coastguard Worker        public_deps = _python_deps + _other_deps
591*61c4878aSAndroid Build Coastguard Worker      }
592*61c4878aSAndroid Build Coastguard Worker    }
593*61c4878aSAndroid Build Coastguard Worker
594*61c4878aSAndroid Build Coastguard Worker    if (_is_package) {
595*61c4878aSAndroid Build Coastguard Worker      # Builds a Python wheel for this package. Records the output directory
596*61c4878aSAndroid Build Coastguard Worker      # in the pw_python_package_wheels metadata key.
597*61c4878aSAndroid Build Coastguard Worker
598*61c4878aSAndroid Build Coastguard Worker      pw_python_action("$target_name._build_wheel") {
599*61c4878aSAndroid Build Coastguard Worker        _wheel_out_dir = "$target_out_dir/$target_name"
600*61c4878aSAndroid Build Coastguard Worker        _wheel_requirement = "$_wheel_out_dir/requirements.txt"
601*61c4878aSAndroid Build Coastguard Worker        metadata = {
602*61c4878aSAndroid Build Coastguard Worker          pw_python_package_wheels = [ _wheel_out_dir ]
603*61c4878aSAndroid Build Coastguard Worker        }
604*61c4878aSAndroid Build Coastguard Worker
605*61c4878aSAndroid Build Coastguard Worker        script = "$dir_pw_build/py/pw_build/generate_python_wheel.py"
606*61c4878aSAndroid Build Coastguard Worker
607*61c4878aSAndroid Build Coastguard Worker        args = [
608*61c4878aSAndroid Build Coastguard Worker          "--package-dir",
609*61c4878aSAndroid Build Coastguard Worker          rebase_path(_setup_dir, root_build_dir),
610*61c4878aSAndroid Build Coastguard Worker          "--out-dir",
611*61c4878aSAndroid Build Coastguard Worker          rebase_path(_wheel_out_dir, root_build_dir),
612*61c4878aSAndroid Build Coastguard Worker        ]
613*61c4878aSAndroid Build Coastguard Worker
614*61c4878aSAndroid Build Coastguard Worker        # Add hashes to the _wheel_requirement output.
615*61c4878aSAndroid Build Coastguard Worker        if (pw_build_PYTHON_PIP_INSTALL_REQUIRE_HASHES) {
616*61c4878aSAndroid Build Coastguard Worker          args += [ "--generate-hashes" ]
617*61c4878aSAndroid Build Coastguard Worker        }
618*61c4878aSAndroid Build Coastguard Worker
619*61c4878aSAndroid Build Coastguard Worker        deps = [ ":${invoker.target_name}" ]
620*61c4878aSAndroid Build Coastguard Worker        foreach(dep, _python_deps) {
621*61c4878aSAndroid Build Coastguard Worker          deps += [ string_replace(dep, "(", ".wheel(") ]
622*61c4878aSAndroid Build Coastguard Worker        }
623*61c4878aSAndroid Build Coastguard Worker
624*61c4878aSAndroid Build Coastguard Worker        outputs = [ _wheel_requirement ]
625*61c4878aSAndroid Build Coastguard Worker      }
626*61c4878aSAndroid Build Coastguard Worker    } else {
627*61c4878aSAndroid Build Coastguard Worker      # Stub for non-package targets.
628*61c4878aSAndroid Build Coastguard Worker      group("$target_name._build_wheel") {
629*61c4878aSAndroid Build Coastguard Worker      }
630*61c4878aSAndroid Build Coastguard Worker    }
631*61c4878aSAndroid Build Coastguard Worker
632*61c4878aSAndroid Build Coastguard Worker    # Create the .install and .wheel targets. To limit unnecessary pip
633*61c4878aSAndroid Build Coastguard Worker    # executions, non-generated packages are only reinstalled when their
634*61c4878aSAndroid Build Coastguard Worker    # setup.py changes. However, targets that depend on the .install subtarget
635*61c4878aSAndroid Build Coastguard Worker    # re-run whenever any source files change.
636*61c4878aSAndroid Build Coastguard Worker    #
637*61c4878aSAndroid Build Coastguard Worker    # These targets just represent the source files if this isn't a package.
638*61c4878aSAndroid Build Coastguard Worker    group("$target_name.install") {
639*61c4878aSAndroid Build Coastguard Worker      public_deps = [ ":${invoker.target_name}" ]
640*61c4878aSAndroid Build Coastguard Worker
641*61c4878aSAndroid Build Coastguard Worker      foreach(dep, _python_deps) {
642*61c4878aSAndroid Build Coastguard Worker        public_deps += [ string_replace(dep, "(", ".install(") ]
643*61c4878aSAndroid Build Coastguard Worker      }
644*61c4878aSAndroid Build Coastguard Worker    }
645*61c4878aSAndroid Build Coastguard Worker
646*61c4878aSAndroid Build Coastguard Worker    group("$target_name.wheel") {
647*61c4878aSAndroid Build Coastguard Worker      public_deps = [ ":${invoker.target_name}.install" ]
648*61c4878aSAndroid Build Coastguard Worker
649*61c4878aSAndroid Build Coastguard Worker      if (_is_package) {
650*61c4878aSAndroid Build Coastguard Worker        public_deps += [ ":${invoker.target_name}._build_wheel" ]
651*61c4878aSAndroid Build Coastguard Worker      }
652*61c4878aSAndroid Build Coastguard Worker
653*61c4878aSAndroid Build Coastguard Worker      foreach(dep, _python_deps) {
654*61c4878aSAndroid Build Coastguard Worker        public_deps += [ string_replace(dep, "(", ".wheel(") ]
655*61c4878aSAndroid Build Coastguard Worker      }
656*61c4878aSAndroid Build Coastguard Worker    }
657*61c4878aSAndroid Build Coastguard Worker
658*61c4878aSAndroid Build Coastguard Worker    # Define the static analysis targets for this package.
659*61c4878aSAndroid Build Coastguard Worker    group("$target_name.lint") {
660*61c4878aSAndroid Build Coastguard Worker      deps = []
661*61c4878aSAndroid Build Coastguard Worker      foreach(_tool, _supported_static_analysis_tools) {
662*61c4878aSAndroid Build Coastguard Worker        deps += [ ":${invoker.target_name}.lint.$_tool" ]
663*61c4878aSAndroid Build Coastguard Worker      }
664*61c4878aSAndroid Build Coastguard Worker    }
665*61c4878aSAndroid Build Coastguard Worker
666*61c4878aSAndroid Build Coastguard Worker    if (_static_analysis != [] || _test_sources != []) {
667*61c4878aSAndroid Build Coastguard Worker      # All packages to install for either general use or test running.
668*61c4878aSAndroid Build Coastguard Worker      _test_install_deps = [ ":$target_name.install" ]
669*61c4878aSAndroid Build Coastguard Worker
670*61c4878aSAndroid Build Coastguard Worker      foreach(dep, _python_test_deps) {
671*61c4878aSAndroid Build Coastguard Worker        _test_install_deps += [ string_replace(dep, "(", ".install(") ]
672*61c4878aSAndroid Build Coastguard Worker        _test_install_deps += [ dep ]
673*61c4878aSAndroid Build Coastguard Worker      }
674*61c4878aSAndroid Build Coastguard Worker    }
675*61c4878aSAndroid Build Coastguard Worker
676*61c4878aSAndroid Build Coastguard Worker    # For packages that are not generated, create targets to run mypy and pylint.
677*61c4878aSAndroid Build Coastguard Worker    foreach(_tool, _static_analysis) {
678*61c4878aSAndroid Build Coastguard Worker      # Run lint tools from the setup or target directory so that the tools detect
679*61c4878aSAndroid Build Coastguard Worker      # config files (e.g. pylintrc or mypy.ini) in that directory. Config files
680*61c4878aSAndroid Build Coastguard Worker      # may be explicitly specified with the pylintrc or mypy_ini arguments.
681*61c4878aSAndroid Build Coastguard Worker      target("_pw_python_static_analysis_$_tool", "$target_name.lint.$_tool") {
682*61c4878aSAndroid Build Coastguard Worker        sources = _all_py_files
683*61c4878aSAndroid Build Coastguard Worker        deps = _test_install_deps
684*61c4878aSAndroid Build Coastguard Worker        python_deps = _python_deps + _python_test_deps
685*61c4878aSAndroid Build Coastguard Worker
686*61c4878aSAndroid Build Coastguard Worker        if (_is_package) {
687*61c4878aSAndroid Build Coastguard Worker          python_metadata_deps = [ _pydeplabel ]
688*61c4878aSAndroid Build Coastguard Worker        }
689*61c4878aSAndroid Build Coastguard Worker
690*61c4878aSAndroid Build Coastguard Worker        _optional_variables = [
691*61c4878aSAndroid Build Coastguard Worker          "mypy_ini",
692*61c4878aSAndroid Build Coastguard Worker          "pylintrc",
693*61c4878aSAndroid Build Coastguard Worker          "ruff_toml",
694*61c4878aSAndroid Build Coastguard Worker        ]
695*61c4878aSAndroid Build Coastguard Worker        forward_variables_from(invoker, _optional_variables)
696*61c4878aSAndroid Build Coastguard Worker        not_needed(_optional_variables)
697*61c4878aSAndroid Build Coastguard Worker      }
698*61c4878aSAndroid Build Coastguard Worker    }
699*61c4878aSAndroid Build Coastguard Worker
700*61c4878aSAndroid Build Coastguard Worker    foreach(_unused_tool, _supported_static_analysis_tools - _static_analysis) {
701*61c4878aSAndroid Build Coastguard Worker      pw_input_group("$target_name.lint.$_unused_tool") {
702*61c4878aSAndroid Build Coastguard Worker        inputs = []
703*61c4878aSAndroid Build Coastguard Worker        if (defined(invoker.pylintrc)) {
704*61c4878aSAndroid Build Coastguard Worker          inputs += [ invoker.pylintrc ]
705*61c4878aSAndroid Build Coastguard Worker        }
706*61c4878aSAndroid Build Coastguard Worker        if (defined(invoker.mypy_ini)) {
707*61c4878aSAndroid Build Coastguard Worker          inputs += [ invoker.mypy_ini ]
708*61c4878aSAndroid Build Coastguard Worker        }
709*61c4878aSAndroid Build Coastguard Worker        if (defined(invoker.ruff_toml)) {
710*61c4878aSAndroid Build Coastguard Worker          inputs += [ invoker.ruff_toml ]
711*61c4878aSAndroid Build Coastguard Worker        }
712*61c4878aSAndroid Build Coastguard Worker      }
713*61c4878aSAndroid Build Coastguard Worker
714*61c4878aSAndroid Build Coastguard Worker      # Generated packages with linting disabled never need the whole file list.
715*61c4878aSAndroid Build Coastguard Worker      not_needed([ "_all_py_files" ])
716*61c4878aSAndroid Build Coastguard Worker    }
717*61c4878aSAndroid Build Coastguard Worker  } else {
718*61c4878aSAndroid Build Coastguard Worker    # Create groups with the public target names ($target_name, $target_name.lint,
719*61c4878aSAndroid Build Coastguard Worker    # $target_name.install, etc.). These are actually wrappers around internal
720*61c4878aSAndroid Build Coastguard Worker    # Python actions instantiated with the default toolchain. This ensures there
721*61c4878aSAndroid Build Coastguard Worker    # is only a single copy of each Python action in the build.
722*61c4878aSAndroid Build Coastguard Worker    #
723*61c4878aSAndroid Build Coastguard Worker    # The $target_name.tests group is created separately below.
724*61c4878aSAndroid Build Coastguard Worker    group("$target_name") {
725*61c4878aSAndroid Build Coastguard Worker      deps = [ ":$target_name($pw_build_PYTHON_TOOLCHAIN)" ]
726*61c4878aSAndroid Build Coastguard Worker    }
727*61c4878aSAndroid Build Coastguard Worker
728*61c4878aSAndroid Build Coastguard Worker    foreach(subtarget, pw_python_package_subtargets - [ "tests" ]) {
729*61c4878aSAndroid Build Coastguard Worker      group("$target_name.$subtarget") {
730*61c4878aSAndroid Build Coastguard Worker        deps =
731*61c4878aSAndroid Build Coastguard Worker            [ ":${invoker.target_name}.$subtarget($pw_build_PYTHON_TOOLCHAIN)" ]
732*61c4878aSAndroid Build Coastguard Worker      }
733*61c4878aSAndroid Build Coastguard Worker    }
734*61c4878aSAndroid Build Coastguard Worker
735*61c4878aSAndroid Build Coastguard Worker    # Everything Python-related is only instantiated in the default toolchain.
736*61c4878aSAndroid Build Coastguard Worker    # Silence not-needed warnings except for in the default toolchain.
737*61c4878aSAndroid Build Coastguard Worker    not_needed("*")
738*61c4878aSAndroid Build Coastguard Worker    not_needed(invoker, "*")
739*61c4878aSAndroid Build Coastguard Worker  }
740*61c4878aSAndroid Build Coastguard Worker
741*61c4878aSAndroid Build Coastguard Worker  # Create a target for each test file.
742*61c4878aSAndroid Build Coastguard Worker  _test_targets = []
743*61c4878aSAndroid Build Coastguard Worker
744*61c4878aSAndroid Build Coastguard Worker  foreach(test, _test_sources) {
745*61c4878aSAndroid Build Coastguard Worker    if (_is_package) {
746*61c4878aSAndroid Build Coastguard Worker      _name = rebase_path(test, _setup_dir)
747*61c4878aSAndroid Build Coastguard Worker    } else {
748*61c4878aSAndroid Build Coastguard Worker      _name = test
749*61c4878aSAndroid Build Coastguard Worker    }
750*61c4878aSAndroid Build Coastguard Worker
751*61c4878aSAndroid Build Coastguard Worker    _test_target = "$target_name.tests." + string_replace(_name, "/", "_")
752*61c4878aSAndroid Build Coastguard Worker
753*61c4878aSAndroid Build Coastguard Worker    if (current_toolchain == pw_build_PYTHON_TOOLCHAIN) {
754*61c4878aSAndroid Build Coastguard Worker      pw_python_action(_test_target) {
755*61c4878aSAndroid Build Coastguard Worker        if (pw_build_PYTHON_TEST_COVERAGE) {
756*61c4878aSAndroid Build Coastguard Worker          module = "coverage"
757*61c4878aSAndroid Build Coastguard Worker          working_directory =
758*61c4878aSAndroid Build Coastguard Worker              rebase_path(get_path_info(test, "dir"), root_build_dir)
759*61c4878aSAndroid Build Coastguard Worker          args = [
760*61c4878aSAndroid Build Coastguard Worker            "run",
761*61c4878aSAndroid Build Coastguard Worker            "--branch",
762*61c4878aSAndroid Build Coastguard Worker
763*61c4878aSAndroid Build Coastguard Worker            # Include all source files in the working_directory when calculating coverage.
764*61c4878aSAndroid Build Coastguard Worker            "--source=.",
765*61c4878aSAndroid Build Coastguard Worker
766*61c4878aSAndroid Build Coastguard Worker            # Test file to run.
767*61c4878aSAndroid Build Coastguard Worker            get_path_info(test, "file"),
768*61c4878aSAndroid Build Coastguard Worker          ]
769*61c4878aSAndroid Build Coastguard Worker
770*61c4878aSAndroid Build Coastguard Worker          # Set the coverage file to a location in out/python/gen/
771*61c4878aSAndroid Build Coastguard Worker          _coverage_data_file = "$target_gen_dir/$target_name.coverage"
772*61c4878aSAndroid Build Coastguard Worker          outputs = [ _coverage_data_file ]
773*61c4878aSAndroid Build Coastguard Worker
774*61c4878aSAndroid Build Coastguard Worker          # The coverage tool only allows setting the output with an environment variable.
775*61c4878aSAndroid Build Coastguard Worker          environment =
776*61c4878aSAndroid Build Coastguard Worker              [ "COVERAGE_FILE=" +
777*61c4878aSAndroid Build Coastguard Worker                rebase_path(_coverage_data_file, get_path_info(test, "dir")) ]
778*61c4878aSAndroid Build Coastguard Worker        } else {
779*61c4878aSAndroid Build Coastguard Worker          script = test
780*61c4878aSAndroid Build Coastguard Worker        }
781*61c4878aSAndroid Build Coastguard Worker
782*61c4878aSAndroid Build Coastguard Worker        stamp = true
783*61c4878aSAndroid Build Coastguard Worker
784*61c4878aSAndroid Build Coastguard Worker        # Make sure the python test deps are added to the PYTHONPATH.
785*61c4878aSAndroid Build Coastguard Worker        python_metadata_deps = _python_test_deps
786*61c4878aSAndroid Build Coastguard Worker
787*61c4878aSAndroid Build Coastguard Worker        # If this is a test for a package, add it to PYTHONPATH as well. This is
788*61c4878aSAndroid Build Coastguard Worker        # required if the test source file isn't in the same directory as the
789*61c4878aSAndroid Build Coastguard Worker        # folder containing the package sources to allow local Python imports.
790*61c4878aSAndroid Build Coastguard Worker        if (_is_package) {
791*61c4878aSAndroid Build Coastguard Worker          python_metadata_deps += [ _pydeplabel ]
792*61c4878aSAndroid Build Coastguard Worker        }
793*61c4878aSAndroid Build Coastguard Worker
794*61c4878aSAndroid Build Coastguard Worker        deps = _test_install_deps
795*61c4878aSAndroid Build Coastguard Worker
796*61c4878aSAndroid Build Coastguard Worker        if (pw_build_TEST_TRANSITIVE_PYTHON_DEPS) {
797*61c4878aSAndroid Build Coastguard Worker          foreach(dep, _python_test_deps) {
798*61c4878aSAndroid Build Coastguard Worker            deps += [ string_replace(dep, "(", ".tests(") ]
799*61c4878aSAndroid Build Coastguard Worker          }
800*61c4878aSAndroid Build Coastguard Worker        }
801*61c4878aSAndroid Build Coastguard Worker      }
802*61c4878aSAndroid Build Coastguard Worker    } else {
803*61c4878aSAndroid Build Coastguard Worker      # Create a public version of each test target, so tests can be executed as
804*61c4878aSAndroid Build Coastguard Worker      # //path/to:package.tests.foo.py.
805*61c4878aSAndroid Build Coastguard Worker      group(_test_target) {
806*61c4878aSAndroid Build Coastguard Worker        deps = [ ":$_test_target($pw_build_PYTHON_TOOLCHAIN)" ]
807*61c4878aSAndroid Build Coastguard Worker      }
808*61c4878aSAndroid Build Coastguard Worker    }
809*61c4878aSAndroid Build Coastguard Worker
810*61c4878aSAndroid Build Coastguard Worker    _test_targets += [ ":$_test_target" ]
811*61c4878aSAndroid Build Coastguard Worker  }
812*61c4878aSAndroid Build Coastguard Worker
813*61c4878aSAndroid Build Coastguard Worker  group("$target_name.tests") {
814*61c4878aSAndroid Build Coastguard Worker    deps = _test_targets
815*61c4878aSAndroid Build Coastguard Worker  }
816*61c4878aSAndroid Build Coastguard Worker
817*61c4878aSAndroid Build Coastguard Worker  _pw_create_aliases_if_name_matches_directory(target_name) {
818*61c4878aSAndroid Build Coastguard Worker  }
819*61c4878aSAndroid Build Coastguard Worker}
820*61c4878aSAndroid Build Coastguard Worker
821*61c4878aSAndroid Build Coastguard Worker# Declares a group of Python packages or other Python groups. pw_python_groups
822*61c4878aSAndroid Build Coastguard Worker# expose the same set of subtargets as pw_python_package (e.g.
823*61c4878aSAndroid Build Coastguard Worker# "$group_name.lint" and "$group_name.tests"), but these apply to all packages
824*61c4878aSAndroid Build Coastguard Worker# in deps and their dependencies.
825*61c4878aSAndroid Build Coastguard Workertemplate("pw_python_group") {
826*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.python_deps)) {
827*61c4878aSAndroid Build Coastguard Worker    _python_deps = invoker.python_deps
828*61c4878aSAndroid Build Coastguard Worker  } else {
829*61c4878aSAndroid Build Coastguard Worker    _python_deps = []
830*61c4878aSAndroid Build Coastguard Worker    not_needed([ "invoker" ])  # Allow empty groups.
831*61c4878aSAndroid Build Coastguard Worker  }
832*61c4878aSAndroid Build Coastguard Worker
833*61c4878aSAndroid Build Coastguard Worker  group(target_name) {
834*61c4878aSAndroid Build Coastguard Worker    deps = _python_deps
835*61c4878aSAndroid Build Coastguard Worker
836*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.other_deps)) {
837*61c4878aSAndroid Build Coastguard Worker      deps += invoker.other_deps
838*61c4878aSAndroid Build Coastguard Worker    }
839*61c4878aSAndroid Build Coastguard Worker  }
840*61c4878aSAndroid Build Coastguard Worker
841*61c4878aSAndroid Build Coastguard Worker  # Create a target group for the Python package metadata only.
842*61c4878aSAndroid Build Coastguard Worker  group("$target_name._package_metadata") {
843*61c4878aSAndroid Build Coastguard Worker    # Forward the package_metadata subtarget for all python_deps.
844*61c4878aSAndroid Build Coastguard Worker    public_deps = []
845*61c4878aSAndroid Build Coastguard Worker    foreach(dep, _python_deps) {
846*61c4878aSAndroid Build Coastguard Worker      public_deps += [ get_label_info(dep, "label_no_toolchain") +
847*61c4878aSAndroid Build Coastguard Worker                       "._package_metadata($pw_build_PYTHON_TOOLCHAIN)" ]
848*61c4878aSAndroid Build Coastguard Worker    }
849*61c4878aSAndroid Build Coastguard Worker  }
850*61c4878aSAndroid Build Coastguard Worker
851*61c4878aSAndroid Build Coastguard Worker  foreach(subtarget, pw_python_package_subtargets) {
852*61c4878aSAndroid Build Coastguard Worker    group("$target_name.$subtarget") {
853*61c4878aSAndroid Build Coastguard Worker      public_deps = []
854*61c4878aSAndroid Build Coastguard Worker      foreach(dep, _python_deps) {
855*61c4878aSAndroid Build Coastguard Worker        # Split out the toolchain to support deps with a toolchain specified.
856*61c4878aSAndroid Build Coastguard Worker        _target = get_label_info(dep, "label_no_toolchain")
857*61c4878aSAndroid Build Coastguard Worker        _toolchain = get_label_info(dep, "toolchain")
858*61c4878aSAndroid Build Coastguard Worker        public_deps += [ "$_target.$subtarget($_toolchain)" ]
859*61c4878aSAndroid Build Coastguard Worker      }
860*61c4878aSAndroid Build Coastguard Worker    }
861*61c4878aSAndroid Build Coastguard Worker  }
862*61c4878aSAndroid Build Coastguard Worker
863*61c4878aSAndroid Build Coastguard Worker  _pw_create_aliases_if_name_matches_directory(target_name) {
864*61c4878aSAndroid Build Coastguard Worker  }
865*61c4878aSAndroid Build Coastguard Worker}
866*61c4878aSAndroid Build Coastguard Worker
867*61c4878aSAndroid Build Coastguard Worker# Declares Python scripts or tests that are not part of a Python package.
868*61c4878aSAndroid Build Coastguard Worker# Similar to pw_python_package, but only supports a subset of its features.
869*61c4878aSAndroid Build Coastguard Worker#
870*61c4878aSAndroid Build Coastguard Worker# pw_python_script accepts the same arguments as pw_python_package, except
871*61c4878aSAndroid Build Coastguard Worker# `setup` cannot be provided.
872*61c4878aSAndroid Build Coastguard Worker#
873*61c4878aSAndroid Build Coastguard Worker# pw_python_script provides the same subtargets as pw_python_package, but
874*61c4878aSAndroid Build Coastguard Worker# $target_name.install and $target_name.wheel only affect the python_deps of
875*61c4878aSAndroid Build Coastguard Worker# this GN target, not the target itself.
876*61c4878aSAndroid Build Coastguard Worker#
877*61c4878aSAndroid Build Coastguard Worker# pw_python_script allows creating a pw_python_action associated with the
878*61c4878aSAndroid Build Coastguard Worker# script. This is provided by passing an 'action' scope to pw_python_script.
879*61c4878aSAndroid Build Coastguard Worker# This functions like a normal action, with a few additions: the action uses the
880*61c4878aSAndroid Build Coastguard Worker# pw_python_script's python_deps and defaults to using the source file as its
881*61c4878aSAndroid Build Coastguard Worker# 'script' argument, if there is only a single source file.
882*61c4878aSAndroid Build Coastguard Workertemplate("pw_python_script") {
883*61c4878aSAndroid Build Coastguard Worker  _package_variables = [
884*61c4878aSAndroid Build Coastguard Worker    "sources",
885*61c4878aSAndroid Build Coastguard Worker    "tests",
886*61c4878aSAndroid Build Coastguard Worker    "python_deps",
887*61c4878aSAndroid Build Coastguard Worker    "python_test_deps",
888*61c4878aSAndroid Build Coastguard Worker    "python_metadata_deps",
889*61c4878aSAndroid Build Coastguard Worker    "other_deps",
890*61c4878aSAndroid Build Coastguard Worker    "inputs",
891*61c4878aSAndroid Build Coastguard Worker    "pylintrc",
892*61c4878aSAndroid Build Coastguard Worker    "mypy_ini",
893*61c4878aSAndroid Build Coastguard Worker    "ruff_toml",
894*61c4878aSAndroid Build Coastguard Worker    "static_analysis",
895*61c4878aSAndroid Build Coastguard Worker  ]
896*61c4878aSAndroid Build Coastguard Worker
897*61c4878aSAndroid Build Coastguard Worker  pw_python_package(target_name) {
898*61c4878aSAndroid Build Coastguard Worker    _pw_standalone = true
899*61c4878aSAndroid Build Coastguard Worker    forward_variables_from(invoker, _package_variables)
900*61c4878aSAndroid Build Coastguard Worker  }
901*61c4878aSAndroid Build Coastguard Worker
902*61c4878aSAndroid Build Coastguard Worker  _pw_create_aliases_if_name_matches_directory(target_name) {
903*61c4878aSAndroid Build Coastguard Worker  }
904*61c4878aSAndroid Build Coastguard Worker
905*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.action)) {
906*61c4878aSAndroid Build Coastguard Worker    pw_python_action("$target_name.action") {
907*61c4878aSAndroid Build Coastguard Worker      forward_variables_from(invoker.action, "*", [ "python_deps" ])
908*61c4878aSAndroid Build Coastguard Worker      forward_variables_from(invoker, [ "testonly" ])
909*61c4878aSAndroid Build Coastguard Worker      python_deps = [ ":${invoker.target_name}" ]
910*61c4878aSAndroid Build Coastguard Worker
911*61c4878aSAndroid Build Coastguard Worker      if (!defined(script) && !defined(module) && defined(invoker.sources)) {
912*61c4878aSAndroid Build Coastguard Worker        _sources = invoker.sources
913*61c4878aSAndroid Build Coastguard Worker        assert(_sources != [] && _sources == [ _sources[0] ],
914*61c4878aSAndroid Build Coastguard Worker               "'script' must be specified unless there is only one source " +
915*61c4878aSAndroid Build Coastguard Worker                   "in 'sources'")
916*61c4878aSAndroid Build Coastguard Worker        script = _sources[0]
917*61c4878aSAndroid Build Coastguard Worker      }
918*61c4878aSAndroid Build Coastguard Worker    }
919*61c4878aSAndroid Build Coastguard Worker  }
920*61c4878aSAndroid Build Coastguard Worker}
921*61c4878aSAndroid Build Coastguard Worker
922*61c4878aSAndroid Build Coastguard Worker# Represents a list of Python requirements, as in a requirements.txt.
923*61c4878aSAndroid Build Coastguard Worker#
924*61c4878aSAndroid Build Coastguard Worker# Args:
925*61c4878aSAndroid Build Coastguard Worker#  files: One or more requirements.txt files.
926*61c4878aSAndroid Build Coastguard Worker#  requirements: A list of requirements.txt-style requirements.
927*61c4878aSAndroid Build Coastguard Workertemplate("pw_python_requirements") {
928*61c4878aSAndroid Build Coastguard Worker  assert(defined(invoker.files) || defined(invoker.requirements),
929*61c4878aSAndroid Build Coastguard Worker         "pw_python_requirements requires a list of requirements.txt files " +
930*61c4878aSAndroid Build Coastguard Worker             "in the 'files' arg or requirements in 'requirements'")
931*61c4878aSAndroid Build Coastguard Worker
932*61c4878aSAndroid Build Coastguard Worker  _requirements_files = []
933*61c4878aSAndroid Build Coastguard Worker
934*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.files)) {
935*61c4878aSAndroid Build Coastguard Worker    _requirements_files += invoker.files
936*61c4878aSAndroid Build Coastguard Worker  }
937*61c4878aSAndroid Build Coastguard Worker
938*61c4878aSAndroid Build Coastguard Worker  if (defined(invoker.requirements)) {
939*61c4878aSAndroid Build Coastguard Worker    _requirements_file = "$target_gen_dir/$target_name.requirements.txt"
940*61c4878aSAndroid Build Coastguard Worker    write_file(_requirements_file, invoker.requirements)
941*61c4878aSAndroid Build Coastguard Worker    _requirements_files += [ _requirements_file ]
942*61c4878aSAndroid Build Coastguard Worker  }
943*61c4878aSAndroid Build Coastguard Worker
944*61c4878aSAndroid Build Coastguard Worker  # The default target represents the requirements themselves.
945*61c4878aSAndroid Build Coastguard Worker  pw_input_group(target_name) {
946*61c4878aSAndroid Build Coastguard Worker    inputs = _requirements_files
947*61c4878aSAndroid Build Coastguard Worker  }
948*61c4878aSAndroid Build Coastguard Worker
949*61c4878aSAndroid Build Coastguard Worker  # Use the same subtargets as pw_python_package so these targets can be listed
950*61c4878aSAndroid Build Coastguard Worker  # as python_deps of pw_python_packages.
951*61c4878aSAndroid Build Coastguard Worker  group("$target_name.install") {
952*61c4878aSAndroid Build Coastguard Worker    # TODO: b/232800695 - Remove reliance on this subtarget existing.
953*61c4878aSAndroid Build Coastguard Worker  }
954*61c4878aSAndroid Build Coastguard Worker
955*61c4878aSAndroid Build Coastguard Worker  # Create stubs for the unused subtargets so that pw_python_requirements can be
956*61c4878aSAndroid Build Coastguard Worker  # used as python_deps.
957*61c4878aSAndroid Build Coastguard Worker  foreach(subtarget, pw_python_package_subtargets - [ "install" ]) {
958*61c4878aSAndroid Build Coastguard Worker    group("$target_name.$subtarget") {
959*61c4878aSAndroid Build Coastguard Worker    }
960*61c4878aSAndroid Build Coastguard Worker  }
961*61c4878aSAndroid Build Coastguard Worker
962*61c4878aSAndroid Build Coastguard Worker  # Create a target group for the Python package metadata only.
963*61c4878aSAndroid Build Coastguard Worker  group("$target_name._package_metadata") {
964*61c4878aSAndroid Build Coastguard Worker    # Forward the package_metadata subtarget for all python_deps.
965*61c4878aSAndroid Build Coastguard Worker    public_deps = []
966*61c4878aSAndroid Build Coastguard Worker    if (defined(invoker.python_deps)) {
967*61c4878aSAndroid Build Coastguard Worker      foreach(dep, invoker.python_deps) {
968*61c4878aSAndroid Build Coastguard Worker        public_deps += [ get_label_info(dep, "label_no_toolchain") +
969*61c4878aSAndroid Build Coastguard Worker                         "._package_metadata($pw_build_PYTHON_TOOLCHAIN)" ]
970*61c4878aSAndroid Build Coastguard Worker      }
971*61c4878aSAndroid Build Coastguard Worker    }
972*61c4878aSAndroid Build Coastguard Worker  }
973*61c4878aSAndroid Build Coastguard Worker
974*61c4878aSAndroid Build Coastguard Worker  _pw_create_aliases_if_name_matches_directory(target_name) {
975*61c4878aSAndroid Build Coastguard Worker  }
976*61c4878aSAndroid Build Coastguard Worker}
977