Merge pull request #854 from henryiii/henryiii/feat/override
This commit is contained in:
@@ -59,6 +59,7 @@ repos:
|
|||||||
- tomli
|
- tomli
|
||||||
- types-certifi
|
- types-certifi
|
||||||
- types-click
|
- types-click
|
||||||
|
- types-dataclasses
|
||||||
- types-jinja2
|
- types-jinja2
|
||||||
- types-pyyaml
|
- types-pyyaml
|
||||||
- types-requests
|
- types-requests
|
||||||
|
|||||||
+30
-31
@@ -2,7 +2,6 @@ import argparse
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
from pathlib import Path
|
|
||||||
from typing import List, Set, Union
|
from typing import List, Set, Union
|
||||||
|
|
||||||
import cibuildwheel
|
import cibuildwheel
|
||||||
@@ -11,14 +10,9 @@ 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.options import compute_options
|
from cibuildwheel.options import CommandLineArguments, Options, compute_options
|
||||||
from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
|
from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
|
||||||
from cibuildwheel.util import (
|
from cibuildwheel.util import BuildSelector, Unbuffered, detect_ci_provider
|
||||||
BuildOptions,
|
|
||||||
BuildSelector,
|
|
||||||
Unbuffered,
|
|
||||||
detect_ci_provider,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@@ -104,7 +98,7 @@ def main() -> None:
|
|||||||
help="Enable pre-release Python versions if available.",
|
help="Enable pre-release Python versions if available.",
|
||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args(namespace=CommandLineArguments())
|
||||||
|
|
||||||
if args.platform != "auto":
|
if args.platform != "auto":
|
||||||
platform = args.platform
|
platform = args.platform
|
||||||
@@ -141,19 +135,21 @@ def main() -> None:
|
|||||||
print(f"cibuildwheel: Unsupported platform: {platform}", file=sys.stderr)
|
print(f"cibuildwheel: Unsupported platform: {platform}", file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
package_dir = Path(args.package_dir)
|
options = compute_options(platform=platform, command_line_arguments=args)
|
||||||
output_dir = Path(
|
|
||||||
args.output_dir
|
|
||||||
if args.output_dir is not None
|
|
||||||
else os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse")
|
|
||||||
)
|
|
||||||
|
|
||||||
build_options = compute_options(
|
package_dir = options.globals.package_dir
|
||||||
platform, package_dir, output_dir, args.config_file, args.archs, args.prerelease_pythons
|
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)
|
||||||
|
|
||||||
identifiers = get_build_identifiers(
|
identifiers = get_build_identifiers(
|
||||||
platform, build_options.build_selector, build_options.architectures
|
platform=platform,
|
||||||
|
build_selector=options.globals.build_selector,
|
||||||
|
architectures=options.globals.architectures,
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.print_build_identifiers:
|
if args.print_build_identifiers:
|
||||||
@@ -168,22 +164,25 @@ def main() -> 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'
|
||||||
sys.stdout = Unbuffered(sys.stdout) # type: ignore[no-untyped-call,assignment]
|
sys.stdout = Unbuffered(sys.stdout) # type: ignore[no-untyped-call,assignment]
|
||||||
|
|
||||||
print_preamble(platform, build_options)
|
print_preamble(platform=platform, options=options, identifiers=identifiers)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
allowed_architectures_check(platform, build_options.architectures)
|
options.check_for_invalid_configuration(identifiers)
|
||||||
|
allowed_architectures_check(platform, options.globals.architectures)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
print("cibuildwheel:", *err.args, file=sys.stderr)
|
print("cibuildwheel:", *err.args, file=sys.stderr)
|
||||||
sys.exit(4)
|
sys.exit(4)
|
||||||
|
|
||||||
if not identifiers:
|
if not identifiers:
|
||||||
print(
|
print(
|
||||||
f"cibuildwheel: No build identifiers selected: {build_options.build_selector}",
|
f"cibuildwheel: No build identifiers selected: {options.globals.build_selector}",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
if not args.allow_empty:
|
if not args.allow_empty:
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
|
|
||||||
|
output_dir = options.globals.output_dir
|
||||||
|
|
||||||
if not output_dir.exists():
|
if not output_dir.exists():
|
||||||
output_dir.mkdir(parents=True)
|
output_dir.mkdir(parents=True)
|
||||||
|
|
||||||
@@ -191,16 +190,16 @@ def main() -> None:
|
|||||||
"\n{n} wheels produced in {m:.0f} minutes:", output_dir
|
"\n{n} wheels produced in {m:.0f} minutes:", output_dir
|
||||||
):
|
):
|
||||||
if platform == "linux":
|
if platform == "linux":
|
||||||
cibuildwheel.linux.build(build_options)
|
cibuildwheel.linux.build(options)
|
||||||
elif platform == "windows":
|
elif platform == "windows":
|
||||||
cibuildwheel.windows.build(build_options)
|
cibuildwheel.windows.build(options)
|
||||||
elif platform == "macos":
|
elif platform == "macos":
|
||||||
cibuildwheel.macos.build(build_options)
|
cibuildwheel.macos.build(options)
|
||||||
else:
|
else:
|
||||||
assert_never(platform)
|
assert_never(platform)
|
||||||
|
|
||||||
|
|
||||||
def print_preamble(platform: str, build_options: BuildOptions) -> None:
|
def print_preamble(platform: str, options: Options, identifiers: List[str]) -> None:
|
||||||
print(
|
print(
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
@@ -216,9 +215,9 @@ 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}")
|
||||||
print(textwrap.indent(str(build_options), " "))
|
print(textwrap.indent(options.summary(identifiers), " "))
|
||||||
|
|
||||||
warnings = detect_warnings(platform, build_options)
|
warnings = detect_warnings(platform=platform, options=options, identifiers=identifiers)
|
||||||
if warnings:
|
if warnings:
|
||||||
print("\nWarnings:")
|
print("\nWarnings:")
|
||||||
for warning in warnings:
|
for warning in warnings:
|
||||||
@@ -254,14 +253,14 @@ def get_build_identifiers(
|
|||||||
return [config.identifier for config in python_configurations]
|
return [config.identifier for config in python_configurations]
|
||||||
|
|
||||||
|
|
||||||
def detect_warnings(platform: str, build_options: BuildOptions) -> List[str]:
|
def detect_warnings(platform: str, options: Options, identifiers: List[str]) -> List[str]:
|
||||||
warnings = []
|
warnings = []
|
||||||
|
|
||||||
# warn about deprecated {python} and {pip}
|
# warn about deprecated {python} and {pip}
|
||||||
for option_name in ["test_command", "before_build"]:
|
for option_name in ["test_command", "before_build"]:
|
||||||
option_value = getattr(build_options, option_name)
|
option_values = [getattr(options.build_options(i), option_name) for i in identifiers]
|
||||||
|
|
||||||
if option_value and ("{python}" in option_value or "{pip}" in option_value):
|
if any(o and ("{python}" in o or "{pip}" in o) for o in option_values):
|
||||||
# Reminder: in an f-string, double braces means literal single brace
|
# Reminder: in an f-string, double braces means literal single brace
|
||||||
msg = (
|
msg = (
|
||||||
f"{option_name}: '{{python}}' and '{{pip}}' are no longer needed, "
|
f"{option_name}: '{{python}}' and '{{pip}}' are no longer needed, "
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class DockerContainer:
|
|||||||
An object that represents a running Docker container.
|
An object that represents a running Docker container.
|
||||||
|
|
||||||
Intended for use as a context manager e.g.
|
Intended for use as a context manager e.g.
|
||||||
`with DockerContainer('ubuntu') as docker:`
|
`with DockerContainer(docker_image = 'ubuntu') as docker:`
|
||||||
|
|
||||||
A bash shell is running in the remote container. When `call()` is invoked,
|
A bash shell is running in the remote container. When `call()` is invoked,
|
||||||
the command is relayed to the remote shell, and the results are streamed
|
the command is relayed to the remote shell, and the results are streamed
|
||||||
@@ -31,7 +31,7 @@ class DockerContainer:
|
|||||||
bash_stdout: IO[bytes]
|
bash_stdout: IO[bytes]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, docker_image: str, simulate_32_bit: bool = False, cwd: Optional[PathOrStr] = None
|
self, *, docker_image: str, simulate_32_bit: bool = False, cwd: Optional[PathOrStr] = None
|
||||||
):
|
):
|
||||||
if not docker_image:
|
if not docker_image:
|
||||||
raise ValueError("Must have a non-empty docker image to run.")
|
raise ValueError("Must have a non-empty docker image to run.")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import dataclasses
|
||||||
from typing import Dict, List, Mapping, Optional
|
from typing import Dict, List, Mapping, Optional
|
||||||
|
|
||||||
import bashlex
|
import bashlex
|
||||||
@@ -60,10 +61,15 @@ class EnvironmentAssignment:
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self.name}={self.value}"
|
return f"{self.name}={self.value}"
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if isinstance(other, EnvironmentAssignment):
|
||||||
|
return self.name == other.name and self.value == other.value
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
class ParsedEnvironment:
|
class ParsedEnvironment:
|
||||||
def __init__(self, assignments: List[EnvironmentAssignment]):
|
assignments: List[EnvironmentAssignment]
|
||||||
self.assignments = assignments
|
|
||||||
|
|
||||||
def as_dictionary(
|
def as_dictionary(
|
||||||
self,
|
self,
|
||||||
@@ -82,7 +88,7 @@ class ParsedEnvironment:
|
|||||||
return [a.as_shell_assignment() for a in self.assignments]
|
return [a.as_shell_assignment() for a in self.assignments]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"ParsedEnvironment({[repr(a) for a in self.assignments]!r})"
|
return f"{self.__class__.__name__}({[repr(a) for a in self.assignments]!r})"
|
||||||
|
|
||||||
|
|
||||||
def parse_environment(env_string: str) -> ParsedEnvironment:
|
def parse_environment(env_string: str) -> ParsedEnvironment:
|
||||||
|
|||||||
+93
-72
@@ -1,15 +1,16 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
from collections import OrderedDict
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
from typing import Iterator, 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
|
||||||
from .logger import log
|
from .logger import log
|
||||||
|
from .options import Options
|
||||||
from .typing import PathOrStr, assert_never
|
from .typing import PathOrStr, assert_never
|
||||||
from .util import (
|
from .util import (
|
||||||
BuildOptions,
|
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
NonPlatformWheelError,
|
NonPlatformWheelError,
|
||||||
get_build_verbosity_extra_flags,
|
get_build_verbosity_extra_flags,
|
||||||
@@ -53,48 +54,55 @@ def get_python_configurations(
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_build_steps(
|
def docker_image_for_python_configuration(config: PythonConfiguration, options: Options) -> str:
|
||||||
options: BuildOptions, python_configurations: List[PythonConfiguration]
|
build_options = options.build_options(config.identifier)
|
||||||
) -> Iterator[BuildStep]:
|
# e.g
|
||||||
platforms = [
|
# identifier is 'cp310-manylinux_x86_64'
|
||||||
("cp", "manylinux_x86_64", "x86_64"),
|
# platform_tag is 'manylinux_x86_64'
|
||||||
("cp", "manylinux_i686", "i686"),
|
# platform_arch is 'x86_64'
|
||||||
("cp", "manylinux_aarch64", "aarch64"),
|
_, platform_tag = config.identifier.split("-", 1)
|
||||||
("cp", "manylinux_ppc64le", "ppc64le"),
|
_, platform_arch = platform_tag.split("_", 1)
|
||||||
("cp", "manylinux_s390x", "s390x"),
|
|
||||||
("pp", "manylinux_x86_64", "pypy_x86_64"),
|
|
||||||
("pp", "manylinux_aarch64", "pypy_aarch64"),
|
|
||||||
("pp", "manylinux_i686", "pypy_i686"),
|
|
||||||
("cp", "musllinux_x86_64", "x86_64"),
|
|
||||||
("cp", "musllinux_i686", "i686"),
|
|
||||||
("cp", "musllinux_aarch64", "aarch64"),
|
|
||||||
("cp", "musllinux_ppc64le", "ppc64le"),
|
|
||||||
("cp", "musllinux_s390x", "s390x"),
|
|
||||||
]
|
|
||||||
|
|
||||||
for implementation, platform_tag, platform_arch in platforms:
|
assert build_options.manylinux_images is not None
|
||||||
assert options.manylinux_images is not None
|
assert build_options.musllinux_images is not None
|
||||||
assert options.musllinux_images is not None
|
|
||||||
|
|
||||||
docker_image = (
|
return (
|
||||||
options.manylinux_images[platform_arch]
|
build_options.manylinux_images[platform_arch]
|
||||||
if platform_tag.startswith("manylinux")
|
if platform_tag.startswith("manylinux")
|
||||||
else options.musllinux_images[platform_arch]
|
else build_options.musllinux_images[platform_arch]
|
||||||
)
|
)
|
||||||
|
|
||||||
platform_configs = [
|
|
||||||
c
|
|
||||||
for c in python_configurations
|
|
||||||
if c.identifier.startswith(implementation) and c.identifier.endswith(platform_tag)
|
|
||||||
]
|
|
||||||
if not platform_configs:
|
|
||||||
continue
|
|
||||||
|
|
||||||
yield BuildStep(platform_configs, platform_tag, docker_image)
|
def get_build_steps(
|
||||||
|
options: Options, python_configurations: List[PythonConfiguration]
|
||||||
|
) -> Iterator[BuildStep]:
|
||||||
|
"""
|
||||||
|
Groups PythonConfigurations into BuildSteps. Each BuildStep represents a
|
||||||
|
separate Docker container.
|
||||||
|
"""
|
||||||
|
steps: OrderedDict[tuple, BuildStep] = OrderedDict() # type: ignore[type-arg]
|
||||||
|
|
||||||
|
for config in python_configurations:
|
||||||
|
_, platform_tag = config.identifier.split("-", 1)
|
||||||
|
|
||||||
|
before_all = options.build_options(config.identifier).before_all
|
||||||
|
docker_image = docker_image_for_python_configuration(config, options)
|
||||||
|
|
||||||
|
step_key = (platform_tag, docker_image, before_all)
|
||||||
|
|
||||||
|
if step_key in steps:
|
||||||
|
steps[step_key].platform_configs.append(config)
|
||||||
|
else:
|
||||||
|
steps[step_key] = BuildStep(
|
||||||
|
platform_configs=[config], platform_tag=platform_tag, docker_image=docker_image
|
||||||
|
)
|
||||||
|
|
||||||
|
yield from steps.values()
|
||||||
|
|
||||||
|
|
||||||
def build_on_docker(
|
def build_on_docker(
|
||||||
options: BuildOptions,
|
*,
|
||||||
|
options: Options,
|
||||||
platform_configs: List[PythonConfiguration],
|
platform_configs: List[PythonConfiguration],
|
||||||
docker: DockerContainer,
|
docker: DockerContainer,
|
||||||
container_project_path: PurePath,
|
container_project_path: PurePath,
|
||||||
@@ -105,16 +113,21 @@ def build_on_docker(
|
|||||||
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)
|
||||||
|
|
||||||
if options.before_all:
|
before_all_options_identifier = platform_configs[0].identifier
|
||||||
|
before_all_options = options.build_options(before_all_options_identifier)
|
||||||
|
|
||||||
|
if before_all_options.before_all:
|
||||||
log.step("Running before_all...")
|
log.step("Running before_all...")
|
||||||
|
|
||||||
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, executor=docker.environment_executor)
|
env = before_all_options.environment.as_dictionary(
|
||||||
|
env, executor=docker.environment_executor
|
||||||
|
)
|
||||||
|
|
||||||
before_all_prepared = prepare_command(
|
before_all_prepared = prepare_command(
|
||||||
options.before_all,
|
before_all_options.before_all,
|
||||||
project=container_project_path,
|
project=container_project_path,
|
||||||
package=container_package_dir,
|
package=container_package_dir,
|
||||||
)
|
)
|
||||||
@@ -122,11 +135,14 @@ def build_on_docker(
|
|||||||
|
|
||||||
for config in platform_configs:
|
for config in platform_configs:
|
||||||
log.build_start(config.identifier)
|
log.build_start(config.identifier)
|
||||||
|
build_options = options.build_options(config.identifier)
|
||||||
|
|
||||||
dependency_constraint_flags: List[PathOrStr] = []
|
dependency_constraint_flags: List[PathOrStr] = []
|
||||||
|
|
||||||
if options.dependency_constraints:
|
if build_options.dependency_constraints:
|
||||||
constraints_file = options.dependency_constraints.get_for_python_version(config.version)
|
constraints_file = build_options.dependency_constraints.get_for_python_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)
|
||||||
@@ -140,7 +156,7 @@ def build_on_docker(
|
|||||||
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, executor=docker.environment_executor)
|
env = build_options.environment.as_dictionary(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"], env=env, capture_output=True).strip()
|
which_python = docker.call(["which", "python"], env=env, capture_output=True).strip()
|
||||||
@@ -159,10 +175,10 @@ def build_on_docker(
|
|||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if options.before_build:
|
if build_options.before_build:
|
||||||
log.step("Running before_build...")
|
log.step("Running before_build...")
|
||||||
before_build_prepared = prepare_command(
|
before_build_prepared = prepare_command(
|
||||||
options.before_build,
|
build_options.before_build,
|
||||||
project=container_project_path,
|
project=container_project_path,
|
||||||
package=container_package_dir,
|
package=container_package_dir,
|
||||||
)
|
)
|
||||||
@@ -175,9 +191,9 @@ def build_on_docker(
|
|||||||
docker.call(["rm", "-rf", built_wheel_dir])
|
docker.call(["rm", "-rf", built_wheel_dir])
|
||||||
docker.call(["mkdir", "-p", built_wheel_dir])
|
docker.call(["mkdir", "-p", built_wheel_dir])
|
||||||
|
|
||||||
verbosity_flags = get_build_verbosity_extra_flags(options.build_verbosity)
|
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
|
||||||
|
|
||||||
if options.build_frontend == "pip":
|
if build_options.build_frontend == "pip":
|
||||||
docker.call(
|
docker.call(
|
||||||
[
|
[
|
||||||
"python",
|
"python",
|
||||||
@@ -191,7 +207,7 @@ def build_on_docker(
|
|||||||
],
|
],
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
elif options.build_frontend == "build":
|
elif build_options.build_frontend == "build":
|
||||||
config_setting = " ".join(verbosity_flags)
|
config_setting = " ".join(verbosity_flags)
|
||||||
docker.call(
|
docker.call(
|
||||||
[
|
[
|
||||||
@@ -206,7 +222,7 @@ def build_on_docker(
|
|||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert_never(options.build_frontend)
|
assert_never(build_options.build_frontend)
|
||||||
|
|
||||||
built_wheel = docker.glob(built_wheel_dir, "*.whl")[0]
|
built_wheel = docker.glob(built_wheel_dir, "*.whl")[0]
|
||||||
|
|
||||||
@@ -217,10 +233,10 @@ def build_on_docker(
|
|||||||
if built_wheel.name.endswith("none-any.whl"):
|
if built_wheel.name.endswith("none-any.whl"):
|
||||||
raise NonPlatformWheelError()
|
raise NonPlatformWheelError()
|
||||||
|
|
||||||
if options.repair_command:
|
if build_options.repair_command:
|
||||||
log.step("Repairing wheel...")
|
log.step("Repairing wheel...")
|
||||||
repair_command_prepared = prepare_command(
|
repair_command_prepared = prepare_command(
|
||||||
options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
|
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
|
||||||
)
|
)
|
||||||
docker.call(["sh", "-c", repair_command_prepared], env=env)
|
docker.call(["sh", "-c", repair_command_prepared], env=env)
|
||||||
else:
|
else:
|
||||||
@@ -228,7 +244,7 @@ def build_on_docker(
|
|||||||
|
|
||||||
repaired_wheels = docker.glob(repaired_wheel_dir, "*.whl")
|
repaired_wheels = docker.glob(repaired_wheel_dir, "*.whl")
|
||||||
|
|
||||||
if options.test_command and options.test_selector(config.identifier):
|
if build_options.test_command and build_options.test_selector(config.identifier):
|
||||||
log.step("Testing wheel...")
|
log.step("Testing wheel...")
|
||||||
|
|
||||||
# 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
|
||||||
@@ -241,9 +257,9 @@ def build_on_docker(
|
|||||||
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']}"
|
||||||
|
|
||||||
if options.before_test:
|
if build_options.before_test:
|
||||||
before_test_prepared = prepare_command(
|
before_test_prepared = prepare_command(
|
||||||
options.before_test,
|
build_options.before_test,
|
||||||
project=container_project_path,
|
project=container_project_path,
|
||||||
package=container_package_dir,
|
package=container_package_dir,
|
||||||
)
|
)
|
||||||
@@ -257,17 +273,17 @@ def build_on_docker(
|
|||||||
# Let's just pick the first one.
|
# Let's just pick the first one.
|
||||||
wheel_to_test = repaired_wheels[0]
|
wheel_to_test = repaired_wheels[0]
|
||||||
docker.call(
|
docker.call(
|
||||||
["pip", "install", str(wheel_to_test) + options.test_extras],
|
["pip", "install", str(wheel_to_test) + build_options.test_extras],
|
||||||
env=virtualenv_env,
|
env=virtualenv_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Install any requirements to run the tests
|
# Install any requirements to run the tests
|
||||||
if options.test_requires:
|
if build_options.test_requires:
|
||||||
docker.call(["pip", "install", *options.test_requires], env=virtualenv_env)
|
docker.call(["pip", "install", *build_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(
|
||||||
options.test_command,
|
build_options.test_command,
|
||||||
project=container_project_path,
|
project=container_project_path,
|
||||||
package=container_package_dir,
|
package=container_package_dir,
|
||||||
)
|
)
|
||||||
@@ -284,11 +300,11 @@ def build_on_docker(
|
|||||||
|
|
||||||
log.step("Copying wheels back to host...")
|
log.step("Copying wheels back to host...")
|
||||||
# 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.globals.output_dir)
|
||||||
log.step_end()
|
log.step_end()
|
||||||
|
|
||||||
|
|
||||||
def build(options: BuildOptions) -> None:
|
def build(options: Options) -> None:
|
||||||
try:
|
try:
|
||||||
# check docker is installed
|
# check docker is installed
|
||||||
subprocess.run(["docker", "--version"], check=True, stdout=subprocess.DEVNULL)
|
subprocess.run(["docker", "--version"], check=True, stdout=subprocess.DEVNULL)
|
||||||
@@ -301,12 +317,12 @@ def build(options: BuildOptions) -> None:
|
|||||||
)
|
)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
assert options.manylinux_images is not None
|
python_configurations = get_python_configurations(
|
||||||
assert options.musllinux_images is not None
|
options.globals.build_selector, options.globals.architectures
|
||||||
python_configurations = get_python_configurations(options.build_selector, options.architectures)
|
)
|
||||||
|
|
||||||
cwd = Path.cwd()
|
cwd = Path.cwd()
|
||||||
abs_package_dir = options.package_dir.resolve()
|
abs_package_dir = options.globals.package_dir.resolve()
|
||||||
if cwd != abs_package_dir and cwd not in abs_package_dir.parents:
|
if cwd != abs_package_dir and cwd not in abs_package_dir.parents:
|
||||||
raise Exception("package_dir must be inside the working directory")
|
raise Exception("package_dir must be inside the working directory")
|
||||||
|
|
||||||
@@ -315,20 +331,23 @@ def build(options: BuildOptions) -> None:
|
|||||||
|
|
||||||
for build_step in get_build_steps(options, python_configurations):
|
for build_step in get_build_steps(options, python_configurations):
|
||||||
try:
|
try:
|
||||||
log.step(f"Starting Docker image {build_step.docker_image}...")
|
ids_to_build = [x.identifier for x in build_step.platform_configs]
|
||||||
|
log.step(
|
||||||
|
f"Starting Docker image {build_step.docker_image} for {', '.join(ids_to_build)}..."
|
||||||
|
)
|
||||||
|
|
||||||
with DockerContainer(
|
with DockerContainer(
|
||||||
build_step.docker_image,
|
docker_image=build_step.docker_image,
|
||||||
simulate_32_bit=build_step.platform_tag.endswith("i686"),
|
simulate_32_bit=build_step.platform_tag.endswith("i686"),
|
||||||
cwd=container_project_path,
|
cwd=container_project_path,
|
||||||
) as docker:
|
) as docker:
|
||||||
|
|
||||||
build_on_docker(
|
build_on_docker(
|
||||||
options,
|
options=options,
|
||||||
build_step.platform_configs,
|
platform_configs=build_step.platform_configs,
|
||||||
docker,
|
docker=docker,
|
||||||
container_project_path,
|
container_project_path=container_project_path,
|
||||||
container_package_dir,
|
container_package_dir=container_package_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
except subprocess.CalledProcessError as error:
|
except subprocess.CalledProcessError as error:
|
||||||
@@ -346,16 +365,18 @@ def _matches_prepared_command(error_cmd: List[str], command_template: str) -> bo
|
|||||||
return error_cmd[2].startswith(command_prefix)
|
return error_cmd[2].startswith(command_prefix)
|
||||||
|
|
||||||
|
|
||||||
def troubleshoot(options: BuildOptions, error: Exception) -> None:
|
def troubleshoot(options: Options, error: Exception) -> None:
|
||||||
|
|
||||||
if isinstance(error, subprocess.CalledProcessError) and (
|
if isinstance(error, subprocess.CalledProcessError) and (
|
||||||
error.cmd[0:4] == ["python", "-m", "pip", "wheel"]
|
error.cmd[0:4] == ["python", "-m", "pip", "wheel"]
|
||||||
or error.cmd[0:3] == ["python", "-m", "build"]
|
or error.cmd[0:3] == ["python", "-m", "build"]
|
||||||
or _matches_prepared_command(error.cmd, options.repair_command)
|
or _matches_prepared_command(
|
||||||
|
error.cmd, options.build_options(None).repair_command
|
||||||
|
) # TODO allow matching of overrides too?
|
||||||
):
|
):
|
||||||
# the wheel build step failed
|
# the wheel build step or the repair step failed
|
||||||
print("Checking for common errors...")
|
print("Checking for common errors...")
|
||||||
so_files = list(options.package_dir.glob("**/*.so"))
|
so_files = list(options.globals.package_dir.glob("**/*.so"))
|
||||||
|
|
||||||
if so_files:
|
if so_files:
|
||||||
print(
|
print(
|
||||||
|
|||||||
+43
-35
@@ -12,10 +12,10 @@ from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Set, Tuple,
|
|||||||
from .architecture import Architecture
|
from .architecture import Architecture
|
||||||
from .environment import ParsedEnvironment
|
from .environment import ParsedEnvironment
|
||||||
from .logger import log
|
from .logger import log
|
||||||
|
from .options import Options
|
||||||
from .typing import Literal, PathOrStr, assert_never
|
from .typing import Literal, PathOrStr, assert_never
|
||||||
from .util import (
|
from .util import (
|
||||||
BuildFrontend,
|
BuildFrontend,
|
||||||
BuildOptions,
|
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
NonPlatformWheelError,
|
NonPlatformWheelError,
|
||||||
download,
|
download,
|
||||||
@@ -322,49 +322,53 @@ def setup_python(
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
def build(options: BuildOptions) -> None:
|
def build(options: Options) -> None:
|
||||||
temp_dir = Path(tempfile.mkdtemp(prefix="cibuildwheel"))
|
temp_dir = Path(tempfile.mkdtemp(prefix="cibuildwheel"))
|
||||||
built_wheel_dir = temp_dir / "built_wheel"
|
built_wheel_dir = temp_dir / "built_wheel"
|
||||||
repaired_wheel_dir = temp_dir / "repaired_wheel"
|
repaired_wheel_dir = temp_dir / "repaired_wheel"
|
||||||
|
|
||||||
|
python_configurations = get_python_configurations(
|
||||||
|
options.globals.build_selector, options.globals.architectures
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if options.before_all:
|
before_all_options_identifier = python_configurations[0].identifier
|
||||||
|
before_all_options = options.build_options(before_all_options_identifier)
|
||||||
|
|
||||||
|
if before_all_options.before_all:
|
||||||
log.step("Running before_all...")
|
log.step("Running before_all...")
|
||||||
env = options.environment.as_dictionary(prev_environment=os.environ)
|
env = before_all_options.environment.as_dictionary(prev_environment=os.environ)
|
||||||
env.setdefault("MACOSX_DEPLOYMENT_TARGET", "10.9")
|
env.setdefault("MACOSX_DEPLOYMENT_TARGET", "10.9")
|
||||||
before_all_prepared = prepare_command(
|
before_all_prepared = prepare_command(
|
||||||
options.before_all, project=".", package=options.package_dir
|
before_all_options.before_all, project=".", package=before_all_options.package_dir
|
||||||
)
|
)
|
||||||
call([before_all_prepared], shell=True, env=env)
|
call([before_all_prepared], shell=True, env=env)
|
||||||
|
|
||||||
python_configurations = get_python_configurations(
|
|
||||||
options.build_selector, options.architectures
|
|
||||||
)
|
|
||||||
|
|
||||||
for config in python_configurations:
|
for config in python_configurations:
|
||||||
|
build_options = options.build_options(config.identifier)
|
||||||
log.build_start(config.identifier)
|
log.build_start(config.identifier)
|
||||||
|
|
||||||
config_is_arm64 = config.identifier.endswith("arm64")
|
config_is_arm64 = config.identifier.endswith("arm64")
|
||||||
config_is_universal2 = config.identifier.endswith("universal2")
|
config_is_universal2 = config.identifier.endswith("universal2")
|
||||||
|
|
||||||
dependency_constraint_flags: Sequence[PathOrStr] = []
|
dependency_constraint_flags: Sequence[PathOrStr] = []
|
||||||
if options.dependency_constraints:
|
if build_options.dependency_constraints:
|
||||||
dependency_constraint_flags = [
|
dependency_constraint_flags = [
|
||||||
"-c",
|
"-c",
|
||||||
options.dependency_constraints.get_for_python_version(config.version),
|
build_options.dependency_constraints.get_for_python_version(config.version),
|
||||||
]
|
]
|
||||||
|
|
||||||
env = setup_python(
|
env = setup_python(
|
||||||
config,
|
config,
|
||||||
dependency_constraint_flags,
|
dependency_constraint_flags,
|
||||||
options.environment,
|
build_options.environment,
|
||||||
options.build_frontend,
|
build_options.build_frontend,
|
||||||
)
|
)
|
||||||
|
|
||||||
if options.before_build:
|
if build_options.before_build:
|
||||||
log.step("Running before_build...")
|
log.step("Running before_build...")
|
||||||
before_build_prepared = prepare_command(
|
before_build_prepared = prepare_command(
|
||||||
options.before_build, project=".", package=options.package_dir
|
build_options.before_build, project=".", package=build_options.package_dir
|
||||||
)
|
)
|
||||||
call(before_build_prepared, env=env, shell=True)
|
call(before_build_prepared, env=env, shell=True)
|
||||||
|
|
||||||
@@ -373,9 +377,9 @@ def build(options: BuildOptions) -> None:
|
|||||||
shutil.rmtree(built_wheel_dir)
|
shutil.rmtree(built_wheel_dir)
|
||||||
built_wheel_dir.mkdir(parents=True)
|
built_wheel_dir.mkdir(parents=True)
|
||||||
|
|
||||||
verbosity_flags = get_build_verbosity_extra_flags(options.build_verbosity)
|
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
|
||||||
|
|
||||||
if options.build_frontend == "pip":
|
if build_options.build_frontend == "pip":
|
||||||
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
|
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
|
||||||
# see https://github.com/pypa/cibuildwheel/pull/369
|
# see https://github.com/pypa/cibuildwheel/pull/369
|
||||||
call(
|
call(
|
||||||
@@ -384,18 +388,20 @@ def build(options: BuildOptions) -> None:
|
|||||||
"-m",
|
"-m",
|
||||||
"pip",
|
"pip",
|
||||||
"wheel",
|
"wheel",
|
||||||
options.package_dir.resolve(),
|
build_options.package_dir.resolve(),
|
||||||
f"--wheel-dir={built_wheel_dir}",
|
f"--wheel-dir={built_wheel_dir}",
|
||||||
"--no-deps",
|
"--no-deps",
|
||||||
*verbosity_flags,
|
*verbosity_flags,
|
||||||
],
|
],
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
elif options.build_frontend == "build":
|
elif build_options.build_frontend == "build":
|
||||||
config_setting = " ".join(verbosity_flags)
|
config_setting = " ".join(verbosity_flags)
|
||||||
build_env = env.copy()
|
build_env = env.copy()
|
||||||
if options.dependency_constraints:
|
if build_options.dependency_constraints:
|
||||||
constr = options.dependency_constraints.get_for_python_version(config.version)
|
constr = build_options.dependency_constraints.get_for_python_version(
|
||||||
|
config.version
|
||||||
|
)
|
||||||
build_env["PIP_CONSTRAINT"] = constr.as_uri()
|
build_env["PIP_CONSTRAINT"] = constr.as_uri()
|
||||||
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
|
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
|
||||||
call(
|
call(
|
||||||
@@ -403,7 +409,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
"python",
|
"python",
|
||||||
"-m",
|
"-m",
|
||||||
"build",
|
"build",
|
||||||
options.package_dir,
|
build_options.package_dir,
|
||||||
"--wheel",
|
"--wheel",
|
||||||
f"--outdir={built_wheel_dir}",
|
f"--outdir={built_wheel_dir}",
|
||||||
f"--config-setting={config_setting}",
|
f"--config-setting={config_setting}",
|
||||||
@@ -411,7 +417,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
env=build_env,
|
env=build_env,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert_never(options.build_frontend)
|
assert_never(build_options.build_frontend)
|
||||||
|
|
||||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||||
|
|
||||||
@@ -422,7 +428,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
if built_wheel.name.endswith("none-any.whl"):
|
if built_wheel.name.endswith("none-any.whl"):
|
||||||
raise NonPlatformWheelError()
|
raise NonPlatformWheelError()
|
||||||
|
|
||||||
if options.repair_command:
|
if build_options.repair_command:
|
||||||
log.step("Repairing wheel...")
|
log.step("Repairing wheel...")
|
||||||
|
|
||||||
if config_is_universal2:
|
if config_is_universal2:
|
||||||
@@ -433,7 +439,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
delocate_archs = "x86_64"
|
delocate_archs = "x86_64"
|
||||||
|
|
||||||
repair_command_prepared = prepare_command(
|
repair_command_prepared = prepare_command(
|
||||||
options.repair_command,
|
build_options.repair_command,
|
||||||
wheel=built_wheel,
|
wheel=built_wheel,
|
||||||
dest_dir=repaired_wheel_dir,
|
dest_dir=repaired_wheel_dir,
|
||||||
delocate_archs=delocate_archs,
|
delocate_archs=delocate_archs,
|
||||||
@@ -446,7 +452,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
|
|
||||||
log.step_end()
|
log.step_end()
|
||||||
|
|
||||||
if options.test_command and options.test_selector(config.identifier):
|
if build_options.test_command and build_options.test_selector(config.identifier):
|
||||||
machine_arch = platform.machine()
|
machine_arch = platform.machine()
|
||||||
testing_archs: List[Literal["x86_64", "arm64"]] = []
|
testing_archs: List[Literal["x86_64", "arm64"]] = []
|
||||||
|
|
||||||
@@ -460,7 +466,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
for testing_arch in testing_archs:
|
for testing_arch in testing_archs:
|
||||||
if config_is_universal2:
|
if config_is_universal2:
|
||||||
arch_specific_identifier = f"{config.identifier}:{testing_arch}"
|
arch_specific_identifier = f"{config.identifier}:{testing_arch}"
|
||||||
if not options.test_selector(arch_specific_identifier):
|
if not build_options.test_selector(arch_specific_identifier):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if machine_arch == "x86_64" and testing_arch == "arm64":
|
if machine_arch == "x86_64" and testing_arch == "arm64":
|
||||||
@@ -541,31 +547,33 @@ def build(options: BuildOptions) -> None:
|
|||||||
# check that we are using the Python from the virtual environment
|
# check that we are using the Python from the virtual environment
|
||||||
call_with_arch(["which", "python"], env=virtualenv_env)
|
call_with_arch(["which", "python"], env=virtualenv_env)
|
||||||
|
|
||||||
if options.before_test:
|
if build_options.before_test:
|
||||||
before_test_prepared = prepare_command(
|
before_test_prepared = prepare_command(
|
||||||
options.before_test, project=".", package=options.package_dir
|
build_options.before_test,
|
||||||
|
project=".",
|
||||||
|
package=build_options.package_dir,
|
||||||
)
|
)
|
||||||
call_with_arch(before_test_prepared, env=virtualenv_env, shell=True)
|
call_with_arch(before_test_prepared, env=virtualenv_env, shell=True)
|
||||||
|
|
||||||
# install the wheel
|
# install the wheel
|
||||||
call_with_arch(
|
call_with_arch(
|
||||||
["pip", "install", f"{repaired_wheel}{options.test_extras}"],
|
["pip", "install", f"{repaired_wheel}{build_options.test_extras}"],
|
||||||
env=virtualenv_env,
|
env=virtualenv_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
# test the wheel
|
# test the wheel
|
||||||
if options.test_requires:
|
if build_options.test_requires:
|
||||||
call_with_arch(
|
call_with_arch(
|
||||||
["pip", "install"] + options.test_requires, env=virtualenv_env
|
["pip", "install"] + build_options.test_requires, env=virtualenv_env
|
||||||
)
|
)
|
||||||
|
|
||||||
# run the tests from $HOME, with an absolute path in the command
|
# run the tests from $HOME, with an absolute path in the command
|
||||||
# (this ensures that Python runs the tests against the installed wheel
|
# (this ensures that Python runs the tests against the installed wheel
|
||||||
# and not the repo code)
|
# and not the repo code)
|
||||||
test_command_prepared = prepare_command(
|
test_command_prepared = prepare_command(
|
||||||
options.test_command,
|
build_options.test_command,
|
||||||
project=Path(".").resolve(),
|
project=Path(".").resolve(),
|
||||||
package=options.package_dir.resolve(),
|
package=build_options.package_dir.resolve(),
|
||||||
)
|
)
|
||||||
call_with_arch(
|
call_with_arch(
|
||||||
test_command_prepared,
|
test_command_prepared,
|
||||||
@@ -578,7 +586,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
shutil.rmtree(venv_dir)
|
shutil.rmtree(venv_dir)
|
||||||
|
|
||||||
# we're all done here; move it to output (overwrite existing)
|
# we're all done here; move it to output (overwrite existing)
|
||||||
shutil.move(str(repaired_wheel), options.output_dir)
|
shutil.move(str(repaired_wheel), build_options.output_dir)
|
||||||
log.build_end()
|
log.build_end()
|
||||||
except subprocess.CalledProcessError as error:
|
except subprocess.CalledProcessError as error:
|
||||||
log.step_end_with_error(
|
log.step_end_with_error(
|
||||||
|
|||||||
+299
-110
@@ -2,31 +2,115 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Dict,
|
||||||
|
Iterator,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
NamedTuple,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
import tomli
|
import tomli
|
||||||
from packaging.specifiers import SpecifierSet
|
from packaging.specifiers import SpecifierSet
|
||||||
|
|
||||||
from .architecture import Architecture
|
from .architecture import Architecture
|
||||||
from .environment import EnvironmentParseError, parse_environment
|
from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
|
||||||
from .projectfiles import get_requires_python_str
|
from .projectfiles import get_requires_python_str
|
||||||
from .typing import PLATFORMS, PlatformName, TypedDict
|
from .typing import PLATFORMS, Literal, PlatformName, TypedDict
|
||||||
from .util import (
|
from .util import (
|
||||||
MANYLINUX_ARCHS,
|
MANYLINUX_ARCHS,
|
||||||
MUSLLINUX_ARCHS,
|
MUSLLINUX_ARCHS,
|
||||||
BuildFrontend,
|
BuildFrontend,
|
||||||
BuildOptions,
|
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
DependencyConstraints,
|
DependencyConstraints,
|
||||||
TestSelector,
|
TestSelector,
|
||||||
resources_dir,
|
resources_dir,
|
||||||
|
selector_matches,
|
||||||
strtobool,
|
strtobool,
|
||||||
|
unwrap,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandLineArguments:
|
||||||
|
platform: Literal["auto", "linux", "macos", "windows"]
|
||||||
|
archs: Optional[str]
|
||||||
|
output_dir: Optional[str]
|
||||||
|
config_file: Optional[str]
|
||||||
|
package_dir: str
|
||||||
|
print_build_identifiers: bool
|
||||||
|
allow_empty: bool
|
||||||
|
prerelease_pythons: bool
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalOptions(NamedTuple):
|
||||||
|
package_dir: Path
|
||||||
|
output_dir: Path
|
||||||
|
build_selector: BuildSelector
|
||||||
|
test_selector: TestSelector
|
||||||
|
architectures: Set[Architecture]
|
||||||
|
|
||||||
|
|
||||||
|
class BuildOptions(NamedTuple):
|
||||||
|
globals: GlobalOptions
|
||||||
|
environment: ParsedEnvironment
|
||||||
|
before_all: str
|
||||||
|
before_build: Optional[str]
|
||||||
|
repair_command: str
|
||||||
|
manylinux_images: Optional[Dict[str, str]]
|
||||||
|
musllinux_images: Optional[Dict[str, str]]
|
||||||
|
dependency_constraints: Optional[DependencyConstraints]
|
||||||
|
test_command: Optional[str]
|
||||||
|
before_test: Optional[str]
|
||||||
|
test_requires: List[str]
|
||||||
|
test_extras: str
|
||||||
|
build_verbosity: int
|
||||||
|
build_frontend: BuildFrontend
|
||||||
|
|
||||||
|
@property
|
||||||
|
def package_dir(self) -> Path:
|
||||||
|
return self.globals.package_dir
|
||||||
|
|
||||||
|
@property
|
||||||
|
def output_dir(self) -> Path:
|
||||||
|
return self.globals.output_dir
|
||||||
|
|
||||||
|
@property
|
||||||
|
def build_selector(self) -> BuildSelector:
|
||||||
|
return self.globals.build_selector
|
||||||
|
|
||||||
|
@property
|
||||||
|
def test_selector(self) -> TestSelector:
|
||||||
|
return self.globals.test_selector
|
||||||
|
|
||||||
|
@property
|
||||||
|
def architectures(self) -> Set[Architecture]:
|
||||||
|
return self.globals.architectures
|
||||||
|
|
||||||
|
|
||||||
Setting = Union[Dict[str, str], List[str], str]
|
Setting = Union[Dict[str, str], List[str], str]
|
||||||
|
|
||||||
|
|
||||||
|
class Override(NamedTuple):
|
||||||
|
select_pattern: str
|
||||||
|
options: Dict[str, Setting]
|
||||||
|
|
||||||
|
|
||||||
|
MANYLINUX_OPTIONS = {f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS}
|
||||||
|
MUSLLINUX_OPTIONS = {f"musllinux-{build_platform}-image" for build_platform in MUSLLINUX_ARCHS}
|
||||||
|
DISALLOWED_OPTIONS = {
|
||||||
|
"linux": {"dependency-versions"},
|
||||||
|
"macos": MANYLINUX_OPTIONS | MUSLLINUX_OPTIONS,
|
||||||
|
"windows": MANYLINUX_OPTIONS | MUSLLINUX_OPTIONS,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TableFmt(TypedDict):
|
class TableFmt(TypedDict):
|
||||||
item: str
|
item: str
|
||||||
sep: str
|
sep: str
|
||||||
@@ -58,29 +142,28 @@ def _dig_first(*pairs: Tuple[Mapping[str, Setting], str], ignore_empty: bool = F
|
|||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
|
|
||||||
|
|
||||||
class ConfigOptions:
|
class OptionsReader:
|
||||||
"""
|
"""
|
||||||
Gets options from the environment, config or defaults, optionally scoped
|
Gets options from the environment, config or defaults, optionally scoped
|
||||||
by the platform.
|
by the platform.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
>>> options = ConfigOptions(package_dir, platform='macos')
|
>>> options_reader = OptionsReader(config_file, platform='macos')
|
||||||
>>> options('cool-color')
|
>>> options_reader.get('cool-color')
|
||||||
|
|
||||||
This will return the value of CIBW_COOL_COLOR_MACOS if it exists,
|
This will return the value of CIBW_COOL_COLOR_MACOS if it exists,
|
||||||
otherwise the value of CIBW_COOL_COLOR, otherwise
|
otherwise the value of CIBW_COOL_COLOR, otherwise
|
||||||
'tool.cibuildwheel.macos.cool-color' or 'tool.cibuildwheel.cool-color'
|
'tool.cibuildwheel.macos.cool-color' or 'tool.cibuildwheel.cool-color'
|
||||||
from pyproject.toml, or from cibuildwheel/resources/defaults.toml. An
|
from `config_file`, or from cibuildwheel/resources/defaults.toml. An
|
||||||
error is thrown if there are any unexpected keys or sections in
|
error is thrown if there are any unexpected keys or sections in
|
||||||
tool.cibuildwheel.
|
tool.cibuildwheel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
package_path: Path,
|
config_file_path: Optional[Path] = None,
|
||||||
config_file: Optional[str] = None,
|
|
||||||
*,
|
*,
|
||||||
platform: str,
|
platform: PlatformName,
|
||||||
disallow: Optional[Dict[str, Set[str]]] = None,
|
disallow: Optional[Dict[str, Set[str]]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.platform = platform
|
self.platform = platform
|
||||||
@@ -94,14 +177,8 @@ class ConfigOptions:
|
|||||||
config_options: Dict[str, Any] = {}
|
config_options: Dict[str, Any] = {}
|
||||||
config_platform_options: Dict[str, Any] = {}
|
config_platform_options: Dict[str, Any] = {}
|
||||||
|
|
||||||
if config_file is not None:
|
if config_file_path is not None:
|
||||||
config_path = Path(config_file.format(package=package_path))
|
config_options, config_platform_options = self._load_file(config_file_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
|
# Validate project config
|
||||||
for option_name in config_options:
|
for option_name in config_options:
|
||||||
@@ -117,12 +194,32 @@ class ConfigOptions:
|
|||||||
self.config_options = config_options
|
self.config_options = config_options
|
||||||
self.config_platform_options = config_platform_options
|
self.config_platform_options = config_platform_options
|
||||||
|
|
||||||
|
self.overrides: List[Override] = []
|
||||||
|
self.current_identifier: Optional[str] = None
|
||||||
|
|
||||||
|
config_overrides = self.config_options.get("overrides")
|
||||||
|
|
||||||
|
if config_overrides is not None:
|
||||||
|
if not isinstance(config_overrides, list):
|
||||||
|
raise ConfigOptionError('"tool.cibuildwheel.overrides" must be a list')
|
||||||
|
|
||||||
|
for config_override in config_overrides:
|
||||||
|
select = config_override.pop("select", None)
|
||||||
|
|
||||||
|
if not select:
|
||||||
|
raise ConfigOptionError('"select" must be set in an override')
|
||||||
|
|
||||||
|
if isinstance(select, list):
|
||||||
|
select = " ".join(select)
|
||||||
|
|
||||||
|
self.overrides.append(Override(select, config_override))
|
||||||
|
|
||||||
def _is_valid_global_option(self, name: str) -> bool:
|
def _is_valid_global_option(self, name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns True if an option with this name is allowed in the
|
Returns True if an option with this name is allowed in the
|
||||||
[tool.cibuildwheel] section of a config file.
|
[tool.cibuildwheel] section of a config file.
|
||||||
"""
|
"""
|
||||||
allowed_option_names = self.default_options.keys() | PLATFORMS
|
allowed_option_names = self.default_options.keys() | PLATFORMS | {"overrides"}
|
||||||
|
|
||||||
return name in allowed_option_names
|
return name in allowed_option_names
|
||||||
|
|
||||||
@@ -151,7 +248,23 @@ class ConfigOptions:
|
|||||||
|
|
||||||
return global_options, platform_options
|
return global_options, platform_options
|
||||||
|
|
||||||
def __call__(
|
@property
|
||||||
|
def active_config_overrides(self) -> List[Override]:
|
||||||
|
if self.current_identifier is None:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
o for o in self.overrides if selector_matches(o.select_pattern, self.current_identifier)
|
||||||
|
]
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def identifier(self, identifier: Optional[str]) -> Iterator[None]:
|
||||||
|
self.current_identifier = identifier
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.current_identifier = None
|
||||||
|
|
||||||
|
def get(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
*,
|
*,
|
||||||
@@ -177,11 +290,15 @@ class ConfigOptions:
|
|||||||
envvar = f"CIBW_{name.upper().replace('-', '_')}"
|
envvar = f"CIBW_{name.upper().replace('-', '_')}"
|
||||||
plat_envvar = f"{envvar}_{self.platform.upper()}"
|
plat_envvar = f"{envvar}_{self.platform.upper()}"
|
||||||
|
|
||||||
|
# later overrides take precedence over earlier ones, so reverse the list
|
||||||
|
active_config_overrides = reversed(self.active_config_overrides)
|
||||||
|
|
||||||
# get the option from the environment, then the config file, then finally the default.
|
# get the option from the environment, then the config file, then finally the default.
|
||||||
# platform-specific options are preferred, if they're allowed.
|
# platform-specific options are preferred, if they're allowed.
|
||||||
result = _dig_first(
|
result = _dig_first(
|
||||||
(os.environ if env_plat else {}, plat_envvar), # type: ignore[arg-type]
|
(os.environ if env_plat else {}, plat_envvar), # type: ignore[arg-type]
|
||||||
(os.environ, envvar),
|
(os.environ, envvar),
|
||||||
|
*[(o.options, name) for o in active_config_overrides],
|
||||||
(self.config_platform_options, name),
|
(self.config_platform_options, name),
|
||||||
(self.config_options, name),
|
(self.config_options, name),
|
||||||
(self.default_platform_options, name),
|
(self.default_platform_options, name),
|
||||||
@@ -203,56 +320,60 @@ class ConfigOptions:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def compute_options(
|
class Options:
|
||||||
platform: PlatformName,
|
def __init__(self, platform: PlatformName, command_line_arguments: CommandLineArguments):
|
||||||
package_dir: Path,
|
self.platform = platform
|
||||||
output_dir: Path,
|
self.command_line_arguments = command_line_arguments
|
||||||
config_file: Optional[str],
|
|
||||||
args_archs: Optional[str],
|
|
||||||
prerelease_pythons: bool,
|
|
||||||
) -> BuildOptions:
|
|
||||||
"""
|
|
||||||
Compute the options from the environment and configuration file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
manylinux_identifiers = {
|
self.reader = OptionsReader(
|
||||||
f"manylinux-{build_platform}-image" for build_platform in MANYLINUX_ARCHS
|
self.config_file_path,
|
||||||
}
|
platform=platform,
|
||||||
musllinux_identifiers = {
|
disallow=DISALLOWED_OPTIONS,
|
||||||
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 "*"
|
@property
|
||||||
skip_config = options("skip", env_plat=False, sep=" ")
|
def config_file_path(self) -> Optional[Path]:
|
||||||
test_skip = options("test-skip", env_plat=False, sep=" ")
|
args = self.command_line_arguments
|
||||||
|
|
||||||
prerelease_pythons = prerelease_pythons or strtobool(
|
if args.config_file is not None:
|
||||||
|
return Path(args.config_file.format(package=args.package_dir))
|
||||||
|
# return pyproject.toml, if it's available
|
||||||
|
pyproject_toml_path = Path(args.package_dir) / "pyproject.toml"
|
||||||
|
if pyproject_toml_path.exists():
|
||||||
|
return pyproject_toml_path
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def package_requires_python_str(self) -> Optional[str]:
|
||||||
|
if not hasattr(self, "_package_requires_python_str"):
|
||||||
|
args = self.command_line_arguments
|
||||||
|
self._package_requires_python_str = get_requires_python_str(Path(args.package_dir))
|
||||||
|
return self._package_requires_python_str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def globals(self) -> GlobalOptions:
|
||||||
|
args = self.command_line_arguments
|
||||||
|
package_dir = Path(args.package_dir)
|
||||||
|
output_dir = Path(
|
||||||
|
args.output_dir
|
||||||
|
if args.output_dir is not None
|
||||||
|
else os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse")
|
||||||
|
)
|
||||||
|
|
||||||
|
build_config = self.reader.get("build", env_plat=False, sep=" ") or "*"
|
||||||
|
skip_config = self.reader.get("skip", env_plat=False, sep=" ")
|
||||||
|
test_skip = self.reader.get("test-skip", env_plat=False, sep=" ")
|
||||||
|
|
||||||
|
prerelease_pythons = args.prerelease_pythons or strtobool(
|
||||||
os.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
|
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.
|
# 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] = (
|
||||||
"CIBW_PROJECT_REQUIRES_PYTHON"
|
os.environ.get("CIBW_PROJECT_REQUIRES_PYTHON") or self.package_requires_python_str
|
||||||
) or get_requires_python_str(package_dir)
|
)
|
||||||
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
|
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
|
||||||
|
|
||||||
build_selector = BuildSelector(
|
build_selector = BuildSelector(
|
||||||
@@ -263,39 +384,38 @@ def compute_options(
|
|||||||
)
|
)
|
||||||
test_selector = TestSelector(skip_config=test_skip)
|
test_selector = TestSelector(skip_config=test_skip)
|
||||||
|
|
||||||
return _compute_single_options(
|
archs_config_str = args.archs or self.reader.get("archs", sep=" ")
|
||||||
options, args_archs, build_selector, test_selector, platform, package_dir, output_dir
|
architectures = Architecture.parse_config(archs_config_str, platform=self.platform)
|
||||||
|
|
||||||
|
return GlobalOptions(
|
||||||
|
package_dir=package_dir,
|
||||||
|
output_dir=output_dir,
|
||||||
|
build_selector=build_selector,
|
||||||
|
test_selector=test_selector,
|
||||||
|
architectures=architectures,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def build_options(self, identifier: Optional[str]) -> BuildOptions:
|
||||||
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.
|
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=" ")
|
with self.reader.identifier(identifier):
|
||||||
|
before_all = self.reader.get("before-all", sep=" && ")
|
||||||
|
|
||||||
build_frontend_str = options("build-frontend", env_plat=False)
|
build_frontend_str = self.reader.get("build-frontend", env_plat=False)
|
||||||
environment_config = options("environment", table={"item": '{k}="{v}"', "sep": " "})
|
environment_config = self.reader.get(
|
||||||
before_build = options("before-build", sep=" && ")
|
"environment", table={"item": '{k}="{v}"', "sep": " "}
|
||||||
repair_command = options("repair-wheel-command", sep=" && ")
|
)
|
||||||
|
before_build = self.reader.get("before-build", sep=" && ")
|
||||||
|
repair_command = self.reader.get("repair-wheel-command", sep=" && ")
|
||||||
|
|
||||||
dependency_versions = options("dependency-versions")
|
dependency_versions = self.reader.get("dependency-versions")
|
||||||
test_command = options("test-command", sep=" && ")
|
test_command = self.reader.get("test-command", sep=" && ")
|
||||||
before_test = options("before-test", sep=" && ")
|
before_test = self.reader.get("before-test", sep=" && ")
|
||||||
test_requires = options("test-requires", sep=" ").split()
|
test_requires = self.reader.get("test-requires", sep=" ").split()
|
||||||
test_extras = options("test-extras", sep=",")
|
test_extras = self.reader.get("test-extras", sep=",")
|
||||||
build_verbosity_str = options("build-verbosity")
|
build_verbosity_str = self.reader.get("build-verbosity")
|
||||||
|
|
||||||
build_frontend: BuildFrontend
|
build_frontend: BuildFrontend
|
||||||
if build_frontend_str == "build":
|
if build_frontend_str == "build":
|
||||||
@@ -303,14 +423,17 @@ def _compute_single_options(
|
|||||||
elif build_frontend_str == "pip":
|
elif build_frontend_str == "pip":
|
||||||
build_frontend = "pip"
|
build_frontend = "pip"
|
||||||
else:
|
else:
|
||||||
msg = f"cibuildwheel: Unrecognised build frontend '{build_frontend}', only 'pip' and 'build' are supported"
|
msg = f"cibuildwheel: Unrecognised build frontend '{build_frontend_str}', only 'pip' and 'build' are supported"
|
||||||
print(msg, file=sys.stderr)
|
print(msg, file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
environment = parse_environment(environment_config)
|
environment = parse_environment(environment_config)
|
||||||
except (EnvironmentParseError, ValueError):
|
except (EnvironmentParseError, ValueError):
|
||||||
print(f'cibuildwheel: Malformed environment option "{environment_config}"', file=sys.stderr)
|
print(
|
||||||
|
f'cibuildwheel: Malformed environment option "{environment_config}"',
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
traceback.print_exc(None, sys.stderr)
|
traceback.print_exc(None, sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
@@ -332,42 +455,38 @@ def _compute_single_options(
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
build_verbosity = 0
|
build_verbosity = 0
|
||||||
|
|
||||||
archs = Architecture.parse_config(archs_config_str, platform=platform)
|
|
||||||
|
|
||||||
manylinux_images: Dict[str, str] = {}
|
manylinux_images: Dict[str, str] = {}
|
||||||
musllinux_images: Dict[str, str] = {}
|
musllinux_images: Dict[str, str] = {}
|
||||||
if platform == "linux":
|
if self.platform == "linux":
|
||||||
pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg"
|
all_pinned_docker_images = _get_pinned_docker_images()
|
||||||
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:
|
for build_platform in MANYLINUX_ARCHS:
|
||||||
pinned_images = all_pinned_docker_images[build_platform]
|
pinned_images = all_pinned_docker_images[build_platform]
|
||||||
|
|
||||||
config_value = options(f"manylinux-{build_platform}-image", ignore_empty=True)
|
config_value = self.reader.get(
|
||||||
|
f"manylinux-{build_platform}-image", ignore_empty=True
|
||||||
|
)
|
||||||
|
|
||||||
if not config_value:
|
if not config_value:
|
||||||
# default to manylinux2010 if it's available, otherwise manylinux2014
|
# default to manylinux2010 if it's available, otherwise manylinux2014
|
||||||
image = pinned_images.get("manylinux2010") or pinned_images.get("manylinux2014")
|
image = pinned_images.get("manylinux2010") or pinned_images.get(
|
||||||
|
"manylinux2014"
|
||||||
|
)
|
||||||
elif config_value in pinned_images:
|
elif config_value in pinned_images:
|
||||||
image = pinned_images[config_value]
|
image = pinned_images[config_value]
|
||||||
else:
|
else:
|
||||||
image = config_value
|
image = config_value
|
||||||
|
|
||||||
|
assert image is not None
|
||||||
manylinux_images[build_platform] = image
|
manylinux_images[build_platform] = image
|
||||||
|
|
||||||
for build_platform in MUSLLINUX_ARCHS:
|
for build_platform in MUSLLINUX_ARCHS:
|
||||||
pinned_images = all_pinned_docker_images[build_platform]
|
pinned_images = all_pinned_docker_images[build_platform]
|
||||||
|
|
||||||
config_value = options(f"musllinux-{build_platform}-image")
|
config_value = self.reader.get(f"musllinux-{build_platform}-image")
|
||||||
|
|
||||||
if config_value is None:
|
if config_value is None:
|
||||||
image = pinned_images.get("musllinux_1_1")
|
image = pinned_images["musllinux_1_1"]
|
||||||
elif config_value in pinned_images:
|
elif config_value in pinned_images:
|
||||||
image = pinned_images[config_value]
|
image = pinned_images[config_value]
|
||||||
else:
|
else:
|
||||||
@@ -376,9 +495,7 @@ def _compute_single_options(
|
|||||||
musllinux_images[build_platform] = image
|
musllinux_images[build_platform] = image
|
||||||
|
|
||||||
return BuildOptions(
|
return BuildOptions(
|
||||||
architectures=archs,
|
globals=self.globals,
|
||||||
package_dir=package_dir,
|
|
||||||
output_dir=output_dir,
|
|
||||||
test_command=test_command,
|
test_command=test_command,
|
||||||
test_requires=test_requires,
|
test_requires=test_requires,
|
||||||
test_extras=test_extras,
|
test_extras=test_extras,
|
||||||
@@ -386,8 +503,6 @@ def _compute_single_options(
|
|||||||
before_build=before_build,
|
before_build=before_build,
|
||||||
before_all=before_all,
|
before_all=before_all,
|
||||||
build_verbosity=build_verbosity,
|
build_verbosity=build_verbosity,
|
||||||
build_selector=build_selector,
|
|
||||||
test_selector=test_selector,
|
|
||||||
repair_command=repair_command,
|
repair_command=repair_command,
|
||||||
environment=environment,
|
environment=environment,
|
||||||
dependency_constraints=dependency_constraints,
|
dependency_constraints=dependency_constraints,
|
||||||
@@ -396,6 +511,80 @@ def _compute_single_options(
|
|||||||
build_frontend=build_frontend,
|
build_frontend=build_frontend,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def check_for_invalid_configuration(self, identifiers: List[str]) -> None:
|
||||||
|
if self.platform in ["macos", "windows"]:
|
||||||
|
before_all_values = {self.build_options(i).before_all for i in identifiers}
|
||||||
|
|
||||||
|
if len(before_all_values) > 1:
|
||||||
|
raise ValueError(
|
||||||
|
unwrap(
|
||||||
|
f"""
|
||||||
|
before_all cannot be set to multiple values. On macOS and Windows,
|
||||||
|
before_all is only run once, at the start of the build. before_all values
|
||||||
|
are: {before_all_values!r}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_for_deprecated_options(self) -> None:
|
||||||
|
build_selector = self.globals.build_selector
|
||||||
|
test_selector = self.globals.test_selector
|
||||||
|
|
||||||
|
deprecated_selectors("CIBW_BUILD", build_selector.build_config, error=True)
|
||||||
|
deprecated_selectors("CIBW_SKIP", build_selector.skip_config)
|
||||||
|
deprecated_selectors("CIBW_TEST_SKIP", test_selector.skip_config)
|
||||||
|
|
||||||
|
def summary(self, identifiers: List[str]) -> str:
|
||||||
|
lines = [
|
||||||
|
f"{option_name}: {option_value!r}"
|
||||||
|
for option_name, option_value in sorted(self.globals._asdict().items())
|
||||||
|
]
|
||||||
|
|
||||||
|
build_option_defaults = self.build_options(identifier=None)
|
||||||
|
|
||||||
|
for option_name, default_value in sorted(build_option_defaults._asdict().items()):
|
||||||
|
if option_name == "globals":
|
||||||
|
continue
|
||||||
|
|
||||||
|
lines.append(f"{option_name}: {default_value!r}")
|
||||||
|
|
||||||
|
# if any identifiers have an overridden value, print that too
|
||||||
|
for identifier in identifiers:
|
||||||
|
option_value = self.build_options(identifier=identifier)._asdict()[option_name]
|
||||||
|
if option_value != default_value:
|
||||||
|
lines.append(f" {identifier}: {option_value!r}")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_options(
|
||||||
|
platform: PlatformName,
|
||||||
|
command_line_arguments: CommandLineArguments,
|
||||||
|
) -> Options:
|
||||||
|
options = Options(platform=platform, command_line_arguments=command_line_arguments)
|
||||||
|
options.check_for_deprecated_options()
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
_all_pinned_docker_images: Optional[ConfigParser] = None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_pinned_docker_images() -> Mapping[str, Mapping[str, str]]:
|
||||||
|
"""
|
||||||
|
This looks like a dict of dicts, e.g.
|
||||||
|
{ 'x86_64': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
|
||||||
|
'i686': {'manylinux1': '...', 'manylinux2010': '...', 'manylinux2014': '...'},
|
||||||
|
'pypy_x86_64': {'manylinux2010': '...' }
|
||||||
|
... }
|
||||||
|
"""
|
||||||
|
global _all_pinned_docker_images
|
||||||
|
|
||||||
|
if _all_pinned_docker_images is None:
|
||||||
|
pinned_docker_images_file = resources_dir / "pinned_docker_images.cfg"
|
||||||
|
_all_pinned_docker_images = ConfigParser()
|
||||||
|
_all_pinned_docker_images.read(pinned_docker_images_file)
|
||||||
|
return _all_pinned_docker_images
|
||||||
|
|
||||||
|
|
||||||
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
|
def deprecated_selectors(name: str, selector: str, *, error: bool = False) -> None:
|
||||||
if "p2" in selector or "p35" in selector:
|
if "p2" in selector or "p35" in selector:
|
||||||
|
|||||||
@@ -4,22 +4,25 @@ import sys
|
|||||||
from typing import TYPE_CHECKING, NoReturn, Set, Union
|
from typing import TYPE_CHECKING, NoReturn, Set, Union
|
||||||
|
|
||||||
if sys.version_info < (3, 8):
|
if sys.version_info < (3, 8):
|
||||||
from typing_extensions import Final, Literal, Protocol, TypedDict
|
from typing_extensions import Final, Literal, OrderedDict, Protocol, TypedDict
|
||||||
else:
|
else:
|
||||||
from typing import Final, Literal, Protocol, TypedDict
|
from typing import Final, Literal, OrderedDict, Protocol, TypedDict
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"Final",
|
"Final",
|
||||||
"Literal",
|
"Literal",
|
||||||
"TypedDict",
|
"PLATFORMS",
|
||||||
"Set",
|
|
||||||
"Union",
|
|
||||||
"PopenBytes",
|
|
||||||
"PathOrStr",
|
"PathOrStr",
|
||||||
"PlatformName",
|
"PlatformName",
|
||||||
"Protocol",
|
"Protocol",
|
||||||
"PLATFORMS",
|
"PLATFORMS",
|
||||||
|
"PopenBytes",
|
||||||
|
"Protocol",
|
||||||
|
"Set",
|
||||||
|
"TypedDict",
|
||||||
|
"OrderedDict",
|
||||||
|
"Union",
|
||||||
"assert_never",
|
"assert_never",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+33
-48
@@ -12,7 +12,7 @@ import urllib.request
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Dict, Iterator, List, NamedTuple, Optional, Set
|
from typing import Dict, Iterator, List, Optional
|
||||||
|
|
||||||
import bracex
|
import bracex
|
||||||
import certifi
|
import certifi
|
||||||
@@ -20,8 +20,6 @@ import tomli
|
|||||||
from packaging.specifiers import SpecifierSet
|
from packaging.specifiers import SpecifierSet
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
|
|
||||||
from .architecture import Architecture
|
|
||||||
from .environment import ParsedEnvironment
|
|
||||||
from .typing import Literal, PathOrStr, PlatformName
|
from .typing import Literal, PathOrStr, PlatformName
|
||||||
|
|
||||||
resources_dir = Path(__file__).parent / "resources"
|
resources_dir = Path(__file__).parent / "resources"
|
||||||
@@ -77,6 +75,20 @@ def read_python_configs(config: PlatformName) -> List[Dict[str, str]]:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def selector_matches(patterns: str, string: str) -> bool:
|
||||||
|
"""
|
||||||
|
Returns True if `string` is matched by any of the wildcard patterns in
|
||||||
|
`patterns`.
|
||||||
|
|
||||||
|
Matching is according to fnmatch, but with shell-like curly brace
|
||||||
|
expansion. For example, 'cp{36,37}-*' would match either of 'cp36-*' or
|
||||||
|
'cp37-*'.
|
||||||
|
"""
|
||||||
|
patterns_list: List[str] = patterns.split()
|
||||||
|
patterns_list = itertools.chain.from_iterable(bracex.expand(p) for p in patterns_list) # type: ignore[assignment]
|
||||||
|
return any(fnmatch.fnmatch(string, pat) for pat in patterns_list)
|
||||||
|
|
||||||
|
|
||||||
class IdentifierSelector:
|
class IdentifierSelector:
|
||||||
"""
|
"""
|
||||||
This class holds a set of build/skip patterns. You call an instance with a
|
This class holds a set of build/skip patterns. You call an instance with a
|
||||||
@@ -97,8 +109,8 @@ class IdentifierSelector:
|
|||||||
requires_python: Optional[SpecifierSet] = None,
|
requires_python: Optional[SpecifierSet] = None,
|
||||||
prerelease_pythons: bool = False,
|
prerelease_pythons: bool = False,
|
||||||
):
|
):
|
||||||
self.build_patterns = build_config.split()
|
self.build_config = build_config
|
||||||
self.skip_patterns = skip_config.split()
|
self.skip_config = skip_config
|
||||||
self.requires_python = requires_python
|
self.requires_python = requires_python
|
||||||
self.prerelease_pythons = prerelease_pythons
|
self.prerelease_pythons = prerelease_pythons
|
||||||
|
|
||||||
@@ -112,30 +124,22 @@ class IdentifierSelector:
|
|||||||
if not self.requires_python.contains(version):
|
if not self.requires_python.contains(version):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
build_patterns = itertools.chain.from_iterable(
|
# filter out the prerelease pythons if self.prerelease_pythons is False
|
||||||
bracex.expand(p) for p in self.build_patterns
|
if not self.prerelease_pythons and selector_matches(
|
||||||
)
|
BuildSelector.PRERELEASE_SKIP, build_id
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
unexpanded_skip_patterns = self.skip_patterns.copy()
|
should_build = selector_matches(self.build_config, build_id)
|
||||||
|
should_skip = selector_matches(self.skip_config, build_id)
|
||||||
|
|
||||||
if not self.prerelease_pythons:
|
return should_build and not should_skip
|
||||||
# filter out the prerelease pythons, alongside the user-defined
|
|
||||||
# skip patterns
|
|
||||||
unexpanded_skip_patterns += BuildSelector.PRERELEASE_SKIP.split()
|
|
||||||
|
|
||||||
skip_patterns = itertools.chain.from_iterable(
|
|
||||||
bracex.expand(p) for p in unexpanded_skip_patterns
|
|
||||||
)
|
|
||||||
|
|
||||||
build: bool = any(fnmatch.fnmatch(build_id, pat) for pat in build_patterns)
|
|
||||||
skip: bool = any(fnmatch.fnmatch(build_id, pat) for pat in skip_patterns)
|
|
||||||
return build and not skip
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
result = f'{self.__class__.__name__}(build_config={" ".join(self.build_patterns)!r}'
|
result = f"{self.__class__.__name__}(build_config={self.build_config!r}"
|
||||||
|
|
||||||
if self.skip_patterns:
|
if self.skip_config:
|
||||||
result += f', skip_config={" ".join(self.skip_patterns)!r}'
|
result += f", skip_config={self.skip_config!r}"
|
||||||
if self.prerelease_pythons:
|
if self.prerelease_pythons:
|
||||||
result += ", prerelease_pythons=True"
|
result += ", prerelease_pythons=True"
|
||||||
|
|
||||||
@@ -222,32 +226,13 @@ class DependencyConstraints:
|
|||||||
return self.base_file_path
|
return self.base_file_path
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self.__class__.__name__}{self.base_file_path!r})"
|
return f"{self.__class__.__name__}({self.base_file_path!r})"
|
||||||
|
|
||||||
|
def __eq__(self, o: object) -> bool:
|
||||||
|
if not isinstance(o, DependencyConstraints):
|
||||||
|
return False
|
||||||
|
|
||||||
class BuildOptions(NamedTuple):
|
return self.base_file_path == o.base_file_path
|
||||||
package_dir: Path
|
|
||||||
output_dir: Path
|
|
||||||
build_selector: BuildSelector
|
|
||||||
architectures: Set[Architecture]
|
|
||||||
environment: ParsedEnvironment
|
|
||||||
before_all: str
|
|
||||||
before_build: Optional[str]
|
|
||||||
repair_command: str
|
|
||||||
manylinux_images: Optional[Dict[str, str]]
|
|
||||||
musllinux_images: Optional[Dict[str, str]]
|
|
||||||
dependency_constraints: Optional[DependencyConstraints]
|
|
||||||
test_command: Optional[str]
|
|
||||||
test_selector: TestSelector
|
|
||||||
before_test: Optional[str]
|
|
||||||
test_requires: List[str]
|
|
||||||
test_extras: str
|
|
||||||
build_verbosity: int
|
|
||||||
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):
|
||||||
|
|||||||
+40
-36
@@ -10,10 +10,10 @@ from zipfile import ZipFile
|
|||||||
from .architecture import Architecture
|
from .architecture import Architecture
|
||||||
from .environment import ParsedEnvironment
|
from .environment import ParsedEnvironment
|
||||||
from .logger import log
|
from .logger import log
|
||||||
|
from .options import Options
|
||||||
from .typing import PathOrStr, assert_never
|
from .typing import PathOrStr, assert_never
|
||||||
from .util import (
|
from .util import (
|
||||||
BuildFrontend,
|
BuildFrontend,
|
||||||
BuildOptions,
|
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
NonPlatformWheelError,
|
NonPlatformWheelError,
|
||||||
download,
|
download,
|
||||||
@@ -246,47 +246,51 @@ def setup_python(
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
def build(options: BuildOptions) -> None:
|
def build(options: Options) -> None:
|
||||||
temp_dir = Path(tempfile.mkdtemp(prefix="cibuildwheel"))
|
temp_dir = Path(tempfile.mkdtemp(prefix="cibuildwheel"))
|
||||||
built_wheel_dir = temp_dir / "built_wheel"
|
built_wheel_dir = temp_dir / "built_wheel"
|
||||||
repaired_wheel_dir = temp_dir / "repaired_wheel"
|
repaired_wheel_dir = temp_dir / "repaired_wheel"
|
||||||
|
|
||||||
|
python_configurations = get_python_configurations(
|
||||||
|
options.globals.build_selector, options.globals.architectures
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if options.before_all:
|
before_all_options_identifier = python_configurations[0].identifier
|
||||||
|
before_all_options = options.build_options(before_all_options_identifier)
|
||||||
|
|
||||||
|
if before_all_options.before_all:
|
||||||
log.step("Running before_all...")
|
log.step("Running before_all...")
|
||||||
env = options.environment.as_dictionary(prev_environment=os.environ)
|
env = before_all_options.environment.as_dictionary(prev_environment=os.environ)
|
||||||
before_all_prepared = prepare_command(
|
before_all_prepared = prepare_command(
|
||||||
options.before_all, project=".", package=options.package_dir
|
before_all_options.before_all, project=".", package=options.globals.package_dir
|
||||||
)
|
)
|
||||||
shell(before_all_prepared, env=env)
|
shell(before_all_prepared, env=env)
|
||||||
|
|
||||||
python_configurations = get_python_configurations(
|
|
||||||
options.build_selector, options.architectures
|
|
||||||
)
|
|
||||||
|
|
||||||
for config in python_configurations:
|
for config in python_configurations:
|
||||||
|
build_options = options.build_options(config.identifier)
|
||||||
log.build_start(config.identifier)
|
log.build_start(config.identifier)
|
||||||
|
|
||||||
dependency_constraint_flags: Sequence[PathOrStr] = []
|
dependency_constraint_flags: Sequence[PathOrStr] = []
|
||||||
if options.dependency_constraints:
|
if build_options.dependency_constraints:
|
||||||
dependency_constraint_flags = [
|
dependency_constraint_flags = [
|
||||||
"-c",
|
"-c",
|
||||||
options.dependency_constraints.get_for_python_version(config.version),
|
build_options.dependency_constraints.get_for_python_version(config.version),
|
||||||
]
|
]
|
||||||
|
|
||||||
# install Python
|
# install Python
|
||||||
env = setup_python(
|
env = setup_python(
|
||||||
config,
|
config,
|
||||||
dependency_constraint_flags,
|
dependency_constraint_flags,
|
||||||
options.environment,
|
build_options.environment,
|
||||||
options.build_frontend,
|
build_options.build_frontend,
|
||||||
)
|
)
|
||||||
|
|
||||||
# run the before_build command
|
# run the before_build command
|
||||||
if options.before_build:
|
if build_options.before_build:
|
||||||
log.step("Running before_build...")
|
log.step("Running before_build...")
|
||||||
before_build_prepared = prepare_command(
|
before_build_prepared = prepare_command(
|
||||||
options.before_build, project=".", package=options.package_dir
|
build_options.before_build, project=".", package=options.globals.package_dir
|
||||||
)
|
)
|
||||||
shell(before_build_prepared, env=env)
|
shell(before_build_prepared, env=env)
|
||||||
|
|
||||||
@@ -295,9 +299,9 @@ def build(options: BuildOptions) -> None:
|
|||||||
shutil.rmtree(built_wheel_dir)
|
shutil.rmtree(built_wheel_dir)
|
||||||
built_wheel_dir.mkdir(parents=True)
|
built_wheel_dir.mkdir(parents=True)
|
||||||
|
|
||||||
verbosity_flags = get_build_verbosity_extra_flags(options.build_verbosity)
|
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
|
||||||
|
|
||||||
if options.build_frontend == "pip":
|
if build_options.build_frontend == "pip":
|
||||||
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
|
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
|
||||||
# see https://github.com/pypa/cibuildwheel/pull/369
|
# see https://github.com/pypa/cibuildwheel/pull/369
|
||||||
call(
|
call(
|
||||||
@@ -306,18 +310,18 @@ def build(options: BuildOptions) -> None:
|
|||||||
"-m",
|
"-m",
|
||||||
"pip",
|
"pip",
|
||||||
"wheel",
|
"wheel",
|
||||||
options.package_dir.resolve(),
|
options.globals.package_dir.resolve(),
|
||||||
f"--wheel-dir={built_wheel_dir}",
|
f"--wheel-dir={built_wheel_dir}",
|
||||||
"--no-deps",
|
"--no-deps",
|
||||||
*get_build_verbosity_extra_flags(options.build_verbosity),
|
*get_build_verbosity_extra_flags(build_options.build_verbosity),
|
||||||
],
|
],
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
elif options.build_frontend == "build":
|
elif build_options.build_frontend == "build":
|
||||||
config_setting = " ".join(verbosity_flags)
|
config_setting = " ".join(verbosity_flags)
|
||||||
build_env = env.copy()
|
build_env = env.copy()
|
||||||
if options.dependency_constraints:
|
if build_options.dependency_constraints:
|
||||||
constraints_path = options.dependency_constraints.get_for_python_version(
|
constraints_path = build_options.dependency_constraints.get_for_python_version(
|
||||||
config.version
|
config.version
|
||||||
)
|
)
|
||||||
# Bug in pip <= 21.1.3 - we can't have a space in the
|
# Bug in pip <= 21.1.3 - we can't have a space in the
|
||||||
@@ -339,7 +343,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
"python",
|
"python",
|
||||||
"-m",
|
"-m",
|
||||||
"build",
|
"build",
|
||||||
options.package_dir,
|
build_options.package_dir,
|
||||||
"--wheel",
|
"--wheel",
|
||||||
f"--outdir={built_wheel_dir}",
|
f"--outdir={built_wheel_dir}",
|
||||||
f"--config-setting={config_setting}",
|
f"--config-setting={config_setting}",
|
||||||
@@ -347,7 +351,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
env=build_env,
|
env=build_env,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert_never(options.build_frontend)
|
assert_never(build_options.build_frontend)
|
||||||
|
|
||||||
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
built_wheel = next(built_wheel_dir.glob("*.whl"))
|
||||||
|
|
||||||
@@ -359,10 +363,10 @@ def build(options: BuildOptions) -> None:
|
|||||||
if built_wheel.name.endswith("none-any.whl"):
|
if built_wheel.name.endswith("none-any.whl"):
|
||||||
raise NonPlatformWheelError()
|
raise NonPlatformWheelError()
|
||||||
|
|
||||||
if options.repair_command:
|
if build_options.repair_command:
|
||||||
log.step("Repairing wheel...")
|
log.step("Repairing wheel...")
|
||||||
repair_command_prepared = prepare_command(
|
repair_command_prepared = prepare_command(
|
||||||
options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
|
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
|
||||||
)
|
)
|
||||||
shell(repair_command_prepared, env=env)
|
shell(repair_command_prepared, env=env)
|
||||||
else:
|
else:
|
||||||
@@ -370,7 +374,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
|
|
||||||
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
|
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
|
||||||
|
|
||||||
if options.test_command and options.test_selector(config.identifier):
|
if build_options.test_command and options.globals.test_selector(config.identifier):
|
||||||
log.step("Testing wheel...")
|
log.step("Testing wheel...")
|
||||||
# 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.
|
||||||
@@ -392,31 +396,31 @@ def build(options: BuildOptions) -> None:
|
|||||||
# check that we are using the Python from the virtual environment
|
# check that we are using the Python from the virtual environment
|
||||||
call(["where", "python"], env=virtualenv_env)
|
call(["where", "python"], env=virtualenv_env)
|
||||||
|
|
||||||
if options.before_test:
|
if build_options.before_test:
|
||||||
before_test_prepared = prepare_command(
|
before_test_prepared = prepare_command(
|
||||||
options.before_test,
|
build_options.before_test,
|
||||||
project=".",
|
project=".",
|
||||||
package=options.package_dir,
|
package=build_options.package_dir,
|
||||||
)
|
)
|
||||||
shell(before_test_prepared, env=virtualenv_env)
|
shell(before_test_prepared, env=virtualenv_env)
|
||||||
|
|
||||||
# install the wheel
|
# install the wheel
|
||||||
call(
|
call(
|
||||||
["pip", "install", str(repaired_wheel) + options.test_extras],
|
["pip", "install", str(repaired_wheel) + build_options.test_extras],
|
||||||
env=virtualenv_env,
|
env=virtualenv_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
# test the wheel
|
# test the wheel
|
||||||
if options.test_requires:
|
if build_options.test_requires:
|
||||||
call(["pip", "install"] + options.test_requires, env=virtualenv_env)
|
call(["pip", "install"] + build_options.test_requires, env=virtualenv_env)
|
||||||
|
|
||||||
# run the tests from c:\, with an absolute path in the command
|
# run the tests from c:\, with an absolute path in the command
|
||||||
# (this ensures that Python runs the tests against the installed wheel
|
# (this ensures that Python runs the tests against the installed wheel
|
||||||
# and not the repo code)
|
# and not the repo code)
|
||||||
test_command_prepared = prepare_command(
|
test_command_prepared = prepare_command(
|
||||||
options.test_command,
|
build_options.test_command,
|
||||||
project=Path(".").resolve(),
|
project=Path(".").resolve(),
|
||||||
package=options.package_dir.resolve(),
|
package=options.globals.package_dir.resolve(),
|
||||||
)
|
)
|
||||||
shell(test_command_prepared, cwd="c:\\", env=virtualenv_env)
|
shell(test_command_prepared, cwd="c:\\", env=virtualenv_env)
|
||||||
|
|
||||||
@@ -424,7 +428,7 @@ def build(options: BuildOptions) -> None:
|
|||||||
shutil.rmtree(venv_dir)
|
shutil.rmtree(venv_dir)
|
||||||
|
|
||||||
# we're all done here; move it to output (remove if already exists)
|
# we're all done here; move it to output (remove if already exists)
|
||||||
shutil.move(str(repaired_wheel), options.output_dir)
|
shutil.move(str(repaired_wheel), build_options.output_dir)
|
||||||
log.build_end()
|
log.build_end()
|
||||||
except subprocess.CalledProcessError as error:
|
except subprocess.CalledProcessError as error:
|
||||||
log.step_end_with_error(
|
log.step_end_with_error(
|
||||||
|
|||||||
+58
-1
@@ -107,6 +107,56 @@ The complete set of defaults for the current version of cibuildwheel are shown b
|
|||||||
not want to change a `pyproject.toml` file. You can specify a different file to
|
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.
|
use with `--config-file` on the command line, as well.
|
||||||
|
|
||||||
|
### Configuration overrides {: #overrides }
|
||||||
|
|
||||||
|
One feature specific to the configuration files is the ability to override
|
||||||
|
settings based on selectors. To use, add a ``tool.cibuildwheel.overrides``
|
||||||
|
array, and specify a ``select`` string. Then any options you set will only
|
||||||
|
apply to items that match that selector. These are applied in order, with later
|
||||||
|
matches overriding earlier ones if multiple selectors match. Environment
|
||||||
|
variables always override static configuration.
|
||||||
|
|
||||||
|
A few of the options below have special handling in overrides. A different
|
||||||
|
`before-all` will trigger a new docker launch on Linux, and cannot be
|
||||||
|
overridden on macOS or Windows. Overriding the image on linux will also
|
||||||
|
generate new docker launches, one per image. Some commands are not supported;
|
||||||
|
`output-dir`, build/skip/test_skip selectors, and architectures cannot be
|
||||||
|
overridden.
|
||||||
|
|
||||||
|
##### Examples:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.cibuildwheel.linux]
|
||||||
|
before-all = "yum install mylib"
|
||||||
|
test-command = "echo 'installed'"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "*-musllinux*"
|
||||||
|
before-all = "apk add mylib"
|
||||||
|
```
|
||||||
|
|
||||||
|
This example will override the before-all command on musllinux only, but will
|
||||||
|
still run the test-command. Note the double brackets, this is an array in TOML,
|
||||||
|
which means it can be given multiple times.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
# Normal options, etc.
|
||||||
|
manylinux-x86_64-image = "manylinux2010"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp36-*"
|
||||||
|
manylinux-x86_64-image = "manylinux1"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp310-*"
|
||||||
|
manylinux-x86_64-image = "manylinux2014"
|
||||||
|
```
|
||||||
|
|
||||||
|
This example will build CPython 3.6 wheels on manylinux1, CPython 3.7-3.9
|
||||||
|
images on manylinux2010, and CPython 3.10 wheels on manylinux2014.
|
||||||
|
|
||||||
|
|
||||||
## Options summary
|
## Options summary
|
||||||
|
|
||||||
<div class="options-toc"></div>
|
<div class="options-toc"></div>
|
||||||
@@ -305,7 +355,8 @@ If not listed above, `auto` is the same as `native`.
|
|||||||
Platform-specific environment variables are also available:<br/>
|
Platform-specific environment variables are also available:<br/>
|
||||||
`CIBW_ARCHS_MACOS` | `CIBW_ARCHS_WINDOWS` | `CIBW_ARCHS_LINUX`
|
`CIBW_ARCHS_MACOS` | `CIBW_ARCHS_WINDOWS` | `CIBW_ARCHS_LINUX`
|
||||||
|
|
||||||
This option can also be set using the [command-line option](#command-line) `--archs`.
|
This option can also be set using the [command-line option](#command-line)
|
||||||
|
`--archs`. This option cannot be set in an `overrides` section in `pyproject.toml`.
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
@@ -564,6 +615,10 @@ The placeholder `{package}` can be used here; it will be replaced by the path to
|
|||||||
|
|
||||||
On Windows and macOS, the version of Python available inside `CIBW_BEFORE_ALL` is whatever is available on the host machine. On Linux, a modern Python version is available on PATH.
|
On Windows and macOS, the version of Python available inside `CIBW_BEFORE_ALL` is whatever is available on the host machine. On Linux, a modern Python version is available on PATH.
|
||||||
|
|
||||||
|
This option has special behavior in the overrides section in `pyproject.toml`.
|
||||||
|
On linux, overriding it triggers a new docker launch. It cannot be overridden
|
||||||
|
on macOS and Windows.
|
||||||
|
|
||||||
Platform-specific environment variables also available:<br/>
|
Platform-specific environment variables also available:<br/>
|
||||||
`CIBW_BEFORE_ALL_MACOS` | `CIBW_BEFORE_ALL_WINDOWS` | `CIBW_BEFORE_ALL_LINUX`
|
`CIBW_BEFORE_ALL_MACOS` | `CIBW_BEFORE_ALL_WINDOWS` | `CIBW_BEFORE_ALL_LINUX`
|
||||||
|
|
||||||
@@ -1144,6 +1199,8 @@ This will skip testing on any identifiers that match the given skip patterns (se
|
|||||||
|
|
||||||
With macOS `universal2` wheels, you can also skip the individual archs inside the wheel using an `:arch` suffix. For example, `cp39-macosx_universal2:x86_64` or `cp39-macosx_universal2:arm64`.
|
With macOS `universal2` wheels, you can also skip the individual archs inside the wheel using an `:arch` suffix. For example, `cp39-macosx_universal2:x86_64` or `cp39-macosx_universal2:arm64`.
|
||||||
|
|
||||||
|
This option is not supported in the overrides section in `pyproject.toml`.
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
!!! tab examples "Environment variables"
|
!!! tab examples "Environment variables"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ install_requires =
|
|||||||
certifi
|
certifi
|
||||||
packaging
|
packaging
|
||||||
tomli
|
tomli
|
||||||
|
dataclasses;python_version < '3.7'
|
||||||
typing_extensions;python_version < '3.8'
|
typing_extensions;python_version < '3.8'
|
||||||
python_requires = >=3.6
|
python_requires = >=3.6
|
||||||
include_package_data = True
|
include_package_data = True
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
MOCK_PACKAGE_DIR = Path("some_package_dir")
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addoption("--run-docker", action="store_true", default=False, help="run docker tests")
|
parser.addoption("--run-docker", action="store_true", default=False, help="run docker tests")
|
||||||
@@ -17,3 +22,22 @@ def pytest_collection_modifyitems(config, items):
|
|||||||
for item in items:
|
for item in items:
|
||||||
if "docker" in item.keywords:
|
if "docker" in item.keywords:
|
||||||
item.add_marker(skip_docker)
|
item.add_marker(skip_docker)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fake_package_dir(monkeypatch):
|
||||||
|
"""
|
||||||
|
Monkey-patch enough for the main() function to run
|
||||||
|
"""
|
||||||
|
real_path_exists = Path.exists
|
||||||
|
|
||||||
|
def mock_path_exists(path):
|
||||||
|
if path == MOCK_PACKAGE_DIR / "setup.py":
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return real_path_exists(path)
|
||||||
|
|
||||||
|
args = ["cibuildwheel", str(MOCK_PACKAGE_DIR)]
|
||||||
|
monkeypatch.setattr(Path, "exists", mock_path_exists)
|
||||||
|
monkeypatch.setattr(sys, "argv", args)
|
||||||
|
return args
|
||||||
|
|||||||
@@ -25,19 +25,19 @@ elif pm == "s390x":
|
|||||||
|
|
||||||
@pytest.mark.docker
|
@pytest.mark.docker
|
||||||
def test_simple():
|
def test_simple():
|
||||||
with DockerContainer(DEFAULT_IMAGE) as container:
|
with DockerContainer(docker_image=DEFAULT_IMAGE) as container:
|
||||||
assert container.call(["echo", "hello"], capture_output=True) == "hello\n"
|
assert container.call(["echo", "hello"], capture_output=True) == "hello\n"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.docker
|
@pytest.mark.docker
|
||||||
def test_no_lf():
|
def test_no_lf():
|
||||||
with DockerContainer(DEFAULT_IMAGE) as container:
|
with DockerContainer(docker_image=DEFAULT_IMAGE) as container:
|
||||||
assert container.call(["printf", "hello"], capture_output=True) == "hello"
|
assert container.call(["printf", "hello"], capture_output=True) == "hello"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.docker
|
@pytest.mark.docker
|
||||||
def test_environment():
|
def test_environment():
|
||||||
with DockerContainer(DEFAULT_IMAGE) as container:
|
with DockerContainer(docker_image=DEFAULT_IMAGE) as container:
|
||||||
assert (
|
assert (
|
||||||
container.call(
|
container.call(
|
||||||
["sh", "-c", "echo $TEST_VAR"], env={"TEST_VAR": "1"}, capture_output=True
|
["sh", "-c", "echo $TEST_VAR"], env={"TEST_VAR": "1"}, capture_output=True
|
||||||
@@ -48,14 +48,16 @@ def test_environment():
|
|||||||
|
|
||||||
@pytest.mark.docker
|
@pytest.mark.docker
|
||||||
def test_cwd():
|
def test_cwd():
|
||||||
with DockerContainer(DEFAULT_IMAGE, cwd="/cibuildwheel/working_directory") as container:
|
with DockerContainer(
|
||||||
|
docker_image=DEFAULT_IMAGE, cwd="/cibuildwheel/working_directory"
|
||||||
|
) as container:
|
||||||
assert container.call(["pwd"], capture_output=True) == "/cibuildwheel/working_directory\n"
|
assert container.call(["pwd"], capture_output=True) == "/cibuildwheel/working_directory\n"
|
||||||
assert container.call(["pwd"], capture_output=True, cwd="/opt") == "/opt\n"
|
assert container.call(["pwd"], capture_output=True, cwd="/opt") == "/opt\n"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.docker
|
@pytest.mark.docker
|
||||||
def test_container_removed():
|
def test_container_removed():
|
||||||
with DockerContainer(DEFAULT_IMAGE) as container:
|
with DockerContainer(docker_image=DEFAULT_IMAGE) as container:
|
||||||
docker_containers_listing = subprocess.run(
|
docker_containers_listing = subprocess.run(
|
||||||
"docker container ls",
|
"docker container ls",
|
||||||
shell=True,
|
shell=True,
|
||||||
@@ -88,7 +90,7 @@ def test_large_environment():
|
|||||||
"d": "0" * long_env_var_length,
|
"d": "0" * long_env_var_length,
|
||||||
}
|
}
|
||||||
|
|
||||||
with DockerContainer(DEFAULT_IMAGE) as container:
|
with DockerContainer(docker_image=DEFAULT_IMAGE) as container:
|
||||||
# check the length of d
|
# check the length of d
|
||||||
assert (
|
assert (
|
||||||
container.call(["sh", "-c", "echo ${#d}"], env=large_environment, capture_output=True)
|
container.call(["sh", "-c", "echo ${#d}"], env=large_environment, capture_output=True)
|
||||||
@@ -98,7 +100,7 @@ def test_large_environment():
|
|||||||
|
|
||||||
@pytest.mark.docker
|
@pytest.mark.docker
|
||||||
def test_binary_output():
|
def test_binary_output():
|
||||||
with DockerContainer(DEFAULT_IMAGE) as container:
|
with DockerContainer(docker_image=DEFAULT_IMAGE) as container:
|
||||||
# note: the below embedded snippets are in python2
|
# note: the below embedded snippets are in python2
|
||||||
|
|
||||||
# check that we can pass though arbitrary binary data without erroring
|
# check that we can pass though arbitrary binary data without erroring
|
||||||
@@ -149,7 +151,7 @@ def test_binary_output():
|
|||||||
|
|
||||||
@pytest.mark.docker
|
@pytest.mark.docker
|
||||||
def test_file_operations(tmp_path: Path):
|
def test_file_operations(tmp_path: Path):
|
||||||
with DockerContainer(DEFAULT_IMAGE) as container:
|
with DockerContainer(docker_image=DEFAULT_IMAGE) as container:
|
||||||
# test copying a file in
|
# test copying a file in
|
||||||
test_binary_data = bytes(random.randrange(256) for _ in range(1000))
|
test_binary_data = bytes(random.randrange(256) for _ in range(1000))
|
||||||
original_test_file = tmp_path / "test.dat"
|
original_test_file = tmp_path / "test.dat"
|
||||||
@@ -165,7 +167,7 @@ def test_file_operations(tmp_path: Path):
|
|||||||
|
|
||||||
@pytest.mark.docker
|
@pytest.mark.docker
|
||||||
def test_dir_operations(tmp_path: Path):
|
def test_dir_operations(tmp_path: Path):
|
||||||
with DockerContainer(DEFAULT_IMAGE) as container:
|
with DockerContainer(docker_image=DEFAULT_IMAGE) as container:
|
||||||
test_binary_data = bytes(random.randrange(256) for _ in range(1000))
|
test_binary_data = bytes(random.randrange(256) for _ in range(1000))
|
||||||
original_test_file = tmp_path / "test.dat"
|
original_test_file = tmp_path / "test.dat"
|
||||||
original_test_file.write_bytes(test_binary_data)
|
original_test_file.write_bytes(test_binary_data)
|
||||||
@@ -195,6 +197,6 @@ def test_dir_operations(tmp_path: Path):
|
|||||||
|
|
||||||
@pytest.mark.docker
|
@pytest.mark.docker
|
||||||
def test_environment_executor():
|
def test_environment_executor():
|
||||||
with DockerContainer(DEFAULT_IMAGE) as container:
|
with DockerContainer(docker_image=DEFAULT_IMAGE) as container:
|
||||||
assignment = EnvironmentAssignment("TEST=$(echo 42)")
|
assignment = EnvironmentAssignment("TEST=$(echo 42)")
|
||||||
assert assignment.evaluated_value({}, container.environment_executor) == "42"
|
assert assignment.evaluated_value({}, container.environment_executor) == "42"
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import textwrap
|
||||||
|
from pathlib import Path
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
import cibuildwheel.docker_container
|
||||||
|
import cibuildwheel.linux
|
||||||
|
from cibuildwheel.options import Options
|
||||||
|
|
||||||
|
from .utils import get_default_command_line_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_linux_container_split(tmp_path: Path, monkeypatch):
|
||||||
|
"""
|
||||||
|
Tests splitting linux builds by docker image and before_all
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = get_default_command_line_arguments()
|
||||||
|
args.platform = "linux"
|
||||||
|
|
||||||
|
(tmp_path / "pyproject.toml").write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
manylinux-x86_64-image = "normal_docker_image"
|
||||||
|
manylinux-i686-image = "normal_docker_image"
|
||||||
|
build = "*-manylinux_x86_64"
|
||||||
|
skip = "pp*"
|
||||||
|
archs = "x86_64 i686"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp{38,39,310}-*"
|
||||||
|
manylinux-x86_64-image = "other_docker_image"
|
||||||
|
manylinux-i686-image = "other_docker_image"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp39-*"
|
||||||
|
before-all = "echo 'a cp39-only command'"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
options = Options("linux", command_line_arguments=args)
|
||||||
|
|
||||||
|
python_configurations = cibuildwheel.linux.get_python_configurations(
|
||||||
|
options.globals.build_selector, options.globals.architectures
|
||||||
|
)
|
||||||
|
|
||||||
|
build_steps = list(cibuildwheel.linux.get_build_steps(options, python_configurations))
|
||||||
|
|
||||||
|
# helper functions to extract test info
|
||||||
|
def identifiers(step):
|
||||||
|
return [c.identifier for c in step.platform_configs]
|
||||||
|
|
||||||
|
def before_alls(step):
|
||||||
|
return [options.build_options(c.identifier).before_all for c in step.platform_configs]
|
||||||
|
|
||||||
|
pprint(build_steps)
|
||||||
|
|
||||||
|
assert build_steps[0].docker_image == "normal_docker_image"
|
||||||
|
assert identifiers(build_steps[0]) == ["cp36-manylinux_x86_64", "cp37-manylinux_x86_64"]
|
||||||
|
assert before_alls(build_steps[0]) == ["", ""]
|
||||||
|
|
||||||
|
assert build_steps[1].docker_image == "other_docker_image"
|
||||||
|
assert identifiers(build_steps[1]) == ["cp38-manylinux_x86_64", "cp310-manylinux_x86_64"]
|
||||||
|
assert before_alls(build_steps[1]) == ["", ""]
|
||||||
|
|
||||||
|
assert build_steps[2].docker_image == "other_docker_image"
|
||||||
|
assert identifiers(build_steps[2]) == ["cp39-manylinux_x86_64"]
|
||||||
|
assert before_alls(build_steps[2]) == ["echo 'a cp39-only command'"]
|
||||||
@@ -15,9 +15,6 @@ class ArgsInterceptor:
|
|||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
|
||||||
MOCK_PACKAGE_DIR = Path("some_package_dir")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_protection(monkeypatch):
|
def mock_protection(monkeypatch):
|
||||||
"""
|
"""
|
||||||
@@ -41,22 +38,8 @@ def mock_protection(monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def fake_package_dir(monkeypatch):
|
def fake_package_dir_autouse(fake_package_dir):
|
||||||
"""
|
pass
|
||||||
Monkey-patch enough for the main() function to run
|
|
||||||
"""
|
|
||||||
real_path_exists = Path.exists
|
|
||||||
|
|
||||||
def mock_path_exists(path):
|
|
||||||
if path == MOCK_PACKAGE_DIR / "setup.py":
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return real_path_exists(path)
|
|
||||||
|
|
||||||
args = ["cibuildwheel", str(MOCK_PACKAGE_DIR)]
|
|
||||||
monkeypatch.setattr(Path, "exists", mock_path_exists)
|
|
||||||
monkeypatch.setattr(sys, "argv", args)
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ from fnmatch import fnmatch
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import tomli
|
||||||
|
|
||||||
from cibuildwheel.__main__ import main
|
from cibuildwheel.__main__ import main
|
||||||
from cibuildwheel.environment import ParsedEnvironment
|
from cibuildwheel.environment import ParsedEnvironment
|
||||||
from cibuildwheel.util import BuildSelector
|
from cibuildwheel.options import BuildOptions, _get_pinned_docker_images
|
||||||
|
from cibuildwheel.util import BuildSelector, resources_dir
|
||||||
|
|
||||||
# CIBW_PLATFORM is tested in main_platform_test.py
|
# CIBW_PLATFORM is tested in main_platform_test.py
|
||||||
|
|
||||||
@@ -18,13 +20,13 @@ def test_output_dir(platform, intercepted_build_args, monkeypatch):
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR
|
assert intercepted_build_args.args[0].globals.output_dir == OUTPUT_DIR
|
||||||
|
|
||||||
|
|
||||||
def test_output_dir_default(platform, intercepted_build_args, monkeypatch):
|
def test_output_dir_default(platform, intercepted_build_args, monkeypatch):
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].output_dir == Path("wheelhouse")
|
assert intercepted_build_args.args[0].globals.output_dir == Path("wheelhouse")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("also_set_environment", [False, True])
|
@pytest.mark.parametrize("also_set_environment", [False, True])
|
||||||
@@ -37,7 +39,7 @@ def test_output_dir_argument(also_set_environment, platform, intercepted_build_a
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].output_dir == OUTPUT_DIR
|
assert intercepted_build_args.args[0].globals.output_dir == OUTPUT_DIR
|
||||||
|
|
||||||
|
|
||||||
def test_build_selector(platform, intercepted_build_args, monkeypatch, allow_empty):
|
def test_build_selector(platform, intercepted_build_args, monkeypatch, allow_empty):
|
||||||
@@ -49,7 +51,7 @@ def test_build_selector(platform, intercepted_build_args, monkeypatch, allow_emp
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
intercepted_build_selector = intercepted_build_args.args[0].build_selector
|
intercepted_build_selector = intercepted_build_args.args[0].globals.build_selector
|
||||||
assert isinstance(intercepted_build_selector, BuildSelector)
|
assert isinstance(intercepted_build_selector, BuildSelector)
|
||||||
assert intercepted_build_selector("build24-this")
|
assert intercepted_build_selector("build24-this")
|
||||||
assert not intercepted_build_selector("skip65-that")
|
assert not intercepted_build_selector("skip65-that")
|
||||||
@@ -97,10 +99,15 @@ def test_manylinux_images(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
|
|
||||||
if platform == "linux":
|
if platform == "linux":
|
||||||
assert fnmatch(intercepted_build_args.args[0].manylinux_images[architecture], full_image)
|
assert fnmatch(
|
||||||
|
build_options.manylinux_images[architecture],
|
||||||
|
full_image,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert intercepted_build_args.args[0].manylinux_images is None
|
assert build_options.manylinux_images is None
|
||||||
|
|
||||||
|
|
||||||
def get_default_repair_command(platform):
|
def get_default_repair_command(platform):
|
||||||
@@ -128,8 +135,10 @@ def test_repair_command(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
|
|
||||||
expected_repair = repair_command or get_default_repair_command(platform)
|
expected_repair = repair_command or get_default_repair_command(platform)
|
||||||
assert intercepted_build_args.args[0].repair_command == expected_repair
|
assert build_options.repair_command == expected_repair
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -147,7 +156,9 @@ def test_environment(environment, platform_specific, platform, intercepted_build
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
intercepted_environment = intercepted_build_args.args[0].environment
|
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
|
intercepted_environment = build_options.environment
|
||||||
|
|
||||||
assert isinstance(intercepted_environment, ParsedEnvironment)
|
assert isinstance(intercepted_environment, ParsedEnvironment)
|
||||||
assert intercepted_environment.as_dictionary(prev_environment={}) == environment
|
assert intercepted_environment.as_dictionary(prev_environment={}) == environment
|
||||||
|
|
||||||
@@ -166,7 +177,9 @@ def test_test_requires(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].test_requires == (test_requires or "").split()
|
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
|
|
||||||
|
assert build_options.test_requires == (test_requires or "").split()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("test_extras", [None, "extras"])
|
@pytest.mark.parametrize("test_extras", [None, "extras"])
|
||||||
@@ -181,9 +194,9 @@ def test_test_extras(test_extras, platform_specific, platform, intercepted_build
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].test_extras == (
|
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
"[" + test_extras + "]" if test_extras else ""
|
|
||||||
)
|
assert build_options.test_extras == ("[" + test_extras + "]" if test_extras else "")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("test_command", [None, "test --command"])
|
@pytest.mark.parametrize("test_command", [None, "test --command"])
|
||||||
@@ -200,7 +213,9 @@ def test_test_command(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].test_command == (test_command or "")
|
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
|
|
||||||
|
assert build_options.test_command == (test_command or "")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("before_build", [None, "before --build"])
|
@pytest.mark.parametrize("before_build", [None, "before --build"])
|
||||||
@@ -217,7 +232,8 @@ def test_before_build(
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].before_build == (before_build or "")
|
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
|
assert build_options.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])
|
||||||
@@ -233,9 +249,10 @@ def test_build_verbosity(
|
|||||||
monkeypatch.setenv("CIBW_BUILD_VERBOSITY", str(build_verbosity))
|
monkeypatch.setenv("CIBW_BUILD_VERBOSITY", str(build_verbosity))
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
|
|
||||||
expected_verbosity = max(-3, min(3, int(build_verbosity or 0)))
|
expected_verbosity = max(-3, min(3, int(build_verbosity or 0)))
|
||||||
assert intercepted_build_args.args[0].build_verbosity == expected_verbosity
|
assert build_options.build_verbosity == expected_verbosity
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -286,4 +303,36 @@ def test_before_all(before_all, platform_specific, platform, intercepted_build_a
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].before_all == (before_all or "")
|
build_options = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
|
|
||||||
|
assert build_options.before_all == (before_all or "")
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults(platform, intercepted_build_args):
|
||||||
|
main()
|
||||||
|
|
||||||
|
build_options: BuildOptions = intercepted_build_args.args[0].build_options(identifier=None)
|
||||||
|
defaults_config_path = resources_dir / "defaults.toml"
|
||||||
|
with defaults_config_path.open("rb") as f:
|
||||||
|
defaults_toml = tomli.load(f)
|
||||||
|
|
||||||
|
root_defaults = defaults_toml["tool"]["cibuildwheel"]
|
||||||
|
platform_defaults = defaults_toml["tool"]["cibuildwheel"][platform]
|
||||||
|
|
||||||
|
defaults = {}
|
||||||
|
defaults.update(root_defaults)
|
||||||
|
defaults.update(platform_defaults)
|
||||||
|
|
||||||
|
# test a few options
|
||||||
|
assert build_options.before_all == defaults["before-all"]
|
||||||
|
repair_wheel_default = defaults["repair-wheel-command"]
|
||||||
|
if isinstance(repair_wheel_default, list):
|
||||||
|
repair_wheel_default = " && ".join(repair_wheel_default)
|
||||||
|
assert build_options.repair_command == repair_wheel_default
|
||||||
|
assert build_options.build_frontend == defaults["build-frontend"]
|
||||||
|
|
||||||
|
if platform == "linux":
|
||||||
|
assert build_options.manylinux_images
|
||||||
|
pinned_images = _get_pinned_docker_images()
|
||||||
|
default_x86_64_image = pinned_images["x86_64"][defaults["manylinux-x86_64-image"]]
|
||||||
|
assert build_options.manylinux_images["x86_64"] == default_x86_64_image
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import pytest
|
|||||||
from cibuildwheel.__main__ import main
|
from cibuildwheel.__main__ import main
|
||||||
from cibuildwheel.architecture import Architecture
|
from cibuildwheel.architecture import Architecture
|
||||||
|
|
||||||
from .conftest import MOCK_PACKAGE_DIR
|
from ..conftest import MOCK_PACKAGE_DIR
|
||||||
|
|
||||||
|
|
||||||
def test_unknown_platform_non_ci(monkeypatch, capsys):
|
def test_unknown_platform_non_ci(monkeypatch, capsys):
|
||||||
@@ -58,26 +58,29 @@ def test_platform_argument(platform, intercepted_build_args, monkeypatch):
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR
|
options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
|
assert options.globals.package_dir == MOCK_PACKAGE_DIR
|
||||||
|
|
||||||
|
|
||||||
def test_platform_environment(platform, intercepted_build_args, monkeypatch):
|
def test_platform_environment(platform, intercepted_build_args, monkeypatch):
|
||||||
main()
|
main()
|
||||||
|
options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
assert intercepted_build_args.args[0].package_dir == MOCK_PACKAGE_DIR
|
assert options.globals.package_dir == MOCK_PACKAGE_DIR
|
||||||
|
|
||||||
|
|
||||||
def test_archs_default(platform, intercepted_build_args, monkeypatch):
|
def test_archs_default(platform, intercepted_build_args, monkeypatch):
|
||||||
|
|
||||||
main()
|
main()
|
||||||
build_options = intercepted_build_args.args[0]
|
options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
if platform == "linux":
|
if platform == "linux":
|
||||||
assert build_options.architectures == {Architecture.x86_64, Architecture.i686}
|
assert options.globals.architectures == {Architecture.x86_64, Architecture.i686}
|
||||||
elif platform == "windows":
|
elif platform == "windows":
|
||||||
assert build_options.architectures == {Architecture.AMD64, Architecture.x86}
|
assert options.globals.architectures == {Architecture.AMD64, Architecture.x86}
|
||||||
else:
|
else:
|
||||||
assert build_options.architectures == {Architecture.x86_64}
|
assert options.globals.architectures == {Architecture.x86_64}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("use_env_var", [False, True])
|
@pytest.mark.parametrize("use_env_var", [False, True])
|
||||||
@@ -96,8 +99,8 @@ def test_archs_argument(platform, intercepted_build_args, monkeypatch, use_env_v
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
main()
|
main()
|
||||||
build_options = intercepted_build_args.args[0]
|
options = intercepted_build_args.args[0]
|
||||||
assert build_options.architectures == {Architecture.ppc64le}
|
assert options.globals.architectures == {Architecture.ppc64le}
|
||||||
|
|
||||||
|
|
||||||
def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch):
|
def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch):
|
||||||
@@ -107,38 +110,38 @@ def test_archs_platform_specific(platform, intercepted_build_args, monkeypatch):
|
|||||||
monkeypatch.setenv("CIBW_ARCHS_MACOS", "x86_64")
|
monkeypatch.setenv("CIBW_ARCHS_MACOS", "x86_64")
|
||||||
|
|
||||||
main()
|
main()
|
||||||
build_options = intercepted_build_args.args[0]
|
options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
if platform == "linux":
|
if platform == "linux":
|
||||||
assert build_options.architectures == {Architecture.ppc64le}
|
assert options.globals.architectures == {Architecture.ppc64le}
|
||||||
elif platform == "windows":
|
elif platform == "windows":
|
||||||
assert build_options.architectures == {Architecture.x86}
|
assert options.globals.architectures == {Architecture.x86}
|
||||||
elif platform == "macos":
|
elif platform == "macos":
|
||||||
assert build_options.architectures == {Architecture.x86_64}
|
assert options.globals.architectures == {Architecture.x86_64}
|
||||||
|
|
||||||
|
|
||||||
def test_archs_platform_native(platform, intercepted_build_args, monkeypatch):
|
def test_archs_platform_native(platform, intercepted_build_args, monkeypatch):
|
||||||
monkeypatch.setenv("CIBW_ARCHS", "native")
|
monkeypatch.setenv("CIBW_ARCHS", "native")
|
||||||
|
|
||||||
main()
|
main()
|
||||||
build_options = intercepted_build_args.args[0]
|
options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
if platform in {"linux", "macos"}:
|
if platform in {"linux", "macos"}:
|
||||||
assert build_options.architectures == {Architecture.x86_64}
|
assert options.globals.architectures == {Architecture.x86_64}
|
||||||
elif platform == "windows":
|
elif platform == "windows":
|
||||||
assert build_options.architectures == {Architecture.AMD64}
|
assert options.globals.architectures == {Architecture.AMD64}
|
||||||
|
|
||||||
|
|
||||||
def test_archs_platform_auto64(platform, intercepted_build_args, monkeypatch):
|
def test_archs_platform_auto64(platform, intercepted_build_args, monkeypatch):
|
||||||
monkeypatch.setenv("CIBW_ARCHS", "auto64")
|
monkeypatch.setenv("CIBW_ARCHS", "auto64")
|
||||||
|
|
||||||
main()
|
main()
|
||||||
build_options = intercepted_build_args.args[0]
|
options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
if platform in {"linux", "macos"}:
|
if platform in {"linux", "macos"}:
|
||||||
assert build_options.architectures == {Architecture.x86_64}
|
assert options.globals.architectures == {Architecture.x86_64}
|
||||||
elif platform == "windows":
|
elif platform == "windows":
|
||||||
assert build_options.architectures == {Architecture.AMD64}
|
assert options.globals.architectures == {Architecture.AMD64}
|
||||||
|
|
||||||
|
|
||||||
def test_archs_platform_auto32(platform, intercepted_build_args, monkeypatch):
|
def test_archs_platform_auto32(platform, intercepted_build_args, monkeypatch):
|
||||||
@@ -152,22 +155,22 @@ def test_archs_platform_auto32(platform, intercepted_build_args, monkeypatch):
|
|||||||
else:
|
else:
|
||||||
main()
|
main()
|
||||||
|
|
||||||
build_options = intercepted_build_args.args[0]
|
options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
if platform == "linux":
|
if platform == "linux":
|
||||||
assert build_options.architectures == {Architecture.i686}
|
assert options.globals.architectures == {Architecture.i686}
|
||||||
elif platform == "windows":
|
elif platform == "windows":
|
||||||
assert build_options.architectures == {Architecture.x86}
|
assert options.globals.architectures == {Architecture.x86}
|
||||||
|
|
||||||
|
|
||||||
def test_archs_platform_all(platform, intercepted_build_args, monkeypatch):
|
def test_archs_platform_all(platform, intercepted_build_args, monkeypatch):
|
||||||
monkeypatch.setenv("CIBW_ARCHS", "all")
|
monkeypatch.setenv("CIBW_ARCHS", "all")
|
||||||
|
|
||||||
main()
|
main()
|
||||||
build_options = intercepted_build_args.args[0]
|
options = intercepted_build_args.args[0]
|
||||||
|
|
||||||
if platform == "linux":
|
if platform == "linux":
|
||||||
assert build_options.architectures == {
|
assert options.globals.architectures == {
|
||||||
Architecture.x86_64,
|
Architecture.x86_64,
|
||||||
Architecture.i686,
|
Architecture.i686,
|
||||||
Architecture.aarch64,
|
Architecture.aarch64,
|
||||||
@@ -175,9 +178,9 @@ def test_archs_platform_all(platform, intercepted_build_args, monkeypatch):
|
|||||||
Architecture.s390x,
|
Architecture.s390x,
|
||||||
}
|
}
|
||||||
elif platform == "windows":
|
elif platform == "windows":
|
||||||
assert build_options.architectures == {Architecture.x86, Architecture.AMD64}
|
assert options.globals.architectures == {Architecture.x86, Architecture.AMD64}
|
||||||
elif platform == "macos":
|
elif platform == "macos":
|
||||||
assert build_options.architectures == {
|
assert options.globals.architectures == {
|
||||||
Architecture.x86_64,
|
Architecture.x86_64,
|
||||||
Architecture.arm64,
|
Architecture.arm64,
|
||||||
Architecture.universal2,
|
Architecture.universal2,
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ def test_no_override(platform, monkeypatch, intercepted_build_args):
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
intercepted_build_selector = intercepted_build_args.args[0].build_selector
|
options = intercepted_build_args.args[0]
|
||||||
|
intercepted_build_selector = options.globals.build_selector
|
||||||
|
|
||||||
assert intercepted_build_selector("cp39-win32")
|
assert intercepted_build_selector("cp39-win32")
|
||||||
assert intercepted_build_selector("cp36-win32")
|
assert intercepted_build_selector("cp36-win32")
|
||||||
@@ -40,7 +41,8 @@ def test_override_env(platform, monkeypatch, intercepted_build_args):
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
intercepted_build_selector = intercepted_build_args.args[0].build_selector
|
options = intercepted_build_args.args[0]
|
||||||
|
intercepted_build_selector = options.globals.build_selector
|
||||||
|
|
||||||
assert intercepted_build_selector.requires_python == SpecifierSet(">=3.8")
|
assert intercepted_build_selector.requires_python == SpecifierSet(">=3.8")
|
||||||
|
|
||||||
@@ -61,7 +63,8 @@ def test_override_setup_cfg(platform, monkeypatch, intercepted_build_args, fake_
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
intercepted_build_selector = intercepted_build_args.args[0].build_selector
|
options = intercepted_build_args.args[0]
|
||||||
|
intercepted_build_selector = options.globals.build_selector
|
||||||
|
|
||||||
assert intercepted_build_selector.requires_python == SpecifierSet(">=3.8")
|
assert intercepted_build_selector.requires_python == SpecifierSet(">=3.8")
|
||||||
|
|
||||||
@@ -82,7 +85,8 @@ def test_override_pyproject_toml(platform, monkeypatch, intercepted_build_args,
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
intercepted_build_selector = intercepted_build_args.args[0].build_selector
|
options = intercepted_build_args.args[0]
|
||||||
|
intercepted_build_selector = options.globals.build_selector
|
||||||
|
|
||||||
assert intercepted_build_selector.requires_python == SpecifierSet(">=3.8")
|
assert intercepted_build_selector.requires_python == SpecifierSet(">=3.8")
|
||||||
|
|
||||||
@@ -107,7 +111,8 @@ def test_override_setup_py_simple(platform, monkeypatch, intercepted_build_args,
|
|||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
intercepted_build_selector = intercepted_build_args.args[0].build_selector
|
options = intercepted_build_args.args[0]
|
||||||
|
intercepted_build_selector = options.globals.build_selector
|
||||||
|
|
||||||
assert intercepted_build_selector.requires_python == SpecifierSet(">=3.7")
|
assert intercepted_build_selector.requires_python == SpecifierSet(">=3.7")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import platform as platform_module
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import cast
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from cibuildwheel import linux, util
|
||||||
|
from cibuildwheel.__main__ import main
|
||||||
|
|
||||||
|
ALL_IDS = {"cp36", "cp37", "cp38", "cp39", "cp310", "pp37"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_build_docker(monkeypatch):
|
||||||
|
def fail_on_call(*args, **kwargs):
|
||||||
|
raise RuntimeError("This should never be called")
|
||||||
|
|
||||||
|
def ignore_call(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def nullcontext(enter_result=None):
|
||||||
|
yield enter_result
|
||||||
|
|
||||||
|
def ignore_context_call(*args, **kwargs):
|
||||||
|
return nullcontext(kwargs)
|
||||||
|
|
||||||
|
monkeypatch.setenv("CIBW_PLATFORM", "linux")
|
||||||
|
monkeypatch.setattr(platform_module, "machine", lambda: "x86_64")
|
||||||
|
|
||||||
|
monkeypatch.setattr(subprocess, "Popen", fail_on_call)
|
||||||
|
monkeypatch.setattr(subprocess, "run", ignore_call)
|
||||||
|
monkeypatch.setattr(util, "download", fail_on_call)
|
||||||
|
monkeypatch.setattr("cibuildwheel.linux.DockerContainer", ignore_context_call)
|
||||||
|
|
||||||
|
monkeypatch.setattr("cibuildwheel.linux.build_on_docker", mock.Mock(spec=linux.build_on_docker))
|
||||||
|
monkeypatch.setattr("cibuildwheel.util.print_new_wheels", ignore_context_call)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_default_launches(mock_build_docker, fake_package_dir, monkeypatch):
|
||||||
|
monkeypatch.setattr(sys, "argv", ["cibuildwheel", "--platform=linux"])
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
build_on_docker = cast(mock.Mock, linux.build_on_docker)
|
||||||
|
|
||||||
|
assert build_on_docker.call_count == 4
|
||||||
|
|
||||||
|
# In Python 3.8+, this can be simplified to [0].kwargs
|
||||||
|
kwargs = build_on_docker.call_args_list[0][1]
|
||||||
|
assert "quay.io/pypa/manylinux2010_x86_64" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert not kwargs["docker"]["simulate_32_bit"]
|
||||||
|
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {f"{x}-manylinux_x86_64" for x in ALL_IDS}
|
||||||
|
|
||||||
|
kwargs = build_on_docker.call_args_list[1][1]
|
||||||
|
assert "quay.io/pypa/manylinux2010_i686" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert kwargs["docker"]["simulate_32_bit"]
|
||||||
|
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {f"{x}-manylinux_i686" for x in ALL_IDS}
|
||||||
|
|
||||||
|
kwargs = build_on_docker.call_args_list[2][1]
|
||||||
|
assert "quay.io/pypa/musllinux_1_1_x86_64" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert not kwargs["docker"]["simulate_32_bit"]
|
||||||
|
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {
|
||||||
|
f"{x}-musllinux_x86_64" for x in ALL_IDS for x in ALL_IDS if "pp" not in x
|
||||||
|
}
|
||||||
|
|
||||||
|
kwargs = build_on_docker.call_args_list[3][1]
|
||||||
|
assert "quay.io/pypa/musllinux_1_1_i686" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert kwargs["docker"]["simulate_32_bit"]
|
||||||
|
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {f"{x}-musllinux_i686" for x in ALL_IDS if "pp" not in x}
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_with_override_launches(mock_build_docker, monkeypatch, tmp_path):
|
||||||
|
pkg_dir = tmp_path / "cibw_package"
|
||||||
|
pkg_dir.mkdir()
|
||||||
|
|
||||||
|
cibw_toml = pkg_dir / "pyproject.toml"
|
||||||
|
cibw_toml.write_text(
|
||||||
|
"""
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
manylinux-x86_64-image = "manylinux2014"
|
||||||
|
|
||||||
|
# Before Python 3.10, manylinux2010 is the most compatible
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp3?-*"
|
||||||
|
manylinux-x86_64-image = "manylinux2010"
|
||||||
|
manylinux-i686-image = "manylinux2010"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp36-manylinux_x86_64"
|
||||||
|
before-all = "true"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.chdir(pkg_dir)
|
||||||
|
monkeypatch.setattr(sys, "argv", ["cibuildwheel", "--platform=linux"])
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
build_on_docker = cast(mock.Mock, linux.build_on_docker)
|
||||||
|
|
||||||
|
assert build_on_docker.call_count == 6
|
||||||
|
|
||||||
|
kwargs = build_on_docker.call_args_list[0][1]
|
||||||
|
assert "quay.io/pypa/manylinux2010_x86_64" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert not kwargs["docker"]["simulate_32_bit"]
|
||||||
|
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {"cp36-manylinux_x86_64"}
|
||||||
|
assert kwargs["options"].build_options("cp36-manylinux_x86_64").before_all == "true"
|
||||||
|
|
||||||
|
kwargs = build_on_docker.call_args_list[1][1]
|
||||||
|
assert "quay.io/pypa/manylinux2010_x86_64" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert not kwargs["docker"]["simulate_32_bit"]
|
||||||
|
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {f"{x}-manylinux_x86_64" for x in ALL_IDS - {"cp36", "cp310", "pp37"}}
|
||||||
|
assert kwargs["options"].build_options("cp37-manylinux_x86_64").before_all == ""
|
||||||
|
|
||||||
|
kwargs = build_on_docker.call_args_list[2][1]
|
||||||
|
assert "quay.io/pypa/manylinux2014_x86_64" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert not kwargs["docker"]["simulate_32_bit"]
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {"cp310-manylinux_x86_64", "pp37-manylinux_x86_64"}
|
||||||
|
|
||||||
|
kwargs = build_on_docker.call_args_list[3][1]
|
||||||
|
assert "quay.io/pypa/manylinux2010_i686" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert kwargs["docker"]["simulate_32_bit"]
|
||||||
|
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {f"{x}-manylinux_i686" for x in ALL_IDS}
|
||||||
|
|
||||||
|
kwargs = build_on_docker.call_args_list[4][1]
|
||||||
|
assert "quay.io/pypa/musllinux_1_1_x86_64" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert not kwargs["docker"]["simulate_32_bit"]
|
||||||
|
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {
|
||||||
|
f"{x}-musllinux_x86_64" for x in ALL_IDS for x in ALL_IDS if "pp" not in x
|
||||||
|
}
|
||||||
|
|
||||||
|
kwargs = build_on_docker.call_args_list[5][1]
|
||||||
|
assert "quay.io/pypa/musllinux_1_1_i686" in kwargs["docker"]["docker_image"]
|
||||||
|
assert kwargs["docker"]["cwd"] == Path("/project")
|
||||||
|
assert kwargs["docker"]["simulate_32_bit"]
|
||||||
|
|
||||||
|
identifiers = {x.identifier for x in kwargs["platform_configs"]}
|
||||||
|
assert identifiers == {f"{x}-musllinux_i686" for x in ALL_IDS if "pp" not in x}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import platform as platform_module
|
||||||
|
|
||||||
|
from cibuildwheel.__main__ import get_build_identifiers
|
||||||
|
from cibuildwheel.environment import parse_environment
|
||||||
|
from cibuildwheel.options import Options, _get_pinned_docker_images
|
||||||
|
|
||||||
|
from .utils import get_default_command_line_arguments
|
||||||
|
|
||||||
|
PYPROJECT_1 = """
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
build = ["cp38*", "cp37*"]
|
||||||
|
environment = {FOO="BAR"}
|
||||||
|
|
||||||
|
test-command = "pyproject"
|
||||||
|
|
||||||
|
manylinux-x86_64-image = "manylinux1"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.macos]
|
||||||
|
test-requires = "else"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp37*"
|
||||||
|
test-command = "pyproject-override"
|
||||||
|
manylinux-x86_64-image = "manylinux2014"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_options_1(tmp_path, monkeypatch):
|
||||||
|
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
||||||
|
f.write(PYPROJECT_1)
|
||||||
|
|
||||||
|
args = get_default_command_line_arguments()
|
||||||
|
args.package_dir = str(tmp_path)
|
||||||
|
|
||||||
|
monkeypatch.setattr(platform_module, "machine", lambda: "x86_64")
|
||||||
|
|
||||||
|
options = Options(platform="linux", command_line_arguments=args)
|
||||||
|
|
||||||
|
identifiers = get_build_identifiers(
|
||||||
|
platform="linux",
|
||||||
|
build_selector=options.globals.build_selector,
|
||||||
|
architectures=options.globals.architectures,
|
||||||
|
)
|
||||||
|
|
||||||
|
override_display = """\
|
||||||
|
test_command: 'pyproject'
|
||||||
|
cp37-manylinux_x86_64: 'pyproject-override'"""
|
||||||
|
|
||||||
|
print(options.summary(identifiers))
|
||||||
|
|
||||||
|
assert override_display in options.summary(identifiers)
|
||||||
|
|
||||||
|
default_build_options = options.build_options(identifier=None)
|
||||||
|
|
||||||
|
assert default_build_options.environment == parse_environment('FOO="BAR"')
|
||||||
|
|
||||||
|
all_pinned_docker_images = _get_pinned_docker_images()
|
||||||
|
pinned_x86_64_docker_image = all_pinned_docker_images["x86_64"]
|
||||||
|
|
||||||
|
local = options.build_options("cp38-manylinux_x86_64")
|
||||||
|
assert local.manylinux_images is not None
|
||||||
|
assert local.test_command == "pyproject"
|
||||||
|
assert local.manylinux_images["x86_64"] == pinned_x86_64_docker_image["manylinux1"]
|
||||||
|
|
||||||
|
local = options.build_options("cp37-manylinux_x86_64")
|
||||||
|
assert local.manylinux_images is not None
|
||||||
|
assert local.test_command == "pyproject-override"
|
||||||
|
assert local.manylinux_images["x86_64"] == pinned_x86_64_docker_image["manylinux2014"]
|
||||||
+121
-57
@@ -1,6 +1,8 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from cibuildwheel.options import ConfigOptionError, ConfigOptions, _dig_first
|
from cibuildwheel.options import ConfigOptionError, OptionsReader, _dig_first
|
||||||
|
|
||||||
PYPROJECT_1 = """
|
PYPROJECT_1 = """
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
@@ -28,39 +30,43 @@ def platform(request):
|
|||||||
|
|
||||||
@pytest.mark.parametrize("fname", ["pyproject.toml", "cibuildwheel.toml"])
|
@pytest.mark.parametrize("fname", ["pyproject.toml", "cibuildwheel.toml"])
|
||||||
def test_simple_settings(tmp_path, platform, fname):
|
def test_simple_settings(tmp_path, platform, fname):
|
||||||
with tmp_path.joinpath(fname).open("w") as f:
|
config_file_path: Path = tmp_path / fname
|
||||||
f.write(PYPROJECT_1)
|
config_file_path.write_text(PYPROJECT_1)
|
||||||
|
|
||||||
options = ConfigOptions(tmp_path, f"{{package}}/{fname}", platform=platform)
|
options_reader = OptionsReader(config_file_path, platform=platform)
|
||||||
|
|
||||||
assert options("build", env_plat=False, sep=" ") == "cp39*"
|
assert options_reader.get("build", env_plat=False, sep=" ") == "cp39*"
|
||||||
|
|
||||||
assert options("test-command") == "pyproject"
|
assert options_reader.get("test-command") == "pyproject"
|
||||||
assert options("archs", sep=" ") == "auto"
|
assert options_reader.get("archs", sep=" ") == "auto"
|
||||||
assert (
|
assert (
|
||||||
options("test-requires", sep=" ")
|
options_reader.get("test-requires", sep=" ")
|
||||||
== {"windows": "something", "macos": "else", "linux": "other many"}[platform]
|
== {"windows": "something", "macos": "else", "linux": "other many"}[platform]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Also testing options for support for both lists and tables
|
# Also testing options for support for both lists and tables
|
||||||
assert (
|
assert (
|
||||||
options("environment", table={"item": '{k}="{v}"', "sep": " "}) == 'THING="OTHER" FOO="BAR"'
|
options_reader.get("environment", table={"item": '{k}="{v}"', "sep": " "})
|
||||||
)
|
|
||||||
assert (
|
|
||||||
options("environment", sep="x", table={"item": '{k}="{v}"', "sep": " "})
|
|
||||||
== 'THING="OTHER" FOO="BAR"'
|
== 'THING="OTHER" FOO="BAR"'
|
||||||
)
|
)
|
||||||
assert options("test-extras", sep=",") == "one,two"
|
assert (
|
||||||
assert options("test-extras", sep=",", table={"item": '{k}="{v}"', "sep": " "}) == "one,two"
|
options_reader.get("environment", sep="x", table={"item": '{k}="{v}"', "sep": " "})
|
||||||
|
== 'THING="OTHER" FOO="BAR"'
|
||||||
|
)
|
||||||
|
assert options_reader.get("test-extras", sep=",") == "one,two"
|
||||||
|
assert (
|
||||||
|
options_reader.get("test-extras", sep=",", table={"item": '{k}="{v}"', "sep": " "})
|
||||||
|
== "one,two"
|
||||||
|
)
|
||||||
|
|
||||||
assert options("manylinux-x86_64-image") == "manylinux1"
|
assert options_reader.get("manylinux-x86_64-image") == "manylinux1"
|
||||||
assert options("manylinux-i686-image") == "manylinux2010"
|
assert options_reader.get("manylinux-i686-image") == "manylinux2010"
|
||||||
|
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
options("environment", sep=" ")
|
options_reader.get("environment", sep=" ")
|
||||||
|
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
options("test-extras", table={"item": '{k}="{v}"', "sep": " "})
|
options_reader.get("test-extras", table={"item": '{k}="{v}"', "sep": " "})
|
||||||
|
|
||||||
|
|
||||||
def test_envvar_override(tmp_path, platform, monkeypatch):
|
def test_envvar_override(tmp_path, platform, monkeypatch):
|
||||||
@@ -70,44 +76,46 @@ def test_envvar_override(tmp_path, platform, monkeypatch):
|
|||||||
monkeypatch.setenv("CIBW_TEST_REQUIRES", "docs")
|
monkeypatch.setenv("CIBW_TEST_REQUIRES", "docs")
|
||||||
monkeypatch.setenv("CIBW_TEST_REQUIRES_LINUX", "scod")
|
monkeypatch.setenv("CIBW_TEST_REQUIRES_LINUX", "scod")
|
||||||
|
|
||||||
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
config_file_path: Path = tmp_path / "pyproject.toml"
|
||||||
f.write(PYPROJECT_1)
|
config_file_path.write_text(PYPROJECT_1)
|
||||||
|
|
||||||
options = ConfigOptions(tmp_path, platform=platform)
|
options_reader = OptionsReader(config_file_path, platform=platform)
|
||||||
|
|
||||||
assert options("archs", sep=" ") == "auto"
|
assert options_reader.get("archs", sep=" ") == "auto"
|
||||||
|
|
||||||
assert options("build", sep=" ") == "cp38*"
|
assert options_reader.get("build", sep=" ") == "cp38*"
|
||||||
assert options("manylinux-x86_64-image") == "manylinux2014"
|
assert options_reader.get("manylinux-x86_64-image") == "manylinux2014"
|
||||||
assert options("manylinux-i686-image") == "manylinux2010"
|
assert options_reader.get("manylinux-i686-image") == "manylinux2010"
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
options("test-requires", sep=" ")
|
options_reader.get("test-requires", sep=" ")
|
||||||
== {"windows": "docs", "macos": "docs", "linux": "scod"}[platform]
|
== {"windows": "docs", "macos": "docs", "linux": "scod"}[platform]
|
||||||
)
|
)
|
||||||
assert options("test-command") == "mytest"
|
assert options_reader.get("test-command") == "mytest"
|
||||||
|
|
||||||
|
|
||||||
def test_project_global_override_default_platform(tmp_path, platform):
|
def test_project_global_override_default_platform(tmp_path, platform):
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
"""
|
"""
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
repair-wheel-command = "repair-project-global"
|
repair-wheel-command = "repair-project-global"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
options = ConfigOptions(tmp_path, platform=platform)
|
options_reader = OptionsReader(pyproject_toml, platform=platform)
|
||||||
assert options("repair-wheel-command") == "repair-project-global"
|
assert options_reader.get("repair-wheel-command") == "repair-project-global"
|
||||||
|
|
||||||
|
|
||||||
def test_env_global_override_default_platform(tmp_path, platform, monkeypatch):
|
def test_env_global_override_default_platform(tmp_path, platform, monkeypatch):
|
||||||
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
|
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
|
||||||
options = ConfigOptions(tmp_path, platform=platform)
|
options_reader = OptionsReader(platform=platform)
|
||||||
assert options("repair-wheel-command") == "repair-env-global"
|
assert options_reader.get("repair-wheel-command") == "repair-env-global"
|
||||||
|
|
||||||
|
|
||||||
def test_env_global_override_project_platform(tmp_path, platform, monkeypatch):
|
def test_env_global_override_project_platform(tmp_path, platform, monkeypatch):
|
||||||
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
|
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
"""
|
"""
|
||||||
[tool.cibuildwheel.linux]
|
[tool.cibuildwheel.linux]
|
||||||
repair-wheel-command = "repair-project-linux"
|
repair-wheel-command = "repair-project-linux"
|
||||||
@@ -117,12 +125,13 @@ repair-wheel-command = "repair-project-windows"
|
|||||||
repair-wheel-command = "repair-project-macos"
|
repair-wheel-command = "repair-project-macos"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
options = ConfigOptions(tmp_path, platform=platform)
|
options_reader = OptionsReader(pyproject_toml, platform=platform)
|
||||||
assert options("repair-wheel-command") == "repair-env-global"
|
assert options_reader.get("repair-wheel-command") == "repair-env-global"
|
||||||
|
|
||||||
|
|
||||||
def test_global_platform_order(tmp_path, platform):
|
def test_global_platform_order(tmp_path, platform):
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
"""
|
"""
|
||||||
[tool.cibuildwheel.linux]
|
[tool.cibuildwheel.linux]
|
||||||
repair-wheel-command = "repair-project-linux"
|
repair-wheel-command = "repair-project-linux"
|
||||||
@@ -134,14 +143,15 @@ repair-wheel-command = "repair-project-macos"
|
|||||||
repair-wheel-command = "repair-project-global"
|
repair-wheel-command = "repair-project-global"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
options = ConfigOptions(tmp_path, platform=platform)
|
options_reader = OptionsReader(pyproject_toml, platform=platform)
|
||||||
assert options("repair-wheel-command") == f"repair-project-{platform}"
|
assert options_reader.get("repair-wheel-command") == f"repair-project-{platform}"
|
||||||
|
|
||||||
|
|
||||||
def test_unexpected_key(tmp_path):
|
def test_unexpected_key(tmp_path):
|
||||||
# Note that platform contents are only checked when running
|
# Note that platform contents are only checked when running
|
||||||
# for that platform.
|
# for that platform.
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
"""
|
"""
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
repairs-wheel-command = "repair-project-linux"
|
repairs-wheel-command = "repair-project-linux"
|
||||||
@@ -149,49 +159,53 @@ repairs-wheel-command = "repair-project-linux"
|
|||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
ConfigOptions(tmp_path, platform="linux")
|
OptionsReader(pyproject_toml, platform="linux")
|
||||||
|
|
||||||
|
|
||||||
def test_unexpected_table(tmp_path):
|
def test_unexpected_table(tmp_path):
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
"""
|
"""
|
||||||
[tool.cibuildwheel.linus]
|
[tool.cibuildwheel.linus]
|
||||||
repair-wheel-command = "repair-project-linux"
|
repair-wheel-command = "repair-project-linux"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
ConfigOptions(tmp_path, platform="linux")
|
OptionsReader(pyproject_toml, platform="linux")
|
||||||
|
|
||||||
|
|
||||||
def test_unsupported_join(tmp_path):
|
def test_unsupported_join(tmp_path):
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
"""
|
"""
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
build = ["1", "2"]
|
build = ["1", "2"]
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
options = ConfigOptions(tmp_path, platform="linux")
|
options_reader = OptionsReader(pyproject_toml, platform="linux")
|
||||||
|
|
||||||
assert "1, 2" == options("build", sep=", ")
|
assert "1, 2" == options_reader.get("build", sep=", ")
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
options("build")
|
options_reader.get("build")
|
||||||
|
|
||||||
|
|
||||||
def test_disallowed_a(tmp_path):
|
def test_disallowed_a(tmp_path):
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
"""
|
"""
|
||||||
[tool.cibuildwheel.windows]
|
[tool.cibuildwheel.windows]
|
||||||
manylinux-x86_64-image = "manylinux1"
|
manylinux-x86_64-image = "manylinux1"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
disallow = {"windows": {"manylinux-x86_64-image"}}
|
disallow = {"windows": {"manylinux-x86_64-image"}}
|
||||||
ConfigOptions(tmp_path, platform="linux", disallow=disallow)
|
OptionsReader(pyproject_toml, platform="linux", disallow=disallow)
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
ConfigOptions(tmp_path, platform="windows", disallow=disallow)
|
OptionsReader(pyproject_toml, platform="windows", disallow=disallow)
|
||||||
|
|
||||||
|
|
||||||
def test_environment_override_empty(tmp_path, monkeypatch):
|
def test_environment_override_empty(tmp_path, monkeypatch):
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
"""
|
"""
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
manylinux-i686-image = "manylinux1"
|
manylinux-i686-image = "manylinux1"
|
||||||
@@ -202,15 +216,15 @@ manylinux-x86_64-image = ""
|
|||||||
monkeypatch.setenv("CIBW_MANYLINUX_I686_IMAGE", "")
|
monkeypatch.setenv("CIBW_MANYLINUX_I686_IMAGE", "")
|
||||||
monkeypatch.setenv("CIBW_MANYLINUX_AARCH64_IMAGE", "manylinux1")
|
monkeypatch.setenv("CIBW_MANYLINUX_AARCH64_IMAGE", "manylinux1")
|
||||||
|
|
||||||
options = ConfigOptions(tmp_path, platform="linux")
|
options_reader = OptionsReader(pyproject_toml, platform="linux")
|
||||||
|
|
||||||
assert options("manylinux-x86_64-image") == ""
|
assert options_reader.get("manylinux-x86_64-image") == ""
|
||||||
assert options("manylinux-i686-image") == ""
|
assert options_reader.get("manylinux-i686-image") == ""
|
||||||
assert options("manylinux-aarch64-image") == "manylinux1"
|
assert options_reader.get("manylinux-aarch64-image") == "manylinux1"
|
||||||
|
|
||||||
assert options("manylinux-x86_64-image", ignore_empty=True) == "manylinux2010"
|
assert options_reader.get("manylinux-x86_64-image", ignore_empty=True) == "manylinux2010"
|
||||||
assert options("manylinux-i686-image", ignore_empty=True) == "manylinux1"
|
assert options_reader.get("manylinux-i686-image", ignore_empty=True) == "manylinux1"
|
||||||
assert options("manylinux-aarch64-image", ignore_empty=True) == "manylinux1"
|
assert options_reader.get("manylinux-aarch64-image", ignore_empty=True) == "manylinux1"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("ignore_empty", (True, False))
|
@pytest.mark.parametrize("ignore_empty", (True, False))
|
||||||
@@ -246,3 +260,53 @@ def test_dig_first(ignore_empty):
|
|||||||
(d4, "other"),
|
(d4, "other"),
|
||||||
ignore_empty=ignore_empty,
|
ignore_empty=ignore_empty,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
PYPROJECT_2 = """
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
build = ["cp38*", "cp37*"]
|
||||||
|
environment = {FOO="BAR"}
|
||||||
|
|
||||||
|
test-command = "pyproject"
|
||||||
|
|
||||||
|
manylinux-x86_64-image = "manylinux1"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.macos]
|
||||||
|
test-requires = "else"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "cp37*"
|
||||||
|
test-command = "pyproject-override"
|
||||||
|
manylinux-x86_64-image = "manylinux2014"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_pyproject_2(tmp_path, platform):
|
||||||
|
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(PYPROJECT_2)
|
||||||
|
|
||||||
|
options_reader = OptionsReader(config_file_path=pyproject_toml, platform=platform)
|
||||||
|
assert options_reader.get("test-command") == "pyproject"
|
||||||
|
|
||||||
|
with options_reader.identifier("random"):
|
||||||
|
assert options_reader.get("test-command") == "pyproject"
|
||||||
|
|
||||||
|
with options_reader.identifier("cp37-something"):
|
||||||
|
assert options_reader.get("test-command") == "pyproject-override"
|
||||||
|
|
||||||
|
|
||||||
|
def test_overrides_not_a_list(tmp_path, platform):
|
||||||
|
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||||
|
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
build = ["cp38*", "cp37*"]
|
||||||
|
[tool.cibuildwheel.overrides]
|
||||||
|
select = "cp37*"
|
||||||
|
test-command = "pyproject-override"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ConfigOptionError):
|
||||||
|
OptionsReader(config_file_path=pyproject_toml, platform=platform)
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from cibuildwheel.options import CommandLineArguments
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_command_line_arguments() -> CommandLineArguments:
|
||||||
|
defaults = CommandLineArguments()
|
||||||
|
|
||||||
|
defaults.platform = "auto"
|
||||||
|
defaults.allow_empty = False
|
||||||
|
defaults.archs = None
|
||||||
|
defaults.config_file = None
|
||||||
|
defaults.output_dir = None
|
||||||
|
defaults.package_dir = "."
|
||||||
|
defaults.prerelease_pythons = False
|
||||||
|
defaults.print_build_identifiers = False
|
||||||
|
|
||||||
|
return defaults
|
||||||
Reference in New Issue
Block a user