blob: c36b7f90fbed6f200d1812f5db277b3555373cca [file] [log] [blame]
# Copyright 2022 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")
import("$dir_pw_build/python.gni")
import("$dir_pw_build/python_action.gni")
# Defines and creates a Python virtualenv. This template is used by Pigweed in
# https://cs.pigweed.dev/pigweed/+/main:pw_env_setup/BUILD.gn to
# create a virtualenv for use within the GN build that all Python actions will
# run in.
#
# Example:
#
# pw_python_venv("test_venv") {
# path = "test-venv"
# constraints = [ "//tools/constraints.list" ]
# requirements = [ "//tools/requirements.txt" ]
# source_packages = [
# "$dir_pw_cli/py",
# "$dir_pw_console/py",
# "//tools:another_pw_python_package",
# ]
# }
#
# Args:
# path: The directory where the virtualenv will be created. This is relative
# to the GN root and must begin with "$root_build_dir/" if it lives in the
# output directory or "//" if it lives in elsewhere.
#
# constraints: A list of constraint files used when performing pip install
# into this virtualenv. By default this is set to pw_build_PIP_CONSTRAINTS
#
# requirements: A list of requirements files to install into this virtualenv
# on creation. By default this is set to pw_build_PIP_REQUIREMENTS
#
# source_packages: A list of in-tree pw_python_package targets that will be
# checked for external third_party pip dependencies to install into this
# virtualenv. Note this list of targets isn't actually installed into the
# virtualenv. Only packages defined inside the [options] install_requires
# section of each pw_python_package's setup.cfg will be pip installed. See
# this page for a setup.cfg example:
# https://setuptools.pypa.io/en/latest/userguide/declarative_config.html
#
template("pw_python_venv") {
assert(defined(invoker.path), "pw_python_venv requires a 'path'")
_path = invoker.path
_generated_requirements_file =
"$target_gen_dir/$target_name/generated_requirements.txt"
_source_packages = []
if (defined(invoker.source_packages)) {
_source_packages += invoker.source_packages
} else {
not_needed([
"_source_packages",
"_generated_requirements_file",
])
}
_source_package_labels = []
foreach(pkg, _source_packages) {
_source_package_labels += [ get_label_info(pkg, "label_no_toolchain") ]
}
if (defined(invoker.requirements)) {
_requirements = invoker.requirements
} else {
_requirements = pw_build_PIP_REQUIREMENTS
}
if (defined(invoker.constraints)) {
_constraints = invoker.constraints
} else {
_constraints = pw_build_PIP_CONSTRAINTS
}
_python_interpreter = _path + "/bin/python"
if (host_os == "win") {
_python_interpreter = _path + "/Scripts/python.exe"
}
_venv_metadata_json_file = "$target_gen_dir/$target_name/venv_metadata.json"
_venv_metadata = {
gn_target_name =
get_label_info(":${invoker.target_name}", "label_no_toolchain")
path = rebase_path(_path, root_build_dir)
generated_requirements =
rebase_path(_generated_requirements_file, root_build_dir)
requirements = rebase_path(_requirements, root_build_dir)
constraints = rebase_path(_constraints, root_build_dir)
interpreter = rebase_path(_python_interpreter, root_build_dir)
source_packages = _source_package_labels
}
write_file(_venv_metadata_json_file, _venv_metadata, "json")
pw_python_action("${target_name}._create_virtualenv") {
_pw_internal_run_in_venv = false
# Note: The if the venv isn't in the out dir then we can't declare
# outputs and must stamp instead.
stamp = true
script = "$dir_pw_build/py/pw_build/create_gn_venv.py"
args = [
"--destination-dir",
rebase_path(_path, root_build_dir),
]
}
if (defined(invoker.source_packages) &&
current_toolchain == pw_build_PYTHON_TOOLCHAIN) {
pw_python_action("${target_name}._generate_3p_requirements") {
inputs = _requirements + _constraints
_pw_internal_run_in_venv = false
_forward_python_metadata_deps = true
script = "$dir_pw_build/py/pw_build/generate_python_requirements.py"
_pkg_gn_labels = []
foreach(pkg, _source_packages) {
_pkg_gn_labels += [ get_label_info(pkg, "label_no_toolchain") +
"($pw_build_PYTHON_TOOLCHAIN)" ]
}
# pw_build/py is always needed for venv creation and Python lint checks.
python_metadata_deps =
[ get_label_info("$dir_pw_build/py", "label_no_toolchain") +
"($pw_build_PYTHON_TOOLCHAIN)" ]
python_metadata_deps += _pkg_gn_labels
args = [
"--requirement",
rebase_path(_generated_requirements_file, root_build_dir),
]
args += [
"--gn-packages",
string_join(",", _pkg_gn_labels),
]
outputs = [ _generated_requirements_file ]
deps = [ ":${invoker.target_name}._create_virtualenv($pw_build_PYTHON_TOOLCHAIN)" ]
}
} else {
group("${target_name}._generate_3p_requirements") {
}
}
if (defined(invoker.source_packages) || defined(invoker.requirements)) {
if (current_toolchain == pw_build_PYTHON_TOOLCHAIN) {
# This target will run 'pip install wheel' in the venv. This is purposely
# run before further pip installs so packages that run bdist_wheel as part
# of their install process will succeed. Packages that run native compiles
# typically do this.
pw_python_action("${target_name}._install_base_3p_deps") {
module = "pip"
_pw_internal_run_in_venv = true
_skip_installing_external_python_deps = true
args = [
"install",
"wheel",
]
inputs = _constraints
foreach(_constraints_file, _constraints) {
args += [
"--constraint",
rebase_path(_constraints_file, root_build_dir),
]
}
deps = [ ":${invoker.target_name}._create_virtualenv($pw_build_PYTHON_TOOLCHAIN)" ]
stamp = true
pool = "$dir_pw_build/pool:pip($default_toolchain)"
}
# Install all 3rd party Python dependencies.
pw_python_action("${target_name}._install_3p_deps") {
module = "pip"
_pw_internal_run_in_venv = true
_skip_installing_external_python_deps = true
args = [ "install" ]
# Note: --no-build-isolation should be avoided for installing 3rd party
# Python packages that use C/C++ extension modules.
# https://setuptools.pypa.io/en/latest/userguide/ext_modules.html
inputs = _constraints + _requirements
# Constraints
foreach(_constraints_file, _constraints) {
args += [
"--constraint",
rebase_path(_constraints_file, root_build_dir),
]
}
# Extra requirements files
foreach(_requirements_file, _requirements) {
args += [
"--requirement",
rebase_path(_requirements_file, root_build_dir),
]
}
# Generated Python requirements file.
if (defined(invoker.source_packages)) {
inputs += [ _generated_requirements_file ]
args += [
"--requirement",
rebase_path(_generated_requirements_file, root_build_dir),
]
}
deps = [
":${invoker.target_name}._generate_3p_requirements($pw_build_PYTHON_TOOLCHAIN)",
":${invoker.target_name}._install_base_3p_deps($pw_build_PYTHON_TOOLCHAIN)",
]
stamp = true
pool = "$dir_pw_build/pool:pip($default_toolchain)"
}
} else {
group("${target_name}._install_3p_deps") {
public_deps = [ ":${target_name}($pw_build_PYTHON_TOOLCHAIN)" ]
}
}
} else {
group("${target_name}._install_3p_deps") {
}
}
# Have this target directly depend on _install_3p_deps
group("$target_name") {
public_deps =
[ ":${target_name}._install_3p_deps($pw_build_PYTHON_TOOLCHAIN)" ]
}
}