Merge pull request #835 from henryiii/henryiii/refactor/prepover

refactor: better code factorization
This commit is contained in:
Matthieu Darbois
2021-09-25 11:24:28 +02:00
committed by GitHub
5 changed files with 514 additions and 441 deletions
+2 -3
View File
@@ -16,10 +16,9 @@ if __name__ == "__main__":
unit_test_args += ["--run-docker"] unit_test_args += ["--run-docker"]
subprocess.run(unit_test_args, check=True) subprocess.run(unit_test_args, check=True)
xdist_test_args = ["-n", "2"] if sys.platform.startswith("linux") else []
# run the integration tests # run the integration tests
xdist_test_args = []
if sys.platform.startswith("linux"):
xdist_test_args = ["-n", "2"]
subprocess.run( subprocess.run(
[ [
sys.executable, sys.executable,
+14 -205
View File
@@ -2,12 +2,8 @@ import argparse
import os import os
import sys import sys
import textwrap import textwrap
import traceback
from configparser import ConfigParser
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Set, Union from typing import List, Set, Union
from packaging.specifiers import SpecifierSet
import cibuildwheel import cibuildwheel
import cibuildwheel.linux import cibuildwheel.linux
@@ -15,38 +11,13 @@ import cibuildwheel.macos
import cibuildwheel.util 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.options import compute_options
from cibuildwheel.options import ConfigOptions
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 (
BuildFrontend,
BuildOptions, BuildOptions,
BuildSelector, BuildSelector,
DependencyConstraints,
TestSelector,
Unbuffered, Unbuffered,
detect_ci_provider, detect_ci_provider,
resources_dir,
)
MANYLINUX_ARCHS = (
"x86_64",
"i686",
"pypy_x86_64",
"aarch64",
"ppc64le",
"s390x",
"pypy_aarch64",
"pypy_i686",
)
MUSLLINUX_ARCHS = (
"x86_64",
"i686",
"aarch64",
"ppc64le",
"s390x",
) )
@@ -171,184 +142,28 @@ def main() -> None:
sys.exit(2) sys.exit(2)
package_dir = Path(args.package_dir) package_dir = Path(args.package_dir)
manylinux_identifiers = {
f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS
}
musllinux_identifiers = {
f"musllinux-{build_platform}-image" for build_platform in MUSLLINUX_ARCHS
}
disallow = {
"linux": {"dependency-versions"},
"macos": manylinux_identifiers | musllinux_identifiers,
"windows": manylinux_identifiers | musllinux_identifiers,
}
options = ConfigOptions(package_dir, args.config_file, platform=platform, disallow=disallow)
output_dir = Path( output_dir = Path(
args.output_dir args.output_dir
if args.output_dir is not None if args.output_dir is not None
else os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse") else os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse")
) )
build_config = options("build", env_plat=False, sep=" ") or "*" build_options = compute_options(
skip_config = options("skip", env_plat=False, sep=" ") platform, package_dir, output_dir, args.config_file, args.archs, args.prerelease_pythons
test_skip = options("test-skip", env_plat=False, sep=" ")
archs_config_str = args.archs or options("archs", sep=" ")
build_frontend_str = options("build-frontend", env_plat=False)
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")
prerelease_pythons = args.prerelease_pythons or cibuildwheel.util.strtobool(
os.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
) )
build_frontend: BuildFrontend identifiers = get_build_identifiers(
if build_frontend_str == "build": platform, build_options.build_selector, build_options.architectures
build_frontend = "build"
elif build_frontend_str == "pip":
build_frontend = "pip"
else:
msg = f"cibuildwheel: Unrecognised build frontend '{build_frontend}', only 'pip' and 'build' are supported"
print(msg, file=sys.stderr)
sys.exit(2)
package_files = {"setup.py", "setup.cfg", "pyproject.toml"}
if not any(package_dir.joinpath(name).exists() for name in package_files):
names = ", ".join(sorted(package_files, reverse=True))
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"
) or get_requires_python_str(package_dir)
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
deprecated_selectors("CIBW_BUILD", build_config, error=True)
deprecated_selectors("CIBW_SKIP", skip_config)
deprecated_selectors("CIBW_TEST_SKIP", test_skip)
build_selector = BuildSelector(
build_config=build_config,
skip_config=skip_config,
requires_python=requires_python,
prerelease_pythons=prerelease_pythons,
) )
test_selector = TestSelector(skip_config=test_skip)
try:
environment = parse_environment(environment_config)
except (EnvironmentParseError, ValueError):
print(f'cibuildwheel: Malformed environment option "{environment_config}"', file=sys.stderr)
traceback.print_exc(None, sys.stderr)
sys.exit(2)
if dependency_versions == "pinned":
dependency_constraints: Optional[
DependencyConstraints
] = DependencyConstraints.with_defaults()
elif dependency_versions == "latest":
dependency_constraints = None
else:
dependency_versions_path = Path(dependency_versions)
dependency_constraints = DependencyConstraints(dependency_versions_path)
if test_extras:
test_extras = f"[{test_extras}]"
try:
build_verbosity = min(3, max(-3, int(build_verbosity_str)))
except ValueError:
build_verbosity = 0
# Add CIBUILDWHEEL environment variable
# This needs to be passed on to the docker container in linux.py
os.environ["CIBUILDWHEEL"] = "1"
archs = Architecture.parse_config(archs_config_str, platform=platform)
identifiers = get_build_identifiers(platform, build_selector, archs)
if args.print_build_identifiers: if args.print_build_identifiers:
for identifier in identifiers: for identifier in identifiers:
print(identifier) print(identifier)
sys.exit(0) sys.exit(0)
manylinux_images: Dict[str, str] = {} # Add CIBUILDWHEEL environment variable
musllinux_images: Dict[str, str] = {} # This needs to be passed on to the docker container in linux.py
if platform == "linux": os.environ["CIBUILDWHEEL"] = "1"
pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg"
all_pinned_docker_images = ConfigParser()
all_pinned_docker_images.read(pinned_docker_images_file)
# all_pinned_docker_images looks like a dict of dicts, e.g.
# { 'x86_64': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
# 'i686': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
# 'pypy_x86_64': {'manylinux2010': '...' }
# ... }
for build_platform in MANYLINUX_ARCHS:
pinned_images = all_pinned_docker_images[build_platform]
config_value = options(f"manylinux-{build_platform}-image", ignore_empty=True)
if not config_value:
# default to manylinux2010 if it's available, otherwise manylinux2014
image = pinned_images.get("manylinux2010") or pinned_images.get("manylinux2014")
elif config_value in pinned_images:
image = pinned_images[config_value]
else:
image = config_value
manylinux_images[build_platform] = image
for build_platform in MUSLLINUX_ARCHS:
pinned_images = all_pinned_docker_images[build_platform]
config_value = options(f"musllinux-{build_platform}-image")
if config_value is None:
image = pinned_images.get("musllinux_1_1")
elif config_value in pinned_images:
image = pinned_images[config_value]
else:
image = config_value
musllinux_images[build_platform] = image
build_options = BuildOptions(
architectures=archs,
package_dir=package_dir,
output_dir=output_dir,
test_command=test_command,
test_requires=test_requires,
test_extras=test_extras,
before_test=before_test,
before_build=before_build,
before_all=before_all,
build_verbosity=build_verbosity,
build_selector=build_selector,
test_selector=test_selector,
repair_command=repair_command,
environment=environment,
dependency_constraints=dependency_constraints,
manylinux_images=manylinux_images or None,
musllinux_images=musllinux_images or None,
build_frontend=build_frontend,
)
# 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'
sys.stdout = Unbuffered(sys.stdout) # type: ignore sys.stdout = Unbuffered(sys.stdout) # type: ignore
@@ -362,7 +177,10 @@ def main() -> None:
sys.exit(4) sys.exit(4)
if not identifiers: if not identifiers:
print(f"cibuildwheel: No build identifiers selected: {build_selector}", file=sys.stderr) print(
f"cibuildwheel: No build identifiers selected: {build_options.build_selector}",
file=sys.stderr,
)
if not args.allow_empty: if not args.allow_empty:
sys.exit(3) sys.exit(3)
@@ -382,14 +200,6 @@ def main() -> None:
assert_never(platform) assert_never(platform)
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
if "p2" in selector or "p35" in selector:
msg = f"cibuildwheel 2.x no longer supports Python < 3.6. Please use the 1.x series or update {name}"
print(msg, file=sys.stderr)
if error:
sys.exit(4)
def print_preamble(platform: str, build_options: BuildOptions) -> None: def print_preamble(platform: str, build_options: BuildOptions) -> None:
print( print(
textwrap.dedent( textwrap.dedent(
@@ -406,8 +216,7 @@ def print_preamble(platform: str, build_options: BuildOptions) -> None:
print("Build options:") print("Build options:")
print(f" platform: {platform!r}") print(f" platform: {platform!r}")
for option, value in sorted(build_options._asdict().items()): print(textwrap.indent(str(build_options), " "))
print(f" {option}: {value!r}")
warnings = detect_warnings(platform, build_options) warnings = detect_warnings(platform, build_options)
if warnings: if warnings:
+96 -73
View File
@@ -2,7 +2,7 @@ import subprocess
import sys import sys
import textwrap import textwrap
from pathlib import Path, PurePath from pathlib import Path, PurePath
from typing import List, NamedTuple, Set from typing import Iterator, List, NamedTuple, Set
from .architecture import Architecture from .architecture import Architecture
from .docker_container import DockerContainer from .docker_container import DockerContainer
@@ -28,6 +28,12 @@ class PythonConfiguration(NamedTuple):
return PurePath(self.path_str) return PurePath(self.path_str)
class BuildStep(NamedTuple):
platform_configs: List[PythonConfiguration]
platform_tag: str
docker_image: str
def get_python_configurations( def get_python_configurations(
build_selector: BuildSelector, build_selector: BuildSelector,
architectures: Set[Architecture], architectures: Set[Architecture],
@@ -47,48 +53,35 @@ def get_python_configurations(
] ]
def build(options: BuildOptions) -> None: def get_build_steps(
try: options: BuildOptions, python_configurations: List[PythonConfiguration]
# check docker is installed ) -> Iterator[BuildStep]:
subprocess.run(["docker", "--version"], check=True, stdout=subprocess.DEVNULL)
except Exception:
print(
"cibuildwheel: Docker not found. Docker is required to run Linux builds. "
"If you're building on Travis CI, add `services: [docker]` to your .travis.yml."
"If you're building on Circle CI in Linux, add a `setup_remote_docker` step to your .circleci/config.yml",
file=sys.stderr,
)
sys.exit(2)
assert options.manylinux_images is not None
assert options.musllinux_images is not None
python_configurations = get_python_configurations(options.build_selector, options.architectures)
platforms = [ platforms = [
("cp", "manylinux_x86_64", options.manylinux_images["x86_64"]), ("cp", "manylinux_x86_64", "x86_64"),
("cp", "manylinux_i686", options.manylinux_images["i686"]), ("cp", "manylinux_i686", "i686"),
("cp", "manylinux_aarch64", options.manylinux_images["aarch64"]), ("cp", "manylinux_aarch64", "aarch64"),
("cp", "manylinux_ppc64le", options.manylinux_images["ppc64le"]), ("cp", "manylinux_ppc64le", "ppc64le"),
("cp", "manylinux_s390x", options.manylinux_images["s390x"]), ("cp", "manylinux_s390x", "s390x"),
("pp", "manylinux_x86_64", options.manylinux_images["pypy_x86_64"]), ("pp", "manylinux_x86_64", "pypy_x86_64"),
("pp", "manylinux_aarch64", options.manylinux_images["pypy_aarch64"]), ("pp", "manylinux_aarch64", "pypy_aarch64"),
("pp", "manylinux_i686", options.manylinux_images["pypy_i686"]), ("pp", "manylinux_i686", "pypy_i686"),
("cp", "musllinux_x86_64", options.musllinux_images["x86_64"]), ("cp", "musllinux_x86_64", "x86_64"),
("cp", "musllinux_i686", options.musllinux_images["i686"]), ("cp", "musllinux_i686", "i686"),
("cp", "musllinux_aarch64", options.musllinux_images["aarch64"]), ("cp", "musllinux_aarch64", "aarch64"),
("cp", "musllinux_ppc64le", options.musllinux_images["ppc64le"]), ("cp", "musllinux_ppc64le", "ppc64le"),
("cp", "musllinux_s390x", options.musllinux_images["s390x"]), ("cp", "musllinux_s390x", "s390x"),
] ]
cwd = Path.cwd() for implementation, platform_tag, platform_arch in platforms:
abs_package_dir = options.package_dir.resolve() assert options.manylinux_images is not None
if cwd != abs_package_dir and cwd not in abs_package_dir.parents: assert options.musllinux_images is not None
raise Exception("package_dir must be inside the working directory")
container_project_path = PurePath("/project") docker_image = (
container_package_dir = container_project_path / abs_package_dir.relative_to(cwd) options.manylinux_images[platform_arch]
container_output_dir = PurePath("/output") if platform_tag.startswith("manylinux")
else options.musllinux_images[platform_arch]
)
for implementation, platform_tag, docker_image in platforms:
platform_configs = [ platform_configs = [
c c
for c in python_configurations for c in python_configurations
@@ -97,13 +90,17 @@ def build(options: BuildOptions) -> None:
if not platform_configs: if not platform_configs:
continue continue
try: yield BuildStep(platform_configs, platform_tag, docker_image)
log.step(f"Starting Docker image {docker_image}...")
with DockerContainer(
docker_image, def build_on_docker(
simulate_32_bit=platform_tag.endswith("i686"), options: BuildOptions,
cwd=container_project_path, platform_configs: List[PythonConfiguration],
) as docker: docker: DockerContainer,
container_project_path: PurePath,
container_package_dir: PurePath,
) -> None:
container_output_dir = PurePath("/output")
log.step("Copying project into Docker...") log.step("Copying project into Docker...")
docker.copy_into(Path.cwd(), container_project_path) docker.copy_into(Path.cwd(), container_project_path)
@@ -114,9 +111,7 @@ def build(options: BuildOptions) -> None:
env = docker.get_environment() env = docker.get_environment()
env["PATH"] = f'/opt/python/cp38-cp38/bin:{env["PATH"]}' env["PATH"] = f'/opt/python/cp38-cp38/bin:{env["PATH"]}'
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
env = options.environment.as_dictionary( env = options.environment.as_dictionary(env, executor=docker.environment_executor)
env, executor=docker.environment_executor
)
before_all_prepared = prepare_command( before_all_prepared = prepare_command(
options.before_all, options.before_all,
@@ -131,9 +126,7 @@ def build(options: BuildOptions) -> None:
dependency_constraint_flags: List[PathOrStr] = [] dependency_constraint_flags: List[PathOrStr] = []
if options.dependency_constraints: if options.dependency_constraints:
constraints_file = options.dependency_constraints.get_for_python_version( constraints_file = options.dependency_constraints.get_for_python_version(config.version)
config.version
)
container_constraints_file = PurePath("/constraints.txt") container_constraints_file = PurePath("/constraints.txt")
docker.copy_into(constraints_file, container_constraints_file) docker.copy_into(constraints_file, container_constraints_file)
@@ -147,14 +140,10 @@ def build(options: BuildOptions) -> None:
python_bin = config.path / "bin" python_bin = config.path / "bin"
env["PATH"] = f'{python_bin}:{env["PATH"]}' env["PATH"] = f'{python_bin}:{env["PATH"]}'
env = options.environment.as_dictionary( env = options.environment.as_dictionary(env, executor=docker.environment_executor)
env, executor=docker.environment_executor
)
# check config python is still on PATH # check config python is still on PATH
which_python = docker.call( which_python = docker.call(["which", "python"], env=env, capture_output=True).strip()
["which", "python"], env=env, capture_output=True
).strip()
if PurePath(which_python) != python_bin / "python": if PurePath(which_python) != python_bin / "python":
print( print(
"cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.", "cibuildwheel: python available on PATH doesn't match our installed instance. If you have modified PATH, ensure that you don't overwrite cibuildwheel's entry or insert python above it.",
@@ -244,17 +233,10 @@ def build(options: BuildOptions) -> None:
# set up a virtual environment to install and test from, to make sure # set up a virtual environment to install and test from, to make sure
# there are no dependencies that were pulled in at build time. # there are no dependencies that were pulled in at build time.
docker.call( docker.call(["pip", "install", "virtualenv", *dependency_constraint_flags], env=env)
["pip", "install", "virtualenv", *dependency_constraint_flags], env=env venv_dir = PurePath(docker.call(["mktemp", "-d"], capture_output=True).strip()) / "venv"
)
venv_dir = (
PurePath(docker.call(["mktemp", "-d"], capture_output=True).strip())
/ "venv"
)
docker.call( docker.call(["python", "-m", "virtualenv", "--no-download", venv_dir], env=env)
["python", "-m", "virtualenv", "--no-download", venv_dir], env=env
)
virtualenv_env = env.copy() virtualenv_env = env.copy()
virtualenv_env["PATH"] = f"{venv_dir / 'bin'}:{virtualenv_env['PATH']}" virtualenv_env["PATH"] = f"{venv_dir / 'bin'}:{virtualenv_env['PATH']}"
@@ -281,9 +263,7 @@ def build(options: BuildOptions) -> None:
# Install any requirements to run the tests # Install any requirements to run the tests
if options.test_requires: if options.test_requires:
docker.call( docker.call(["pip", "install", *options.test_requires], env=virtualenv_env)
["pip", "install", *options.test_requires], env=virtualenv_env
)
# Run the tests from a different directory # Run the tests from a different directory
test_command_prepared = prepare_command( test_command_prepared = prepare_command(
@@ -291,9 +271,7 @@ def build(options: BuildOptions) -> None:
project=container_project_path, project=container_project_path,
package=container_package_dir, package=container_package_dir,
) )
docker.call( docker.call(["sh", "-c", test_command_prepared], cwd="/root", env=virtualenv_env)
["sh", "-c", test_command_prepared], cwd="/root", env=virtualenv_env
)
# clean up test environment # clean up test environment
docker.call(["rm", "-rf", venv_dir]) docker.call(["rm", "-rf", venv_dir])
@@ -308,6 +286,51 @@ def build(options: BuildOptions) -> None:
# copy the output back into the host # copy the output back into the host
docker.copy_out(container_output_dir, options.output_dir) docker.copy_out(container_output_dir, options.output_dir)
log.step_end() log.step_end()
def build(options: BuildOptions) -> None:
try:
# check docker is installed
subprocess.run(["docker", "--version"], check=True, stdout=subprocess.DEVNULL)
except Exception:
print(
"cibuildwheel: Docker not found. Docker is required to run Linux builds. "
"If you're building on Travis CI, add `services: [docker]` to your .travis.yml."
"If you're building on Circle CI in Linux, add a `setup_remote_docker` step to your .circleci/config.yml",
file=sys.stderr,
)
sys.exit(2)
assert options.manylinux_images is not None
assert options.musllinux_images is not None
python_configurations = get_python_configurations(options.build_selector, options.architectures)
cwd = Path.cwd()
abs_package_dir = options.package_dir.resolve()
if cwd != abs_package_dir and cwd not in abs_package_dir.parents:
raise Exception("package_dir must be inside the working directory")
container_project_path = PurePath("/project")
container_package_dir = container_project_path / abs_package_dir.relative_to(cwd)
for build_step in get_build_steps(options, python_configurations):
try:
log.step(f"Starting Docker image {build_step.docker_image}...")
with DockerContainer(
build_step.docker_image,
simulate_32_bit=build_step.platform_tag.endswith("i686"),
cwd=container_project_path,
) as docker:
build_on_docker(
options,
build_step.platform_configs,
docker,
container_project_path,
container_package_dir,
)
except subprocess.CalledProcessError as error: except subprocess.CalledProcessError as error:
log.step_end_with_error( log.step_end_with_error(
f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}" f"Command {error.cmd} failed with code {error.returncode}. {error.stdout}"
+221 -2
View File
@@ -1,11 +1,28 @@
import os import os
import sys
import traceback
from configparser import ConfigParser
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union
import toml import toml
from packaging.specifiers import SpecifierSet
from .typing import PLATFORMS, TypedDict from .architecture import Architecture
from .util import resources_dir from .environment import EnvironmentParseError, parse_environment
from .projectfiles import get_requires_python_str
from .typing import PLATFORMS, PlatformName, TypedDict
from .util import (
MANYLINUX_ARCHS,
MUSLLINUX_ARCHS,
BuildFrontend,
BuildOptions,
BuildSelector,
DependencyConstraints,
TestSelector,
resources_dir,
strtobool,
)
Setting = Union[Dict[str, str], List[str], str] Setting = Union[Dict[str, str], List[str], str]
@@ -183,3 +200,205 @@ class ConfigOptions:
return str(result) return str(result)
else: else:
return result return result
def compute_options(
platform: PlatformName,
package_dir: Path,
output_dir: Path,
config_file: Optional[str],
args_archs: Optional[str],
prerelease_pythons: bool,
) -> BuildOptions:
"""
Compute the options from the environment and configuration file.
"""
manylinux_identifiers = {
f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS
}
musllinux_identifiers = {
f"musllinux-{build_platform}-image" for build_platform in MUSLLINUX_ARCHS
}
disallow = {
"linux": {"dependency-versions"},
"macos": manylinux_identifiers | musllinux_identifiers,
"windows": manylinux_identifiers | musllinux_identifiers,
}
options = ConfigOptions(package_dir, config_file, platform=platform, disallow=disallow)
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=" ")
prerelease_pythons = prerelease_pythons or strtobool(
os.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
)
deprecated_selectors("CIBW_BUILD", build_config, error=True)
deprecated_selectors("CIBW_SKIP", skip_config)
deprecated_selectors("CIBW_TEST_SKIP", test_skip)
package_files = {"setup.py", "setup.cfg", "pyproject.toml"}
if not any(package_dir.joinpath(name).exists() for name in package_files):
names = ", ".join(sorted(package_files, reverse=True))
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"
) or get_requires_python_str(package_dir)
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
build_selector = BuildSelector(
build_config=build_config,
skip_config=skip_config,
requires_python=requires_python,
prerelease_pythons=prerelease_pythons,
)
test_selector = TestSelector(skip_config=test_skip)
return _compute_single_options(
options, args_archs, build_selector, test_selector, platform, package_dir, output_dir
)
def _compute_single_options(
options: ConfigOptions,
args_archs: Optional[str],
build_selector: BuildSelector,
test_selector: TestSelector,
platform: PlatformName,
package_dir: Path,
output_dir: Path,
) -> BuildOptions:
"""
Compute BuildOptions for a single run configuration.
"""
# Can't be configured per selector
before_all = options("before-all", sep=" && ")
archs_config_str = args_archs or options("archs", sep=" ")
build_frontend_str = options("build-frontend", env_plat=False)
environment_config = options("environment", table={"item": '{k}="{v}"', "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_frontend: BuildFrontend
if build_frontend_str == "build":
build_frontend = "build"
elif build_frontend_str == "pip":
build_frontend = "pip"
else:
msg = f"cibuildwheel: Unrecognised build frontend '{build_frontend}', only 'pip' and 'build' are supported"
print(msg, file=sys.stderr)
sys.exit(2)
try:
environment = parse_environment(environment_config)
except (EnvironmentParseError, ValueError):
print(f'cibuildwheel: Malformed environment option "{environment_config}"', file=sys.stderr)
traceback.print_exc(None, sys.stderr)
sys.exit(2)
if dependency_versions == "pinned":
dependency_constraints: Optional[
DependencyConstraints
] = DependencyConstraints.with_defaults()
elif dependency_versions == "latest":
dependency_constraints = None
else:
dependency_versions_path = Path(dependency_versions)
dependency_constraints = DependencyConstraints(dependency_versions_path)
if test_extras:
test_extras = f"[{test_extras}]"
try:
build_verbosity = min(3, max(-3, int(build_verbosity_str)))
except ValueError:
build_verbosity = 0
archs = Architecture.parse_config(archs_config_str, platform=platform)
manylinux_images: Dict[str, str] = {}
musllinux_images: Dict[str, str] = {}
if platform == "linux":
pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg"
all_pinned_docker_images = ConfigParser()
all_pinned_docker_images.read(pinned_docker_images_file)
# all_pinned_docker_images looks like a dict of dicts, e.g.
# { 'x86_64': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
# 'i686': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
# 'pypy_x86_64': {'manylinux2010': '...' }
# ... }
for build_platform in MANYLINUX_ARCHS:
pinned_images = all_pinned_docker_images[build_platform]
config_value = options(f"manylinux-{build_platform}-image", ignore_empty=True)
if not config_value:
# default to manylinux2010 if it's available, otherwise manylinux2014
image = pinned_images.get("manylinux2010") or pinned_images.get("manylinux2014")
elif config_value in pinned_images:
image = pinned_images[config_value]
else:
image = config_value
manylinux_images[build_platform] = image
for build_platform in MUSLLINUX_ARCHS:
pinned_images = all_pinned_docker_images[build_platform]
config_value = options(f"musllinux-{build_platform}-image")
if config_value is None:
image = pinned_images.get("musllinux_1_1")
elif config_value in pinned_images:
image = pinned_images[config_value]
else:
image = config_value
musllinux_images[build_platform] = image
return BuildOptions(
architectures=archs,
package_dir=package_dir,
output_dir=output_dir,
test_command=test_command,
test_requires=test_requires,
test_extras=test_extras,
before_test=before_test,
before_build=before_build,
before_all=before_all,
build_verbosity=build_verbosity,
build_selector=build_selector,
test_selector=test_selector,
repair_command=repair_command,
environment=environment,
dependency_constraints=dependency_constraints,
manylinux_images=manylinux_images or None,
musllinux_images=musllinux_images or None,
build_frontend=build_frontend,
)
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
if "p2" in selector or "p35" in selector:
msg = f"cibuildwheel 2.x no longer supports Python < 3.6. Please use the 1.x series or update {name}"
print(msg, file=sys.stderr)
if error:
sys.exit(4)
+23
View File
@@ -30,6 +30,25 @@ install_certifi_script = resources_dir / "install_certifi.py"
BuildFrontend = Literal["pip", "build"] BuildFrontend = Literal["pip", "build"]
MANYLINUX_ARCHS = (
"x86_64",
"i686",
"pypy_x86_64",
"aarch64",
"ppc64le",
"s390x",
"pypy_aarch64",
"pypy_i686",
)
MUSLLINUX_ARCHS = (
"x86_64",
"i686",
"aarch64",
"ppc64le",
"s390x",
)
def prepare_command(command: str, **kwargs: PathOrStr) -> str: def prepare_command(command: str, **kwargs: PathOrStr) -> str:
""" """
@@ -225,6 +244,10 @@ class BuildOptions(NamedTuple):
build_verbosity: int build_verbosity: int
build_frontend: BuildFrontend build_frontend: BuildFrontend
def __str__(self) -> str:
res = (f"{option}: {value!r}" for option, value in sorted(self._asdict().items()))
return "\n".join(res)
class NonPlatformWheelError(Exception): class NonPlatformWheelError(Exception):
def __init__(self) -> None: def __init__(self) -> None: