From f62cbc303a5394a260158078ee2d6bccf5602c32 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 21 Jun 2021 12:26:46 -0400 Subject: [PATCH] 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 Co-authored-by: Joe Rickerby --- README.md | 1 + cibuildwheel/__main__.py | 157 ++--- cibuildwheel/options.py | 171 +++++ cibuildwheel/resources/defaults.toml | 39 ++ docs/extra.css | 81 ++- docs/extra.js | 11 + docs/options.md | 735 +++++++++++++++++----- unit_test/main_tests/main_options_test.py | 9 +- unit_test/options_toml_test.py | 190 ++++++ 9 files changed, 1111 insertions(+), 283 deletions(-) create mode 100644 cibuildwheel/options.py create mode 100644 cibuildwheel/resources/defaults.toml create mode 100644 unit_test/options_toml_test.py diff --git a/README.md b/README.md index 12c662e1..36720028 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ Options | | [`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 | +These options can be specified in a pyproject.toml file, as well; see [configuration](https://cibuildwheel.readthedocs.io/en/stable/options/#configuration). Working examples ---------------- diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py index beccd007..1d3ce40c 100644 --- a/cibuildwheel/__main__.py +++ b/cibuildwheel/__main__.py @@ -5,7 +5,7 @@ import textwrap import traceback from configparser import ConfigParser 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 @@ -16,6 +16,7 @@ import cibuildwheel.util import cibuildwheel.windows from cibuildwheel.architecture import Architecture, allowed_architectures_check from cibuildwheel.environment import EnvironmentParseError, parse_environment +from cibuildwheel.options import ConfigOptions from cibuildwheel.projectfiles import get_requires_python_str from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never from cibuildwheel.util import ( @@ -28,39 +29,16 @@ from cibuildwheel.util import ( resources_dir, ) - -@overload -def get_option_from_environment( - option_name: str, *, platform: Optional[str] = None, default: str -) -> str: - ... # noqa: E704 - - -@overload -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) +MANYLINUX_ARCHS = ( + "x86_64", + "i686", + "pypy_x86_64", + "aarch64", + "ppc64le", + "s390x", + "pypy_aarch64", + "pypy_i686", +) def main() -> None: @@ -69,8 +47,9 @@ def main() -> None: parser = argparse.ArgumentParser( description="Build wheels for all the platforms.", epilog=""" - Most options are supplied via environment variables. - See https://github.com/pypa/cibuildwheel#options for info. + Most options are supplied via environment variables or in + --config-file (pyproject.toml usually). See + https://github.com/pypa/cibuildwheel#options for info. """, ) @@ -104,10 +83,17 @@ def main() -> None: parser.add_argument( "--output-dir", - default=os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse"), 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( "package_dir", default=".", @@ -176,40 +162,40 @@ def main() -> None: sys.exit(2) package_dir = Path(args.package_dir) - output_dir = Path(args.output_dir) - if platform == "linux": - repair_command_default = "auditwheel repair -w {dest_dir} {wheel}" - elif platform == "macos": - repair_command_default = "delocate-listdeps {wheel} && delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" - elif platform == "windows": - repair_command_default = "" - else: - assert_never(platform) + manylinux_identifiers = { + f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS + } + disallow = { + "linux": {"dependency-versions"}, + "macos": manylinux_identifiers, + "windows": manylinux_identifiers, + } + 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( 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): names = ", ".join(sorted(package_files, reverse=True)) - print( - f"cibuildwheel: Could not find any of {{{names}}} at root of package", file=sys.stderr - ) + msg = f"cibuildwheel: Could not find any of {{{names}}} at root of package" + print(msg, file=sys.stderr) 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 requires_python_str: Optional[str] = os.environ.get( "CIBW_PROJECT_REQUIRES_PYTHON" @@ -270,13 +256,6 @@ def main() -> None: # This needs to be passed on to the docker container in linux.py 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) identifiers = get_build_identifiers(platform, build_selector, archs) @@ -286,7 +265,7 @@ def main() -> None: print(identifier) sys.exit(0) - manylinux_images: Optional[Dict[str, str]] = None + manylinux_images: Dict[str, str] = {} if platform == "linux": pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg" all_pinned_docker_images = ConfigParser() @@ -297,22 +276,10 @@ def main() -> None: # 'pypy_x86_64': {'manylinux2010': '...' } # ... } - manylinux_images = {} - - for build_platform in [ - "x86_64", - "i686", - "pypy_x86_64", - "aarch64", - "ppc64le", - "s390x", - "pypy_aarch64", - "pypy_i686", - ]: + for build_platform in MANYLINUX_ARCHS: pinned_images = all_pinned_docker_images[build_platform] - config_name = f"CIBW_MANYLINUX_{build_platform.upper()}_IMAGE" - config_value = os.environ.get(config_name) + config_value = options(f"manylinux-{build_platform}-image") if config_value is None: # default to manylinux2010 if it's available, otherwise manylinux2014 @@ -340,7 +307,7 @@ def main() -> None: repair_command=repair_command, environment=environment, 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' diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py new file mode 100644 index 00000000..6631d762 --- /dev/null +++ b/cibuildwheel/options.py @@ -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.] 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 diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml new file mode 100644 index 00000000..a9ecff02 --- /dev/null +++ b/cibuildwheel/resources/defaults.toml @@ -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] diff --git a/docs/extra.css b/docs/extra.css index 3fc5c37d..4ed1e23d 100644 --- a/docs/extra.css +++ b/docs/extra.css @@ -1,6 +1,10 @@ /* Global styles */ +body { + overflow-wrap: break-word; +} + p { margin-bottom: 12px; line-height: 26px; @@ -20,9 +24,9 @@ code, pre, tt { font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; } -.hljs { - padding: 1em; - background-color: #f6f8fa; +pre code, pre code.hljs { + padding: 1em; + background-color: #f6f8fa; } h1, h2, h3, h4, h5, h6 { @@ -112,7 +116,21 @@ h1, h2, h3, h4, h5, h6 { background-color: white; display: flex; 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 */ @@ -126,15 +144,13 @@ h1, h2, h3, h4, h5, h6 { font-size: 0.9em; font-weight: 600; padding: 8px 16px; - border-bottom: 2px solid transparent; - - /* put the border on top of the parent border */ - margin-bottom: -2px; + border-bottom: 2px solid #ccc; } .tabs-header button:focus-visible { /* preserve an outline for accesibility purposes */ outline: 1px solid currentColor; + outline-offset: -1px; } /* Change background color of buttons on hover */ @@ -153,4 +169,53 @@ h1, h2, h3, h4, h5, h6 { background-color: #f1f6fa; padding: 1px 1em; 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; } diff --git a/docs/extra.js b/docs/extra.js index 9ef194fa..23bed98c 100644 --- a/docs/extra.js +++ b/docs/extra.js @@ -20,6 +20,17 @@ while (true) { tabContainer.insertBefore(firstTab); 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) { headerContainer.children().removeClass('active') headerContainer.children().eq(index).addClass('active') diff --git a/docs/options.md b/docs/options.md index f0afc6e1..832a8fc4 100644 --- a/docs/options.md +++ b/docs/options.md @@ -1,15 +1,12 @@ -## Options summary - -
- ## Setting options -cibuildwheel is configured using environment variables that can be set using -your CI config. +cibuildwheel can either be configured using environment variables, or from +config file such as `pyproject.toml`. -For example, to configure cibuildwheel to run tests, add the following YAML to -your CI config file: +### Environment variables {: #environment-variables} +Environment variables can be set in your CI config. For example, to configure +cibuildwheel to run tests, add the following YAML to your CI config file: !!! tab "GitHub Actions" @@ -76,7 +73,43 @@ your CI config file: CIBW_TEST_COMMAND: "pytest {project}/tests" ``` +### Configuration file {: #configuration-file} +You can configure cibuildwheel with a config file, such as `pyproject.toml`. +Options have the same names as the environment variable overrides, but are +placed in `[tool.cibuildwheel]` and are lower case, with dashes, following +common [TOML][] practice. Anything placed in subsections `linux`, `windows`, +or `macos` will only affect those platforms. Lists can be used instead of +strings for items that are natually a list. Multiline strings also work just +like in in the environment variables. Environment variables will take +precedence if defined. + +The example above using environment variables could have been written like this: + +```toml +[tool.cibuildwheel] +test-requires = "pytest" +test-command = "pytest {project}/tests" +``` + +The complete set of defaults for the current version of cibuildwheel are shown below: + +```toml +{% include "../cibuildwheel/resources/defaults.toml" %} +``` + + +!!! tip + Static configuration works across all CI systems, and can be used locally if + you run `cibuildwheel --plat linux`. This is preferred, but environment + variables are better if you need to change per-matrix element + (`CIBW_BUILD` is often in this category, for example), or if you cannot or do + not want to change a `pyproject.toml` file. You can specify a different file to + use with `--config-file` on the command line, as well. + +## Options summary + +
## Build selection @@ -94,7 +127,7 @@ Default: `auto` - For `macos`, you need a Mac machine. Note that cibuildwheel is going to install MacPython on your system, so you probably don't want to run this on your development machine. - For `windows`, you need to run in Windows. cibuildwheel will install required versions of Python to `C:\cibw\python` using NuGet. -This option can also be set using the [command-line option](#command-line) `--platform`. +This option can also be set using the [command-line option](#command-line) `--platform`. This option is not available in the `pyproject.toml` config. !!! tip If you have Docker installed, you can locally debug your cibuildwheel Linux config, instead of pushing to CI to test every change. For example: @@ -110,7 +143,7 @@ This option can also be set using the [command-line option](#command-line) `--pl > Choose the Python versions to build -Space-separated list of builds to build and skip. Each build has an identifier like `cp38-manylinux_x86_64` or `cp37-macosx_x86_64` - you can list specific ones to build and cibuildwheel will only build those, and/or list ones to skip and cibuildwheel won't try to build them. +List of builds to build and skip. Each build has an identifier like `cp38-manylinux_x86_64` or `cp37-macosx_x86_64` - you can list specific ones to build and cibuildwheel will only build those, and/or list ones to skip and cibuildwheel won't try to build them. When both options are specified, both conditions are applied and only builds with a tag that matches `CIBW_BUILD` and does not match `CIBW_SKIP` will be built. @@ -136,37 +169,80 @@ See the [cibuildwheel 1 documentation](https://cibuildwheel.readthedocs.io/en/1. #### Examples -```yaml -# Only build on CPython 3.6 -CIBW_BUILD: cp36-* +!!! tab examples "Environment variables" -# Skip building on CPython 3.6 on the Mac -CIBW_SKIP: cp36-macosx_x86_64 + ```yaml + # Only build on CPython 3.6 + CIBW_BUILD: cp36-* -# Skip building on CPython 3.8 on the Mac -CIBW_SKIP: cp38-macosx_x86_64 + # Skip building on CPython 3.6 on the Mac + CIBW_SKIP: cp36-macosx_x86_64 -# Skip building on CPython 3.6 on all platforms -CIBW_SKIP: cp36-* + # Skip building on CPython 3.8 on the Mac + CIBW_SKIP: cp38-macosx_x86_64 -# Skip CPython 3.6 on Windows -CIBW_SKIP: cp36-win* + # Skip building on CPython 3.6 on all platforms + CIBW_SKIP: cp36-* -# Skip CPython 3.6 on 32-bit Windows -CIBW_SKIP: cp36-win32 + # Skip CPython 3.6 on Windows + CIBW_SKIP: cp36-win* -# Skip CPython 3.6 and CPython 3.7 -CIBW_SKIP: cp36-* cp37-* + # Skip CPython 3.6 on 32-bit Windows + CIBW_SKIP: cp36-win32 -# Skip Python 3.6 on Linux -CIBW_SKIP: cp36-manylinux* + # Skip CPython 3.6 and CPython 3.7 + CIBW_SKIP: cp36-* cp37-* -# Skip 32-bit builds -CIBW_SKIP: "*-win32 *-manylinux_i686" + # Skip Python 3.6 on Linux + CIBW_SKIP: cp36-manylinux* -# Disable building PyPy wheels on all platforms -CIBW_SKIP: pp* -``` + # Skip 32-bit builds + CIBW_SKIP: "*-win32 *-manylinux_i686" + + # Disable building PyPy wheels on all platforms + CIBW_SKIP: pp* + ``` + + Separate multiple selectors with a space. + +!!! tab examples "pyproject.toml" + + ```toml + [tool.cibuildwheel] + # Only build on CPython 3.6 + build = "cp36-*" + + # Skip building on CPython 3.6 on the Mac + skip = "cp36-macosx_x86_64" + + # Skip building on CPython 3.8 on the Mac + skip = "cp38-macosx_x86_64" + + # Skip building on CPython 3.6 on all platforms + skip = "cp36-*" + + # Skip CPython 3.6 on Windows + skip = "cp36-win*" + + # Skip CPython 3.6 on 32-bit Windows + skip = "cp36-win32" + + # Skip CPython 3.6 and CPython 3.7 + skip = ["cp36-*", "cp37-*"] + + # Skip Python 3.6 on Linux + skip = "cp36-manylinux*" + + # Skip 32-bit builds + skip = ["*-win32", "*-manylinux_i686"] + + # Disable building PyPy wheels on all platforms + skip = "pp*" + ``` + + It is generally recommened to set `CIBW_BUILD` as an environment variable, though `skip` + tends to be useful in a config file; you can statically declare that you don't + support pypy, for example.