Skip to content

Commit 82dcd45

Browse files
authored
Packaging inherits from pkgenv, deps and document tox 4 packaging changes (#2813)
Resolves #2543
1 parent 31c8d1f commit 82dcd45

File tree

13 files changed

+166
-9
lines changed

13 files changed

+166
-9
lines changed

docs/changelog/2543.doc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Document breaking changes with tox 4 and packaging environments - by :user:`gaborbernat`.

docs/changelog/2543.feature.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Packaging environments now inherit from the ``pkgenv`` section, allowing to set all your packaging options in one place,
2+
and support the ``deps`` key to set additional dependencies that will be installed after ``pyprojec.toml`` static
3+
``requires`` but before backends dynamic requires - by :user:`gaborbernat`.

docs/upgrading.rst

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,96 @@ This is best avoided by updating to non-legacy usage:
222222
223223
# or, equivalently...
224224
$ tox r -e list
225+
226+
Packaging environments
227+
----------------------
228+
229+
Isolated environment on by default
230+
++++++++++++++++++++++++++++++++++
231+
``tox`` now always uses an isolated build environment when building your projects package. The previous flag to enable
232+
this called ``isolated_build`` has been removed.
233+
234+
Packaging configuration and inheritance
235+
+++++++++++++++++++++++++++++++++++++++
236+
Isolated build environments are tox environments themselves and may be configured on their own. Their name is defined
237+
as follows:
238+
239+
- For source distributions this environment will match a virtual environment with the same python interpreter as tox is
240+
using. The name of this environment will by default ``.pkg`` (can be changed via :ref:`package_env` config on a per
241+
test environment basis).
242+
- For wheels (including editable wheels as defined by :pep:`660`) their name will be ``.pkg-<impl><python_version>``, so
243+
for example if you're building a wheel for a Python 3.10 environment the packaging environment will be
244+
``.pkg-cpython311`` (can be changed via :ref:`wheel_build_env` config on a per test environment basis).
245+
246+
To change a packaging environments settings you can use:
247+
248+
.. code-block:: ini
249+
250+
[testenv:.pkg]
251+
pass_env =
252+
PKG_CONFIG
253+
PKG_CONFIG_PATH
254+
PKG_CONFIG_SYSROOT_DIR
255+
256+
[testenv:.pkg-cpython311]
257+
pass_env =
258+
PKG_CONFIG
259+
PKG_CONFIG_PATH
260+
PKG_CONFIG_SYSROOT_DIR
261+
262+
Packaging environments no longer inherit their settings from the ``testenv`` section, as this caused issues when
263+
some test environment settings conflicted with packaging setting. However starting with ``tox>=4.2`` all packaging
264+
environments inherit from the ``pkgenv`` section, allowing you to define packaging common packaging settings in one
265+
central place, while still allowing you to override it when needed on a per package environment basis:
266+
267+
.. code-block:: ini
268+
269+
[pkgenv]
270+
pass_env =
271+
PKG_CONFIG
272+
PKG_CONFIG_PATH
273+
PKG_CONFIG_SYSROOT_DIR
274+
275+
[testenv:.pkg-cpython311]
276+
pass_env =
277+
{[pkgenv]pass_env}
278+
IS_311 = yes
279+
280+
[testenv:magic]
281+
package = sdist
282+
pass_env = {[pkgenv]pass_env} # sdist install builds wheel -> need packaging settings
283+
284+
Note that specific packaging environments are defined under ``testenv:.pkg`` and **not** ``pkgenv:.pkg``, this is due
285+
backwards compatibility.
286+
287+
Universal wheels
288+
++++++++++++++++
289+
If your project builds universal wheels you can avoid using multiple build environments for each targeted python by
290+
setting :ref:`wheel_build_env` to the same packaging environment via:
291+
292+
.. code-block:: ini
293+
294+
[testenv]
295+
package = wheel
296+
wheel_build_env = .pkg
297+
298+
Editable mode
299+
+++++++++++++
300+
``tox`` now defaults to using editable wheels when develop mode is enabled and the build backend supports it,
301+
as defined by :pep:`660` by setting :ref:`package` to ``editable``. In case the backend does not support it, will
302+
fallback to :ref:`package` to ``editable-legacy``, and invoke pip with ``-e``. In the later case will also print a
303+
message to make this setting explicit in your configuration (explicit better than implicit):
304+
305+
.. code-block:: ini
306+
307+
[testenv:dev]
308+
package = editable-legacy
309+
310+
If you want to use the new standardized method to achieve the editable install effect you should ensure your backend
311+
version is above the version this feature was added to it, for example for setuptools:
312+
313+
.. code-block:: ini
314+
315+
[testenv:dev]
316+
deps = setuptools>=64
317+
package = editable

src/tox/config/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,18 @@ def get_env(
152152
:param loaders: loaders to use for this configuration (only used for creation)
153153
:return: the tox environments config
154154
"""
155-
section, base = self._src.get_tox_env_section(item)
155+
section, base_test, base_pkg = self._src.get_tox_env_section(item)
156156
conf_set = self.get_section_config(
157157
section,
158-
base=None if package else base,
158+
base=base_pkg if package else base_test,
159159
of_type=EnvConfigSet,
160160
for_env=item,
161161
loaders=loaders,
162162
)
163163
return conf_set
164164

165165
def clear_env(self, name: str) -> None:
166-
section, _ = self._src.get_tox_env_section(name)
166+
section, _, __ = self._src.get_tox_env_section(name)
167167
del self._key_to_conf_set[(section.key, name)]
168168

169169

src/tox/config/source/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def envs(self, core_conf: CoreConfigSet) -> Iterator[str]:
9898
raise NotImplementedError
9999

100100
@abstractmethod
101-
def get_tox_env_section(self, item: str) -> tuple[Section, list[str]]:
101+
def get_tox_env_section(self, item: str) -> tuple[Section, list[str], list[str]]:
102102
""":returns: the section for a tox environment"""
103103
raise NotImplementedError
104104

src/tox/config/source/ini.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ..loader.section import Section
1515
from ..sets import ConfigSet
1616
from .api import Source
17-
from .ini_section import CORE, TEST_ENV_PREFIX, IniSection
17+
from .ini_section import CORE, PKG_ENV_PREFIX, TEST_ENV_PREFIX, IniSection
1818

1919

2020
class IniSource(Source):
@@ -62,8 +62,8 @@ def get_base_sections(self, base: list[str], in_section: Section) -> Iterator[Se
6262
if in_section.prefix is not None: # no prefix specified, so this could imply our own prefix
6363
yield IniSection(in_section.prefix, a_base)
6464

65-
def get_tox_env_section(self, item: str) -> tuple[Section, list[str]]:
66-
return IniSection.test_env(item), [TEST_ENV_PREFIX]
65+
def get_tox_env_section(self, item: str) -> tuple[Section, list[str], list[str]]:
66+
return IniSection.test_env(item), [TEST_ENV_PREFIX], [PKG_ENV_PREFIX]
6767

6868
def envs(self, core_config: ConfigSet) -> Iterator[str]:
6969
seen = set()

src/tox/config/source/ini_section.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ def names(self) -> list[str]:
2020

2121

2222
TEST_ENV_PREFIX = "testenv"
23+
PKG_ENV_PREFIX = "pkgenv"
2324
CORE = IniSection(None, "tox")
2425

2526
__all__ = [
2627
"IniSection",
2728
"CORE",
2829
"TEST_ENV_PREFIX",
30+
"PKG_ENV_PREFIX",
2931
]

src/tox/tox_env/python/package.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from abc import ABC, abstractmethod
77
from pathlib import Path
8-
from typing import TYPE_CHECKING, Any, Generator, Iterator, Sequence, cast
8+
from typing import TYPE_CHECKING, Any, Generator, Iterator, List, Sequence, cast
99

1010
from packaging.requirements import Requirement
1111

@@ -56,13 +56,22 @@ def _setup_env(self) -> None:
5656
"""setup the tox environment"""
5757
super()._setup_env()
5858
self._install(self.requires(), PythonPackageToxEnv.__name__, "requires")
59+
self._install(self.conf["deps"], PythonPackageToxEnv.__name__, "deps")
5960

6061
@abstractmethod
6162
def requires(self) -> tuple[Requirement, ...] | PythonDeps:
6263
raise NotImplementedError
6364

6465
def register_run_env(self, run_env: RunToxEnv) -> Generator[tuple[str, str], PackageToxEnv, None]:
6566
yield from super().register_run_env(run_env)
67+
if run_env.conf["package"] != "skip" and "deps" not in self.conf:
68+
self.conf.add_config(
69+
keys="deps",
70+
of_type=List[Requirement],
71+
default=[],
72+
desc="Name of the python dependencies as specified by PEP-440",
73+
)
74+
6675
if (
6776
not isinstance(run_env, Python)
6877
or run_env.conf["package"] not in {"wheel", "editable"}

src/tox/util/ci.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121

2222
def is_ci() -> bool:
2323
""":return: a flag indicating if running inside a CI env or not"""
24-
return any(e in os.environ if v is None else os.environ.get(e) == v for e, v in _ENV_VARS.items())
24+
for env_key, value in _ENV_VARS.items():
25+
if env_key in os.environ if value is None else os.environ.get(env_key) == value:
26+
if env_key == "TEAMCITY_VERSION" and os.environ.get(env_key) == "LOCAL":
27+
continue
28+
return True
29+
return False
2530

2631

2732
__all__ = [

tests/session/cmd/test_show_config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,20 @@ def test_show_config_matching_env_section(tox_project: ToxProjectCreator) -> Non
254254
outcome = project.run("c", "-e", "a,b", "-k", "deps")
255255
outcome.assert_success()
256256
assert outcome.out.count("c>=1") == 2, outcome.out
257+
258+
259+
def test_package_env_inherits_from_pkgenv(tox_project: ToxProjectCreator, demo_pkg_inline: Path) -> None:
260+
project = tox_project({"tox.ini": "[pkgenv]\npass_env = A, B\ndeps=C\n D"})
261+
outcome = project.run("c", "--root", str(demo_pkg_inline), "-k", "deps", "pass_env", "-e", "py,.pkg")
262+
outcome.assert_success()
263+
exp = """
264+
[testenv:.pkg]
265+
deps =
266+
C
267+
D
268+
pass_env =
269+
A
270+
B
271+
"""
272+
exp = dedent(exp)
273+
assert exp in outcome.out

0 commit comments

Comments
 (0)