Skip to content

Commit 4b7d1ff

Browse files
authored
PEP-660 support (#2502)
1 parent 70601f9 commit 4b7d1ff

File tree

11 files changed

+66
-37
lines changed

11 files changed

+66
-37
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
__pycache__
1111
*.swp
1212
*.egg-info
13+
/tests/demo_pkg_setuptools/build/lib/demo_pkg_setuptools/__init__.py

docs/changelog/2502.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add support for editable wheels, make it the default development mode and rename ``dev-legacy`` mode to
2+
``editable-legacy`` - by :user:`gaborbernat`.

src/tox/tox_env/python/package.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@ class SdistPackage(PythonPathPackageWithDeps):
3939
"""sdist package"""
4040

4141

42-
class DevLegacyPackage(PythonPathPackageWithDeps):
43-
"""legacy dev package"""
42+
class EditableLegacyPackage(PythonPathPackageWithDeps):
43+
"""legacy editable package"""
44+
45+
46+
class EditablePackage(PythonPathPackageWithDeps):
47+
"""PEP-660 editable package"""
4448

4549

4650
class PythonPackageToxEnv(Python, PackageToxEnv, ABC):
@@ -59,7 +63,11 @@ def requires(self) -> tuple[Requirement, ...] | PythonDeps:
5963

6064
def register_run_env(self, run_env: RunToxEnv) -> Generator[tuple[str, str], PackageToxEnv, None]:
6165
yield from super().register_run_env(run_env)
62-
if not isinstance(run_env, Python) or run_env.conf["package"] != "wheel" or "wheel_build_env" in run_env.conf:
66+
if (
67+
not isinstance(run_env, Python)
68+
or run_env.conf["package"] not in {"wheel", "editable"}
69+
or "wheel_build_env" in run_env.conf
70+
):
6371
return
6472

6573
def default_wheel_tag(conf: Config, env_name: str | None) -> str: # noqa: U100

src/tox/tox_env/python/pip/pip_install.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from tox.tox_env.installer import Installer
1616
from tox.tox_env.package import PathPackage
1717
from tox.tox_env.python.api import Python
18-
from tox.tox_env.python.package import DevLegacyPackage, SdistPackage, WheelPackage
18+
from tox.tox_env.python.package import EditableLegacyPackage, EditablePackage, SdistPackage, WheelPackage
1919
from tox.tox_env.python.pip.req_file import PythonDeps
2020

2121

@@ -123,18 +123,20 @@ def _recreate_if_diff(of_type: str, new_opts: list[str], old_opts: list[str], fm
123123

124124
def _install_list_of_deps(
125125
self,
126-
arguments: Sequence[Requirement | WheelPackage | SdistPackage | DevLegacyPackage | PathPackage],
126+
arguments: Sequence[
127+
Requirement | WheelPackage | SdistPackage | EditableLegacyPackage | EditablePackage | PathPackage
128+
],
127129
section: str,
128130
of_type: str,
129131
) -> None:
130132
groups: dict[str, list[str]] = defaultdict(list)
131133
for arg in arguments:
132134
if isinstance(arg, Requirement):
133135
groups["req"].append(str(arg))
134-
elif isinstance(arg, (WheelPackage, SdistPackage)):
136+
elif isinstance(arg, (WheelPackage, SdistPackage, EditablePackage)):
135137
groups["req"].extend(str(i) for i in arg.deps)
136138
groups["pkg"].append(str(arg.path))
137-
elif isinstance(arg, DevLegacyPackage):
139+
elif isinstance(arg, EditableLegacyPackage):
138140
groups["req"].extend(str(i) for i in arg.deps)
139141
groups["dev_pkg"].append(str(arg.path))
140142
else:

src/tox/tox_env/python/runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def register_config(self) -> None:
3939

4040
@property
4141
def _package_types(self) -> tuple[str, ...]:
42-
return "wheel", "sdist", "dev-legacy", "skip", "external"
42+
return "wheel", "sdist", "editable", "editable-legacy", "skip", "external"
4343

4444
def _register_package_conf(self) -> bool:
4545
# provision package type
@@ -58,7 +58,7 @@ def _register_package_conf(self) -> bool:
5858
)
5959
develop_mode = self.conf["use_develop"] or getattr(self.options, "develop", False)
6060
if develop_mode:
61-
self.conf.add_constant(["package"], desc, "dev-legacy")
61+
self.conf.add_constant(["package"], desc, "editable")
6262
else:
6363
self.conf.add_config(keys="package", of_type=str, default=self.default_pkg_type, desc=desc)
6464

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def extract_install_info(self, for_env: EnvConfigSet, path: Path) -> list[Packag
116116
assert self._sdist_meta_tox_env is not None # the register run env is guaranteed to be called before this
117117
with self._sdist_meta_tox_env.display_context(self._has_display_suspended):
118118
self._sdist_meta_tox_env.root = next(work_dir.iterdir()) # contains a single egg info folder
119-
deps = self._sdist_meta_tox_env.get_package_dependencies()
119+
deps = self._sdist_meta_tox_env.get_package_dependencies(for_env)
120120
package = SdistPackage(path, dependencies_with_extras(deps, extras))
121121
return [package]
122122

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

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@
1919
from tox.tox_env.api import ToxEnvCreateArgs
2020
from tox.tox_env.errors import Fail
2121
from tox.tox_env.package import Package, PackageToxEnv
22-
from tox.tox_env.python.package import DevLegacyPackage, PythonPackageToxEnv, SdistPackage, WheelPackage
22+
from tox.tox_env.python.package import (
23+
EditableLegacyPackage,
24+
EditablePackage,
25+
PythonPackageToxEnv,
26+
SdistPackage,
27+
WheelPackage,
28+
)
2329
from tox.tox_env.register import ToxEnvRegister
2430
from tox.tox_env.runner import RunToxEnv
2531

@@ -128,6 +134,9 @@ def register_run_env(self, run_env: RunToxEnv) -> Generator[tuple[str, str], Pac
128134

129135
def _setup_env(self) -> None:
130136
super()._setup_env()
137+
if "editable" in self.builds:
138+
build_requires = self._frontend.get_requires_for_build_editable().requires
139+
self.installer.install(build_requires, PythonPackageToxEnv.__name__, "requires_for_build_editable")
131140
if "wheel" in self.builds:
132141
build_requires = self._frontend.get_requires_for_build_wheel().requires
133142
self.installer.install(build_requires, PythonPackageToxEnv.__name__, "requires_for_build_wheel")
@@ -151,28 +160,29 @@ def perform_packaging(self, for_env: EnvConfigSet) -> list[Package]:
151160
"""build the package to install"""
152161
deps = self._load_deps(for_env)
153162
of_type: str = for_env["package"]
154-
if of_type == "dev-legacy":
163+
if of_type == "editable-legacy":
155164
self.setup()
156165
deps = [*self.requires(), *self._frontend.get_requires_for_build_sdist().requires] + deps
157-
package: Package = DevLegacyPackage(self.core["tox_root"], deps) # the folder itself is the package
166+
package: Package = EditableLegacyPackage(self.core["tox_root"], deps) # the folder itself is the package
158167
elif of_type == "sdist":
159168
self.setup()
160169
with self._pkg_lock:
161170
package = SdistPackage(self._frontend.build_sdist(sdist_directory=self.pkg_dir).sdist, deps)
162-
elif of_type == "wheel":
171+
elif of_type in {"wheel", "editable"}:
163172
w_env = self._wheel_build_envs.get(for_env["wheel_build_env"])
164173
if w_env is not None and w_env is not self:
165174
with w_env.display_context(self._has_display_suspended):
166175
return w_env.perform_packaging(for_env)
167176
else:
168177
self.setup()
169178
with self._pkg_lock:
170-
path = self._frontend.build_wheel(
179+
method = "build_editable" if of_type == "editable" else "build_wheel"
180+
path = getattr(self._frontend, method)(
171181
wheel_directory=self.pkg_dir,
172182
metadata_directory=self.meta_folder,
173183
config_settings=self._wheel_config_settings,
174184
).wheel
175-
package = WheelPackage(path, deps)
185+
package = (EditablePackage if of_type == "editable" else WheelPackage)(path, deps)
176186
else: # pragma: no cover # for when we introduce new packaging types and don't implement
177187
raise TypeError(f"cannot handle package type {of_type}") # pragma: no cover
178188
return [package]
@@ -209,38 +219,42 @@ def _load_deps_from_built_metadata(self, for_env: EnvConfigSet) -> list[Requirem
209219
# to calculate the package metadata, otherwise ourselves
210220
of_type: str = for_env["package"]
211221
reqs: list[Requirement] | None = None
212-
if of_type == "wheel": # wheel packages
222+
if of_type in ("wheel", "editable"): # wheel packages
213223
w_env = self._wheel_build_envs.get(for_env["wheel_build_env"])
214224
if w_env is not None and w_env is not self:
215225
with w_env.display_context(self._has_display_suspended):
216-
reqs = w_env.get_package_dependencies() if isinstance(w_env, Pep517VirtualEnvPackager) else []
226+
if isinstance(w_env, Pep517VirtualEnvPackager):
227+
reqs = w_env.get_package_dependencies(for_env)
228+
else:
229+
reqs = []
217230
if reqs is None:
218-
reqs = self.get_package_dependencies()
231+
reqs = self.get_package_dependencies(for_env)
219232
extras: set[str] = for_env["extras"]
220233
deps = dependencies_with_extras(reqs, extras)
221234
return deps
222235

223-
def get_package_dependencies(self) -> list[Requirement]:
236+
def get_package_dependencies(self, for_env: EnvConfigSet) -> list[Requirement]:
224237
with self._pkg_lock:
225238
if self._package_dependencies is None: # pragma: no branch
226-
self._ensure_meta_present()
239+
self._ensure_meta_present(for_env)
227240
requires: list[str] = cast(PathDistribution, self._distribution_meta).requires or []
228241
self._package_dependencies = [Requirement(i) for i in requires] # pragma: no branch
229242
return self._package_dependencies
230243

231-
def _ensure_meta_present(self) -> None:
244+
def _ensure_meta_present(self, for_env: EnvConfigSet) -> None:
232245
if self._distribution_meta is not None: # pragma: no branch
233246
return # pragma: no cover
234247
self.setup()
235-
dist_info = self._frontend.prepare_metadata_for_build_wheel(
236-
self.meta_folder,
237-
self._wheel_config_settings,
238-
).metadata
248+
end = self._frontend
249+
if for_env["package"] == "editable":
250+
dist_info = end.prepare_metadata_for_build_editable(self.meta_folder, self._wheel_config_settings).metadata
251+
else:
252+
dist_info = end.prepare_metadata_for_build_wheel(self.meta_folder, self._wheel_config_settings).metadata
239253
self._distribution_meta = Distribution.at(str(dist_info))
240254

241255
@property
242256
def _wheel_config_settings(self) -> ConfigSettings | None:
243-
return {"--global-option": ["--bdist-dir", str(self.env_dir / "build")]}
257+
return {"--build-option": []}
244258

245259
def requires(self) -> tuple[Requirement, ...]:
246260
return self._frontend.requires
@@ -258,16 +272,17 @@ def __init__(self, root: Path, env: Pep517VirtualEnvPackager) -> None:
258272
)
259273
self.build_wheel = pkg_cache(self.build_wheel) # type: ignore
260274
self.build_sdist = pkg_cache(self.build_sdist) # type: ignore
275+
self.build_editable = pkg_cache(self.build_editable) # type: ignore
261276

262277
@property
263278
def backend_cmd(self) -> Sequence[str]:
264279
return ["python"] + self.backend_args
265280

266281
def _send(self, cmd: str, **kwargs: Any) -> tuple[Any, str, str]:
267282
try:
268-
if cmd == "prepare_metadata_for_build_wheel":
283+
if cmd in ("prepare_metadata_for_build_wheel", "prepare_metadata_for_build_editable"):
269284
# given we'll build a wheel we might skip the prepare step
270-
if "wheel" in self._tox_env.builds:
285+
if "wheel" in self._tox_env.builds or "editable" in self._tox_env.builds:
271286
result = {
272287
"code": 1,
273288
"exc_type": "AvoidRedundant",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[build-system]
2-
requires = ["setuptools>=45", "wheel>=0.33"]
2+
requires = ["setuptools>=63"]
33
build-backend = 'setuptools.build_meta'

tests/session/cmd/test_sequential.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,9 @@ def test_skip_develop_mode(tox_project: ToxProjectCreator, demo_pkg_setuptools:
283283
calls = [(i[0][0].conf.name, i[0][3].run_id) for i in execute_calls.call_args_list]
284284
expected = [
285285
(".pkg", "install_requires"),
286-
(".pkg", "prepare_metadata_for_build_wheel"),
287-
(".pkg", "get_requires_for_build_sdist"),
288-
("py", "install_package_deps"),
286+
(".pkg", "get_requires_for_build_editable"),
287+
(".pkg", "install_requires_for_build_editable"),
288+
(".pkg", "build_editable"),
289289
("py", "install_package"),
290290
(".pkg", "_exit"),
291291
]

tests/session/cmd/test_show_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def test_show_config_ini_comment_path(tox_project: ToxProjectCreator, tmp_path:
197197
def test_show_config_cli_flag(tox_project: ToxProjectCreator) -> None:
198198
project = tox_project({"tox.ini": "", "pyproject.toml": ""})
199199
result = project.run("c", "-e", "py,.pkg", "-k", "package", "recreate", "--develop", "-r", "--no-recreate-pkg")
200-
expected = "[testenv:py]\npackage = dev-legacy\nrecreate = True\n\n[testenv:.pkg]\nrecreate = False\n"
200+
expected = "[testenv:py]\npackage = editable\nrecreate = True\n\n[testenv:.pkg]\nrecreate = False\n"
201201
assert result.out == expected
202202

203203

0 commit comments

Comments
 (0)