• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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