# 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)" ] } }