Skip to content

Commit

Permalink
Use PEP-621 to load project dependencies (#2499)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaborbernat committed Sep 10, 2022
1 parent 252299c commit 0ee8853
Show file tree
Hide file tree
Showing 15 changed files with 246 additions and 90 deletions.
1 change: 1 addition & 0 deletions docs/changelog/2499.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support PEP-621 static metadata for getting package dependencies - by :user:`gaborbernat`.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies = [
"platformdirs>=2.5.2",
"pluggy>=1",
"pyproject-api>=0.1.1",
"tomli>=2.0.1",
'tomli>=2.0.1;python_version<"3.11"',
"virtualenv>=20.16.5",
'importlib-metadata>=4.12; python_version < "3.8"',
'typing-extensions>=4.3; python_version < "3.8"',
Expand Down
9 changes: 7 additions & 2 deletions src/tox/config/source/legacy_toml.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from __future__ import annotations

import sys
from pathlib import Path

import tomli
if sys.version_info >= (3, 11): # pragma: no cover (py311+)
import tomllib
else: # pragma: no cover (py311+)
import tomli as tomllib


from .ini import IniSource

Expand All @@ -14,7 +19,7 @@ def __init__(self, path: Path):
if path.name != self.FILENAME or not path.exists():
raise ValueError
with path.open("rb") as file_handler:
toml_content = tomli.load(file_handler)
toml_content = tomllib.load(file_handler)
try:
content = toml_content["tool"]["tox"]["legacy_tox_ini"]
except KeyError:
Expand Down
2 changes: 1 addition & 1 deletion src/tox/execute/pep517_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def local_execute(self, options: ExecuteOptions) -> tuple[LocalSubProcessExecute
self.is_alive = True
break
if b"failed to start backend" in status.err:
from tox.tox_env.python.virtual_env.package.pep517 import ToxBackendFailed
from tox.tox_env.python.virtual_env.package.pyproject import ToxBackendFailed

failure = BackendFailed(
result={
Expand Down
4 changes: 2 additions & 2 deletions src/tox/plugin/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from tox.session.cmd.run import parallel, sequential
from tox.tox_env import package as package_api
from tox.tox_env.python.virtual_env import runner
from tox.tox_env.python.virtual_env.package import cmd_builder, pep517
from tox.tox_env.python.virtual_env.package import cmd_builder, pyproject
from tox.tox_env.register import REGISTER, ToxEnvRegister

from ..execute import Outcome
Expand All @@ -39,7 +39,7 @@ def _register_plugins(self, inline: ModuleType | None) -> None:
loader_api,
provision,
runner,
pep517,
pyproject,
cmd_builder,
legacy,
version_flag,
Expand Down
3 changes: 2 additions & 1 deletion src/tox/provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ def tox_add_option(parser: ArgumentParser) -> None:


def provision(state: State) -> int | bool:
# remove the dev and marker to allow local development of the package
state.conf.core.add_config(
keys=["min_version", "minversion"],
of_type=Version,
# do not include local version specifier (because it's not allowed in version spec per PEP-440)
default=Version(current_version.split("+")[0]),
default=Version(current_version),
desc="Define the minimal tox version required to run",
)
state.conf.core.add_config(
Expand Down
2 changes: 1 addition & 1 deletion src/tox/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ def enable_pep517_backend_coverage() -> Iterator[None]: # noqa: PT004
yield # pragma: no cover
return # pragma: no cover
# the COV_ env variables needs to be passed on for the PEP-517 backend
from tox.tox_env.python.virtual_env.package.pep517 import Pep517VirtualEnvPackager
from tox.tox_env.python.virtual_env.package.pyproject import Pep517VirtualEnvPackager

def default_pass_env(self: Pep517VirtualEnvPackager) -> list[str]:
result = previous(self)
Expand Down
2 changes: 1 addition & 1 deletion src/tox/session/cmd/run/single.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from tox.execute.api import Outcome, StdinSource
from tox.tox_env.api import ToxEnv
from tox.tox_env.errors import Fail, Skip
from tox.tox_env.python.virtual_env.package.pep517 import ToxBackendFailed
from tox.tox_env.python.virtual_env.package.pyproject import ToxBackendFailed
from tox.tox_env.runner import RunToxEnv

LOGGER = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion src/tox/tox_env/python/virtual_env/package/cmd_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from tox.tox_env.register import ToxEnvRegister
from tox.tox_env.runner import RunToxEnv

from .pep517 import Pep517VirtualEnvPackager
from .pyproject import Pep517VirtualEnvPackager
from .util import dependencies_with_extras

if sys.version_info >= (3, 8): # pragma: no cover (py38+)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
from importlib.metadata import Distribution, PathDistribution
else: # pragma: no cover (<py38)
from importlib_metadata import Distribution, PathDistribution

if sys.version_info >= (3, 11): # pragma: no cover (py311+)
import tomllib
else: # pragma: no cover (py311+)
import tomli as tomllib

ConfigSettings = Optional[Dict[str, Any]]


Expand Down Expand Up @@ -143,23 +149,14 @@ def _teardown(self) -> None:

def perform_packaging(self, for_env: EnvConfigSet) -> list[Package]:
"""build the package to install"""
deps = self._load_deps(for_env)
of_type: str = for_env["package"]

reqs: list[Requirement] | None = None
if of_type == "wheel":
w_env = self._wheel_build_envs.get(for_env["wheel_build_env"])
if w_env is not None and w_env is not self:
with w_env.display_context(self._has_display_suspended):
reqs = w_env.get_package_dependencies() if isinstance(w_env, Pep517VirtualEnvPackager) else []
if reqs is None:
reqs = self.get_package_dependencies()

extras: set[str] = for_env["extras"]
deps = dependencies_with_extras(reqs, extras)
if of_type == "dev-legacy":
self.setup()
deps = [*self.requires(), *self._frontend.get_requires_for_build_sdist().requires] + deps
package: Package = DevLegacyPackage(self.core["tox_root"], deps) # the folder itself is the package
elif of_type == "sdist":
self.setup()
with self._pkg_lock:
package = SdistPackage(self._frontend.build_sdist(sdist_directory=self.pkg_dir).sdist, deps)
elif of_type == "wheel":
Expand All @@ -168,6 +165,7 @@ def perform_packaging(self, for_env: EnvConfigSet) -> list[Package]:
with w_env.display_context(self._has_display_suspended):
return w_env.perform_packaging(for_env)
else:
self.setup()
with self._pkg_lock:
path = self._frontend.build_wheel(
wheel_directory=self.pkg_dir,
Expand All @@ -179,6 +177,49 @@ def perform_packaging(self, for_env: EnvConfigSet) -> list[Package]:
raise TypeError(f"cannot handle package type {of_type}") # pragma: no cover
return [package]

def _load_deps(self, for_env: EnvConfigSet) -> list[Requirement]:
# first check if this is statically available via PEP-621
deps = self._load_deps_from_static(for_env)
if deps is None:
deps = self._load_deps_from_built_metadata(for_env)
return deps

def _load_deps_from_static(self, for_env: EnvConfigSet) -> list[Requirement] | None:
pyproject_file = self.core["package_root"] / "pyproject.toml"
if not pyproject_file.exists(): # check if it's static PEP-621 metadata
return None
with pyproject_file.open("rb") as file_handler:
pyproject = tomllib.load(file_handler)
if "project" not in pyproject:
return None # is not a PEP-621 pyproject
project = pyproject["project"]
extras: set[str] = for_env["extras"]
for dynamic in project.get("dynamic", []):
if dynamic == "dependencies" or (extras and dynamic == "optional-dependencies"):
return None # if any dependencies are dynamic we can just calculate all dynamically

deps: list[Requirement] = [Requirement(i) for i in project.get("dependencies", [])]
optional_deps = project.get("optional-dependencies", {})
for extra in extras:
deps.extend(Requirement(i) for i in optional_deps.get(extra, []))
return deps

def _load_deps_from_built_metadata(self, for_env: EnvConfigSet) -> list[Requirement]:
# dependencies might depend on the python environment we're running in => if we build a wheel use that env
# to calculate the package metadata, otherwise ourselves
of_type: str = for_env["package"]
reqs: list[Requirement] | None = None
if of_type == "wheel": # wheel packages
w_env = self._wheel_build_envs.get(for_env["wheel_build_env"])
if w_env is not None and w_env is not self:
with w_env.display_context(self._has_display_suspended):
reqs = w_env.get_package_dependencies() if isinstance(w_env, Pep517VirtualEnvPackager) else []
if reqs is None:
reqs = self.get_package_dependencies()
extras: set[str] = for_env["extras"]
deps = dependencies_with_extras(reqs, extras)
return deps

def get_package_dependencies(self) -> list[Requirement]:
with self._pkg_lock:
if self._package_dependencies is None: # pragma: no branch
Expand Down
2 changes: 2 additions & 0 deletions tests/demo_pkg_inline/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
{}
Platform: UNKNOWN
UNKNOWN
""".format(
pkg_name,
version,
"\n ".join(os.environ.get("METADATA_EXTRA", "").split("\n")),
),
wheel: """
Wheel-Version: 1.0
Expand Down
61 changes: 0 additions & 61 deletions tests/tox_env/python/virtual_env/package/test_package_pep517.py

This file was deleted.

0 comments on commit 0ee8853

Please sign in to comment.