/src/tox/package/builder/isolated.py
https://github.com/tox-dev/tox · Python · 149 lines · 114 code · 30 blank · 5 comment · 28 complexity · 84d0362d54914b25d6c0433276196692 MD5 · raw file
- from __future__ import unicode_literals
- import json
- import os
- from collections import namedtuple
- import six
- from packaging.requirements import Requirement
- from packaging.utils import canonicalize_name
- from tox import reporter
- from tox.config import DepConfig, get_py_project_toml
- from tox.constants import BUILD_ISOLATED, BUILD_REQUIRE_SCRIPT
- BuildInfo = namedtuple(
- "BuildInfo",
- ["requires", "backend_module", "backend_object", "backend_paths"],
- )
- def build(config, session):
- build_info = get_build_info(config.setupdir)
- package_venv = session.getvenv(config.isolated_build_env)
- package_venv.envconfig.deps_matches_subset = True
- # we allow user specified dependencies so the users can write extensions to
- # install additional type of dependencies (e.g. binary)
- user_specified_deps = package_venv.envconfig.deps
- package_venv.envconfig.deps = [DepConfig(r, None) for r in build_info.requires]
- package_venv.envconfig.deps.extend(user_specified_deps)
- if package_venv.setupenv():
- package_venv.finishvenv()
- if isinstance(package_venv.status, Exception):
- raise package_venv.status
- build_requires = get_build_requires(build_info, package_venv, config.setupdir)
- # we need to filter out requirements already specified in pyproject.toml or user deps
- base_build_deps = {
- canonicalize_name(Requirement(r.name).name) for r in package_venv.envconfig.deps
- }
- build_requires_dep = [
- DepConfig(r, None)
- for r in build_requires
- if canonicalize_name(Requirement(r).name) not in base_build_deps
- ]
- if build_requires_dep:
- with package_venv.new_action("build_requires", package_venv.envconfig.envdir) as action:
- package_venv.run_install_command(packages=build_requires_dep, action=action)
- package_venv.finishvenv()
- return perform_isolated_build(build_info, package_venv, config.distdir, config.setupdir)
- def get_build_info(folder):
- toml_file = folder.join("pyproject.toml")
- # as per https://www.python.org/dev/peps/pep-0517/
- def abort(message):
- reporter.error("{} inside {}".format(message, toml_file))
- raise SystemExit(1)
- if not toml_file.exists():
- reporter.error("missing {}".format(toml_file))
- raise SystemExit(1)
- config_data = get_py_project_toml(toml_file)
- if "build-system" not in config_data:
- abort("build-system section missing")
- build_system = config_data["build-system"]
- if "requires" not in build_system:
- abort("missing requires key at build-system section")
- if "build-backend" not in build_system:
- abort("missing build-backend key at build-system section")
- requires = build_system["requires"]
- if not isinstance(requires, list) or not all(isinstance(i, six.text_type) for i in requires):
- abort("requires key at build-system section must be a list of string")
- backend = build_system["build-backend"]
- if not isinstance(backend, six.text_type):
- abort("build-backend key at build-system section must be a string")
- args = backend.split(":")
- module = args[0]
- obj = args[1] if len(args) > 1 else ""
- backend_paths = build_system.get("backend-path", [])
- if not isinstance(backend_paths, list):
- abort("backend-path key at build-system section must be a list, if specified")
- backend_paths = [folder.join(p) for p in backend_paths]
- normalized_folder = os.path.normcase(str(folder.realpath()))
- normalized_paths = (os.path.normcase(str(path.realpath())) for path in backend_paths)
- if not all(
- os.path.commonprefix((normalized_folder, path)) == normalized_folder
- for path in normalized_paths
- ):
- abort("backend-path must exist in the project root")
- return BuildInfo(requires, module, obj, backend_paths)
- def perform_isolated_build(build_info, package_venv, dist_dir, setup_dir):
- with package_venv.new_action(
- "perform-isolated-build",
- package_venv.envconfig.envdir,
- ) as action:
- # need to start with an empty (but existing) source distribution folder
- if dist_dir.exists():
- dist_dir.remove(rec=1, ignore_errors=True)
- dist_dir.ensure_dir()
- result = package_venv._pcall(
- [
- package_venv.envconfig.envpython,
- BUILD_ISOLATED,
- str(dist_dir),
- build_info.backend_module,
- build_info.backend_object,
- os.path.pathsep.join(str(p) for p in build_info.backend_paths),
- ],
- returnout=True,
- action=action,
- cwd=setup_dir,
- )
- reporter.verbosity2(result)
- return dist_dir.join(result.split("\n")[-2])
- def get_build_requires(build_info, package_venv, setup_dir):
- with package_venv.new_action("get-build-requires", package_venv.envconfig.envdir) as action:
- result = package_venv._pcall(
- [
- package_venv.envconfig.envpython,
- BUILD_REQUIRE_SCRIPT,
- build_info.backend_module,
- build_info.backend_object,
- os.path.pathsep.join(str(p) for p in build_info.backend_paths),
- ],
- returnout=True,
- action=action,
- cwd=setup_dir,
- )
- return json.loads(result.split("\n")[-2])