blob: 1af9464a695b48e821112415a5b5b606c72fb6bc [file] [log] [blame]
# Copyright 2019 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
import("//build_overrides/pigweed.gni")
# Defines an action to run a Python script.
#
# This wraps a regular Python script action with an invocation of a script-
# runner script which resolves GN paths to filesystem paths and locates output
# files for binary targets.
#
# The interface to this template is identical to that of a regular "action"
# which runs a Python script, except for two key differences:
#
# 1. Regular GN actions typically require a call to rebase_path to resolve
# GN paths to filesystem paths. This template requires that all paths
# remain GN paths, but are made absolute.
#
# This means that an "action" argument of the form:
#
# rebase_path("my/relative/path:optional_target", root_build_dir)
#
# Becomes:
#
# get_path_info("my/relative/path:optional_target", "abspath")
#
# 2. The behavior of the runner script depends on whether a provided path is a
# regular build path or an output path (starting with "$root_out_dir").
# If an output path is provided and the path has a target, the script
# assumes that the target refers to a file built by Ninja and tries to
# locate it within the output directory.
#
# Additionally, this template can accept a boolean "stamp" argument. If set to
# true, the script runner will touch a file to indicate the success of the run.
# This is provided so that individual Python scripts are not required to define
# an output file if they do not have one.
#
# Path resolution examples (assuming the build directory is //out):
#
# BEFORE AFTER
#
# //my_module ../my_module
# //my_module:foo ../my_module:foo
# //my_module/file.txt ../my_module/file.txt
# $root_out_dir/my_module ../out/obj/my_module
# $target_out_dir ../out/obj/my_module (in //my_module/BUILD.gn)
# $target_out_dir/out.json ../out/obj/my_module/out.json
# $target_out_dir:foo ../out/obj/my_module/foo.elf (toolchain-dependent)
# $target_out_dir:foo ../out/obj/my_module/foo.exe (toolchain-dependent)
#
# Arguments beyond normal action() target arguments:
#
# module Used in place of the script argument to run the
# provided Python module with `python -m` instead of a
# script. Either script or module must be provided.
#
# capture_output (=true) If true, script output is hidden unless the script
# fails with an error. Defaults to true.
#
# stamp File to touch if the script is successful. If set to
# true, a generic file is used. If false or not set,
# no file is touched.
#
# directory The directory from which to execute the Python
# script. Paths in args may need to be adjusted to be
# relative to this directory.
#
# environment Environment variables to set, passed as a list of
# NAME=VALUE strings.
#
template("pw_python_script") {
assert(defined(invoker.script) != defined(invoker.module),
"pw_python_script must have either a script or module to run, " +
"but not both")
_script_args = [
# GN root directory relative to the build directory (in which the runner
# script is invoked).
"--gn-root",
rebase_path("//"),
# Current directory, used to resolve relative paths.
"--current-path",
rebase_path("."),
"--default-toolchain=$default_toolchain",
"--current-toolchain=$current_toolchain",
]
if (defined(invoker.directory)) {
_script_args += [
"--directory",
rebase_path(invoker.directory),
]
}
if (defined(invoker.environment)) {
foreach(variable, invoker.environment) {
_script_args += [ "--env=$variable" ]
}
}
if (defined(invoker.inputs)) {
_inputs = invoker.inputs
} else {
_inputs = []
}
# List the script to run as an input so that the action is re-run when it is
# modified.
if (defined(invoker.script)) {
_inputs += [ invoker.script ]
}
if (defined(invoker.outputs)) {
_outputs = invoker.outputs
} else {
_outputs = []
}
# If a stamp file is requested, add it as an output of the runner script.
if (defined(invoker.stamp) && invoker.stamp != false) {
if (invoker.stamp == true) {
_stamp_file = "$target_gen_dir/$target_name.pw_pystamp"
} else {
_stamp_file = invoker.stamp
}
_outputs += [ _stamp_file ]
_script_args += [
"--touch",
rebase_path(_stamp_file),
]
}
# Capture output or not.
# Note: capture defaults to true.
if (defined(invoker.capture_output)) {
forward_variables_from(invoker, [ "capture_output" ])
} else {
capture_output = true
}
if (capture_output) {
_script_args += [ "--capture-output" ]
}
if (defined(invoker.module)) {
_script_args += [
"--module",
invoker.module,
]
}
# "--" indicates the end of arguments to the runner script.
# Everything beyond this point is interpreted as the command and arguments
# of the Python script to run.
_script_args += [ "--" ]
if (defined(invoker.script)) {
_script_args += [ rebase_path(invoker.script) ]
}
if (defined(invoker.args)) {
_script_args += invoker.args
}
if (defined(invoker._pw_action_foreach) && invoker._pw_action_foreach) {
_action_type = "action_foreach"
} else {
_action_type = "action"
}
target(_action_type, target_name) {
_ignore_vars = [
"script",
"args",
"inputs",
"outputs",
]
forward_variables_from(invoker, "*", _ignore_vars)
script = "$dir_pw_build/py/pw_build/python_runner.py"
args = _script_args
inputs = _inputs
outputs = _outputs
}
}
# Runs pw_python_script once per file over a set of sources.
#
# This template brings pw_python_script's features to action_foreach. Usage is
# the same as pw_python_script, except that sources must be provided and source
# expansion (e.g. "{{source}}") may be used in args and outputs.
#
# See the pw_python_script and action_foreach documentation for full details.
template("pw_python_script_foreach") {
assert(defined(invoker.sources) && invoker.sources != [],
"pw_python_script_foreach requires a list of one or more sources")
pw_python_script(target_name) {
if (defined(invoker.stamp) && invoker.stamp != false) {
if (invoker.stamp == true) {
# Use source file names in the generated stamp file path so they are
# unique for each source.
stamp = "$target_gen_dir/{{source_file_part}}.pw_pystamp"
} else {
stamp = invoker.stamp
}
} else {
stamp = false
}
forward_variables_from(invoker, "*", [ "stamp" ])
_pw_action_foreach = true
}
}