pw_build: Integrated actions for pw_python_script - Support adding an action to a pw_python_script. This bridges the gap between pw_python_script and pw_python_action and prevents accidentally creating an action that lacks dependencies on its pw_python_script target. - Require either script or module in pw_python_action. - Ensure pw_python_actions rerun when any files in their python_deps change, even if the packages do not have to be reinstalled. Change-Id: I104bc73b63293b61a0a47e0dcc12f1595f8d4d35 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/43740 Commit-Queue: Wyatt Hepler <hepler@google.com> Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_build/python.gni b/pw_build/python.gni index c7ff3e4..50d247f 100644 --- a/pw_build/python.gni +++ b/pw_build/python.gni
@@ -686,8 +686,14 @@ # pw_python_script provides the same subtargets as pw_python_package, but # $target_name.install and $target_name.wheel only affect the python_deps of # this GN target, not the target itself. +# +# pw_python_script allows creating a pw_python_action associated with the +# script. This is provided by passing an 'action' scope to pw_python_script. +# This functions like a normal action, with a few additions: the action uses the +# pw_python_script's python_deps and defaults to using the source file as its +# 'script' argument, if there is only a single source file. template("pw_python_script") { - _supported_variables = [ + _package_variables = [ "sources", "tests", "python_deps", @@ -700,11 +706,26 @@ pw_python_package(target_name) { _pw_standalone = true - forward_variables_from(invoker, _supported_variables) + forward_variables_from(invoker, _package_variables) } _pw_create_aliases_if_name_matches_directory(target_name) { } + + if (defined(invoker.action)) { + pw_python_action("$target_name.action") { + forward_variables_from(invoker.action, "*", [ "python_deps" ]) + python_deps = [ ":${invoker.target_name}" ] + + if (!defined(script) && !defined(module) && defined(invoker.sources)) { + _sources = invoker.sources + assert(_sources != [] && _sources == [ _sources[0] ], + "'script' must be specified unless there is only one source " + + "in 'sources'") + script = _sources[0] + } + } + } } # Represents a list of Python requirements, as in a requirements.txt.
diff --git a/pw_build/python.rst b/pw_build/python.rst index c1d25c9..7ea0b27 100644 --- a/pw_build/python.rst +++ b/pw_build/python.rst
@@ -131,6 +131,25 @@ those ``setup``. These targets can be installed, but this only installs their dependencies. +``pw_python_script`` allows creating a +:ref:`pw_python_action <module-pw_build-python-action>` associated with the +script. To create an action, pass an ``action`` scope to ``pw_python_script``. +If there is only a single source file, it serves as the action's ``script`` by +default. + +An action in ``pw_python_script`` can always be replaced with a standalone +``pw_python_action``, but using the embedded action has some advantages: + +- The embedded action target bridges the gap between actions and Python targets. + A Python script can be expressed in a single, concise GN target, rather than + in two overlapping, dependent targets. +- The action automatically depends on the ``pw_python_script``. This ensures + that the script's dependencies are installed and the action automatically + reruns when the script's sources change, without needing to specify a + dependency, a step which is easy to forget. +- Using a ``pw_python_script`` with an embedded action is a simple way to check + an existing action's script with Pylint or Mypy or to add tests. + pw_python_group =============== Represents a group of ``pw_python_package`` and ``pw_python_script`` targets.
diff --git a/pw_build/python_action.gni b/pw_build/python_action.gni index 05b1e90..c0396aa 100644 --- a/pw_build/python_action.gni +++ b/pw_build/python_action.gni
@@ -53,6 +53,9 @@ # python_deps Dependencies on pw_python_package or related Python targets. # template("pw_python_action") { + assert(defined(invoker.script) != defined(invoker.module), + "pw_python_action requires either 'script' or 'module'") + _script_args = [ # GN root directory relative to the build directory (in which the runner # script is invoked). @@ -155,6 +158,10 @@ _deps += [ get_label_info(dep, "label_no_toolchain") + ".install(" + get_label_info(dep, "toolchain") + ")" ] } + + # Add the base target as a dep so the action reruns when any source files + # change, even if the package does not have to be reinstalled. + _deps += invoker.python_deps } target(_action_type, target_name) {