Compare commits
11
Commits
v4.1.0
...
inherit-extend
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04709b1bfe | ||
|
|
de7e10c7db | ||
|
|
b652a90184 | ||
|
|
e97e4562b1 | ||
|
|
077d334118 | ||
|
|
291be6a345 | ||
|
|
78d8530df9 | ||
|
|
dc30bfb527 | ||
|
|
aeea2cba0a | ||
|
|
ce96a012ef | ||
|
|
034e6353e8 |
@@ -19,7 +19,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
allow-prereleases: true
|
allow-prereleases: true
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
|
|
||||||
- name: Install tooling
|
- name: Install tooling
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jobs:
|
|||||||
dist:
|
dist:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -39,13 +39,13 @@ jobs:
|
|||||||
name: Linters (mypy, ruff, etc.)
|
name: Linters (mypy, ruff, etc.)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
|
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
|
||||||
- name: PyLint checks
|
- name: PyLint checks
|
||||||
run: uvx nox -s pylint -- --output-format=github
|
run: uvx nox -s pylint -- --output-format=github
|
||||||
@@ -55,14 +55,14 @@ jobs:
|
|||||||
needs: lint
|
needs: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
name: Install Python
|
name: Install Python
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync --no-dev --group test
|
run: uv sync --no-dev --group test
|
||||||
- name: Generate a sample project
|
- name: Generate a sample project
|
||||||
@@ -117,7 +117,7 @@ jobs:
|
|||||||
test_select: android
|
test_select: android
|
||||||
timeout-minutes: 180
|
timeout-minutes: 180
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
python-version: ${{ matrix.python_version }}
|
python-version: ${{ matrix.python_version }}
|
||||||
allow-prereleases: true
|
allow-prereleases: true
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
|
|
||||||
- name: Free up disk space
|
- name: Free up disk space
|
||||||
if: runner.os == 'Linux' && matrix.test_select != 'android'
|
if: runner.os == 'Linux' && matrix.test_select != 'android'
|
||||||
@@ -247,13 +247,13 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
archs: ${{ steps.archs.outputs.archs }}
|
archs: ${{ steps.archs.outputs.archs }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync --no-dev --group test
|
run: uv sync --no-dev --group test
|
||||||
- name: Get qemu emulated architectures
|
- name: Get qemu emulated architectures
|
||||||
@@ -272,13 +272,13 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
arch: ${{ fromJSON(needs.emulated-archs.outputs.archs) }}
|
arch: ${{ fromJSON(needs.emulated-archs.outputs.archs) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync --no-dev --group test
|
run: uv sync --no-dev --group test
|
||||||
|
|
||||||
@@ -296,14 +296,14 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 180
|
timeout-minutes: 180
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
name: Install Python 3.12
|
name: Install Python 3.12
|
||||||
with:
|
with:
|
||||||
python-version: '3.12'
|
python-version: '3.12'
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: uv sync --no-dev --group test
|
run: uv sync --no-dev --group test
|
||||||
@@ -348,7 +348,7 @@ jobs:
|
|||||||
- os: macos-15
|
- os: macos-15
|
||||||
test_select: android
|
test_select: android
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Download a sample project
|
- name: Download a sample project
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ jobs:
|
|||||||
permission-contents: write
|
permission-contents: write
|
||||||
permission-pull-requests: write
|
permission-pull-requests: write
|
||||||
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.14"
|
python-version: "3.14"
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ repos:
|
|||||||
- dependency-groups>=1.2
|
- dependency-groups>=1.2
|
||||||
- humanize
|
- humanize
|
||||||
- nox>=2025.2.9
|
- nox>=2025.2.9
|
||||||
- orjson
|
|
||||||
- packaging
|
- packaging
|
||||||
- pygithub
|
- pygithub
|
||||||
- pytest
|
- pytest
|
||||||
|
|||||||
+22
-22
@@ -40,6 +40,28 @@ additionalProperties: false
|
|||||||
description: cibuildwheel's settings.
|
description: cibuildwheel's settings.
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
inherit:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
audit-command: {"$ref": "#/$defs/inherit"}
|
||||||
|
audit-requires: {"$ref": "#/$defs/inherit"}
|
||||||
|
before-all: {"$ref": "#/$defs/inherit"}
|
||||||
|
before-build: {"$ref": "#/$defs/inherit"}
|
||||||
|
xbuild-tools: {"$ref": "#/$defs/inherit"}
|
||||||
|
xbuild-files: {"$ref": "#/$defs/inherit"}
|
||||||
|
before-test: {"$ref": "#/$defs/inherit"}
|
||||||
|
config-settings: {"$ref": "#/$defs/inherit"}
|
||||||
|
container-engine: {"$ref": "#/$defs/inherit"}
|
||||||
|
environment: {"$ref": "#/$defs/inherit"}
|
||||||
|
environment-pass: {"$ref": "#/$defs/inherit"}
|
||||||
|
repair-wheel-command: {"$ref": "#/$defs/inherit"}
|
||||||
|
test-command: {"$ref": "#/$defs/inherit"}
|
||||||
|
test-extras: {"$ref": "#/$defs/inherit"}
|
||||||
|
test-sources: {"$ref": "#/$defs/inherit"}
|
||||||
|
test-requires: {"$ref": "#/$defs/inherit"}
|
||||||
|
test-environment: {"$ref": "#/$defs/inherit"}
|
||||||
|
test-runtime: {"$ref": "#/$defs/inherit"}
|
||||||
audit-command:
|
audit-command:
|
||||||
description: Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.
|
description: Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.
|
||||||
type: string_array
|
type: string_array
|
||||||
@@ -315,28 +337,6 @@ items:
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
properties:
|
properties:
|
||||||
select: {}
|
select: {}
|
||||||
inherit:
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
audit-command: {"$ref": "#/$defs/inherit"}
|
|
||||||
audit-requires: {"$ref": "#/$defs/inherit"}
|
|
||||||
before-all: {"$ref": "#/$defs/inherit"}
|
|
||||||
before-build: {"$ref": "#/$defs/inherit"}
|
|
||||||
xbuild-tools: {"$ref": "#/$defs/inherit"}
|
|
||||||
xbuild-files: {"$ref": "#/$defs/inherit"}
|
|
||||||
before-test: {"$ref": "#/$defs/inherit"}
|
|
||||||
config-settings: {"$ref": "#/$defs/inherit"}
|
|
||||||
container-engine: {"$ref": "#/$defs/inherit"}
|
|
||||||
environment: {"$ref": "#/$defs/inherit"}
|
|
||||||
environment-pass: {"$ref": "#/$defs/inherit"}
|
|
||||||
repair-wheel-command: {"$ref": "#/$defs/inherit"}
|
|
||||||
test-command: {"$ref": "#/$defs/inherit"}
|
|
||||||
test-extras: {"$ref": "#/$defs/inherit"}
|
|
||||||
test-sources: {"$ref": "#/$defs/inherit"}
|
|
||||||
test-requires: {"$ref": "#/$defs/inherit"}
|
|
||||||
test-environment: {"$ref": "#/$defs/inherit"}
|
|
||||||
test-runtime: {"$ref": "#/$defs/inherit"}
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -107,13 +107,29 @@ class Architecture(StrEnum):
|
|||||||
case "auto32":
|
case "auto32":
|
||||||
result |= cls.bitness_archs(platform=platform, bitness="32")
|
result |= cls.bitness_archs(platform=platform, bitness="32")
|
||||||
case _:
|
case _:
|
||||||
try:
|
if arch := cls._parse_arch_name(arch_str, platform=platform):
|
||||||
result.add(cls(arch_str))
|
result.add(arch)
|
||||||
except ValueError as e:
|
else:
|
||||||
msg = f"Invalid architecture '{arch_str}'"
|
msg = f"Invalid architecture '{arch_str}'"
|
||||||
raise errors.ConfigurationError(msg) from e
|
raise errors.ConfigurationError(msg)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parse_arch_name(cls, arch_str: str, platform: PlatformName) -> Self | None:
|
||||||
|
"""Resolve an architecture name case-insensitively.
|
||||||
|
|
||||||
|
The same value (e.g. "arm64"/"ARM64") can map to different members
|
||||||
|
depending on the platform, so prefer a member valid for ``platform``
|
||||||
|
before falling back to any case-insensitive match.
|
||||||
|
"""
|
||||||
|
for arch in cls.all_archs(platform):
|
||||||
|
if arch.value.lower() == arch_str.lower():
|
||||||
|
return arch
|
||||||
|
for arch in cls:
|
||||||
|
if arch.value.lower() == arch_str.lower():
|
||||||
|
return arch
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def native_arch(cls, platform: PlatformName) -> Self | None:
|
def native_arch(cls, platform: PlatformName) -> Self | None:
|
||||||
native_machine = platform_module.machine()
|
native_machine = platform_module.machine()
|
||||||
|
|||||||
+129
-49
@@ -50,7 +50,13 @@ from cibuildwheel.projectfiles import get_requires_python_str, resolve_dependenc
|
|||||||
from cibuildwheel.selector import BuildSelector, EnableGroup, TestSelector, selector_matches
|
from cibuildwheel.selector import BuildSelector, EnableGroup, TestSelector, selector_matches
|
||||||
from cibuildwheel.typing import PLATFORMS, PlatformName
|
from cibuildwheel.typing import PLATFORMS, PlatformName
|
||||||
from cibuildwheel.util import resources
|
from cibuildwheel.util import resources
|
||||||
from cibuildwheel.util.helpers import format_safe, parse_key_value_string, strtobool, unwrap
|
from cibuildwheel.util.helpers import (
|
||||||
|
format_safe,
|
||||||
|
parse_arbitrary_key_value_string,
|
||||||
|
parse_key_value_string,
|
||||||
|
strtobool,
|
||||||
|
unwrap,
|
||||||
|
)
|
||||||
from cibuildwheel.util.packaging import DependencyConstraints
|
from cibuildwheel.util.packaging import DependencyConstraints
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
@@ -436,6 +442,28 @@ def _stringify_setting(
|
|||||||
return setting
|
return setting
|
||||||
|
|
||||||
|
|
||||||
|
def parse_inherit(config: str | dict[str, str] | None) -> dict[str, InheritRule]:
|
||||||
|
inherit_dict: dict[str, str]
|
||||||
|
|
||||||
|
if config is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if isinstance(config, str):
|
||||||
|
parsed = parse_arbitrary_key_value_string(config, default_value="append")
|
||||||
|
inherit_dict = {k: "".join(v) for k, v in parsed.items()}
|
||||||
|
elif isinstance(config, dict):
|
||||||
|
inherit_dict = config
|
||||||
|
else:
|
||||||
|
msg = "'inherit' must be a string or a table"
|
||||||
|
raise OptionsReaderError(msg)
|
||||||
|
|
||||||
|
if not all(v in {"none", "append", "prepend"} for v in inherit_dict.values()):
|
||||||
|
msg = "'inherit' must contain only {'none', 'append', 'prepend'} values"
|
||||||
|
raise OptionsReaderError(msg)
|
||||||
|
|
||||||
|
return {k: InheritRule[v.upper()] for k, v in inherit_dict.items()}
|
||||||
|
|
||||||
|
|
||||||
class OptionsReader:
|
class OptionsReader:
|
||||||
"""
|
"""
|
||||||
Gets options from the environment, config or defaults, optionally scoped
|
Gets options from the environment, config or defaults, optionally scoped
|
||||||
@@ -484,45 +512,18 @@ class OptionsReader:
|
|||||||
self._validate_platform_option(option_name)
|
self._validate_platform_option(option_name)
|
||||||
|
|
||||||
self.config_options = config_options
|
self.config_options = config_options
|
||||||
|
self.config_options_inherit = parse_inherit(config_options.get("inherit"))
|
||||||
self.config_platform_options = config_platform_options
|
self.config_platform_options = config_platform_options
|
||||||
|
self.config_platform_options_inherit = parse_inherit(config_platform_options.get("inherit"))
|
||||||
|
|
||||||
self.overrides: list[Override] = []
|
|
||||||
self.current_identifier: str | None = None
|
self.current_identifier: str | None = None
|
||||||
|
|
||||||
config_overrides = self.config_options.get("overrides")
|
|
||||||
|
|
||||||
if config_overrides is not None:
|
|
||||||
if not isinstance(config_overrides, list):
|
|
||||||
msg = "'tool.cibuildwheel.overrides' must be a list"
|
|
||||||
raise OptionsReaderError(msg)
|
|
||||||
|
|
||||||
for config_override in config_overrides:
|
|
||||||
select = config_override.pop("select", None)
|
|
||||||
|
|
||||||
if not select:
|
|
||||||
msg = "'select' must be set in an override"
|
|
||||||
raise OptionsReaderError(msg)
|
|
||||||
|
|
||||||
if isinstance(select, list):
|
|
||||||
select = " ".join(select)
|
|
||||||
|
|
||||||
inherit = config_override.pop("inherit", {})
|
|
||||||
if not isinstance(inherit, dict) or not all(
|
|
||||||
i in {"none", "append", "prepend"} for i in inherit.values()
|
|
||||||
):
|
|
||||||
msg = "'inherit' must be a dict containing only {'none', 'append', 'prepend'} values"
|
|
||||||
raise OptionsReaderError(msg)
|
|
||||||
|
|
||||||
inherit_enum = {k: InheritRule[v.upper()] for k, v in inherit.items()}
|
|
||||||
|
|
||||||
self.overrides.append(Override(select, config_override, inherit_enum))
|
|
||||||
|
|
||||||
def _validate_global_option(self, name: str) -> None:
|
def _validate_global_option(self, name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Raises an error if an option with this name is not allowed in the
|
Raises an error if an option with this name is not allowed in the
|
||||||
[tool.cibuildwheel] section of a config file.
|
[tool.cibuildwheel] section of a config file.
|
||||||
"""
|
"""
|
||||||
allowed_option_names = self.default_options.keys() | PLATFORMS | {"overrides"}
|
allowed_option_names = self.default_options.keys() | PLATFORMS | {"inherit", "overrides"}
|
||||||
|
|
||||||
if name not in allowed_option_names:
|
if name not in allowed_option_names:
|
||||||
msg = f"Option {name!r} not supported in a config file."
|
msg = f"Option {name!r} not supported in a config file."
|
||||||
@@ -541,7 +542,9 @@ class OptionsReader:
|
|||||||
msg = f"{name!r} is not allowed in {disallowed_platform_options}"
|
msg = f"{name!r} is not allowed in {disallowed_platform_options}"
|
||||||
raise OptionsReaderError(msg)
|
raise OptionsReaderError(msg)
|
||||||
|
|
||||||
allowed_option_names = self.default_options.keys() | self.default_platform_options.keys()
|
allowed_option_names = (
|
||||||
|
self.default_options.keys() | self.default_platform_options.keys() | {"inherit"}
|
||||||
|
)
|
||||||
|
|
||||||
if name not in allowed_option_names:
|
if name not in allowed_option_names:
|
||||||
msg = f"Option {name!r} not supported in the {self.platform!r} section"
|
msg = f"Option {name!r} not supported in the {self.platform!r} section"
|
||||||
@@ -562,6 +565,56 @@ class OptionsReader:
|
|||||||
|
|
||||||
return global_options, platform_options
|
return global_options, platform_options
|
||||||
|
|
||||||
|
@functools.cached_property
|
||||||
|
def overrides(self) -> list[Override]:
|
||||||
|
config_overrides = self.config_options.get("overrides")
|
||||||
|
overrides: list[Override] = []
|
||||||
|
|
||||||
|
if config_overrides is not None:
|
||||||
|
if not isinstance(config_overrides, list):
|
||||||
|
msg = "'tool.cibuildwheel.overrides' must be a list"
|
||||||
|
raise OptionsReaderError(msg)
|
||||||
|
|
||||||
|
for config_override in config_overrides:
|
||||||
|
select = config_override.pop("select", None)
|
||||||
|
|
||||||
|
if not select:
|
||||||
|
msg = "'select' must be set in an override"
|
||||||
|
raise OptionsReaderError(msg)
|
||||||
|
|
||||||
|
if isinstance(select, list):
|
||||||
|
select = " ".join(select)
|
||||||
|
|
||||||
|
inherit = config_override.pop("inherit", {})
|
||||||
|
|
||||||
|
overrides.append(Override(select, config_override, parse_inherit(inherit)))
|
||||||
|
|
||||||
|
return overrides
|
||||||
|
|
||||||
|
@functools.cached_property
|
||||||
|
def env_inherit(self) -> dict[str, InheritRule]:
|
||||||
|
env_inherit_str = self.env.get("CIBW_INHERIT", "")
|
||||||
|
try:
|
||||||
|
return parse_inherit(env_inherit_str)
|
||||||
|
except OptionsReaderError as e:
|
||||||
|
msg = f"Failed to parse CIBW_INHERIT environment variable. {e}"
|
||||||
|
raise errors.ConfigurationError(msg) from e
|
||||||
|
|
||||||
|
@functools.cached_property
|
||||||
|
def env_platform_inherit(self) -> dict[str, InheritRule]:
|
||||||
|
env_inherit = self.env_inherit
|
||||||
|
|
||||||
|
# find the rules which have -{platform} on the end of their key,
|
||||||
|
# remove the platform suffix from the key and return the resulting
|
||||||
|
# rule.
|
||||||
|
platform_suffix = f"-{self.platform}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
key.removesuffix(platform_suffix): value
|
||||||
|
for key, value in env_inherit.items()
|
||||||
|
if key.endswith(platform_suffix)
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active_config_overrides(self) -> list[Override]:
|
def active_config_overrides(self) -> list[Override]:
|
||||||
if self.current_identifier is None:
|
if self.current_identifier is None:
|
||||||
@@ -585,7 +638,7 @@ class OptionsReader:
|
|||||||
env_plat: bool = True,
|
env_plat: bool = True,
|
||||||
option_format: OptionFormat | None = None,
|
option_format: OptionFormat | None = None,
|
||||||
ignore_empty: bool = False,
|
ignore_empty: bool = False,
|
||||||
env_rule: InheritRule = InheritRule.NONE,
|
default_env_rule: InheritRule = InheritRule.NONE,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Get and return the value for the named option from environment,
|
Get and return the value for the named option from environment,
|
||||||
@@ -609,16 +662,37 @@ class OptionsReader:
|
|||||||
# get the option from the default, then the config file, then finally the environment.
|
# get the option from the default, then the config file, then finally the environment.
|
||||||
# platform-specific options are preferred, if they're allowed.
|
# platform-specific options are preferred, if they're allowed.
|
||||||
return _resolve_cascade(
|
return _resolve_cascade(
|
||||||
(self.default_options.get(name), InheritRule.NONE),
|
(
|
||||||
(self.default_platform_options.get(name), InheritRule.NONE),
|
self.default_options.get(name),
|
||||||
(self.config_options.get(name), InheritRule.NONE),
|
InheritRule.NONE,
|
||||||
(self.config_platform_options.get(name), InheritRule.NONE),
|
),
|
||||||
|
(
|
||||||
|
self.default_platform_options.get(name),
|
||||||
|
InheritRule.NONE,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
self.config_options.get(name),
|
||||||
|
self.config_options_inherit.get(name, InheritRule.NONE),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
self.config_platform_options.get(name),
|
||||||
|
self.config_platform_options_inherit.get(name, InheritRule.NONE),
|
||||||
|
),
|
||||||
*[
|
*[
|
||||||
(o.options.get(name), o.inherit.get(name, InheritRule.NONE))
|
(
|
||||||
|
o.options.get(name),
|
||||||
|
o.inherit.get(name, InheritRule.NONE),
|
||||||
|
)
|
||||||
for o in self.active_config_overrides
|
for o in self.active_config_overrides
|
||||||
],
|
],
|
||||||
(self.env.get(envvar), env_rule),
|
(
|
||||||
(self.env.get(plat_envvar) if env_plat else None, env_rule),
|
self.env.get(envvar),
|
||||||
|
self.env_inherit.get(name, default_env_rule),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
self.env.get(plat_envvar) if env_plat else None,
|
||||||
|
self.env_platform_inherit.get(name, default_env_rule),
|
||||||
|
),
|
||||||
ignore_empty=ignore_empty,
|
ignore_empty=ignore_empty,
|
||||||
option_format=option_format,
|
option_format=option_format,
|
||||||
)
|
)
|
||||||
@@ -691,7 +765,10 @@ class Options:
|
|||||||
allow_empty = args.allow_empty or strtobool(self.env.get("CIBW_ALLOW_EMPTY", "0"))
|
allow_empty = args.allow_empty or strtobool(self.env.get("CIBW_ALLOW_EMPTY", "0"))
|
||||||
|
|
||||||
enable_groups = self.reader.get(
|
enable_groups = self.reader.get(
|
||||||
"enable", env_plat=False, option_format=ListFormat(sep=" "), env_rule=InheritRule.APPEND
|
"enable",
|
||||||
|
env_plat=False,
|
||||||
|
option_format=ListFormat(sep=" "),
|
||||||
|
default_env_rule=InheritRule.APPEND,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
enable = {
|
enable = {
|
||||||
@@ -797,12 +874,11 @@ class Options:
|
|||||||
if xbuild_tools == ["\u0000"]:
|
if xbuild_tools == ["\u0000"]:
|
||||||
xbuild_tools = None
|
xbuild_tools = None
|
||||||
|
|
||||||
xbuild_files = parse_key_value_string(
|
xbuild_files = parse_arbitrary_key_value_string(
|
||||||
self.reader.get(
|
self.reader.get(
|
||||||
"xbuild-files",
|
"xbuild-files",
|
||||||
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False),
|
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False),
|
||||||
),
|
),
|
||||||
kw_arg_names=["*"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
test_sources = shlex.split(
|
test_sources = shlex.split(
|
||||||
@@ -833,9 +909,11 @@ class Options:
|
|||||||
msg = f"Failed to parse test runtime config. {e}"
|
msg = f"Failed to parse test runtime config. {e}"
|
||||||
raise errors.ConfigurationError(msg) from e
|
raise errors.ConfigurationError(msg) from e
|
||||||
|
|
||||||
test_requires = self.reader.get(
|
test_requires = shlex.split(
|
||||||
"test-requires", option_format=ListFormat(sep=" ")
|
self.reader.get(
|
||||||
).split()
|
"test-requires", option_format=ListFormat(sep=" ", quote=shlex.quote)
|
||||||
|
)
|
||||||
|
)
|
||||||
test_extras = self.reader.get("test-extras", option_format=ListFormat(sep=","))
|
test_extras = self.reader.get("test-extras", option_format=ListFormat(sep=","))
|
||||||
test_groups_str = self.reader.get("test-groups", option_format=ListFormat(sep=" "))
|
test_groups_str = self.reader.get("test-groups", option_format=ListFormat(sep=" "))
|
||||||
test_groups = [x for x in test_groups_str.split() if x]
|
test_groups = [x for x in test_groups_str.split() if x]
|
||||||
@@ -932,9 +1010,11 @@ class Options:
|
|||||||
)
|
)
|
||||||
audit_command = audit_command_str.split(" && ") if audit_command_str else []
|
audit_command = audit_command_str.split(" && ") if audit_command_str else []
|
||||||
|
|
||||||
audit_requires = self.reader.get(
|
audit_requires = shlex.split(
|
||||||
"audit-requires", option_format=ListFormat(sep=" ")
|
self.reader.get(
|
||||||
).split()
|
"audit-requires", option_format=ListFormat(sep=" ", quote=shlex.quote)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return BuildOptions(
|
return BuildOptions(
|
||||||
globals=self.globals,
|
globals=self.globals,
|
||||||
|
|||||||
@@ -218,6 +218,36 @@ def validate_pyodide_build_version(
|
|||||||
raise errors.FatalError(msg)
|
raise errors.FatalError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_pyodide_target_python(
|
||||||
|
xbuildenv_info: PyodideXBuildEnvInfo, python_configuration: PythonConfiguration
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Validate that the resolved Pyodide xbuildenv targets the same Python version
|
||||||
|
as the identifier being built.
|
||||||
|
|
||||||
|
This is to catch the case where a global ``pyodide-version`` is applied to an
|
||||||
|
identifier whose Python version does not match, such as setting
|
||||||
|
``pyodide-version = "3Y.0.0"`` (a Python 3.Y environment) while
|
||||||
|
building ``cp3X-pyodide_wasm32``.
|
||||||
|
"""
|
||||||
|
xbuildenv_version = xbuildenv_info["version"]
|
||||||
|
identifier = python_configuration.identifier
|
||||||
|
expected_version = python_configuration.version
|
||||||
|
default_version = python_configuration.default_pyodide_version
|
||||||
|
xbuildenv_python_minor = ".".join(xbuildenv_info["python"].split(".")[:2])
|
||||||
|
if xbuildenv_python_minor != expected_version:
|
||||||
|
msg = unwrap_preserving_paragraphs(f"""
|
||||||
|
The `pyodide-version` option is set to {xbuildenv_version}, which
|
||||||
|
provides Python {xbuildenv_python_minor}, but the {identifier} build
|
||||||
|
needs Python {expected_version}.
|
||||||
|
|
||||||
|
Either remove `pyodide-version` so {identifier} uses its default
|
||||||
|
({default_version}), or stop building {identifier} so that
|
||||||
|
`pyodide-version` only applies to an identifier it matches.
|
||||||
|
""")
|
||||||
|
raise errors.ConfigurationError(msg)
|
||||||
|
|
||||||
|
|
||||||
def install_xbuildenv(env: dict[str, str], xbuildenv_cache_path: Path, pyodide_version: str) -> str:
|
def install_xbuildenv(env: dict[str, str], xbuildenv_cache_path: Path, pyodide_version: str) -> str:
|
||||||
"""Install a particular Pyodide xbuildenv version and set a path to the Pyodide root."""
|
"""Install a particular Pyodide xbuildenv version and set a path to the Pyodide root."""
|
||||||
pyodide_root = xbuildenv_cache_path / pyodide_version / "xbuildenv" / "pyodide-root"
|
pyodide_root = xbuildenv_cache_path / pyodide_version / "xbuildenv" / "pyodide-root"
|
||||||
@@ -337,6 +367,10 @@ def setup_python(
|
|||||||
xbuildenv_info=xbuildenv_info,
|
xbuildenv_info=xbuildenv_info,
|
||||||
pyodide_build_version=pyodide_build_version,
|
pyodide_build_version=pyodide_build_version,
|
||||||
)
|
)
|
||||||
|
validate_pyodide_target_python(
|
||||||
|
xbuildenv_info=xbuildenv_info,
|
||||||
|
python_configuration=python_configuration,
|
||||||
|
)
|
||||||
|
|
||||||
xbuildenv_cache_path = CIBW_CACHE_PATH / f"pyodide-build-{pyodide_build_version}"
|
xbuildenv_cache_path = CIBW_CACHE_PATH / f"pyodide-build-{pyodide_build_version}"
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,67 @@
|
|||||||
"description": "cibuildwheel's settings.",
|
"description": "cibuildwheel's settings.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"inherit": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"audit-command": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"audit-requires": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"before-all": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"before-build": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"xbuild-tools": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"before-test": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"config-settings": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"container-engine": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"environment-pass": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"repair-wheel-command": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"test-command": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"test-extras": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"test-sources": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"test-requires": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"test-environment": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
|
"test-runtime": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "CIBW_INHERIT"
|
||||||
|
},
|
||||||
"audit-command": {
|
"audit-command": {
|
||||||
"description": "Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.",
|
"description": "Execute a shell command to audit each wheel after it is repaired. Use {wheel} for each wheel path, or {abi3_wheel} to only audit abi3 wheels.",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
@@ -689,64 +750,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"inherit": {
|
"inherit": {
|
||||||
"type": "object",
|
"$ref": "#/properties/inherit"
|
||||||
"additionalProperties": false,
|
|
||||||
"properties": {
|
|
||||||
"audit-command": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"audit-requires": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"before-all": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"before-build": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"xbuild-tools": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"xbuild-files": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"before-test": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"config-settings": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"container-engine": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"environment": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"environment-pass": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"repair-wheel-command": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"test-command": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"test-extras": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"test-sources": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"test-requires": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"test-environment": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
},
|
|
||||||
"test-runtime": {
|
|
||||||
"$ref": "#/$defs/inherit"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"audit-command": {
|
"audit-command": {
|
||||||
"$ref": "#/properties/audit-command"
|
"$ref": "#/properties/audit-command"
|
||||||
@@ -875,6 +879,9 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"inherit": {
|
||||||
|
"$ref": "#/properties/inherit"
|
||||||
|
},
|
||||||
"audit-command": {
|
"audit-command": {
|
||||||
"$ref": "#/properties/audit-command"
|
"$ref": "#/properties/audit-command"
|
||||||
},
|
},
|
||||||
@@ -1014,6 +1021,9 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"inherit": {
|
||||||
|
"$ref": "#/properties/inherit"
|
||||||
|
},
|
||||||
"audit-command": {
|
"audit-command": {
|
||||||
"$ref": "#/properties/audit-command"
|
"$ref": "#/properties/audit-command"
|
||||||
},
|
},
|
||||||
@@ -1099,6 +1109,9 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"inherit": {
|
||||||
|
"$ref": "#/properties/inherit"
|
||||||
|
},
|
||||||
"audit-command": {
|
"audit-command": {
|
||||||
"$ref": "#/properties/audit-command"
|
"$ref": "#/properties/audit-command"
|
||||||
},
|
},
|
||||||
@@ -1184,6 +1197,9 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"inherit": {
|
||||||
|
"$ref": "#/properties/inherit"
|
||||||
|
},
|
||||||
"audit-command": {
|
"audit-command": {
|
||||||
"$ref": "#/properties/audit-command"
|
"$ref": "#/properties/audit-command"
|
||||||
},
|
},
|
||||||
@@ -1256,6 +1272,9 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"inherit": {
|
||||||
|
"$ref": "#/properties/inherit"
|
||||||
|
},
|
||||||
"audit-command": {
|
"audit-command": {
|
||||||
"$ref": "#/properties/audit-command"
|
"$ref": "#/properties/audit-command"
|
||||||
},
|
},
|
||||||
@@ -1341,6 +1360,9 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"inherit": {
|
||||||
|
"$ref": "#/properties/inherit"
|
||||||
|
},
|
||||||
"audit-command": {
|
"audit-command": {
|
||||||
"$ref": "#/properties/audit-command"
|
"$ref": "#/properties/audit-command"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ def parse_key_value_string(
|
|||||||
if kw_arg_names is None:
|
if kw_arg_names is None:
|
||||||
kw_arg_names = []
|
kw_arg_names = []
|
||||||
|
|
||||||
all_field_names = None if ("*" in kw_arg_names) else [*positional_arg_names, *kw_arg_names]
|
all_field_names = [*positional_arg_names, *kw_arg_names]
|
||||||
|
|
||||||
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";")
|
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";")
|
||||||
shlexer.commenters = ""
|
shlexer.commenters = ""
|
||||||
@@ -128,7 +128,7 @@ def parse_key_value_string(
|
|||||||
# check to see if the option name is specified
|
# check to see if the option name is specified
|
||||||
field_name, sep, first_value = field[0].partition(":")
|
field_name, sep, first_value = field[0].partition(":")
|
||||||
if sep:
|
if sep:
|
||||||
if (all_field_names is not None) and (field_name not in all_field_names):
|
if field_name not in all_field_names:
|
||||||
msg = f"Failed to parse {key_value_string!r}. Unknown field name {field_name!r}"
|
msg = f"Failed to parse {key_value_string!r}. Unknown field name {field_name!r}"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
@@ -147,6 +147,51 @@ def parse_key_value_string(
|
|||||||
return dict(result)
|
return dict(result)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arbitrary_key_value_string(
|
||||||
|
key_value_string: str, default_value: str | None = None
|
||||||
|
) -> dict[str, list[str]]:
|
||||||
|
"""
|
||||||
|
Parses a string like
|
||||||
|
|
||||||
|
"before-build; before-test: append; after-test: prepend"
|
||||||
|
or
|
||||||
|
"package1: some/header.h some/library.a; package2: other/header.h"
|
||||||
|
|
||||||
|
There are no restrictions on the keys than can be set.
|
||||||
|
|
||||||
|
No positional arguments are allowed. Words without a colon attached are
|
||||||
|
interpreted as keys. Keys without a value will be assigned the
|
||||||
|
default_value if provided, otherwise throw an error.
|
||||||
|
"""
|
||||||
|
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";")
|
||||||
|
shlexer.commenters = ""
|
||||||
|
shlexer.whitespace_split = True
|
||||||
|
parts = list(shlexer)
|
||||||
|
# parts now looks like
|
||||||
|
# ['before-build', ';', 'before-test:', 'append', ';', 'after-test:', 'prepend']
|
||||||
|
|
||||||
|
# split by semicolon
|
||||||
|
result: defaultdict[str, list[str]] = defaultdict(list)
|
||||||
|
fields = [list(group) for k, group in itertools.groupby(parts, lambda x: x == ";") if not k]
|
||||||
|
for field in fields:
|
||||||
|
# check to see if the option name is specified
|
||||||
|
field_name, sep, first_value = field[0].partition(":")
|
||||||
|
if sep:
|
||||||
|
# the colon was present, so the first value is the value after the colon
|
||||||
|
values = ([first_value] if first_value else []) + field[1:]
|
||||||
|
result[field_name] += values
|
||||||
|
else:
|
||||||
|
# no colon, so it's a key (or set of keys) without values
|
||||||
|
if default_value is None:
|
||||||
|
msg = f"Failed to parse {key_value_string!r}. No value specified for {field_name!r}. Expected ':' followed by a value."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
for key in field:
|
||||||
|
result[key].append(default_value)
|
||||||
|
|
||||||
|
return dict(result)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(order=True)
|
@dataclasses.dataclass(order=True)
|
||||||
class FlexibleVersion:
|
class FlexibleVersion:
|
||||||
version_parts: tuple[int, ...] = dataclasses.field(init=False, repr=False)
|
version_parts: tuple[int, ...] = dataclasses.field(init=False, repr=False)
|
||||||
|
|||||||
+71
-18
@@ -66,10 +66,11 @@ placed in `[tool.cibuildwheel]` and are lower case, with dashes, following
|
|||||||
common [TOML](https://toml.io) practice. Anything placed in subsections
|
common [TOML](https://toml.io) practice. Anything placed in subsections
|
||||||
named after a platform will only affect those platforms. Platform-specific
|
named after a platform will only affect those platforms. Platform-specific
|
||||||
values replace the corresponding global value for that platform; table options
|
values replace the corresponding global value for that platform; table options
|
||||||
are not merged key by key. Lists can be used instead of strings for items that
|
are not merged key by key unless you configure [inheritance](#inherit). Lists can
|
||||||
are naturally a list. Multiline strings also work just like in the environment
|
be used instead of strings for items that are naturally a list. Multiline strings
|
||||||
variables. Environment variable overrides, such as `CIBW_TEST_COMMAND` and
|
also work just like in the environment variables. Environment variable overrides,
|
||||||
`CIBW_TEST_COMMAND_LINUX`, will take precedence if defined.
|
such as `CIBW_TEST_COMMAND` and `CIBW_TEST_COMMAND_LINUX`, take precedence if
|
||||||
|
defined.
|
||||||
|
|
||||||
The example above using environment variables could have been written like this:
|
The example above using environment variables could have been written like this:
|
||||||
|
|
||||||
@@ -113,9 +114,9 @@ trigger new containers, one per image.
|
|||||||
|
|
||||||
The ``output-dir``, ``build``, ``skip``, ``test_skip`` selectors, and architectures cannot be overridden.
|
The ``output-dir``, ``build``, ``skip``, ``test_skip`` selectors, and architectures cannot be overridden.
|
||||||
|
|
||||||
You can specify a table of overrides in `inherit={}`, any list or table in this
|
By default, values in an override replace values from the main configuration or
|
||||||
list will inherit from previous overrides or the main configuration. The valid
|
earlier overrides. You can instead [extend a list or table option](#inherit) by
|
||||||
options are `"none"` (the default), `"append"`, and `"prepend"`.
|
setting an `inherit` rule for it.
|
||||||
|
|
||||||
#### Examples:
|
#### Examples:
|
||||||
|
|
||||||
@@ -175,14 +176,50 @@ This example will provide the command `"pyproject-before && pyproject && pyproje
|
|||||||
on Python 3.11, and will have `environment = {FOO="BAZ", "PYTHON"="MONTY", "HAM"="EGGS"}`.
|
on Python 3.11, and will have `environment = {FOO="BAZ", "PYTHON"="MONTY", "HAM"="EGGS"}`.
|
||||||
|
|
||||||
|
|
||||||
## Extending existing options {: #inherit }
|
## Option inheritance {: #inherit }
|
||||||
|
|
||||||
In the TOML configuration, you can choose how tables and lists are inherited.
|
As cibuildwheel reads its configuration, each layer normally replaces the value
|
||||||
By default, all values are overridden completely (`"none"`) but sometimes you'd
|
from the previous layer. The layers, from lowest to highest precedence, are:
|
||||||
rather `"append"` or `"prepend"` to an existing list or table. You can do this
|
|
||||||
with the `inherit` table in overrides. For example, if you want to add an environment
|
1. cibuildwheel's defaults
|
||||||
variable for CPython 3.11, without `inherit` you'd have to repeat all the
|
2. `[tool.cibuildwheel]`
|
||||||
original environment variables in the override. With `inherit`, it's just:
|
3. `[tool.cibuildwheel.<platform>]`
|
||||||
|
4. matching `[[tool.cibuildwheel.overrides]]` entries, in order
|
||||||
|
5. `CIBW_<OPTION>`
|
||||||
|
6. `CIBW_<OPTION>_<PLATFORM>`
|
||||||
|
|
||||||
|
For list and table options, you can use an `inherit` rule to merge a value with
|
||||||
|
the value accumulated from the preceding layers instead. The available rules
|
||||||
|
are `"none"` (replace the previous value, the default), `"append"`, and
|
||||||
|
`"prepend"`.
|
||||||
|
|
||||||
|
In `pyproject.toml`, set the rule in the same table as the value it applies to.
|
||||||
|
For example, this adds Twine checks to the default audit configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
inherit.audit-requires = "append"
|
||||||
|
inherit.audit-command = "append"
|
||||||
|
audit-requires = ["twine"]
|
||||||
|
audit-command = "twine check {wheel}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Inheritance can also combine global and platform-specific configuration. This
|
||||||
|
example runs a Linux-specific setup command before the global command:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
before-all = "make -C third_party_lib"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.linux]
|
||||||
|
inherit.before-all = "prepend"
|
||||||
|
before-all = "yum install -y libffi-devel"
|
||||||
|
```
|
||||||
|
|
||||||
|
The same mechanism remains available in overrides. For example, if you want to
|
||||||
|
add an environment variable for CPython 3.11, without `inherit` you'd have to
|
||||||
|
repeat all the original environment variables in the override. With `inherit`,
|
||||||
|
it's just:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[tool.cibuildwheel.overrides]]
|
[[tool.cibuildwheel.overrides]]
|
||||||
@@ -210,7 +247,23 @@ repair-wheel-command = "echo 'After repair'"
|
|||||||
```
|
```
|
||||||
|
|
||||||
As seen in this example, you can have multiple overrides match - they match top
|
As seen in this example, you can have multiple overrides match - they match top
|
||||||
to bottom, with the config being accumulated. If you need platform-specific
|
to bottom, with the config being accumulated.
|
||||||
inheritance, you can use `select = "*-????linux_*"` for Linux, `select =
|
|
||||||
"*-win_*"` for Windows, and `select = "*-macosx_*"` for macOS. As always,
|
For environment variables, specify the rules in `CIBW_INHERIT`. Rules are
|
||||||
environment variables will completely override any TOML configuration.
|
separated by semicolons and use lowercase option names. A rule without an
|
||||||
|
explicit value defaults to `append`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
CIBW_AUDIT_REQUIRES: twine
|
||||||
|
CIBW_AUDIT_COMMAND: "twine check {wheel}"
|
||||||
|
CIBW_INHERIT: "audit-requires; audit-command"
|
||||||
|
```
|
||||||
|
|
||||||
|
To control a platform-specific environment variable, add the lowercase platform
|
||||||
|
suffix to the option name. For example, this prepends `CIBW_BEFORE_ALL_LINUX` to
|
||||||
|
the value accumulated from the lower-precedence layers:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
CIBW_BEFORE_ALL_LINUX: yum install -y libffi-devel
|
||||||
|
CIBW_INHERIT: "before-all-linux: prepend"
|
||||||
|
```
|
||||||
|
|||||||
+19
-5
@@ -807,9 +807,9 @@ instead.
|
|||||||
### `before-build` {: #before-build env-var toml}
|
### `before-build` {: #before-build env-var toml}
|
||||||
> Execute a shell command preparing each wheel's build
|
> Execute a shell command preparing each wheel's build
|
||||||
|
|
||||||
A shell command to run before building the wheel. This option allows you to run a command in **each** Python environment before the `pip wheel` command. This is useful if you need to set up some dependency so it's available during the build.
|
A shell command to run before building the wheel. This option allows you to run a command in **each** Python environment before the wheel is built. This is useful if you need to set up some dependency so it's available during the build.
|
||||||
|
|
||||||
If dependencies are required to build your wheel (for example if you include a header from a Python module), instead of using this command, we recommend adding requirements to a `pyproject.toml` file's `build-system.requires` array instead. This is reproducible, and users who do not get your wheels (such as Alpine or ClearLinux users) will still benefit.
|
If dependencies are required to build your wheel (for example if you include a header from a Python module), instead of using this command, we recommend adding requirements to a `pyproject.toml` file's `build-system.requires` array instead. This is reproducible, and users who do not get your wheels (such as BSD operating system variants) will still benefit.
|
||||||
|
|
||||||
The active Python binary can be accessed using `python`, and pip with `pip`; cibuildwheel makes sure the right version of Python and pip will be executed. The placeholder `{package}` can be used here; it will be replaced by the path to the package being built by cibuildwheel.
|
The active Python binary can be accessed using `python`, and pip with `pip`; cibuildwheel makes sure the right version of Python and pip will be executed. The placeholder `{package}` can be used here; it will be replaced by the path to the package being built by cibuildwheel.
|
||||||
|
|
||||||
@@ -1444,10 +1444,11 @@ Run shell commands to verify your wheels once they are built. Multiple commands
|
|||||||
audit-requires = ["twine"]
|
audit-requires = ["twine"]
|
||||||
audit-command = "twine check {wheel}"
|
audit-command = "twine check {wheel}"
|
||||||
|
|
||||||
# Add an additional audit command using overrides, keeping the default abi3audit check
|
# Add an additional audit command, keeping the default abi3audit check
|
||||||
[[tool.cibuildwheel.overrides]]
|
[tool.cibuildwheel]
|
||||||
select = "*"
|
inherit.audit-requires = "append"
|
||||||
inherit.audit-command = "append"
|
inherit.audit-command = "append"
|
||||||
|
audit-requires = ["twine"]
|
||||||
audit-command = "twine check {wheel}"
|
audit-command = "twine check {wheel}"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1463,6 +1464,11 @@ Run shell commands to verify your wheels once they are built. Multiple commands
|
|||||||
# Use twine check to validate wheel metadata
|
# Use twine check to validate wheel metadata
|
||||||
CIBW_AUDIT_REQUIRES: "twine"
|
CIBW_AUDIT_REQUIRES: "twine"
|
||||||
CIBW_AUDIT_COMMAND: "twine check {wheel}"
|
CIBW_AUDIT_COMMAND: "twine check {wheel}"
|
||||||
|
|
||||||
|
# Add twine check to the default audit configuration
|
||||||
|
CIBW_AUDIT_REQUIRES: "twine"
|
||||||
|
CIBW_AUDIT_COMMAND: "twine check {wheel}"
|
||||||
|
CIBW_INHERIT: "audit-requires; audit-command"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -1670,6 +1676,10 @@ Platform-specific environment variables are also available:<br/>
|
|||||||
# Install specific versions of test dependencies
|
# Install specific versions of test dependencies
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
test-requires = ["pytest==8.2.2", "packaging==24.1"]
|
test-requires = ["pytest==8.2.2", "packaging==24.1"]
|
||||||
|
|
||||||
|
# Dependency specifiers with environment markers are supported
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
test-requires = ["pytest", "pyzstd; python_version >= '3.14'"]
|
||||||
```
|
```
|
||||||
|
|
||||||
In configuration files, you can use an array, and the items will be joined with a space.
|
In configuration files, you can use an array, and the items will be joined with a space.
|
||||||
@@ -1682,6 +1692,10 @@ Platform-specific environment variables are also available:<br/>
|
|||||||
|
|
||||||
# Install specific versions of test dependencies
|
# Install specific versions of test dependencies
|
||||||
CIBW_TEST_REQUIRES: pytest==8.2.2 packaging==24.1
|
CIBW_TEST_REQUIRES: pytest==8.2.2 packaging==24.1
|
||||||
|
|
||||||
|
# Use shell-style quoting around specifiers containing spaces, such as
|
||||||
|
# those with environment markers
|
||||||
|
CIBW_TEST_REQUIRES: "pytest 'pyzstd; python_version >= \"3.14\"'"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -405,7 +405,11 @@ def test_environment_markers(tmp_path: Path) -> None:
|
|||||||
**cp313_env,
|
**cp313_env,
|
||||||
"CIBW_TEST_COMMAND": f"python -m pytest {test_filename}",
|
"CIBW_TEST_COMMAND": f"python -m pytest {test_filename}",
|
||||||
"CIBW_TEST_SOURCES": test_filename,
|
"CIBW_TEST_SOURCES": test_filename,
|
||||||
"CIBW_TEST_REQUIRES": "pytest certifi;sys_platform=='android' platformdirs;sys_platform!='android'",
|
"CIBW_TEST_REQUIRES": (
|
||||||
|
"pytest"
|
||||||
|
" 'certifi; sys_platform == \"android\"'"
|
||||||
|
" 'platformdirs; sys_platform != \"android\"'"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import sys
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import cibuildwheel.architecture
|
import cibuildwheel.architecture
|
||||||
|
import cibuildwheel.errors
|
||||||
from cibuildwheel.architecture import Architecture, arch_synonym
|
from cibuildwheel.architecture import Architecture, arch_synonym
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
@@ -142,3 +143,33 @@ def test_arch_synonym(
|
|||||||
arch: str, from_platform: PlatformName, to_platform: PlatformName, expected: str | None
|
arch: str, from_platform: PlatformName, to_platform: PlatformName, expected: str | None
|
||||||
) -> None:
|
) -> None:
|
||||||
assert arch_synonym(arch, from_platform, to_platform) == expected
|
assert arch_synonym(arch, from_platform, to_platform) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("config", "platform", "expected"),
|
||||||
|
[
|
||||||
|
# Case-insensitive, platform-resolved names (issue #2373).
|
||||||
|
("arm64", "windows", {Architecture.ARM64}),
|
||||||
|
("ARM64", "windows", {Architecture.ARM64}),
|
||||||
|
("amd64", "windows", {Architecture.AMD64}),
|
||||||
|
("AMD64", "windows", {Architecture.AMD64}),
|
||||||
|
("X86", "windows", {Architecture.x86}),
|
||||||
|
("x86", "windows", {Architecture.x86}),
|
||||||
|
# macOS resolves the lowercase arm64 even when given uppercase.
|
||||||
|
("ARM64", "macos", {Architecture.arm64}),
|
||||||
|
("arm64", "macos", {Architecture.arm64}),
|
||||||
|
("X86_64", "macos", {Architecture.x86_64}),
|
||||||
|
# Multiple, mixed-case archs in one config string.
|
||||||
|
("arm64 amd64", "windows", {Architecture.ARM64, Architecture.AMD64}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_arch_parse_config_case_insensitive(
|
||||||
|
config: str, platform: PlatformName, expected: set[Architecture]
|
||||||
|
) -> None:
|
||||||
|
assert Architecture.parse_config(config, platform) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("platform", ["windows", "macos", "linux"])
|
||||||
|
def test_arch_parse_config_invalid(platform: PlatformName) -> None:
|
||||||
|
with pytest.raises(cibuildwheel.errors.ConfigurationError):
|
||||||
|
Architecture.parse_config("nonexistent", platform)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"""Tests for reading and resolving configuration options."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import shlex
|
import shlex
|
||||||
@@ -5,6 +7,7 @@ from typing import Any, cast
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from cibuildwheel import errors
|
||||||
from cibuildwheel.options import (
|
from cibuildwheel.options import (
|
||||||
EnvironmentFormat,
|
EnvironmentFormat,
|
||||||
InheritRule,
|
InheritRule,
|
||||||
@@ -472,7 +475,11 @@ test-command = "pyproject-override"
|
|||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(OptionsReaderError):
|
with pytest.raises(OptionsReaderError):
|
||||||
OptionsReader(config_file_path=pyproject_toml, platform=cast("Any", platform), env={})
|
print(
|
||||||
|
OptionsReader(
|
||||||
|
config_file_path=pyproject_toml, platform=cast("Any", platform), env={}
|
||||||
|
).overrides
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_config_settings(tmp_path: Path) -> None:
|
def test_config_settings(tmp_path: Path) -> None:
|
||||||
@@ -549,6 +556,112 @@ before-all = ["override2"]
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_global_inherit(tmp_path: Path) -> None:
|
||||||
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
inherit = {audit-command = "append"}
|
||||||
|
audit-command = "twine check {wheel}"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
options_reader = OptionsReader(pyproject_toml, platform="linux", env={})
|
||||||
|
|
||||||
|
assert (
|
||||||
|
options_reader.get("audit-command", option_format=ListFormat(" && "))
|
||||||
|
== "abi3audit --strict --report {abi3_wheel} && twine check {wheel}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_platform_inherit(tmp_path: Path) -> None:
|
||||||
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
before-all = "global"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.linux]
|
||||||
|
inherit = {before-all = "prepend"}
|
||||||
|
before-all = "linux"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
options_reader = OptionsReader(pyproject_toml, platform="linux", env={})
|
||||||
|
|
||||||
|
assert options_reader.get("before-all", option_format=ListFormat(" && ")) == "linux && global"
|
||||||
|
|
||||||
|
|
||||||
|
def test_environment_inherit(tmp_path: Path) -> None:
|
||||||
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
before-all = "config"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
options_reader = OptionsReader(
|
||||||
|
pyproject_toml,
|
||||||
|
platform="linux",
|
||||||
|
env={
|
||||||
|
"CIBW_BEFORE_ALL": "env",
|
||||||
|
"CIBW_BEFORE_ALL_LINUX": "linux-env",
|
||||||
|
# A key without a value defaults to append.
|
||||||
|
"CIBW_INHERIT": "before-all; before-all-linux: prepend",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
options_reader.get("before-all", option_format=ListFormat(" && "))
|
||||||
|
== "linux-env && config && env"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_environment_inherit_none_overrides_default_rule(tmp_path: Path) -> None:
|
||||||
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
enable = ["cpython-freethreading"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
options_reader = OptionsReader(
|
||||||
|
pyproject_toml,
|
||||||
|
platform="linux",
|
||||||
|
env={"CIBW_ENABLE": "pypy", "CIBW_INHERIT": "enable: none"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
options_reader.get(
|
||||||
|
"enable", option_format=ListFormat(" "), default_env_rule=InheritRule.APPEND
|
||||||
|
)
|
||||||
|
== "pypy"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_config_inherit_rule(tmp_path: Path) -> None:
|
||||||
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
inherit = {before-all = "invalid"}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(OptionsReaderError, match="must contain only"):
|
||||||
|
OptionsReader(pyproject_toml, platform="linux", env={})
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_environment_inherit_rule() -> None:
|
||||||
|
options_reader = OptionsReader(platform="linux", env={"CIBW_INHERIT": "before-all: invalid"})
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
errors.ConfigurationError, match="Failed to parse CIBW_INHERIT environment variable"
|
||||||
|
):
|
||||||
|
options_reader.get("before-all", option_format=ListFormat(" && "))
|
||||||
|
|
||||||
|
|
||||||
def test_audit_command_option(tmp_path: Path, platform: PlatformName) -> None:
|
def test_audit_command_option(tmp_path: Path, platform: PlatformName) -> None:
|
||||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||||
pyproject_toml.write_text(
|
pyproject_toml.write_text(
|
||||||
@@ -25,12 +25,19 @@ from cibuildwheel.options import (
|
|||||||
_get_pinned_container_images,
|
_get_pinned_container_images,
|
||||||
)
|
)
|
||||||
from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers
|
from cibuildwheel.platforms import ALL_PLATFORM_MODULES, get_build_identifiers
|
||||||
|
from cibuildwheel.platforms.pyodide import (
|
||||||
|
PyodideXBuildEnvInfo,
|
||||||
|
validate_pyodide_target_python,
|
||||||
|
)
|
||||||
|
from cibuildwheel.platforms.pyodide import (
|
||||||
|
PythonConfiguration as PyodidePythonConfiguration,
|
||||||
|
)
|
||||||
from cibuildwheel.util import resources
|
from cibuildwheel.util import resources
|
||||||
from cibuildwheel.util.packaging import DependencyConstraints
|
from cibuildwheel.util.packaging import DependencyConstraints
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Sequence
|
from collections.abc import Callable, Sequence
|
||||||
|
|
||||||
PYPROJECT_1 = """
|
PYPROJECT_1 = """
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
@@ -106,6 +113,91 @@ def test_options_1(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|||||||
assert local.pyodide_version == "0.29.4"
|
assert local.pyodide_version == "0.29.4"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def make_pyodide_xbuildenv_info() -> Callable[[str, str], PyodideXBuildEnvInfo]:
|
||||||
|
def _make(version: str, python: str) -> PyodideXBuildEnvInfo:
|
||||||
|
return {
|
||||||
|
"version": version,
|
||||||
|
"python": python,
|
||||||
|
"emscripten": "5.0.3",
|
||||||
|
"pyodide_build": {"min": None, "max": None},
|
||||||
|
"compatible": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
return _make
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def make_pyodide_python_configuration() -> Callable[[str, str], PyodidePythonConfiguration]:
|
||||||
|
def _make(identifier: str, version: str) -> PyodidePythonConfiguration:
|
||||||
|
return PyodidePythonConfiguration(
|
||||||
|
version=version,
|
||||||
|
identifier=identifier,
|
||||||
|
default_pyodide_version="0.0.0",
|
||||||
|
node_version="v22",
|
||||||
|
)
|
||||||
|
|
||||||
|
return _make
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_pyodide_target_python_matching(
|
||||||
|
make_pyodide_xbuildenv_info: Callable[[str, str], PyodideXBuildEnvInfo],
|
||||||
|
make_pyodide_python_configuration: Callable[[str, str], PyodidePythonConfiguration],
|
||||||
|
) -> None:
|
||||||
|
xbuildenv_info = make_pyodide_xbuildenv_info("314.0.0", "3.14.2")
|
||||||
|
config = make_pyodide_python_configuration("cp314-pyodide_wasm32", "3.14")
|
||||||
|
# should not raise
|
||||||
|
validate_pyodide_target_python(xbuildenv_info, config)
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_pyodide_target_python_mismatch(
|
||||||
|
make_pyodide_xbuildenv_info: Callable[[str, str], PyodideXBuildEnvInfo],
|
||||||
|
make_pyodide_python_configuration: Callable[[str, str], PyodidePythonConfiguration],
|
||||||
|
) -> None:
|
||||||
|
# a Python 3.14 xbuildenv applied to a cp313-pyodide_wasm32 build, when a global
|
||||||
|
# pyodide-version is set across mismatched targets
|
||||||
|
xbuildenv_info = make_pyodide_xbuildenv_info("314.0.0", "3.14.2")
|
||||||
|
config = make_pyodide_python_configuration("cp313-pyodide_wasm32", "3.13")
|
||||||
|
|
||||||
|
with pytest.raises(errors.ConfigurationError) as exc_info:
|
||||||
|
validate_pyodide_target_python(xbuildenv_info, config)
|
||||||
|
|
||||||
|
message = str(exc_info.value)
|
||||||
|
assert "cp313-pyodide_wasm32" in message
|
||||||
|
assert "314.0.0" in message
|
||||||
|
assert "3.13" in message
|
||||||
|
assert "3.14" in message
|
||||||
|
|
||||||
|
|
||||||
|
def test_test_and_audit_requires_with_dependency_specifiers(tmp_path: Path) -> None:
|
||||||
|
"""Regression test for https://github.com/pypa/cibuildwheel/issues/2912"""
|
||||||
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
test-requires = [
|
||||||
|
"pytest",
|
||||||
|
"pyzstd; python_version >= '3.14'",
|
||||||
|
"zarr>=3",
|
||||||
|
]
|
||||||
|
audit-requires = ["abi3audit; python_version >= '3.9'"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
args = CommandLineArguments.defaults()
|
||||||
|
args.package_dir = tmp_path
|
||||||
|
|
||||||
|
options = Options(platform="linux", command_line_arguments=args, env={})
|
||||||
|
build_options = options.build_options(identifier=None)
|
||||||
|
|
||||||
|
assert build_options.test_requires == [
|
||||||
|
"pytest",
|
||||||
|
"pyzstd; python_version >= '3.14'",
|
||||||
|
"zarr>=3",
|
||||||
|
]
|
||||||
|
assert build_options.audit_requires == ["abi3audit; python_version >= '3.9'"]
|
||||||
|
|
||||||
|
|
||||||
def test_passthrough(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_passthrough(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
||||||
f.write(PYPROJECT_1)
|
f.write(PYPROJECT_1)
|
||||||
|
|||||||
+53
-11
@@ -11,6 +11,7 @@ from cibuildwheel.util.file import copy_test_sources, remove_on_error
|
|||||||
from cibuildwheel.util.helpers import (
|
from cibuildwheel.util.helpers import (
|
||||||
FlexibleVersion,
|
FlexibleVersion,
|
||||||
format_safe,
|
format_safe,
|
||||||
|
parse_arbitrary_key_value_string,
|
||||||
parse_key_value_string,
|
parse_key_value_string,
|
||||||
prepare_command,
|
prepare_command,
|
||||||
unwrap,
|
unwrap,
|
||||||
@@ -218,20 +219,61 @@ def test_parse_key_value_string_unknown_name() -> None:
|
|||||||
with pytest.raises(ValueError, match=r"Failed to parse 'key: value'. Unknown field name 'key'"):
|
with pytest.raises(ValueError, match=r"Failed to parse 'key: value'. Unknown field name 'key'"):
|
||||||
parse_key_value_string("key: value")
|
parse_key_value_string("key: value")
|
||||||
|
|
||||||
# Unknown fields can be enabled by passing "*".
|
|
||||||
assert parse_key_value_string(
|
def test_parse_arbitrary_key_value_string_basic() -> None:
|
||||||
"key: value",
|
assert parse_arbitrary_key_value_string("before-test: append; after-test: prepend") == {
|
||||||
kw_arg_names=["*"],
|
"before-test": ["append"],
|
||||||
) == {
|
"after-test": ["prepend"],
|
||||||
"key": ["value"],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert parse_key_value_string(
|
|
||||||
"key1: value1a value1b; key2: value2",
|
def test_parse_arbitrary_key_value_string_multiple_values() -> None:
|
||||||
kw_arg_names=["*"],
|
assert parse_arbitrary_key_value_string(
|
||||||
|
"package1: some/header.h some/library.a; package2: other/header.h"
|
||||||
) == {
|
) == {
|
||||||
"key1": ["value1a", "value1b"],
|
"package1": ["some/header.h", "some/library.a"],
|
||||||
"key2": ["value2"],
|
"package2": ["other/header.h"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_arbitrary_key_value_string_keys_without_values_default() -> None:
|
||||||
|
assert parse_arbitrary_key_value_string(
|
||||||
|
"before-build; before-test: prepend", default_value="append"
|
||||||
|
) == {
|
||||||
|
"before-build": ["append"],
|
||||||
|
"before-test": ["prepend"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_arbitrary_key_value_string_keys_without_values_no_default() -> None:
|
||||||
|
with pytest.raises(ValueError, match="No value specified"):
|
||||||
|
parse_arbitrary_key_value_string("before-build")
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_arbitrary_key_value_string_empty() -> None:
|
||||||
|
assert parse_arbitrary_key_value_string("") == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_arbitrary_key_value_string_duplicate_keys() -> None:
|
||||||
|
assert parse_arbitrary_key_value_string("key: val1; key: val2") == {
|
||||||
|
"key": ["val1", "val2"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_arbitrary_key_value_string_key_only_with_colon() -> None:
|
||||||
|
assert parse_arbitrary_key_value_string("key:") == {"key": []}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_arbitrary_key_value_string_quoted_values() -> None:
|
||||||
|
assert parse_arbitrary_key_value_string('key: "hello world"') == {"key": ["hello world"]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_arbitrary_key_value_string_multiple_bare_keys_with_default() -> None:
|
||||||
|
"""works, but should remain undocumented"""
|
||||||
|
assert parse_arbitrary_key_value_string("a b c", default_value="yes") == {
|
||||||
|
"a": ["yes"],
|
||||||
|
"b": ["yes"],
|
||||||
|
"c": ["yes"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user