1# Copyright 2020 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14 15import("//build_overrides/pigweed.gni") 16 17# Defines an action that runs a Python script. 18# 19# This wraps a regular Python script GN action with an invocation of a script- 20# runner script that adds useful features. pw_python_action() uses the same 21# actions as GN's action(), with the following additions or changes: 22# 23# module May be used in place of the script argument to run the 24# provided Python module with `python -m` instead of a script. 25# Either script or module must be provided. 26# 27# capture_output If true, script output is hidden unless the script fails 28# with an error. Defaults to true. 29# 30# stamp File to touch if the script is successful. Actions that 31# don't create output files can use this stamp file instead of 32# creating their own placeholder file. If true, a generic file 33# is used. If false or not set, no file is touched. 34# 35# environment Environment variables to set, passed as a list of NAME=VALUE 36# strings. 37# 38# args Same as the standard action args, except special expressions 39# may be used to extract information not normally accessible 40# in GN. These include the following: 41# 42# <TARGET_FILE(//some/label:here)> - expands to the 43# output file (such as a .a or .elf) from a GN target 44# <TARGET_FILE_IF_EXISTS(//some/label:here)> - expands to 45# the output file if the target exists, or nothing 46# <TARGET_OBJECTS(//some/label:here)> - expands to the 47# object files produced by the provided GN target 48# 49# python_deps Dependencies on pw_python_package or related Python targets. 50# 51# working_directory Switch to the provided working directory before running 52# the Python script or action. 53# 54template("pw_python_action") { 55 assert(defined(invoker.script) != defined(invoker.module), 56 "pw_python_action requires either 'script' or 'module'") 57 58 _script_args = [ 59 # GN root directory relative to the build directory (in which the runner 60 # script is invoked). 61 "--gn-root", 62 rebase_path("//", root_build_dir), 63 64 # Current directory, used to resolve relative paths. 65 "--current-path", 66 rebase_path(".", root_build_dir), 67 68 # pip lockfile, prevents pip from running in parallel with other Python 69 # actions. 70 "--lockfile", 71 rebase_path("$root_out_dir/pip.lock", root_build_dir), 72 73 "--default-toolchain=$default_toolchain", 74 "--current-toolchain=$current_toolchain", 75 ] 76 77 if (defined(invoker.environment)) { 78 foreach(variable, invoker.environment) { 79 _script_args += [ "--env=$variable" ] 80 } 81 } 82 83 if (defined(invoker.inputs)) { 84 _inputs = invoker.inputs 85 } else { 86 _inputs = [] 87 } 88 89 # List the script to run as an input so that the action is re-run when it is 90 # modified. 91 if (defined(invoker.script)) { 92 _inputs += [ invoker.script ] 93 } 94 95 if (defined(invoker.outputs)) { 96 _outputs = invoker.outputs 97 } else { 98 _outputs = [] 99 } 100 101 # If a stamp file is requested, add it as an output of the runner script. 102 if (defined(invoker.stamp) && invoker.stamp != false) { 103 if (invoker.stamp == true) { 104 _stamp_file = "$target_gen_dir/$target_name.pw_pystamp" 105 } else { 106 _stamp_file = invoker.stamp 107 } 108 109 _outputs += [ _stamp_file ] 110 _script_args += [ 111 "--touch", 112 rebase_path(_stamp_file, root_build_dir), 113 ] 114 } 115 116 # Capture output or not (defaults to true). 117 if (!defined(invoker.capture_output) || invoker.capture_output) { 118 _script_args += [ "--capture-output" ] 119 } 120 121 if (defined(invoker.module)) { 122 _script_args += [ 123 "--module", 124 invoker.module, 125 ] 126 } 127 128 if (defined(invoker.working_directory)) { 129 _script_args += [ 130 "--working-directory", 131 invoker.working_directory, 132 ] 133 } 134 135 # "--" indicates the end of arguments to the runner script. 136 # Everything beyond this point is interpreted as the command and arguments 137 # of the Python script to run. 138 _script_args += [ "--" ] 139 140 if (defined(invoker.script)) { 141 _script_args += [ rebase_path(invoker.script, root_build_dir) ] 142 } 143 144 if (defined(invoker.args)) { 145 _script_args += invoker.args 146 } 147 148 if (defined(invoker._pw_action_type)) { 149 _action_type = invoker._pw_action_type 150 } else { 151 _action_type = "action" 152 } 153 154 if (defined(invoker.deps)) { 155 _deps = invoker.deps 156 } else { 157 _deps = [] 158 } 159 160 if (defined(invoker.python_deps)) { 161 foreach(dep, invoker.python_deps) { 162 _deps += [ get_label_info(dep, "label_no_toolchain") + ".install(" + 163 get_label_info(dep, "toolchain") + ")" ] 164 } 165 166 # Add the base target as a dep so the action reruns when any source files 167 # change, even if the package does not have to be reinstalled. 168 _deps += invoker.python_deps 169 } 170 171 target(_action_type, target_name) { 172 _ignore_vars = [ 173 "script", 174 "args", 175 "deps", 176 "inputs", 177 "outputs", 178 ] 179 forward_variables_from(invoker, "*", _ignore_vars) 180 181 script = "$dir_pw_build/py/pw_build/python_runner.py" 182 args = _script_args 183 inputs = _inputs 184 outputs = _outputs 185 deps = _deps 186 } 187} 188 189# Runs pw_python_action once per file over a set of sources. 190# 191# This template brings pw_python_action's features to action_foreach. Usage is 192# the same as pw_python_action, except that sources must be provided and source 193# expansion (e.g. "{{source}}") may be used in args and outputs. 194# 195# See the pw_python_action and action_foreach documentation for full details. 196template("pw_python_action_foreach") { 197 assert(defined(invoker.sources) && invoker.sources != [], 198 "pw_python_action_foreach requires a list of one or more sources") 199 200 pw_python_action(target_name) { 201 if (defined(invoker.stamp) && invoker.stamp != false) { 202 if (invoker.stamp == true) { 203 # Use source file names in the generated stamp file path so they are 204 # unique for each source. 205 stamp = "$target_gen_dir/{{source_file_part}}.pw_pystamp" 206 } else { 207 stamp = invoker.stamp 208 } 209 } else { 210 stamp = false 211 } 212 213 forward_variables_from(invoker, "*", [ "stamp" ]) 214 215 _pw_action_type = "action_foreach" 216 } 217} 218