diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 44e211ec66..25bbce6962 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -813,7 +813,9 @@ jobs: # itself" (#1633, measured with conda 26.7.2). So 3.15 leaves base on its # own Python: `conda build --python` below builds against 3.15 regardless, # measured to build and import pypcapkit-*-py315_0. Drop the exception - # once conda publishes a py315 build. + # once conda publishes a py315 build. #2123: nor on macos-26-intel, where + # changing base's Python rebuilds conda-forge's libmambapy at a version + # conda-libmamba-solver cannot drive, failing "Install conda build tools". - name: Conda environment creation and activation uses: conda-incubator/setup-miniconda@be893c923ea9cf1cf7cd510fbdde27c7e18cbdcb # v4.1.0 env: @@ -826,7 +828,7 @@ jobs: # that leg installs Miniforge. An empty string names no installer, so # the other legs keep the bundled or downloaded Miniconda. miniforge-version: ${{ matrix.os == 'macos-26-intel' && 'latest' || '' }} - python-version: ${{ matrix.python-version != '3.15' && matrix.python-version || '' }} + python-version: ${{ matrix.os != 'macos-26-intel' && matrix.python-version != '3.15' && matrix.python-version || '' }} activate-environment: base auto-update-conda: false auto-activate: true diff --git a/.github/workflows/cron-conda.yml b/.github/workflows/cron-conda.yml index ff03ec9f28..b872d476bf 100644 --- a/.github/workflows/cron-conda.yml +++ b/.github/workflows/cron-conda.yml @@ -542,7 +542,8 @@ jobs: mkdir -p conda/wheels python -m pip download -r conda/requirements.txt -d conda/wheels - # See create-release.yml's step of the same name for the 3.15 exception. + # See create-release.yml's step of the same name for the 3.15 and Intel + # exceptions (#2123). - name: Conda environment creation and activation id: miniconda continue-on-error: ${{ matrix.os == 'windows-latest' }} @@ -554,7 +555,7 @@ jobs: # #2044: Miniforge on the Intel leg; see create-release.yml's step of # the same name. miniforge-version: ${{ matrix.os == 'macos-26-intel' && 'latest' || '' }} - python-version: ${{ matrix.python-version != '3.15' && matrix.python-version || '' }} + python-version: ${{ matrix.os != 'macos-26-intel' && matrix.python-version != '3.15' && matrix.python-version || '' }} activate-environment: base auto-update-conda: false auto-activate: true diff --git a/conda/pypcapkit/meta.yaml b/conda/pypcapkit/meta.yaml index 85e8ce7e33..989f56c6cb 100644 --- a/conda/pypcapkit/meta.yaml +++ b/conda/pypcapkit/meta.yaml @@ -17,6 +17,10 @@ build: entry_points: - pcapkit-cli = pcapkit.__main__:main - pcapkit-vendor = pcapkit.vendor.__main__:main + # #2123: every binary comes from a wheel and links only system libraries, so + # none needs an rpath into the prefix. On osx-64, adding one fails on the + # vendored chardet extensions, which have no header room for it. + binary_relocation: false # [osx and x86_64] #script: "{{ PYTHON }} -m pip install . -vv" requirements: diff --git a/tests/project/test_conda_intel_miniforge_2044.py b/tests/project/test_conda_intel_miniforge_2044.py index a2ceeca51c..4b1780663f 100644 --- a/tests/project/test_conda_intel_miniforge_2044.py +++ b/tests/project/test_conda_intel_miniforge_2044.py @@ -17,15 +17,29 @@ ``installer-url`` first, then Miniforge when ``miniforge-version`` or ``miniforge-variant`` is non-empty, else Miniconda. +#2123: with Miniforge, the Intel legs broke twice more. Installing the matrix +Python into base rebuilt conda-forge's libmambapy at 2.10.0, which +conda-libmamba-solver 26.10.0 cannot drive, so the Intel legs now leave base's +Python alone, as 3.15 does everywhere; :class:`TestTheIntelLegsKeepBasesPython` +pins that. And conda-build 26.9's rpath relocation failed on the vendored chardet +extensions, so :file:`conda/pypcapkit/meta.yaml` turns it off on osx-64 alone; +:class:`TestOsx64SkipsBinaryRelocation` evaluates that line's selector as +conda-build does, for each leg's platform. + """ from __future__ import annotations +import ast +import pathlib +import re import unittest from typing import TYPE_CHECKING from tests import _workflows +from tests.project.test_python_versions_1660 import script as versions_script from tests.project.test_release_gates import _as_string, evaluate_expression +from util import release_conda if TYPE_CHECKING: from typing import Any @@ -110,5 +124,91 @@ def test_no_leg_names_two_installers(self) -> None: self.assertEqual(received['installer-url'], '') +def python_input(entry: 'dict[str, Any]', os: 'str', python: 'str') -> 'str': + """The ``python-version`` setup-miniconda receives on leg ``os`` with ``python``.""" + context = {'matrix': {'os': os, 'python-version': python}} # type: dict[str, object] + return _as_string(evaluate_expression(str(entry['with']['python-version']), context)).strip() + + +class TestTheIntelLegsKeepBasesPython(unittest.TestCase): + """#2123: no Python install into base on ``macos-26-intel``; the other legs as before.""" + + supported = () # type: tuple[str, ...] + + @classmethod + def setUpClass(cls) -> None: + script = versions_script() + cls.supported = tuple(script.tier(script.load(), 'cpython', 'supported')) + + def test_the_intel_leg_installs_no_python(self) -> None: + self.assertTrue(self.supported, 'no supported CPython to check') + for name, job_id in JOBS: + for python in self.supported: + with self.subTest(workflow=name, job=job_id, python=python): + self.assertEqual(python_input(step(name, job_id), INTEL, python), '') + + def test_the_other_legs_install_the_matrix_python_but_315(self) -> None: + for name, job_id in JOBS: + for os in legs(name, job_id): + if os == INTEL: + continue + for python in self.supported: + with self.subTest(workflow=name, job=job_id, os=os, python=python): + expected = '' if python == '3.15' else python + self.assertEqual(python_input(step(name, job_id), os, python), expected) + + +#: The recipe whose ``build:`` section carries the relocation switch. +META = pathlib.Path(__file__).resolve().parents[2] / 'conda' / 'pypcapkit' / 'meta.yaml' +#: A ``key: value # [selector]`` line, as conda-build reads one. +SELECTED = re.compile(r'^\s*(?P[\w-]+):\s*(?P\S+)\s*#\s*\[(?P[^\]]+)\]\s*$') +#: conda-build's ``ARCH_MAP``: the subdir arch names that are not selector names. +ARCH_MAP = {'32': 'x86', '64': 'x86_64'} + + +def selectors(subdir: 'str') -> 'dict[str, bool]': + """The OS and arch selectors conda-build 26.9's ``get_selectors`` sets for ``subdir``.""" + subdirs = set(release_conda.PLATFORMS.values()) | {subdir, 'linux-aarch64', 'linux-32'} + names = {'unix': subdir.startswith(('linux-', 'osx-', 'emscripten-'))} + for os in {entry.split('-')[0] for entry in subdirs}: + names[os] = subdir.startswith(f'{os}-') + for arch in {entry.split('-')[1] for entry in subdirs}: + names[ARCH_MAP.get(arch, arch)] = subdir.endswith(f'-{arch}') + return names + + +def holds(selector: 'str', names: 'dict[str, bool]') -> 'bool': + """Whether ``selector``, of names, ``and``, ``or`` and ``not``, holds; an unknown name raises.""" + def walk(node: 'ast.AST') -> 'bool': + if isinstance(node, ast.BoolOp): + values = [walk(value) for value in node.values] + return all(values) if isinstance(node.op, ast.And) else any(values) + if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not): + return not walk(node.operand) + if isinstance(node, ast.Name): + return names[node.id] + raise AssertionError(f'unsupported selector {selector!r}') + return walk(ast.parse(selector, mode='eval').body) + + +class TestOsx64SkipsBinaryRelocation(unittest.TestCase): + """#2123: ``binary_relocation: false`` on osx-64 alone, so the other platforms still relocate.""" + + def line(self) -> 're.Match[str]': + found = [match for match in map(SELECTED.match, META.read_text(encoding='utf-8').splitlines()) + if match and match['key'] == 'binary_relocation'] + self.assertEqual(len(found), 1, f'{META.name} has {len(found)} selected binary_relocation lines') + return found[0] + + def test_the_line_turns_relocation_off(self) -> None: + self.assertEqual(self.line()['value'], 'false') + + def test_it_applies_to_the_intel_legs_platform_alone(self) -> None: + selector = self.line()['selector'] + for os, subdir in sorted(release_conda.PLATFORMS.items()): + with self.subTest(os=os, subdir=subdir): + self.assertEqual(holds(selector, selectors(subdir)), os == INTEL) + + if __name__ == '__main__': unittest.main() diff --git a/tests/project/test_python_matrix_tiers_1633.py b/tests/project/test_python_matrix_tiers_1633.py index b9e73e149c..f64a3aa297 100644 --- a/tests/project/test_python_matrix_tiers_1633.py +++ b/tests/project/test_python_matrix_tiers_1633.py @@ -462,7 +462,8 @@ def test_no_conda_leg_is_ktlo_and_315_leaves_base_python_alone(self) -> None: setup = next(step for step in spec['steps'] if str(step.get('uses', '')).startswith('conda-incubator/setup-miniconda@')) self.assertEqual(setup['with']['python-version'], - "${{ matrix.python-version != '3.15' && matrix.python-version || '' }}") + "${{ matrix.os != 'macos-26-intel' && matrix.python-version != '3.15' && " + "matrix.python-version || '' }}") #: ``pip`` for an install step, which runs util/pip_install.py on the real