Skip to content

Commit 451aa9c

Browse files
prevent machine ISA from overriding explicit env factors (#3904)
- Machine ISA (e.g., arm64 from sysconfig.get_platform()) is no longer unconditionally added as an implicit factor, preventing it from conflicting with explicit ISA factors in the env name - When the env name contains an explicit ISA (e.g., py39-x86_64), only that ISA's factor conditions match — the machine's ISA is excluded - When no env factor conflicts (e.g., env is just py39), the machine ISA still works as an implicit factor - Fix applied to both INI (filter_for_env) and TOML (_replace_if_toml) config paths <!-- Thank you for your contribution! Please, make sure you address all the checklists (for details on how see [development documentation](http://tox.readthedocs.org/en/latest/development.html#development))! --> - [ ] ran the linter to address style issues (`tox -e fix`) - [ ] wrote descriptive pull request text - [ ] ensured there are test(s) validating the fix - [ ] added news fragment in `docs/changelog` folder - [ ] updated/extended the documentation --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 106f036 commit 451aa9c

6 files changed

Lines changed: 99 additions & 6 deletions

File tree

docs/changelog/3903.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Prevent implicit machine ISA (e.g. ``arm64``, ``x86_64``) from overriding explicit architecture factors in environment
2+
names, fixing cross-architecture conflicts in multiline factor conditionals - by :user:`rahuldevikar`.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ dependencies = [
5656
"platformdirs>=4.9.4",
5757
"pluggy>=1.6",
5858
"pyproject-api>=1.10",
59+
"python-discovery>=1.2.1",
5960
"tomli>=2.4; python_version<'3.11'",
6061
"tomli-w>=1.2",
6162
"typing-extensions>=4.15; python_version<'3.11'",

src/tox/config/loader/ini/factor.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from itertools import chain, groupby, product
99
from typing import TYPE_CHECKING
1010

11+
from python_discovery import KNOWN_ARCHITECTURES
12+
1113
if TYPE_CHECKING:
1214
from collections.abc import Iterator
1315

@@ -16,13 +18,19 @@
1618

1719

1820
def filter_for_env(value: str, name: str | None) -> str:
19-
current = (
21+
env_factors = (
2022
set(chain.from_iterable([(i for i, _ in a) for a in find_factor_groups(name)])) if name is not None else set()
2123
)
24+
current = set(env_factors)
2225
current.add(sys.platform)
2326
parts = sysconfig.get_platform().rsplit("-", 1)
2427
if len(parts) > 1:
25-
current.add(parts[-1])
28+
machine = parts[-1]
29+
# Add machine ISA implicitly only when the env name does not already contain
30+
# an architecture factor; when it does the explicit ISA takes precedence and
31+
# adding the machine ISA would cause cross-architecture conflicts (#3903).
32+
if not (env_factors & KNOWN_ARCHITECTURES):
33+
current.add(machine)
2634
overall: list[str] = []
2735
active_continuation = False
2836
pending_skip = False

src/tox/config/loader/toml/_replace.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from pathlib import Path
1111
from typing import TYPE_CHECKING, Any, cast
1212

13+
from python_discovery import KNOWN_ARCHITECTURES
14+
1315
from tox.config.loader.ini.factor import find_factor_groups
1416
from tox.config.loader.replacer import (
1517
MatchError,
@@ -42,18 +44,19 @@ def __init__(self, conf: Config | None, loader: TomlLoader, args: ConfigLoadArgs
4244
self.loader = loader
4345
self.args = args
4446
self.factors = self._extract_factors(args.env_name)
47+
self.factors.add(sys.platform)
4548

4649
@staticmethod
4750
def _extract_factors(env_name: str | None) -> set[str]:
48-
"""Extract factors from environment name and add platform."""
4951
if env_name is None:
50-
factors = set()
52+
factors: set[str] = set()
5153
else:
5254
factors = set(chain.from_iterable([(i for i, _ in a) for a in find_factor_groups(env_name)]))
53-
factors.add(sys.platform)
5455
parts = sysconfig.get_platform().rsplit("-", 1)
5556
if len(parts) > 1:
56-
factors.add(parts[-1])
57+
machine = parts[-1]
58+
if not (factors & KNOWN_ARCHITECTURES):
59+
factors.add(machine)
5760
return factors
5861

5962
def __call__( # noqa: C901, PLR0912

tests/config/loader/ini/test_factor.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import sys
4+
import sysconfig
45
from textwrap import dedent
56
from typing import TYPE_CHECKING
67

@@ -511,3 +512,33 @@ def test_platform_factor(tox_ini_conf: ToxIniCreator) -> None:
511512
assert 'print("Windows")' in str(commands)
512513
assert 'print("Linux")' not in str(commands)
513514
assert 'print("Darwin")' not in str(commands)
515+
516+
517+
def test_machine_isa_does_not_override_explicit_env_factor() -> None:
518+
"""Regression test for #3903: explicit ISA in env name takes precedence over machine ISA."""
519+
parts = sysconfig.get_platform().rsplit("-", 1)
520+
if len(parts) < 2:
521+
pytest.skip("sysconfig.get_platform() has no machine component")
522+
machine = parts[-1]
523+
other_isa = "x86_64" if machine != "x86_64" else "arm64"
524+
525+
value = f"{other_isa}: {other_isa}_value\n{machine}: {machine}_value"
526+
# When the env name explicitly contains an ISA factor different from the machine,
527+
# only the env factor's condition should match, not the machine ISA.
528+
result = filter_for_env(value, name=f"py39-{other_isa}")
529+
assert f"{other_isa}_value" in result
530+
assert f"{machine}_value" not in result
531+
532+
533+
def test_machine_isa_implicit_when_no_env_isa() -> None:
534+
"""Machine ISA is added implicitly when no ISA factor is in the env name."""
535+
parts = sysconfig.get_platform().rsplit("-", 1)
536+
if len(parts) < 2:
537+
pytest.skip("sysconfig.get_platform() has no machine component")
538+
machine = parts[-1]
539+
540+
value = f"{machine}: {machine}_value\nother: other_value"
541+
# No ISA in the env name, so machine ISA should be added implicitly.
542+
result = filter_for_env(value, name="py39")
543+
assert f"{machine}_value" in result
544+
assert "other_value" not in result

tests/config/source/test_toml_pyproject.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import sys
4+
import sysconfig
45
from textwrap import dedent
56
from typing import TYPE_CHECKING
67

@@ -1070,3 +1071,50 @@ def test_config_in_toml_replace_ref_command(tox_project: ToxProjectCreator) -> N
10701071
assert "python" in outcome.out
10711072
assert "pip" in outcome.out
10721073
assert "freeze" in outcome.out
1074+
1075+
1076+
def test_toml_machine_isa_does_not_override_explicit_env_factor(tox_project: ToxProjectCreator) -> None:
1077+
"""Regression test for #3903: explicit ISA in env name takes precedence over machine ISA in TOML."""
1078+
parts = sysconfig.get_platform().rsplit("-", 1)
1079+
if len(parts) < 2:
1080+
pytest.skip("sysconfig.get_platform() has no machine component")
1081+
machine = parts[-1]
1082+
other_isa = "x86_64" if machine != "x86_64" else "arm64"
1083+
1084+
project = tox_project({
1085+
"pyproject.toml": dedent(f"""
1086+
[tool.tox.env_run_base]
1087+
package = "skip"
1088+
description = {{ replace = "if", condition = "factor.{machine}", then = "{machine}_val", \
1089+
else = {{ replace = "if", condition = "factor.{other_isa}", then = "{other_isa}_val", else = "unknown" }} }}
1090+
1091+
[tool.tox.env.py39-{other_isa}]
1092+
"""),
1093+
})
1094+
# Env name contains other_isa, so only other_isa condition should match, not machine ISA.
1095+
outcome = project.run("c", "-e", f"py39-{other_isa}", "-k", "description")
1096+
outcome.assert_success()
1097+
assert f"{other_isa}_val" in outcome.out
1098+
assert f"{machine}_val" not in outcome.out
1099+
1100+
1101+
def test_toml_machine_isa_implicit_when_no_env_isa(tox_project: ToxProjectCreator) -> None:
1102+
"""Machine ISA is added implicitly to TOML factors when no ISA factor is in the env name."""
1103+
parts = sysconfig.get_platform().rsplit("-", 1)
1104+
if len(parts) < 2:
1105+
pytest.skip("sysconfig.get_platform() has no machine component")
1106+
machine = parts[-1]
1107+
1108+
project = tox_project({
1109+
"pyproject.toml": dedent(f"""
1110+
[tool.tox.env_run_base]
1111+
package = "skip"
1112+
description = {{ replace = "if", condition = "factor.{machine}", then = "matched", else = "no-match" }}
1113+
1114+
[tool.tox.env.py39]
1115+
"""),
1116+
})
1117+
# No ISA in env name, so machine ISA should be added implicitly.
1118+
outcome = project.run("c", "-e", "py39", "-k", "description")
1119+
outcome.assert_success()
1120+
assert "matched" in outcome.out

0 commit comments

Comments
 (0)