feat: configuration support (#684)
* feat: configuration support
* refactor: include manylinux, minor redesign
* "project global" overrides "default platform"
This allows overriding `repair-wheel-command` in the `[tool.cibuildwheel.global]` section.
* fix: add PyPy images
* refactor: remove global
* fix: support tables and arrays
* docs: add pyproject.toml config to docs
* docs: updates based on feedback
* fix: only join if sensible
* refactor: remove manylinux dict
* docs: examples headers and sections on each
* feat: support changing the config from the command line
* fix: use {package}
* tests: add a few tests and mention config one more place
* Restyle examples tabs to remove indent
* Unrelated docs improvements
* Absorb extra content into the tab
* Make the tabs smaller and to the right
* Copy edits to options
* Fix header ids
* Use fewer [tool.cibuildwheel] headers
* docs: minor additions/fixes
* feat: disallow options on some platforms
* docs: make the examples a tiny bit more tabby
* fix: enforce tables/lists are optionally separate
* Some unrelated fixes to the other tab styling
* Minor fix to tab styling
* Refactor to make code a bit more linear and immutable
* Minor docs changes
Co-authored-by: mayeut <mayeut@users.noreply.github.com>
Co-authored-by: Joe Rickerby <joerick@mac.com>
This commit is contained in:
co-authored by
mayeut
Joe Rickerby
parent
f7bdccfbd7
commit
f62cbc303a
@@ -123,6 +123,7 @@ Options
|
|||||||
| | [`CIBW_TEST_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-skip) | Skip running tests on some builds |
|
| | [`CIBW_TEST_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-skip) | Skip running tests on some builds |
|
||||||
| **Other** | [`CIBW_BUILD_VERBOSITY`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-verbosity) | Increase/decrease the output of pip wheel |
|
| **Other** | [`CIBW_BUILD_VERBOSITY`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-verbosity) | Increase/decrease the output of pip wheel |
|
||||||
|
|
||||||
|
These options can be specified in a pyproject.toml file, as well; see [configuration](https://cibuildwheel.readthedocs.io/en/stable/options/#configuration).
|
||||||
|
|
||||||
Working examples
|
Working examples
|
||||||
----------------
|
----------------
|
||||||
|
|||||||
+62
-95
@@ -5,7 +5,7 @@ import textwrap
|
|||||||
import traceback
|
import traceback
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Set, Union, overload
|
from typing import Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
from packaging.specifiers import SpecifierSet
|
from packaging.specifiers import SpecifierSet
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ import cibuildwheel.util
|
|||||||
import cibuildwheel.windows
|
import cibuildwheel.windows
|
||||||
from cibuildwheel.architecture import Architecture, allowed_architectures_check
|
from cibuildwheel.architecture import Architecture, allowed_architectures_check
|
||||||
from cibuildwheel.environment import EnvironmentParseError, parse_environment
|
from cibuildwheel.environment import EnvironmentParseError, parse_environment
|
||||||
|
from cibuildwheel.options import ConfigOptions
|
||||||
from cibuildwheel.projectfiles import get_requires_python_str
|
from cibuildwheel.projectfiles import get_requires_python_str
|
||||||
from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
|
from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
|
||||||
from cibuildwheel.util import (
|
from cibuildwheel.util import (
|
||||||
@@ -28,39 +29,16 @@ from cibuildwheel.util import (
|
|||||||
resources_dir,
|
resources_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MANYLINUX_ARCHS = (
|
||||||
@overload
|
"x86_64",
|
||||||
def get_option_from_environment(
|
"i686",
|
||||||
option_name: str, *, platform: Optional[str] = None, default: str
|
"pypy_x86_64",
|
||||||
) -> str:
|
"aarch64",
|
||||||
... # noqa: E704
|
"ppc64le",
|
||||||
|
"s390x",
|
||||||
|
"pypy_aarch64",
|
||||||
@overload
|
"pypy_i686",
|
||||||
def get_option_from_environment(
|
)
|
||||||
option_name: str, *, platform: Optional[str] = None, default: None = None
|
|
||||||
) -> Optional[str]:
|
|
||||||
... # noqa: E704 E302
|
|
||||||
|
|
||||||
|
|
||||||
def get_option_from_environment(
|
|
||||||
option_name: str, *, platform: Optional[str] = None, default: Optional[str] = None
|
|
||||||
) -> Optional[str]: # noqa: E302
|
|
||||||
"""
|
|
||||||
Returns an option from the environment, optionally scoped by the platform.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
get_option_from_environment('CIBW_COLOR', platform='macos')
|
|
||||||
|
|
||||||
This will return the value of CIBW_COLOR_MACOS if it exists, otherwise the value of
|
|
||||||
CIBW_COLOR.
|
|
||||||
"""
|
|
||||||
if platform:
|
|
||||||
option = os.environ.get(f"{option_name}_{platform.upper()}")
|
|
||||||
if option is not None:
|
|
||||||
return option
|
|
||||||
|
|
||||||
return os.environ.get(option_name, default)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@@ -69,8 +47,9 @@ def main() -> None:
|
|||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Build wheels for all the platforms.",
|
description="Build wheels for all the platforms.",
|
||||||
epilog="""
|
epilog="""
|
||||||
Most options are supplied via environment variables.
|
Most options are supplied via environment variables or in
|
||||||
See https://github.com/pypa/cibuildwheel#options for info.
|
--config-file (pyproject.toml usually). See
|
||||||
|
https://github.com/pypa/cibuildwheel#options for info.
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,10 +83,17 @@ def main() -> None:
|
|||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--output-dir",
|
"--output-dir",
|
||||||
default=os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse"),
|
|
||||||
help="Destination folder for the wheels.",
|
help="Destination folder for the wheels.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--config-file",
|
||||||
|
help="""
|
||||||
|
TOML config file for cibuildwheel. Defaults to pyproject.toml, but
|
||||||
|
can be overridden with this option.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"package_dir",
|
"package_dir",
|
||||||
default=".",
|
default=".",
|
||||||
@@ -176,40 +162,40 @@ def main() -> None:
|
|||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
package_dir = Path(args.package_dir)
|
package_dir = Path(args.package_dir)
|
||||||
output_dir = Path(args.output_dir)
|
|
||||||
|
|
||||||
if platform == "linux":
|
manylinux_identifiers = {
|
||||||
repair_command_default = "auditwheel repair -w {dest_dir} {wheel}"
|
f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS
|
||||||
elif platform == "macos":
|
}
|
||||||
repair_command_default = "delocate-listdeps {wheel} && delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}"
|
disallow = {
|
||||||
elif platform == "windows":
|
"linux": {"dependency-versions"},
|
||||||
repair_command_default = ""
|
"macos": manylinux_identifiers,
|
||||||
else:
|
"windows": manylinux_identifiers,
|
||||||
assert_never(platform)
|
}
|
||||||
|
options = ConfigOptions(package_dir, args.config_file, platform=platform, disallow=disallow)
|
||||||
|
output_dir = Path(
|
||||||
|
args.output_dir
|
||||||
|
if args.output_dir is not None
|
||||||
|
else os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse")
|
||||||
|
)
|
||||||
|
|
||||||
|
build_config = options("build", env_plat=False, sep=" ") or "*"
|
||||||
|
skip_config = options("skip", env_plat=False, sep=" ")
|
||||||
|
test_skip = options("test-skip", env_plat=False, sep=" ")
|
||||||
|
|
||||||
|
archs_config_str = args.archs or options("archs", sep=" ")
|
||||||
|
|
||||||
|
environment_config = options("environment", table={"item": '{k}="{v}"', "sep": " "})
|
||||||
|
before_all = options("before-all", sep=" && ")
|
||||||
|
before_build = options("before-build", sep=" && ")
|
||||||
|
repair_command = options("repair-wheel-command", sep=" && ")
|
||||||
|
|
||||||
|
dependency_versions = options("dependency-versions")
|
||||||
|
test_command = options("test-command", sep=" && ")
|
||||||
|
before_test = options("before-test", sep=" && ")
|
||||||
|
test_requires = options("test-requires", sep=" ").split()
|
||||||
|
test_extras = options("test-extras", sep=",")
|
||||||
|
build_verbosity_str = options("build-verbosity")
|
||||||
|
|
||||||
build_config = os.environ.get("CIBW_BUILD") or "*"
|
|
||||||
skip_config = os.environ.get("CIBW_SKIP", "")
|
|
||||||
test_skip = os.environ.get("CIBW_TEST_SKIP", "")
|
|
||||||
environment_config = get_option_from_environment(
|
|
||||||
"CIBW_ENVIRONMENT", platform=platform, default=""
|
|
||||||
)
|
|
||||||
before_all = get_option_from_environment("CIBW_BEFORE_ALL", platform=platform, default="")
|
|
||||||
before_build = get_option_from_environment("CIBW_BEFORE_BUILD", platform=platform)
|
|
||||||
repair_command = get_option_from_environment(
|
|
||||||
"CIBW_REPAIR_WHEEL_COMMAND", platform=platform, default=repair_command_default
|
|
||||||
)
|
|
||||||
dependency_versions = get_option_from_environment(
|
|
||||||
"CIBW_DEPENDENCY_VERSIONS", platform=platform, default="pinned"
|
|
||||||
)
|
|
||||||
test_command = get_option_from_environment("CIBW_TEST_COMMAND", platform=platform)
|
|
||||||
before_test = get_option_from_environment("CIBW_BEFORE_TEST", platform=platform)
|
|
||||||
test_requires = get_option_from_environment(
|
|
||||||
"CIBW_TEST_REQUIRES", platform=platform, default=""
|
|
||||||
).split()
|
|
||||||
test_extras = get_option_from_environment("CIBW_TEST_EXTRAS", platform=platform, default="")
|
|
||||||
build_verbosity_str = get_option_from_environment(
|
|
||||||
"CIBW_BUILD_VERBOSITY", platform=platform, default=""
|
|
||||||
)
|
|
||||||
prerelease_pythons = args.prerelease_pythons or cibuildwheel.util.strtobool(
|
prerelease_pythons = args.prerelease_pythons or cibuildwheel.util.strtobool(
|
||||||
os.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
|
os.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
|
||||||
)
|
)
|
||||||
@@ -218,11 +204,11 @@ def main() -> None:
|
|||||||
|
|
||||||
if not any(package_dir.joinpath(name).exists() for name in package_files):
|
if not any(package_dir.joinpath(name).exists() for name in package_files):
|
||||||
names = ", ".join(sorted(package_files, reverse=True))
|
names = ", ".join(sorted(package_files, reverse=True))
|
||||||
print(
|
msg = f"cibuildwheel: Could not find any of {{{names}}} at root of package"
|
||||||
f"cibuildwheel: Could not find any of {{{names}}} at root of package", file=sys.stderr
|
print(msg, file=sys.stderr)
|
||||||
)
|
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
# This is not supported in tool.cibuildwheel, as it comes from a standard location.
|
||||||
# Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py
|
# Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py
|
||||||
requires_python_str: Optional[str] = os.environ.get(
|
requires_python_str: Optional[str] = os.environ.get(
|
||||||
"CIBW_PROJECT_REQUIRES_PYTHON"
|
"CIBW_PROJECT_REQUIRES_PYTHON"
|
||||||
@@ -270,13 +256,6 @@ def main() -> None:
|
|||||||
# This needs to be passed on to the docker container in linux.py
|
# This needs to be passed on to the docker container in linux.py
|
||||||
os.environ["CIBUILDWHEEL"] = "1"
|
os.environ["CIBUILDWHEEL"] = "1"
|
||||||
|
|
||||||
if args.archs is not None:
|
|
||||||
archs_config_str = args.archs
|
|
||||||
else:
|
|
||||||
archs_config_str = get_option_from_environment(
|
|
||||||
"CIBW_ARCHS", platform=platform, default="auto"
|
|
||||||
)
|
|
||||||
|
|
||||||
archs = Architecture.parse_config(archs_config_str, platform=platform)
|
archs = Architecture.parse_config(archs_config_str, platform=platform)
|
||||||
|
|
||||||
identifiers = get_build_identifiers(platform, build_selector, archs)
|
identifiers = get_build_identifiers(platform, build_selector, archs)
|
||||||
@@ -286,7 +265,7 @@ def main() -> None:
|
|||||||
print(identifier)
|
print(identifier)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
manylinux_images: Optional[Dict[str, str]] = None
|
manylinux_images: Dict[str, str] = {}
|
||||||
if platform == "linux":
|
if platform == "linux":
|
||||||
pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg"
|
pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg"
|
||||||
all_pinned_docker_images = ConfigParser()
|
all_pinned_docker_images = ConfigParser()
|
||||||
@@ -297,22 +276,10 @@ def main() -> None:
|
|||||||
# 'pypy_x86_64': {'manylinux2010': '...' }
|
# 'pypy_x86_64': {'manylinux2010': '...' }
|
||||||
# ... }
|
# ... }
|
||||||
|
|
||||||
manylinux_images = {}
|
for build_platform in MANYLINUX_ARCHS:
|
||||||
|
|
||||||
for build_platform in [
|
|
||||||
"x86_64",
|
|
||||||
"i686",
|
|
||||||
"pypy_x86_64",
|
|
||||||
"aarch64",
|
|
||||||
"ppc64le",
|
|
||||||
"s390x",
|
|
||||||
"pypy_aarch64",
|
|
||||||
"pypy_i686",
|
|
||||||
]:
|
|
||||||
pinned_images = all_pinned_docker_images[build_platform]
|
pinned_images = all_pinned_docker_images[build_platform]
|
||||||
|
|
||||||
config_name = f"CIBW_MANYLINUX_{build_platform.upper()}_IMAGE"
|
config_value = options(f"manylinux-{build_platform}-image")
|
||||||
config_value = os.environ.get(config_name)
|
|
||||||
|
|
||||||
if config_value is None:
|
if config_value is None:
|
||||||
# default to manylinux2010 if it's available, otherwise manylinux2014
|
# default to manylinux2010 if it's available, otherwise manylinux2014
|
||||||
@@ -340,7 +307,7 @@ def main() -> None:
|
|||||||
repair_command=repair_command,
|
repair_command=repair_command,
|
||||||
environment=environment,
|
environment=environment,
|
||||||
dependency_constraints=dependency_constraints,
|
dependency_constraints=dependency_constraints,
|
||||||
manylinux_images=manylinux_images,
|
manylinux_images=manylinux_images or None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Python is buffering by default when running on the CI platforms, giving problems interleaving subprocess call output with unflushed calls to 'print'
|
# Python is buffering by default when running on the CI platforms, giving problems interleaving subprocess call output with unflushed calls to 'print'
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
|
import toml
|
||||||
|
|
||||||
|
from .typing import PLATFORMS, TypedDict
|
||||||
|
from .util import resources_dir
|
||||||
|
|
||||||
|
Setting = Union[Dict[str, str], List[str], str]
|
||||||
|
|
||||||
|
|
||||||
|
class TableFmt(TypedDict):
|
||||||
|
item: str
|
||||||
|
sep: str
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigOptionError(KeyError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _dig_first(*pairs: Tuple[Mapping[str, Any], str]) -> Setting:
|
||||||
|
"""
|
||||||
|
Return the first dict item that matches from pairs of dicts and keys.
|
||||||
|
Final result is will throw a KeyError if missing.
|
||||||
|
|
||||||
|
_dig_first((dict1, "key1"), (dict2, "key2"), ...)
|
||||||
|
"""
|
||||||
|
(dict_like, key), *others = pairs
|
||||||
|
return dict_like.get(key, _dig_first(*others)) if others else dict_like[key]
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigOptions:
|
||||||
|
"""
|
||||||
|
Gets options from the environment, config or defaults, optionally scoped
|
||||||
|
by the platform.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> options = ConfigOptions(package_dir, platform='macos')
|
||||||
|
>>> options('cool-color')
|
||||||
|
|
||||||
|
This will return the value of CIBW_COOL_COLOR_MACOS if it exists,
|
||||||
|
otherwise the value of CIBW_COOL_COLOR, otherwise
|
||||||
|
'tool.cibuildwheel.macos.cool-color' or 'tool.cibuildwheel.cool-color'
|
||||||
|
from pyproject.toml, or from cibuildwheel/resources/defaults.toml. An
|
||||||
|
error is thrown if there are any unexpected keys or sections in
|
||||||
|
tool.cibuildwheel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
package_path: Path,
|
||||||
|
config_file: Optional[str] = None,
|
||||||
|
*,
|
||||||
|
platform: str,
|
||||||
|
disallow: Optional[Dict[str, Set[str]]] = None,
|
||||||
|
) -> None:
|
||||||
|
self.platform = platform
|
||||||
|
self.disallow = disallow or {}
|
||||||
|
|
||||||
|
# Open defaults.toml, loading both global and platform sections
|
||||||
|
defaults_path = resources_dir / "defaults.toml"
|
||||||
|
self.default_options, self.default_platform_options = self._load_file(defaults_path)
|
||||||
|
|
||||||
|
# load the project config file
|
||||||
|
config_options: Dict[str, Any] = {}
|
||||||
|
config_platform_options: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
if config_file is not None:
|
||||||
|
config_path = Path(config_file.format(package=package_path))
|
||||||
|
config_options, config_platform_options = self._load_file(config_path)
|
||||||
|
else:
|
||||||
|
# load pyproject.toml, if it's available
|
||||||
|
pyproject_toml_path = package_path / "pyproject.toml"
|
||||||
|
if pyproject_toml_path.exists():
|
||||||
|
config_options, config_platform_options = self._load_file(pyproject_toml_path)
|
||||||
|
|
||||||
|
# validate project config
|
||||||
|
for option_name in config_options:
|
||||||
|
if not self._is_valid_global_option(option_name):
|
||||||
|
raise ConfigOptionError(f'Option "{option_name}" not supported in a config file')
|
||||||
|
|
||||||
|
for option_name in config_platform_options:
|
||||||
|
if not self._is_valid_platform_option(option_name):
|
||||||
|
raise ConfigOptionError(
|
||||||
|
f'Option "{option_name}" not supported in the "{self.platform}" section'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.config_options = config_options
|
||||||
|
self.config_platform_options = config_platform_options
|
||||||
|
|
||||||
|
def _is_valid_global_option(self, name: str) -> bool:
|
||||||
|
"""
|
||||||
|
Returns True if an option with this name is allowed in the
|
||||||
|
[tool.cibuildwheel] section of a config file.
|
||||||
|
"""
|
||||||
|
allowed_option_names = self.default_options.keys() | PLATFORMS
|
||||||
|
|
||||||
|
return name in allowed_option_names
|
||||||
|
|
||||||
|
def _is_valid_platform_option(self, name: str) -> bool:
|
||||||
|
"""
|
||||||
|
Returns True if an option with this name is allowed in the
|
||||||
|
[tool.cibuildwheel.<current-platform>] section of a config file.
|
||||||
|
"""
|
||||||
|
disallowed_platform_options = self.disallow.get(self.platform, set())
|
||||||
|
if name in disallowed_platform_options:
|
||||||
|
return False
|
||||||
|
|
||||||
|
allowed_option_names = self.default_options.keys() | self.default_platform_options.keys()
|
||||||
|
|
||||||
|
return name in allowed_option_names
|
||||||
|
|
||||||
|
def _load_file(self, filename: Path) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Load a toml file, returns global and platform as separate dicts.
|
||||||
|
"""
|
||||||
|
config = toml.load(filename)
|
||||||
|
|
||||||
|
global_options = config.get("tool", {}).get("cibuildwheel", {})
|
||||||
|
platform_options = global_options.get(self.platform, {})
|
||||||
|
|
||||||
|
return global_options, platform_options
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
|
env_plat: bool = True,
|
||||||
|
sep: Optional[str] = None,
|
||||||
|
table: Optional[TableFmt] = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Get and return the value for the named option from environment,
|
||||||
|
configuration file, or the default. If env_plat is False, then don't
|
||||||
|
accept platform versions of the environment variable. If this is an
|
||||||
|
array it will be merged with "sep" before returning. If it is a table,
|
||||||
|
it will be formatted with "table['item']" using {k} and {v} and merged
|
||||||
|
with "table['sep']".
|
||||||
|
"""
|
||||||
|
|
||||||
|
if name not in self.default_options and name not in self.default_platform_options:
|
||||||
|
raise ConfigOptionError(f"{name} must be in cibuildwheel/resources/defaults.toml file")
|
||||||
|
|
||||||
|
# Environment variable form
|
||||||
|
envvar = f"CIBW_{name.upper().replace('-', '_')}"
|
||||||
|
plat_envvar = f"{envvar}_{self.platform.upper()}"
|
||||||
|
|
||||||
|
# get the option from the environment, then the config file, then finally the default.
|
||||||
|
# platform-specific options are preferred, if they're allowed.
|
||||||
|
result = _dig_first(
|
||||||
|
(os.environ if env_plat else {}, plat_envvar), # type: ignore
|
||||||
|
(os.environ, envvar),
|
||||||
|
(self.config_platform_options, name),
|
||||||
|
(self.config_options, name),
|
||||||
|
(self.default_platform_options, name),
|
||||||
|
(self.default_options, name),
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(result, dict):
|
||||||
|
if table is None:
|
||||||
|
raise ConfigOptionError(f"{name} does not accept a table")
|
||||||
|
return table["sep"].join(table["item"].format(k=k, v=v) for k, v in result.items())
|
||||||
|
elif isinstance(result, list):
|
||||||
|
if sep is None:
|
||||||
|
raise ConfigOptionError(f"{name} does not accept a list")
|
||||||
|
return sep.join(result)
|
||||||
|
elif isinstance(result, int):
|
||||||
|
return str(result)
|
||||||
|
else:
|
||||||
|
return result
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
[tool.cibuildwheel]
|
||||||
|
build = "*"
|
||||||
|
skip = ""
|
||||||
|
test-skip = ""
|
||||||
|
|
||||||
|
archs = ["auto"]
|
||||||
|
dependency-versions = "pinned"
|
||||||
|
environment = {}
|
||||||
|
build-verbosity = ""
|
||||||
|
|
||||||
|
before-all = ""
|
||||||
|
before-build = ""
|
||||||
|
repair-wheel-command = ""
|
||||||
|
|
||||||
|
test-command = ""
|
||||||
|
before-test = ""
|
||||||
|
test-requires = []
|
||||||
|
test-extras = []
|
||||||
|
|
||||||
|
manylinux-x86_64-image = "manylinux2010"
|
||||||
|
manylinux-i686-image = "manylinux2010"
|
||||||
|
manylinux-aarch64-image = "manylinux2014"
|
||||||
|
manylinux-ppc64le-image = "manylinux2014"
|
||||||
|
manylinux-s390x-image = "manylinux2014"
|
||||||
|
manylinux-pypy_x86_64-image = "manylinux2010"
|
||||||
|
manylinux-pypy_i686-image = "manylinux2010"
|
||||||
|
manylinux-pypy_aarch64-image = "manylinux2014"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.cibuildwheel.linux]
|
||||||
|
repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.macos]
|
||||||
|
repair-wheel-command = [
|
||||||
|
"delocate-listdeps {wheel}",
|
||||||
|
"delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.cibuildwheel.windows]
|
||||||
+73
-8
@@ -1,6 +1,10 @@
|
|||||||
|
|
||||||
/* Global styles */
|
/* Global styles */
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
line-height: 26px;
|
line-height: 26px;
|
||||||
@@ -20,9 +24,9 @@ code, pre, tt {
|
|||||||
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
|
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs {
|
pre code, pre code.hljs {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
background-color: #f6f8fa;
|
background-color: #f6f8fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
@@ -112,7 +116,21 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
border-bottom: 2px solid #ccc;
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.tabs-header::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
.tabs-header > * {
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style the buttons that are used to open the tab content */
|
/* Style the buttons that are used to open the tab content */
|
||||||
@@ -126,15 +144,13 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid #ccc;
|
||||||
|
|
||||||
/* put the border on top of the parent border */
|
|
||||||
margin-bottom: -2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-header button:focus-visible {
|
.tabs-header button:focus-visible {
|
||||||
/* preserve an outline for accesibility purposes */
|
/* preserve an outline for accesibility purposes */
|
||||||
outline: 1px solid currentColor;
|
outline: 1px solid currentColor;
|
||||||
|
outline-offset: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Change background color of buttons on hover */
|
/* Change background color of buttons on hover */
|
||||||
@@ -153,4 +169,53 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
background-color: #f1f6fa;
|
background-color: #f1f6fa;
|
||||||
padding: 1px 1em;
|
padding: 1px 1em;
|
||||||
padding-top: 0.8em;
|
padding-top: 0.8em;
|
||||||
|
|
||||||
|
/* don't collapse inner margins */
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Examples tabs styling */
|
||||||
|
|
||||||
|
.tabs.examples .tabs-header {
|
||||||
|
justify-content: flex-end;
|
||||||
|
background-color: transparent;
|
||||||
|
border-bottom-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.tabs.examples .tabs-header::before {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.tabs.examples .tabs-header button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-bottom: none;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
font-size: 0.8em;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.tabs-header button:hover {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs.examples .tabs-header button.active {
|
||||||
|
border-bottom-style: none;
|
||||||
|
background: #f6f8fa;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.tabs.examples .tabs-content {
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
}
|
||||||
|
.tabs.examples .tabs-content .tab > * {
|
||||||
|
padding-left: 0.8em;
|
||||||
|
padding-right: 0.8em;
|
||||||
|
}
|
||||||
|
.tabs.examples .tabs-content .tab > pre {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
.tabs.examples pre:first-child {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,17 @@ while (true) {
|
|||||||
tabContainer.insertBefore(firstTab);
|
tabContainer.insertBefore(firstTab);
|
||||||
tabContainer.append(headerContainer, contentContainer)
|
tabContainer.append(headerContainer, contentContainer)
|
||||||
|
|
||||||
|
// add extra classes from the first tab to the container
|
||||||
|
const classes = Array.from(firstTab[0].classList)
|
||||||
|
|
||||||
|
for (let i = 0; i < classes.length; i++) {
|
||||||
|
const element = classes[i];
|
||||||
|
if (element == 'tab' || element == 'admonition') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tabContainer.addClass(element)
|
||||||
|
}
|
||||||
|
|
||||||
const selectTab = function (index) {
|
const selectTab = function (index) {
|
||||||
headerContainer.children().removeClass('active')
|
headerContainer.children().removeClass('active')
|
||||||
headerContainer.children().eq(index).addClass('active')
|
headerContainer.children().eq(index).addClass('active')
|
||||||
|
|||||||
+561
-174
File diff suppressed because it is too large
Load Diff
@@ -200,7 +200,7 @@ def test_test_command(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].test_command == test_command
|
assert intercepted_build_args.args[0].test_command == (test_command or "")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("before_build", [None, "before --build"])
|
@pytest.mark.parametrize("before_build", [None, "before --build"])
|
||||||
@@ -217,7 +217,7 @@ def test_before_build(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].before_build == before_build
|
assert intercepted_build_args.args[0].before_build == (before_build or "")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("build_verbosity", [None, 0, 2, -2, 4, -4])
|
@pytest.mark.parametrize("build_verbosity", [None, 0, 2, -2, 4, -4])
|
||||||
@@ -286,7 +286,4 @@ def test_before_all(before_all, platform_specific, platform, intercepted_build_a
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
if before_all is None:
|
assert intercepted_build_args.args[0].before_all == (before_all or "")
|
||||||
before_all = ""
|
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].before_all == before_all
|
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from cibuildwheel.options import ConfigOptionError, ConfigOptions
|
||||||
|
|
||||||
|
PYPROJECT_1 = """
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
build = "cp39*"
|
||||||
|
environment = {THING = "OTHER", FOO="BAR"}
|
||||||
|
|
||||||
|
test-command = "pyproject"
|
||||||
|
test-requires = "something"
|
||||||
|
test-extras = ["one", "two"]
|
||||||
|
|
||||||
|
manylinux-x86_64-image = "manylinux1"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.macos]
|
||||||
|
test-requires = "else"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.linux]
|
||||||
|
test-requires = ["other", "many"]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(params=["linux", "macos", "windows"])
|
||||||
|
def platform(request):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fname", ["pyproject.toml", "cibuildwheel.toml"])
|
||||||
|
def test_simple_settings(tmp_path, platform, fname):
|
||||||
|
with tmp_path.joinpath(fname).open("w") as f:
|
||||||
|
f.write(PYPROJECT_1)
|
||||||
|
|
||||||
|
options = ConfigOptions(tmp_path, f"{{package}}/{fname}", platform=platform)
|
||||||
|
|
||||||
|
assert options("build", env_plat=False, sep=" ") == "cp39*"
|
||||||
|
|
||||||
|
assert options("test-command") == "pyproject"
|
||||||
|
assert options("archs", sep=" ") == "auto"
|
||||||
|
assert (
|
||||||
|
options("test-requires", sep=" ")
|
||||||
|
== {"windows": "something", "macos": "else", "linux": "other many"}[platform]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Also testing options for support for both lists and tables
|
||||||
|
assert (
|
||||||
|
options("environment", table={"item": '{k}="{v}"', "sep": " "}) == 'THING="OTHER" FOO="BAR"'
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
options("environment", sep="x", table={"item": '{k}="{v}"', "sep": " "})
|
||||||
|
== 'THING="OTHER" FOO="BAR"'
|
||||||
|
)
|
||||||
|
assert options("test-extras", sep=",") == "one,two"
|
||||||
|
assert options("test-extras", sep=",", table={"item": '{k}="{v}"', "sep": " "}) == "one,two"
|
||||||
|
|
||||||
|
assert options("manylinux-x86_64-image") == "manylinux1"
|
||||||
|
assert options("manylinux-i686-image") == "manylinux2010"
|
||||||
|
|
||||||
|
with pytest.raises(ConfigOptionError):
|
||||||
|
options("environment", sep=" ")
|
||||||
|
|
||||||
|
with pytest.raises(ConfigOptionError):
|
||||||
|
options("test-extras", table={"item": '{k}="{v}"', "sep": " "})
|
||||||
|
|
||||||
|
|
||||||
|
def test_envvar_override(tmp_path, platform, monkeypatch):
|
||||||
|
monkeypatch.setenv("CIBW_BUILD", "cp38*")
|
||||||
|
monkeypatch.setenv("CIBW_MANYLINUX_X86_64_IMAGE", "manylinux2014")
|
||||||
|
monkeypatch.setenv("CIBW_TEST_COMMAND", "mytest")
|
||||||
|
monkeypatch.setenv("CIBW_TEST_REQUIRES", "docs")
|
||||||
|
monkeypatch.setenv("CIBW_TEST_REQUIRES_LINUX", "scod")
|
||||||
|
|
||||||
|
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
||||||
|
f.write(PYPROJECT_1)
|
||||||
|
|
||||||
|
options = ConfigOptions(tmp_path, platform=platform)
|
||||||
|
|
||||||
|
assert options("archs", sep=" ") == "auto"
|
||||||
|
|
||||||
|
assert options("build", sep=" ") == "cp38*"
|
||||||
|
assert options("manylinux-x86_64-image") == "manylinux2014"
|
||||||
|
assert options("manylinux-i686-image") == "manylinux2010"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
options("test-requires", sep=" ")
|
||||||
|
== {"windows": "docs", "macos": "docs", "linux": "scod"}[platform]
|
||||||
|
)
|
||||||
|
assert options("test-command") == "mytest"
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_global_override_default_platform(tmp_path, platform):
|
||||||
|
tmp_path.joinpath("pyproject.toml").write_text(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
repair-wheel-command = "repair-project-global"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
options = ConfigOptions(tmp_path, platform=platform)
|
||||||
|
assert options("repair-wheel-command") == "repair-project-global"
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_global_override_default_platform(tmp_path, platform, monkeypatch):
|
||||||
|
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
|
||||||
|
options = ConfigOptions(tmp_path, platform=platform)
|
||||||
|
assert options("repair-wheel-command") == "repair-env-global"
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_global_override_project_platform(tmp_path, platform, monkeypatch):
|
||||||
|
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
|
||||||
|
tmp_path.joinpath("pyproject.toml").write_text(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel.linux]
|
||||||
|
repair-wheel-command = "repair-project-linux"
|
||||||
|
[tool.cibuildwheel.windows]
|
||||||
|
repair-wheel-command = "repair-project-windows"
|
||||||
|
[tool.cibuildwheel.macos]
|
||||||
|
repair-wheel-command = "repair-project-macos"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
options = ConfigOptions(tmp_path, platform=platform)
|
||||||
|
assert options("repair-wheel-command") == "repair-env-global"
|
||||||
|
|
||||||
|
|
||||||
|
def test_global_platform_order(tmp_path, platform):
|
||||||
|
tmp_path.joinpath("pyproject.toml").write_text(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel.linux]
|
||||||
|
repair-wheel-command = "repair-project-linux"
|
||||||
|
[tool.cibuildwheel.windows]
|
||||||
|
repair-wheel-command = "repair-project-windows"
|
||||||
|
[tool.cibuildwheel.macos]
|
||||||
|
repair-wheel-command = "repair-project-macos"
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
repair-wheel-command = "repair-project-global"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
options = ConfigOptions(tmp_path, platform=platform)
|
||||||
|
assert options("repair-wheel-command") == f"repair-project-{platform}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_unexpected_key(tmp_path):
|
||||||
|
# Note that platform contents are only checked when running
|
||||||
|
# for that platform.
|
||||||
|
tmp_path.joinpath("pyproject.toml").write_text(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
repairs-wheel-command = "repair-project-linux"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ConfigOptionError):
|
||||||
|
ConfigOptions(tmp_path, platform="linux")
|
||||||
|
|
||||||
|
|
||||||
|
def test_unexpected_table(tmp_path):
|
||||||
|
tmp_path.joinpath("pyproject.toml").write_text(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel.linus]
|
||||||
|
repair-wheel-command = "repair-project-linux"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
with pytest.raises(ConfigOptionError):
|
||||||
|
ConfigOptions(tmp_path, platform="linux")
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsupported_join(tmp_path):
|
||||||
|
tmp_path.joinpath("pyproject.toml").write_text(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
build = ["1", "2"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
options = ConfigOptions(tmp_path, platform="linux")
|
||||||
|
|
||||||
|
assert "1, 2" == options("build", sep=", ")
|
||||||
|
with pytest.raises(ConfigOptionError):
|
||||||
|
options("build")
|
||||||
|
|
||||||
|
|
||||||
|
def test_disallowed_a(tmp_path):
|
||||||
|
tmp_path.joinpath("pyproject.toml").write_text(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel.windows]
|
||||||
|
manylinux-x64_86-image = "manylinux1"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
disallow = {"windows": {"manylinux-x64_86-image"}}
|
||||||
|
ConfigOptions(tmp_path, platform="linux", disallow=disallow)
|
||||||
|
with pytest.raises(ConfigOptionError):
|
||||||
|
ConfigOptions(tmp_path, platform="windows", disallow=disallow)
|
||||||
Reference in New Issue
Block a user