blob: a9fbdad3884a65c7692b99dc1cc3a145d94178f0 [file] [log] [blame]
# Copyright 2021 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Defines a Python binary.
#
# Example
#
# ```
# python_binary("main") {
# main_source = "main.py"
# main_callable = "main"
# sources = [
# "foo.py",
# "bar.py",
# ]
# enable_mypy = true
# output_name = "main.pyz"
# deps = [ "//path/to/lib" ]
# }
# ```
#
# Parameters
#
# main_source (required)
# Source file including the entry callable for this binary.
# This file will typically contain
# ```
# if __name__ == "__main__":
# main()
# ```
# Type: path
#
# main_callable (optional)
# Main callable, which serves as the entry point of the output zip archive.
# In the example above, this is "main".
# Type: string
# Default: main
#
# output_name (optional)
# Name of the output Python zip archive, must have .pyz as extension.
# Type: string
# Default: ${target_name}.pyz
#
# data_sources (optional)
# List of data sources for this python binary. The sources are accessible
# at runtime as below:
# ```python
# from importlib.resources import files
# import <data_package_name>
#
# with files(<data_package_name>).joinpath(<source_name>).open("rb"):
# ...
# ```
# Type: list(path)
#
# data_package_name (required if |data_sources| is provided, else optional)
# Name of the data package to store |data_sources| in.
# See "data_sources" above for more detail.
# Type: string
#
# enable_mypy (optional)
# If true, enable MyPy type checking on the target and respective deps.
# Type: boolean
# Default: False
#
# unbuffered_output (optional)
# If true, run the Python interpreter in unbuffered mode (-u) by default.
# WARNING: This only affects running the output as a binary without
# specifying an interpreter (./foo.pyz, not python3 foo.pyz).
# Type: boolean
# Default: False
#
# sources
# deps
# visibility
# testonly
template("python_binary") {
assert(defined(invoker.main_source), "main_source is required")
_library_infos_target = "${target_name}_library_infos"
_library_infos_json = "${target_gen_dir}/${target_name}_library_infos.json"
generated_file(_library_infos_target) {
forward_variables_from(invoker, [ "testonly" ])
visibility = [ ":*" ]
if (defined(invoker.deps)) {
# Ensure dependent action() sees these dependencies as well.
public_deps = invoker.deps
}
outputs = [ _library_infos_json ]
output_conversion = "json"
data_keys = [ "library_info" ]
walk_keys = [ "library_info_barrier" ]
}
_data_sources = []
if (defined(invoker.data_sources)) {
assert(defined(invoker.data_package_name),
"data_package_name is required if data_sources are provided")
_data_sources = invoker.data_sources
}
action(target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
sources = [ invoker.main_source ]
if (defined(invoker.sources)) {
sources += invoker.sources
}
inputs = [ _library_infos_json ] + _data_sources
# Note that this inherits invoker.deps, which is important for the case
# where main_source is generated by one of them.
deps = [ ":${_library_infos_target}" ]
# Output must be a .pyz, so our build knows to use a vendored Python
# interpreter to run them.
#
# Output a single .pyz file makes the output deterministic, otherwise we'd
# have to list out all the library sources that will be copied to output
# directory, which is not possible because they are not known until the
# generated JSON file is parsed at build time.
_output = "${target_out_dir}/${target_name}.pyz"
if (defined(invoker.output_name)) {
assert(get_path_info(invoker.output_name, "extension") == "pyz",
"output_name must have .pyz as extension")
_output = "${target_out_dir}/${invoker.output_name}"
}
outputs = [ _output ]
_main_callable = "main"
if (defined(invoker.main_callable)) {
_main_callable = invoker.main_callable
}
inputs += [
"//build/python/mypy_checker.py",
"//pyproject.toml",
]
script = "//build/python/package_python_binary.py"
depfile = "${target_out_dir}/${target_name}.d"
args = [ "--sources" ] + rebase_path(sources, root_build_dir) + [
"--target_name",
target_name,
"--main_source",
rebase_path(invoker.main_source, root_build_dir),
"--main_callable",
_main_callable,
"--library_infos",
rebase_path(_library_infos_json, root_build_dir),
"--data_sources",
] + rebase_path(_data_sources, root_build_dir) +
[
"--depfile",
rebase_path(depfile, root_build_dir),
"--gen_dir",
rebase_path(target_gen_dir, root_build_dir),
"--output",
rebase_path(_output, root_build_dir),
]
if (defined(invoker.data_package_name)) {
args += [
"--data_package_name",
invoker.data_package_name,
]
}
if (defined(invoker.enable_mypy) && invoker.enable_mypy) {
args += [ "--enable_mypy" ]
}
if (defined(invoker.unbuffered_output) && invoker.unbuffered_output) {
args += [ "--unbuffered" ]
}
}
}