Skip to content

Commit e66e346

Browse files
Ensure get_requires* hook is called before prepare_metadata* (#3044)
* Cache all hooks in Pep517VirtualEnvFrontend This is useful when a hook is called multiple times (like get_requires_for_build_wheel, which is called before prepare_metadata_for_build_wheel and build_wheel). * Extract build requires installation in Pep517VirtualEnvPackager This method can then be re-used if we need to call prepare_metadata_for_build_*. * Ensure get_requires* is called before prepare_metadata* To improve compatibility with PEP 517, the dependencies should be installed before the prepare_metadata* hooks are called. * Fix linting * Add changelog entry * Fix affected tests * PR Feedback Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net> * Improve bug fix message Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net> * Fix tests Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net> * More fixes Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net> --------- Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net> Co-authored-by: Bernát Gábor <bgabor8@bloomberg.net>
1 parent 40411cf commit e66e346

File tree

5 files changed

+35
-18
lines changed

5 files changed

+35
-18
lines changed

docs/changelog/3043.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Ensure that ``get_requires_for_build_wheel`` is called before ``prepare_metadata_for_build_wheel``, and
2+
``get_requires_for_build_editable`` is called before ``prepare_metadata_for_build_editable`` - by :user:`abravalheri`.

src/tox/tox_env/python/virtual_env/package/pyproject.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
from collections import defaultdict
77
from contextlib import contextmanager
8+
from itertools import chain
89
from pathlib import Path
910
from threading import RLock
1011
from typing import TYPE_CHECKING, Any, Dict, Generator, Iterator, NoReturn, Optional, Sequence, cast
@@ -97,6 +98,7 @@ def __init__(self, create_args: ToxEnvCreateArgs) -> None:
9798
super().__init__(create_args)
9899
self._frontend_: Pep517VirtualEnvFrontend | None = None
99100
self.builds: defaultdict[str, list[EnvConfigSet]] = defaultdict(list)
101+
self.call_require_hooks: set[str] = set()
100102
self._distribution_meta: PathDistribution | None = None
101103
self._package_dependencies: list[Requirement] | None = None
102104
self._package_name: str | None = None
@@ -150,21 +152,23 @@ def meta_folder_if_populated(self) -> Path | None:
150152
def register_run_env(self, run_env: RunToxEnv) -> Generator[tuple[str, str], PackageToxEnv, None]:
151153
yield from super().register_run_env(run_env)
152154
build_type = run_env.conf["package"]
155+
self.call_require_hooks.add(build_type)
153156
self.builds[build_type].append(run_env.conf)
154157

155158
def _setup_env(self) -> None:
156159
super()._setup_env()
157-
if "editable" in self.builds:
160+
if "sdist" in self.call_require_hooks or "external" in self.call_require_hooks:
161+
self._setup_build_requires("sdist")
162+
if "wheel" in self.call_require_hooks:
163+
self._setup_build_requires("wheel")
164+
if "editable" in self.call_require_hooks:
158165
if not self._frontend.optional_hooks["build_editable"]:
159166
raise BuildEditableNotSupportedError
160-
build_requires = self._frontend.get_requires_for_build_editable().requires
161-
self._install(build_requires, PythonPackageToxEnv.__name__, "requires_for_build_editable")
162-
if "wheel" in self.builds:
163-
build_requires = self._frontend.get_requires_for_build_wheel().requires
164-
self._install(build_requires, PythonPackageToxEnv.__name__, "requires_for_build_wheel")
165-
if "sdist" in self.builds or "external" in self.builds:
166-
build_requires = self._frontend.get_requires_for_build_sdist().requires
167-
self._install(build_requires, PythonPackageToxEnv.__name__, "requires_for_build_sdist")
167+
self._setup_build_requires("editable")
168+
169+
def _setup_build_requires(self, of_type: str) -> None:
170+
requires = getattr(self._frontend, f"get_requires_for_build_{of_type}")().requires
171+
self._install(requires, PythonPackageToxEnv.__name__, f"requires_for_build_{of_type}")
168172

169173
def _teardown(self) -> None:
170174
executor = self._frontend.backend_executor
@@ -187,6 +191,7 @@ def perform_packaging(self, for_env: EnvConfigSet) -> list[Package]:
187191
try:
188192
deps = self._load_deps(for_env)
189193
except BuildEditableNotSupportedError:
194+
self.call_require_hooks.remove("editable")
190195
targets = [e for e in self.builds.pop("editable") if e["package"] == "editable"]
191196
names = ", ".join(sorted({t.env_name for t in targets if t.env_name}))
192197

@@ -199,6 +204,7 @@ def perform_packaging(self, for_env: EnvConfigSet) -> list[Package]:
199204
for env in targets:
200205
env._defined["package"].value = "editable-legacy" # type: ignore[attr-defined] # noqa: SLF001
201206
self.builds["editable-legacy"].append(env)
207+
self._run_state["setup"] = False # force setup again as we need to provision wheel to get dependencies
202208
deps = self._load_deps(for_env)
203209
of_type: str = for_env["package"]
204210
if of_type == "editable-legacy":
@@ -309,12 +315,13 @@ def get_package_name(self, for_env: EnvConfigSet) -> str:
309315
def _ensure_meta_present(self, for_env: EnvConfigSet) -> None:
310316
if self._distribution_meta is not None: # pragma: no branch
311317
return # pragma: no cover
318+
# even if we don't build a wheel we need the requirements for it should we want to build its metadata
319+
target = "editable" if for_env["package"] == "editable" else "wheel"
320+
self.call_require_hooks.add(target)
321+
312322
self.setup()
313-
end = self._frontend
314-
if for_env["package"] == "editable":
315-
dist_info = end.prepare_metadata_for_build_editable(self.meta_folder, self._wheel_config_settings).metadata
316-
else:
317-
dist_info = end.prepare_metadata_for_build_wheel(self.meta_folder, self._wheel_config_settings).metadata
323+
hook = getattr(self._frontend, f"prepare_metadata_for_build_{target}")
324+
dist_info = hook(self.meta_folder, self._wheel_config_settings).metadata
318325
self._distribution_meta = Distribution.at(str(dist_info))
319326

320327
@property
@@ -332,12 +339,16 @@ def __init__(self, root: Path, env: Pep517VirtualEnvPackager) -> None:
332339
self._backend_executor_: LocalSubProcessPep517Executor | None = None
333340
into: dict[str, Any] = {}
334341

335-
for build_type in ("editable", "sdist", "wheel"): # wrap build methods in a cache wrapper
342+
for hook in chain(
343+
(f"get_requires_for_build_{build_type}" for build_type in ["editable", "wheel", "sdist"]),
344+
(f"prepare_metadata_for_build_{build_type}" for build_type in ["editable", "wheel"]),
345+
(f"build_{build_type}" for build_type in ["editable", "wheel", "sdist"]),
346+
): # wrap build methods in a cache wrapper
336347

337-
def key(*args: Any, bound_return: str = build_type, **kwargs: Any) -> str: # noqa: ARG001
348+
def key(*args: Any, bound_return: str = hook, **kwargs: Any) -> str: # noqa: ARG001
338349
return bound_return
339350

340-
setattr(self, f"build_{build_type}", cached(into, key=key)(getattr(self, f"build_{build_type}")))
351+
setattr(self, hook, cached(into, key=key)(getattr(self, hook)))
341352

342353
@property
343354
def backend_cmd(self) -> Sequence[str]:

tests/tox_env/python/virtual_env/package/test_package_cmd_builder.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def test_tox_install_pkg_sdist(tox_project: ToxProjectCreator, pkg_with_extras_p
5656
(".pkg_external_sdist_meta", "install_requires", ["setuptools", "wheel"]),
5757
(".pkg_external_sdist_meta", "_optional_hooks", []),
5858
(".pkg_external_sdist_meta", "get_requires_for_build_sdist", []),
59+
(".pkg_external_sdist_meta", "get_requires_for_build_wheel", []), # required before prepare_metadata*
60+
(".pkg_external_sdist_meta", "install_requires_for_build_wheel", ["wheel"]),
5961
(".pkg_external_sdist_meta", "prepare_metadata_for_build_wheel", []),
6062
("py", "install_package_deps", deps),
6163
("py", "install_package", ["--force-reinstall", "--no-deps", str(pkg_with_extras_project_sdist)]),

tests/tox_env/python/virtual_env/package/test_package_pyproject.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ def test_pyproject_deps_static_with_dynamic( # noqa: PLR0913
213213
expected_calls = [
214214
(".pkg", "_optional_hooks"),
215215
(".pkg", "get_requires_for_build_sdist"),
216+
(".pkg", "get_requires_for_build_wheel"),
216217
(".pkg", "build_wheel"),
217218
(".pkg", "build_sdist"),
218219
("py", "install_package_deps"),
@@ -239,10 +240,10 @@ def test_pyproject_no_build_editable_fallback(tox_project: ToxProjectCreator, de
239240

240241
expected_calls = [
241242
(".pkg", "_optional_hooks"),
243+
(".pkg", "get_requires_for_build_wheel"),
242244
(".pkg", "build_wheel"),
243245
(".pkg", "get_requires_for_build_sdist"),
244246
("a", "install_package"),
245-
(".pkg", "get_requires_for_build_sdist"),
246247
("b", "install_package"),
247248
(".pkg", "_exit"),
248249
]

tests/tox_env/test_tox_env_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def test_package_only(
2424
expected_calls = [
2525
(".pkg", "_optional_hooks"),
2626
(".pkg", "get_requires_for_build_sdist"),
27+
(".pkg", "get_requires_for_build_wheel"),
2728
(".pkg", "build_wheel"),
2829
(".pkg", "build_sdist"),
2930
(".pkg", "_exit"),

0 commit comments

Comments
 (0)