aboutsummaryrefslogtreecommitdiff
path: root/pw_build/python_venv.gni
diff options
context:
space:
mode:
Diffstat (limited to 'pw_build/python_venv.gni')
-rw-r--r--pw_build/python_venv.gni274
1 files changed, 245 insertions, 29 deletions
diff --git a/pw_build/python_venv.gni b/pw_build/python_venv.gni
index c36b7f90f..6a5ae770c 100644
--- a/pw_build/python_venv.gni
+++ b/pw_build/python_venv.gni
@@ -46,6 +46,9 @@ import("$dir_pw_build/python_action.gni")
# requirements: A list of requirements files to install into this virtualenv
# on creation. By default this is set to pw_build_PIP_REQUIREMENTS
#
+# pip_generate_hashes: (Default: false) Use --generate-hashes When
+# running pip-compile to compute the final requirements.txt
+#
# 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
@@ -54,6 +57,8 @@ import("$dir_pw_build/python_action.gni")
# this page for a setup.cfg example:
# https://setuptools.pypa.io/en/latest/userguide/declarative_config.html
#
+# output_logs: (Default: true) Commands will output logs.
+#
template("pw_python_venv") {
assert(defined(invoker.path), "pw_python_venv requires a 'path'")
@@ -62,6 +67,9 @@ template("pw_python_venv") {
_generated_requirements_file =
"$target_gen_dir/$target_name/generated_requirements.txt"
+ _compiled_requirements_file =
+ "$target_gen_dir/$target_name/compiled_requirements.txt"
+
_source_packages = []
if (defined(invoker.source_packages)) {
_source_packages += invoker.source_packages
@@ -71,6 +79,14 @@ template("pw_python_venv") {
"_generated_requirements_file",
])
}
+ _output_logs = true
+ if (defined(invoker.output_logs)) {
+ _output_logs = invoker.output_logs
+ }
+ if (!defined(invoker.output_logs) ||
+ current_toolchain != pw_build_PYTHON_TOOLCHAIN) {
+ not_needed([ "_output_logs" ])
+ }
_source_package_labels = []
foreach(pkg, _source_packages) {
@@ -101,6 +117,8 @@ template("pw_python_venv") {
path = rebase_path(_path, root_build_dir)
generated_requirements =
rebase_path(_generated_requirements_file, root_build_dir)
+ compiled_requirements =
+ rebase_path(_compiled_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)
@@ -115,10 +133,17 @@ template("pw_python_venv") {
# outputs and must stamp instead.
stamp = true
+ # The virtualenv should depend on the version of Python currently in use.
+ stampfile = "$target_gen_dir/$target_name.pw_pystamp"
+ depfile = "$target_gen_dir/$target_name.d"
script = "$dir_pw_build/py/pw_build/create_gn_venv.py"
args = [
+ "--depfile",
+ rebase_path(depfile, root_build_dir),
"--destination-dir",
rebase_path(_path, root_build_dir),
+ "--stampfile",
+ rebase_path(stampfile, root_build_dir),
]
}
@@ -145,9 +170,19 @@ template("pw_python_venv") {
python_metadata_deps += _pkg_gn_labels
args = [
- "--requirement",
+ "--gn-root-build-dir",
+ rebase_path(root_build_dir, root_build_dir),
+ "--output-requirement-file",
rebase_path(_generated_requirements_file, root_build_dir),
]
+
+ if (_constraints != []) {
+ args += [ "--constraint-files" ]
+ }
+ foreach(_constraints_file, _constraints) {
+ args += [ rebase_path(_constraints_file, root_build_dir) ]
+ }
+
args += [
"--gn-packages",
string_join(",", _pkg_gn_labels),
@@ -161,27 +196,131 @@ template("pw_python_venv") {
}
}
+ _pip_generate_hashes = false
+ if (defined(invoker.pip_generate_hashes)) {
+ _pip_generate_hashes = invoker.pip_generate_hashes
+ } else {
+ not_needed([ "_pip_generate_hashes" ])
+ }
+
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.
+ # Compile requirements with hashes
+ pw_python_action("${target_name}._compile_requirements") {
+ module = "piptools"
+
+ # Set the venv to run this pip install in.
+ _pw_internal_run_in_venv = true
+ _skip_installing_external_python_deps = true
+ venv = get_label_info(":${invoker.target_name}", "label_no_toolchain")
+
+ _pip_args = []
+ if (pw_build_PYTHON_PIP_INSTALL_OFFLINE) {
+ _pip_args += [ "--no-index" ]
+ }
+ if (pw_build_PYTHON_PIP_INSTALL_DISABLE_CACHE) {
+ _pip_args += [ "--no-cache-dir" ]
+ }
+ if (pw_build_PYTHON_PIP_INSTALL_FIND_LINKS != []) {
+ foreach(link_dir, pw_build_PYTHON_PIP_INSTALL_FIND_LINKS) {
+ _pip_args +=
+ [ "--find-links=" + rebase_path(link_dir, root_build_dir) ]
+ }
+ }
+
+ args = [ "compile" ]
+
+ if (_pip_generate_hashes) {
+ args += [
+ "--generate-hashes",
+ "--reuse-hashes",
+ ]
+ }
+
+ args += [
+ "--resolver=backtracking",
+
+ # --allow-unsafe will force pinning pip and setuptools.
+ "--allow-unsafe",
+ "--output-file",
+ rebase_path(_compiled_requirements_file, root_build_dir),
+
+ # Input requirements file:
+ rebase_path(_generated_requirements_file, root_build_dir),
+ ]
+
+ # Pass offline related pip args through the pip-compile command.
+ if (_pip_args != []) {
+ args += [
+ "--pip-args",
+ string_join(" ", _pip_args),
+ ]
+ }
+
+ # Extra requirements files
+ foreach(_requirements_file, _requirements) {
+ args += [ rebase_path(_requirements_file, root_build_dir) ]
+ }
+
+ inputs = []
+
+ # NOTE: constraint files are included in the content of the
+ # _generated_requirements_file. This occurs in the
+ # ._generate_3p_requirements target.
+ inputs += _constraints
+ inputs += _requirements
+ inputs += [ _generated_requirements_file ]
+
+ deps = [
+ ":${invoker.target_name}._generate_3p_requirements($pw_build_PYTHON_TOOLCHAIN)",
+ ":${invoker.target_name}._install_base_3p_deps($pw_build_PYTHON_TOOLCHAIN)",
+ ]
+ outputs = [ _compiled_requirements_file ]
+ }
+
+ # This target will run 'pip install' in the venv to provide base
+ # dependencies needed to run piptools commands. That is required for the
+ # _compile_requirements sub target.
pw_python_action("${target_name}._install_base_3p_deps") {
module = "pip"
+
+ # Set the venv to run this pip install in.
_pw_internal_run_in_venv = true
_skip_installing_external_python_deps = true
+ venv = get_label_info(":${invoker.target_name}", "label_no_toolchain")
+
+ _base_requirement_file = "$dir_pw_env_setup/py/pw_env_setup/virtualenv_setup/python_base_requirements.txt"
+
args = [
"install",
- "wheel",
+ "--requirement",
+ rebase_path(_base_requirement_file, root_build_dir),
]
- inputs = _constraints
-
- foreach(_constraints_file, _constraints) {
+ if (_output_logs) {
+ _pip_install_log_file =
+ "$target_gen_dir/$target_name/pip_install_log.txt"
args += [
- "--constraint",
- rebase_path(_constraints_file, root_build_dir),
+ "--log",
+ rebase_path(_pip_install_log_file, root_build_dir),
]
+ outputs = [ _pip_install_log_file ]
+ }
+
+ # NOTE: Constraints should be ignored for this step.
+
+ if (pw_build_PYTHON_PIP_INSTALL_OFFLINE) {
+ args += [ "--no-index" ]
+ }
+ if (pw_build_PYTHON_PIP_INSTALL_DISABLE_CACHE) {
+ args += [ "--no-cache-dir" ]
+ }
+ if (pw_build_PYTHON_PIP_INSTALL_FIND_LINKS != []) {
+ foreach(link_dir, pw_build_PYTHON_PIP_INSTALL_FIND_LINKS) {
+ args += [
+ "--find-links",
+ rebase_path(link_dir, root_build_dir),
+ ]
+ }
}
deps = [ ":${invoker.target_name}._create_virtualenv($pw_build_PYTHON_TOOLCHAIN)" ]
@@ -192,55 +331,132 @@ template("pw_python_venv") {
# Install all 3rd party Python dependencies.
pw_python_action("${target_name}._install_3p_deps") {
module = "pip"
+
+ # Set the venv to run this pip install in.
_pw_internal_run_in_venv = true
_skip_installing_external_python_deps = true
- args = [ "install" ]
+ venv = get_label_info(":${invoker.target_name}", "label_no_toolchain")
- # 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
+ args = pw_build_PYTHON_PIP_DEFAULT_OPTIONS
+ args += [
+ "install",
+ "--upgrade",
+ ]
- # Constraints
- foreach(_constraints_file, _constraints) {
+ if (_output_logs) {
+ _pip_install_log_file =
+ "$target_gen_dir/$target_name/pip_install_log.txt"
args += [
- "--constraint",
- rebase_path(_constraints_file, root_build_dir),
+ "--log",
+ rebase_path(_pip_install_log_file, root_build_dir),
]
}
- # Extra requirements files
- foreach(_requirements_file, _requirements) {
- args += [
- "--requirement",
- rebase_path(_requirements_file, root_build_dir),
- ]
+ if (_pip_generate_hashes) {
+ args += [ "--require-hashes" ]
+ }
+
+ if (pw_build_PYTHON_PIP_INSTALL_OFFLINE) {
+ args += [ "--no-index" ]
+ }
+ if (pw_build_PYTHON_PIP_INSTALL_DISABLE_CACHE) {
+ args += [ "--no-cache-dir" ]
+ }
+ if (pw_build_PYTHON_PIP_INSTALL_FIND_LINKS != []) {
+ foreach(link_dir, pw_build_PYTHON_PIP_INSTALL_FIND_LINKS) {
+ args += [
+ "--find-links",
+ rebase_path(link_dir, root_build_dir),
+ ]
+ }
}
- # Generated Python requirements file.
+ # 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 + [ _compiled_requirements_file ]
+
+ # Use the pip-tools compiled requiremets file. This contains the fully
+ # expanded list of deps with constraints applied.
if (defined(invoker.source_packages)) {
- inputs += [ _generated_requirements_file ]
+ inputs += [ _compiled_requirements_file ]
args += [
"--requirement",
- rebase_path(_generated_requirements_file, root_build_dir),
+ rebase_path(_compiled_requirements_file, root_build_dir),
]
}
deps = [
+ ":${invoker.target_name}._compile_requirements($pw_build_PYTHON_TOOLCHAIN)",
":${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)"
}
+
+ # Target to create a Python package cache for this pw_python_venv.
+ pw_python_action("${target_name}.vendor_wheels") {
+ _wheel_output_dir = "$target_gen_dir/$target_name/wheels"
+ _pip_download_logfile =
+ "$target_gen_dir/$target_name/pip_download_log.txt"
+ _pip_wheel_logfile = "$target_gen_dir/$target_name/pip_wheel_log.txt"
+ metadata = {
+ pw_python_package_wheels = [ _wheel_output_dir ]
+ }
+
+ script = "$dir_pw_build/py/pw_build/generate_python_wheel_cache.py"
+
+ # Set the venv to run this pip install in.
+ _pw_internal_run_in_venv = true
+ _skip_installing_external_python_deps = true
+ venv = get_label_info(":${invoker.target_name}", "label_no_toolchain")
+
+ args = [
+ "--pip-download-log",
+ rebase_path(_pip_download_logfile, root_build_dir),
+ "--wheel-dir",
+ rebase_path(_wheel_output_dir, root_build_dir),
+ "-r",
+ rebase_path(_compiled_requirements_file, root_build_dir),
+ ]
+
+ if (pw_build_PYTHON_PIP_DOWNLOAD_ALL_PLATFORMS) {
+ args += [ "--download-all-platforms" ]
+ }
+
+ deps = [
+ ":${invoker.target_name}._compile_requirements($pw_build_PYTHON_TOOLCHAIN)",
+ ":${invoker.target_name}._generate_3p_requirements($pw_build_PYTHON_TOOLCHAIN)",
+ ]
+
+ outputs = [
+ _wheel_output_dir,
+ _pip_wheel_logfile,
+ _pip_download_logfile,
+ ]
+ pool = "$dir_pw_build/pool:pip($default_toolchain)"
+ }
+
+ # End pw_build_PYTHON_TOOLCHAIN check
} else {
+ group("${target_name}._compile_requirements") {
+ public_deps = [ ":${target_name}($pw_build_PYTHON_TOOLCHAIN)" ]
+ }
group("${target_name}._install_3p_deps") {
public_deps = [ ":${target_name}($pw_build_PYTHON_TOOLCHAIN)" ]
}
+ group("${target_name}.vendor_wheels") {
+ public_deps = [ ":${target_name}($pw_build_PYTHON_TOOLCHAIN)" ]
+ }
}
} else {
+ group("${target_name}._compile_requirements") {
+ }
group("${target_name}._install_3p_deps") {
}
+ group("${target_name}.vendor_wheels") {
+ }
}
# Have this target directly depend on _install_3p_deps