From 297e4a609990b9237e276102b170e62cdaa1fde0 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 26 Aug 2023 19:37:29 +0100 Subject: [PATCH 01/17] Add args param to build-frontend option --- cibuildwheel/linux.py | 11 ++--- cibuildwheel/macos.py | 19 +++++---- cibuildwheel/oci_container.py | 4 +- cibuildwheel/options.py | 24 +++++------ cibuildwheel/util.py | 49 ++++++++++++++++++----- cibuildwheel/windows.py | 19 +++++---- test/test_build_frontend_args.py | 34 ++++++++++++++++ unit_test/main_tests/main_options_test.py | 2 +- 8 files changed, 115 insertions(+), 47 deletions(-) create mode 100644 test/test_build_frontend_args.py diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index a4d7c184..0c6184ef 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -16,9 +16,9 @@ from .options import Options from .typing import PathOrStr from .util import ( AlreadyBuiltWheelError, + BuildFrontendConfig, BuildSelector, NonPlatformWheelError, - build_frontend_or_default, find_compatible_wheel, get_build_verbosity_extra_flags, prepare_command, @@ -177,7 +177,7 @@ def build_in_container( for config in platform_configs: log.build_start(config.identifier) build_options = options.build_options(config.identifier) - build_frontend = build_frontend_or_default(build_options.build_frontend) + build_frontend = build_options.build_frontend or BuildFrontendConfig("pip") dependency_constraint_flags: list[PathOrStr] = [] @@ -243,9 +243,10 @@ def build_in_container( container.call(["rm", "-rf", built_wheel_dir]) container.call(["mkdir", "-p", built_wheel_dir]) - extra_flags = split_config_settings(build_options.config_settings, build_frontend) + extra_flags = split_config_settings(build_options.config_settings, build_frontend.name) + extra_flags += build_frontend.args - if build_frontend == "pip": + if build_frontend.name == "pip": extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity) container.call( [ @@ -260,7 +261,7 @@ def build_in_container( ], env=env, ) - elif build_frontend == "build": + elif build_frontend.name == "build": if not 0 <= build_options.build_verbosity < 2: msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring." log.warning(msg) diff --git a/cibuildwheel/macos.py b/cibuildwheel/macos.py index ddc7c469..77ae492a 100644 --- a/cibuildwheel/macos.py +++ b/cibuildwheel/macos.py @@ -25,10 +25,10 @@ from .typing import PathOrStr from .util import ( CIBW_CACHE_PATH, AlreadyBuiltWheelError, - BuildFrontend, + BuildFrontendConfig, + BuildFrontendName, BuildSelector, NonPlatformWheelError, - build_frontend_or_default, call, detect_ci_provider, download, @@ -165,7 +165,7 @@ def setup_python( python_configuration: PythonConfiguration, dependency_constraint_flags: Sequence[PathOrStr], environment: ParsedEnvironment, - build_frontend: BuildFrontend, + build_frontend: BuildFrontendName, ) -> dict[str, str]: tmp.mkdir() implementation_id = python_configuration.identifier.split("-")[0] @@ -334,7 +334,7 @@ def build(options: Options, tmp_path: Path) -> None: for config in python_configurations: build_options = options.build_options(config.identifier) - build_frontend = build_frontend_or_default(build_options.build_frontend) + build_frontend = build_options.build_frontend or BuildFrontendConfig("pip") log.build_start(config.identifier) identifier_tmp_dir = tmp_path / config.identifier @@ -357,7 +357,7 @@ def build(options: Options, tmp_path: Path) -> None: config, dependency_constraint_flags, build_options.environment, - build_frontend, + build_frontend.name, ) compatible_wheel = find_compatible_wheel(built_wheels, config.identifier) @@ -378,9 +378,12 @@ def build(options: Options, tmp_path: Path) -> None: log.step("Building wheel...") built_wheel_dir.mkdir() - extra_flags = split_config_settings(build_options.config_settings, build_frontend) + extra_flags = split_config_settings( + build_options.config_settings, build_frontend.name + ) + extra_flags += build_frontend.args - if build_frontend == "pip": + if build_frontend.name == "pip": extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity) # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org # see https://github.com/pypa/cibuildwheel/pull/369 @@ -395,7 +398,7 @@ def build(options: Options, tmp_path: Path) -> None: *extra_flags, env=env, ) - elif build_frontend == "build": + elif build_frontend.name == "build": if not 0 <= build_options.build_verbosity < 2: msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring." log.warning(msg) diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index 1ea22edd..966ab968 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -29,7 +29,9 @@ class OCIContainerEngineConfig: @staticmethod def from_config_string(config_string: str) -> OCIContainerEngineConfig: - config_dict = parse_key_value_string(config_string, ["name"]) + config_dict = parse_key_value_string( + config_string, ["name"], ["create_args", "create-args"] + ) name = " ".join(config_dict["name"]) if name not in {"docker", "podman"}: msg = f"unknown container engine {name}" diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index 581e576b..d85eb02b 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -27,7 +27,7 @@ from .typing import PLATFORMS, PlatformName from .util import ( MANYLINUX_ARCHS, MUSLLINUX_ARCHS, - BuildFrontend, + BuildFrontendConfig, BuildSelector, DependencyConstraints, TestSelector, @@ -92,7 +92,7 @@ class BuildOptions: test_requires: list[str] test_extras: str build_verbosity: int - build_frontend: BuildFrontend | Literal["default"] + build_frontend: BuildFrontendConfig | None config_settings: str @property @@ -488,7 +488,6 @@ class Options: with self.reader.identifier(identifier): before_all = self.reader.get("before-all", sep=" && ") - build_frontend_str = self.reader.get("build-frontend", env_plat=False) environment_config = self.reader.get( "environment", table={"item": '{k}="{v}"', "sep": " "} ) @@ -506,17 +505,16 @@ class Options: test_extras = self.reader.get("test-extras", sep=",") build_verbosity_str = self.reader.get("build-verbosity") - build_frontend: BuildFrontend | Literal["default"] - if build_frontend_str == "build": - build_frontend = "build" - elif build_frontend_str == "pip": - build_frontend = "pip" - elif build_frontend_str == "default": - build_frontend = "default" + build_frontend_str = self.reader.get("build-frontend", env_plat=False) + build_frontend: BuildFrontendConfig | None + if not build_frontend_str or build_frontend_str == "default": + build_frontend = None else: - msg = f"cibuildwheel: Unrecognised build frontend {build_frontend_str!r}, only 'pip' and 'build' are supported" - print(msg, file=sys.stderr) - sys.exit(2) + try: + build_frontend = BuildFrontendConfig.from_config_string(build_frontend_str) + except ValueError as e: + print(f"cibuildwheel: {e}", file=sys.stderr) + sys.exit(2) try: environment = parse_environment(environment_config) diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 7875ea40..793e5cd9 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -57,16 +57,6 @@ install_certifi_script: Final[Path] = resources_dir / "install_certifi.py" test_fail_cwd_file: Final[Path] = resources_dir / "testing_temp_dir_file.py" -BuildFrontend = Literal["pip", "build"] - - -def build_frontend_or_default( - setting: BuildFrontend | Literal["default"], default: BuildFrontend = "pip" -) -> BuildFrontend: - if setting == "default": - return default - return setting - MANYLINUX_ARCHS: Final[tuple[str, ...]] = ( "x86_64", @@ -376,6 +366,34 @@ class DependencyConstraints: return self.base_file_path.name +BuildFrontendName = Literal["pip", "build"] + + +@dataclass(frozen=True) +class BuildFrontendConfig: + name: BuildFrontendName + args: Sequence[str] = () + + @staticmethod + def from_config_string(config_string: str) -> BuildFrontendConfig: + config_dict = parse_key_value_string(config_string, ["name"], ["args"]) + name = " ".join(config_dict["name"]) + if name not in {"pip", "build"}: + msg = f"Unrecognised build frontend {name}, only 'pip' and 'build' are supported" + raise ValueError(msg) + + name = typing.cast(BuildFrontendName, name) + + args = config_dict.get("args") or [] + return BuildFrontendConfig(name=name, args=args) + + def options_summary(self) -> str | dict[str, str]: + if not self.args: + return self.name + else: + return {"name": self.name, "args": repr(self.args)} + + class NonPlatformWheelError(Exception): def __init__(self) -> None: message = textwrap.dedent( @@ -699,13 +717,19 @@ def fix_ansi_codes_for_github_actions(text: str) -> str: def parse_key_value_string( - key_value_string: str, positional_arg_names: list[str] | None = None + key_value_string: str, + positional_arg_names: Sequence[str] | None = None, + kw_arg_names: Sequence[str] | None = None, ) -> dict[str, list[str]]: """ Parses a string like "docker; create_args: --some-option=value another-option" """ if positional_arg_names is None: positional_arg_names = [] + if kw_arg_names is None: + kw_arg_names = [] + + all_field_names = [*positional_arg_names, *kw_arg_names] shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";:") shlexer.commenters = "" @@ -721,6 +745,9 @@ def parse_key_value_string( if len(field) > 1 and field[1] == ":": field_name = field[0] values = field[2:] + if field_name not in all_field_names: + msg = f"Failed to parse {key_value_string!r}. Unknown field name {field_name!r}" + raise ValueError(msg) else: try: field_name = positional_arg_names[field_i] diff --git a/cibuildwheel/windows.py b/cibuildwheel/windows.py index c291d96d..881821bd 100644 --- a/cibuildwheel/windows.py +++ b/cibuildwheel/windows.py @@ -25,10 +25,10 @@ from .typing import PathOrStr from .util import ( CIBW_CACHE_PATH, AlreadyBuiltWheelError, - BuildFrontend, + BuildFrontendConfig, + BuildFrontendName, BuildSelector, NonPlatformWheelError, - build_frontend_or_default, call, download, find_compatible_wheel, @@ -216,7 +216,7 @@ def setup_python( python_configuration: PythonConfiguration, dependency_constraint_flags: Sequence[PathOrStr], environment: ParsedEnvironment, - build_frontend: BuildFrontend, + build_frontend: BuildFrontendName, ) -> dict[str, str]: tmp.mkdir() implementation_id = python_configuration.identifier.split("-")[0] @@ -369,7 +369,7 @@ def build(options: Options, tmp_path: Path) -> None: for config in python_configurations: build_options = options.build_options(config.identifier) - build_frontend = build_frontend_or_default(build_options.build_frontend) + build_frontend = build_options.build_frontend or BuildFrontendConfig("pip") log.build_start(config.identifier) identifier_tmp_dir = tmp_path / config.identifier @@ -390,7 +390,7 @@ def build(options: Options, tmp_path: Path) -> None: config, dependency_constraint_flags, build_options.environment, - build_frontend, + build_frontend.name, ) compatible_wheel = find_compatible_wheel(built_wheels, config.identifier) @@ -414,9 +414,12 @@ def build(options: Options, tmp_path: Path) -> None: log.step("Building wheel...") built_wheel_dir.mkdir() - extra_flags = split_config_settings(build_options.config_settings, build_frontend) + extra_flags = split_config_settings( + build_options.config_settings, build_frontend.name + ) + extra_flags += build_frontend.args - if build_frontend == "pip": + if build_frontend.name == "pip": extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity) # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org # see https://github.com/pypa/cibuildwheel/pull/369 @@ -431,7 +434,7 @@ def build(options: Options, tmp_path: Path) -> None: *extra_flags, env=env, ) - elif build_frontend == "build": + elif build_frontend.name == "build": if not 0 <= build_options.build_verbosity < 2: msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring." log.warning(msg) diff --git a/test/test_build_frontend_args.py b/test/test_build_frontend_args.py new file mode 100644 index 00000000..c2b0fc37 --- /dev/null +++ b/test/test_build_frontend_args.py @@ -0,0 +1,34 @@ +import subprocess + +import pytest + +from . import utils +from .test_projects.c import new_c_project + + +@pytest.mark.parametrize("frontend_name", ["pip", "build"]) +def test_build_frontend_args(tmp_path, capfd, frontend_name): + project = new_c_project() + project_dir = tmp_path / "project" + project.generate(project_dir) + + # the build will fail because the frontend is called with '-h' - it prints the help message + with pytest.raises(subprocess.CalledProcessError): + utils.cibuildwheel_run( + project_dir, + add_env={ + "CIBW_BUILD": "cp311-*", + "CIBW_BUILD_FRONTEND": f"{frontend_name}; args: -h", + }, + ) + + captured = capfd.readouterr() + print(captured.out) + + # check that the help message was printed + if frontend_name == "pip": + assert "Usage:" in captured.out + assert "Wheel Options:" in captured.out + else: + assert "usage:" in captured.out + assert "A simple, correct Python build frontend." in captured.out diff --git a/unit_test/main_tests/main_options_test.py b/unit_test/main_tests/main_options_test.py index 8d2010ee..5b85da73 100644 --- a/unit_test/main_tests/main_options_test.py +++ b/unit_test/main_tests/main_options_test.py @@ -365,7 +365,7 @@ def test_defaults(platform, intercepted_build_args): 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"] + assert build_options.build_frontend is None if platform == "linux": assert build_options.manylinux_images From 061ea627e2fe9870f547a4c8a56ff7b8887ab8c2 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 26 Aug 2023 19:47:49 +0100 Subject: [PATCH 02/17] Add docs --- docs/options.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/options.md b/docs/options.md index 8df4e8f2..b597684c 100644 --- a/docs/options.md +++ b/docs/options.md @@ -504,9 +504,19 @@ This option can also be set using the [command-line option](#command-line) `--pr ### `CIBW_BUILD_FRONTEND` {: #build-frontend} > Set the tool to use to build, either "pip" (default for now) or "build" -Choose which build backend to use. Can either be "pip", which will run +Options: + +- `pip[;args: ...]` +- `build[;args: ...]` + +Default: `pip` + +Choose which build frontend to use. Can either be "pip", which will run `python -m pip wheel`, or "build", which will run `python -m build --wheel`. +You can specify extra arguments to pass to `pip wheel` or `build` using the +optional `args` option. + !!! tip Until v2.0.0, [pip] was the only way to build wheels, and is still the default. However, we expect that at some point in the future, cibuildwheel @@ -526,6 +536,9 @@ Choose which build backend to use. Can either be "pip", which will run # Ensure pip is used even if the default changes in the future CIBW_BUILD_FRONTEND: "pip" + + # supply an extra argument to 'pip wheel' + CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" ``` !!! tab examples "pyproject.toml" @@ -537,6 +550,9 @@ Choose which build backend to use. Can either be "pip", which will run # Ensure pip is used even if the default changes in the future build-frontend = "pip" + + # supply an extra argument to 'pip wheel' + build-frontend = { name = "pip", args = ["--no-build-isolation"] } ``` ### `CIBW_CONFIG_SETTINGS` {: #config-settings} From 81ef5fac4304fda24f0a96e5b04eb986b4435b6b Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Wed, 30 Aug 2023 09:28:34 +0100 Subject: [PATCH 03/17] Add a unit test for the TOML form of the option --- unit_test/options_test.py | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/unit_test/options_test.py b/unit_test/options_test.py index 134db012..8818ba7e 100644 --- a/unit_test/options_test.py +++ b/unit_test/options_test.py @@ -253,3 +253,57 @@ def test_container_engine_option(tmp_path: Path, toml_assignment, result_name, r assert parsed_container_engine.name == result_name assert parsed_container_engine.create_args == result_create_args + + +@pytest.mark.parametrize( + ("toml_assignment", "result_name", "result_args"), + [ + ( + "", + None, + None, + ), + ( + 'build-frontend = "build"', + "build", + [], + ), + ( + 'build-frontend = {name = "build"}', + "build", + [], + ), + ( + 'build-frontend = "pip; args: --some-option"', + "pip", + ["--some-option"], + ), + ( + 'build-frontend = {name = "pip", args = ["--some-option"]}', + "pip", + ["--some-option"], + ), + ], +) +def test_build_frontend_option(tmp_path: Path, toml_assignment, result_name, result_args): + args = CommandLineArguments.defaults() + args.package_dir = tmp_path + + tmp_path.joinpath("pyproject.toml").write_text( + textwrap.dedent( + f"""\ + [tool.cibuildwheel] + {toml_assignment} + """ + ) + ) + + options = Options(platform="linux", command_line_arguments=args, env={}) + parsed_build_frontend = options.build_options(identifier=None).build_frontend + + if toml_assignment: + assert parsed_build_frontend is not None + assert parsed_build_frontend.name == result_name + assert parsed_build_frontend.args == result_args + else: + assert parsed_build_frontend is None From 5311f8868456ac4f49832c2b55c5507d31db4dd9 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Wed, 30 Aug 2023 09:28:47 +0100 Subject: [PATCH 04/17] Fix for missing table in option parsing --- cibuildwheel/options.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index d85eb02b..d4306a7a 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -505,7 +505,11 @@ class Options: test_extras = self.reader.get("test-extras", sep=",") build_verbosity_str = self.reader.get("build-verbosity") - build_frontend_str = self.reader.get("build-frontend", env_plat=False) + build_frontend_str = self.reader.get( + "build-frontend", + env_plat=False, + table={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote}, + ) build_frontend: BuildFrontendConfig | None if not build_frontend_str or build_frontend_str == "default": build_frontend = None From 4f888e27c3867b0d04740275afd7a03d71d4df3b Mon Sep 17 00:00:00 2001 From: Lisandro Dalcin Date: Sun, 27 Aug 2023 09:45:55 +0300 Subject: [PATCH 05/17] feature: Automatically pass SOURCE_DATE_EPOCH to Linux containers Co-authored-by: Joe Rickerby --- cibuildwheel/oci_container.py | 1 + docs/options.md | 3 +++ unit_test/oci_container_test.py | 11 +++++++++++ 3 files changed, 15 insertions(+) diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index 1ea22edd..9f080eda 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -115,6 +115,7 @@ class OCIContainer: self.engine.name, "create", "--env=CIBUILDWHEEL", + "--env=SOURCE_DATE_EPOCH", f"--name={self.name}", "--interactive", "--volume=/:/host", # ignored on CircleCI diff --git a/docs/options.md b/docs/options.md index 8df4e8f2..4fffcb35 100644 --- a/docs/options.md +++ b/docs/options.md @@ -664,6 +664,9 @@ A list of environment variables to pass into the linux container during the buil To specify more than one environment variable, separate the variable names by spaces. +!!! note + cibuildwheel automatically passes the environment variable [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/docs/source-date-epoch/) if defined. + #### Examples !!! tab examples "Environment passthrough" diff --git a/unit_test/oci_container_test.py b/unit_test/oci_container_test.py index ccbd0555..a4b200d9 100644 --- a/unit_test/oci_container_test.py +++ b/unit_test/oci_container_test.py @@ -72,6 +72,17 @@ def test_environment(container_engine): ) +def test_environment_pass(container_engine, monkeypatch): + monkeypatch.setenv("CIBUILDWHEEL", "1") + monkeypatch.setenv("SOURCE_DATE_EPOCH", "1489957071") + with OCIContainer(engine=container_engine, image=DEFAULT_IMAGE) as container: + assert container.call(["sh", "-c", "echo $CIBUILDWHEEL"], capture_output=True) == "1\n" + assert ( + container.call(["sh", "-c", "echo $SOURCE_DATE_EPOCH"], capture_output=True) + == "1489957071\n" + ) + + def test_cwd(container_engine): with OCIContainer( engine=container_engine, image=DEFAULT_IMAGE, cwd="/cibuildwheel/working_directory" From f34ae7731db77b6012ab4b8651e99478904bf70c Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 13 Sep 2023 15:45:40 -0400 Subject: [PATCH 06/17] fix(setup.py): look inside if name == main block Signed-off-by: Henry Schreiner --- cibuildwheel/projectfiles.py | 50 ++++++++++++++++++++++++-- unit_test/projectfiles_test.py | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/cibuildwheel/projectfiles.py b/cibuildwheel/projectfiles.py index e006aca4..73593fc7 100644 --- a/cibuildwheel/projectfiles.py +++ b/cibuildwheel/projectfiles.py @@ -8,6 +8,43 @@ from pathlib import Path from ._compat import tomllib +def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None: + for _ in range(depth): + node = getattr(node, "parent", None) + return node + + +def is_main(parent: ast.AST | None) -> bool: + if parent is None: + return False + + # This would be much nicer with 3.10's pattern matching! + if not isinstance(parent, ast.If): + return False + if not isinstance(parent.test, ast.Compare): + return False + + try: + (op,) = parent.test.ops + (comp,) = parent.test.comparators + except ValueError: + return False + + if not isinstance(op, ast.Eq): + return False + + values = {comp, parent.test.left} + + mains = {x for x in values if isinstance(x, ast.Constant) and x.value == "__main__"} + if len(mains) != 1: + return False + consts = {x for x in values if isinstance(x, ast.Name) and x.id == "__name__"} + if len(consts) != 1: + return False + + return True + + class Analyzer(ast.NodeVisitor): def __init__(self) -> None: self.requires_python: str | None = None @@ -19,13 +56,22 @@ class Analyzer(ast.NodeVisitor): super().visit(node) def visit_keyword(self, node: ast.keyword) -> None: + # Must not be nested except for if __name__ == "__main__" + self.generic_visit(node) - # Must not be nested in an if or other structure # This will be Module -> Expr -> Call -> keyword + parent = get_parent(node, 4) + unnested = parent is None + + # This will be Module -> If -> Expr -> Call -> keyword + name_main_unnested = ( + parent is not None and get_parent(parent) is None and is_main(get_parent(node, 3)) + ) + if ( node.arg == "python_requires" - and not hasattr(node.parent.parent.parent, "parent") # type: ignore[attr-defined] and isinstance(node.value, ast.Constant) + and (unnested or name_main_unnested) ): self.requires_python = node.value.value diff --git a/unit_test/projectfiles_test.py b/unit_test/projectfiles_test.py index e7e21ba5..b1839eda 100644 --- a/unit_test/projectfiles_test.py +++ b/unit_test/projectfiles_test.py @@ -26,6 +26,72 @@ def test_read_setup_py_simple(tmp_path): assert get_requires_python_str(tmp_path) == "1.23" +def test_read_setup_py_if_main(tmp_path): + with open(tmp_path / "setup.py", "w") as f: + f.write( + dedent( + """ + from setuptools import setup + + if __name__ == "__main__": + setup( + name = "hello", + other = 23, + example = ["item", "other"], + python_requires = "1.23", + ) + """ + ) + ) + + assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23" + assert get_requires_python_str(tmp_path) == "1.23" + + +def test_read_setup_py_if_main_reversed(tmp_path): + with open(tmp_path / "setup.py", "w") as f: + f.write( + dedent( + """ + from setuptools import setup + + if "__main__" == __name__: + setup( + name = "hello", + other = 23, + example = ["item", "other"], + python_requires = "1.23", + ) + """ + ) + ) + + assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "1.23" + assert get_requires_python_str(tmp_path) == "1.23" + + +def test_read_setup_py_if_invalid(tmp_path): + with open(tmp_path / "setup.py", "w") as f: + f.write( + dedent( + """ + from setuptools import setup + + if True: + setup( + name = "hello", + other = 23, + example = ["item", "other"], + python_requires = "1.23", + ) + """ + ) + ) + + assert not setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) + assert not get_requires_python_str(tmp_path) + + def test_read_setup_py_full(tmp_path): with open(tmp_path / "setup.py", "w", encoding="utf8") as f: f.write( From cc50337ecd6c50f8561d28b9b7d4bdcc2ae52119 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:11:07 +0000 Subject: [PATCH 07/17] chore(deps): bump docker/setup-qemu-action from 2 to 3 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 131c1dfd..5297c386 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -103,7 +103,7 @@ jobs: - name: Set up QEMU id: qemu - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: platforms: all From f5e60d647fe700ba6e357c30376e8a48f91e5974 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 18 Sep 2023 11:57:32 -0400 Subject: [PATCH 08/17] fix: include examples too Signed-off-by: Henry Schreiner --- examples/github-with-qemu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/github-with-qemu.yml b/examples/github-with-qemu.yml index 1c9f66b5..f26c71e4 100644 --- a/examples/github-with-qemu.yml +++ b/examples/github-with-qemu.yml @@ -15,7 +15,7 @@ jobs: - name: Set up QEMU if: runner.os == 'Linux' - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: platforms: all From a873dd9cbf9e3c4c73a1fd11ac31cf835f6eb502 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Mon, 18 Sep 2023 18:34:53 +0100 Subject: [PATCH 09/17] Bump version: v2.16.0 --- README.md | 24 +++++++++++++----------- cibuildwheel/__init__.py | 2 +- docs/changelog.md | 12 ++++++++++++ docs/faq.md | 6 +++--- docs/setup.md | 4 ++-- examples/appveyor-minimal.yml | 2 +- examples/azure-pipelines-minimal.yml | 6 +++--- examples/circleci-minimal.yml | 6 +++--- examples/cirrus-ci-intel-mac.yml | 2 +- examples/cirrus-ci-minimal.yml | 2 +- examples/github-apple-silicon.yml | 2 +- examples/github-deploy.yml | 2 +- examples/github-minimal.yml | 2 +- examples/github-with-qemu.yml | 2 +- examples/gitlab-minimal.yml | 4 ++-- examples/gitlab-with-qemu.yml | 2 +- examples/travis-ci-deploy.yml | 2 +- examples/travis-ci-minimal.yml | 2 +- examples/travis-ci-test-and-deploy.yml | 4 ++-- setup.cfg | 2 +- 20 files changed, 52 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index c48fd1e7..4aaff6fd 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ jobs: - uses: actions/setup-python@v3 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.15.0 + run: python -m pip install cibuildwheel==2.16.0 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse @@ -213,6 +213,18 @@ Changelog +### v2.16.0 + +_18 September 2023_ + +- ✨ Add the ability to pass additional flags to a build frontend through the [CIBW_BUILD_FRONTEND](https://cibuildwheel.readthedocs.io/en/stable/options/#build-frontend) option (#1588). +- ✨ The environment variable SOURCE_DATE_EPOCH is now automatically passed through to container Linux builds (useful for [reproducible builds](https://reproducible-builds.org/docs/source-date-epoch/)!) (#1589) +- 🛠 Updates the prerelease CPython 3.12 version to 3.12.0rc2 (#1604) +- 🐛 Fix `requires_python` auto-detection from setup.py when the call to `setup()` is within an `if __name__ == "__main__" block (#1613) +- 🐛 Fix a bug that prevented building Linux wheels in Docker on a Windows host (#1573) +- 🐛 `--only` can now select prerelease-pythons (#1564) +- 📚 Docs & examples updates (#1582, #1593, #1598, #1615) + ### v2.15.0 _8 August 2023_ @@ -243,16 +255,6 @@ _10 June 2023_ - 🛠 Updates the prerelease CPython 3.12 version to 3.12.0b2. (#1516) - 🛠 Adds a moving `v.` tag for use in GitHub Actions workflow files. If you use this, you'll get the latest patch release within a minor version. Additionally, Dependabot won't send you PRs for patch releases. (#1517) -### v2.13.0 - -_28 May 2023_ - -- ✨ Adds CPython 3.12 support, under the prerelease flag [CIBW_PRERELEASE_PYTHONS](https://cibuildwheel.readthedocs.io/en/stable/options/#prerelease-pythons). This version of cibuildwheel uses 3.12.0b1. - - While CPython is in beta, the ABI can change, so your wheels might not be compatible with the final release. For this reason, we don't recommend distributing wheels until RC1, at which point 3.12 will be available in cibuildwheel without the flag. (#1507) - -- ✨ Adds the ability to pass arguments to the container engine when the container is created, using the [CIBW_CONTAINER_ENGINE](https://cibuildwheel.readthedocs.io/en/stable/options/#container-engine) option. (#1499) - --- diff --git a/cibuildwheel/__init__.py b/cibuildwheel/__init__.py index 79a44705..3e983dea 100644 --- a/cibuildwheel/__init__.py +++ b/cibuildwheel/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "2.15.0" +__version__ = "2.16.0" diff --git a/docs/changelog.md b/docs/changelog.md index 8d99c633..3224fc2a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,18 @@ title: Changelog # Changelog +### v2.16.0 + +_18 September 2023_ + +- ✨ Add the ability to pass additional flags to a build frontend through the [CIBW_BUILD_FRONTEND](https://cibuildwheel.readthedocs.io/en/stable/options/#build-frontend) option (#1588). +- ✨ The environment variable SOURCE_DATE_EPOCH is now automatically passed through to container Linux builds (useful for [reproducible builds](https://reproducible-builds.org/docs/source-date-epoch/)!) (#1589) +- 🛠 Updates the prerelease CPython 3.12 version to 3.12.0rc2 (#1604) +- 🐛 Fix `requires_python` auto-detection from setup.py when the call to `setup()` is within an `if __name__ == "__main__" block (#1613) +- 🐛 Fix a bug that prevented building Linux wheels in Docker on a Windows host (#1573) +- 🐛 `--only` can now select prerelease-pythons (#1564) +- 📚 Docs & examples updates (#1582, #1593, #1598, #1615) + ### v2.15.0 _8 August 2023_ diff --git a/docs/faq.md b/docs/faq.md index 91f2f670..e9028797 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -159,7 +159,7 @@ There are two suggested methods for keeping cibuildwheel up to date that instead If you use GitHub Actions for builds, you can use cibuildwheel as an action: ```yaml -uses: pypa/cibuildwheel@v2.15.0 +uses: pypa/cibuildwheel@v2.16.0 ``` This is a composite step that just runs cibuildwheel using pipx. You can set command-line options as `with:` parameters, and use `env:` as normal. @@ -181,7 +181,7 @@ The second option, and the only one that supports other CI systems, is using a ` ```bash # requirements-cibw.txt -cibuildwheel==2.15.0 +cibuildwheel==2.16.0 ``` Then your install step would have `python -m pip install -r requirements-cibw.txt` in it. Your `.github/dependabot.yml` file could look like this: @@ -325,7 +325,7 @@ Solutions to this vary, but the simplest is to use pipx: # most runners have pipx preinstalled, but in case you don't python3 -m pip install pipx -pipx run cibuildwheel==2.15.0 --output-dir wheelhouse +pipx run cibuildwheel==2.16.0 --output-dir wheelhouse pipx run twine upload wheelhouse/*.whl ``` diff --git a/docs/setup.md b/docs/setup.md index 7c04b1bf..1e7e52cc 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -184,7 +184,7 @@ To build Linux, Mac, and Windows wheels using GitHub Actions, create a `.github/ - uses: actions/checkout@v4 - name: Build wheels - run: pipx run cibuildwheel==2.15.0 + run: pipx run cibuildwheel==2.16.0 - uses: actions/upload-artifact@v3 with: @@ -219,7 +219,7 @@ To build Linux, Mac, and Windows wheels using GitHub Actions, create a `.github/ - uses: actions/setup-python@v3 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.15.0 + run: python -m pip install cibuildwheel==2.16.0 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse diff --git a/examples/appveyor-minimal.yml b/examples/appveyor-minimal.yml index 5ba6b666..ec675448 100644 --- a/examples/appveyor-minimal.yml +++ b/examples/appveyor-minimal.yml @@ -12,7 +12,7 @@ stack: python 3.7 init: - cmd: set PATH=C:\Python37;C:\Python37\Scripts;%PATH% -install: python -m pip install cibuildwheel==2.15.0 +install: python -m pip install cibuildwheel==2.16.0 build_script: python -m cibuildwheel --output-dir wheelhouse diff --git a/examples/azure-pipelines-minimal.yml b/examples/azure-pipelines-minimal.yml index 90865ffc..ac9ba041 100644 --- a/examples/azure-pipelines-minimal.yml +++ b/examples/azure-pipelines-minimal.yml @@ -6,7 +6,7 @@ jobs: - bash: | set -o errexit python3 -m pip install --upgrade pip - pip3 install cibuildwheel==2.15.0 + pip3 install cibuildwheel==2.16.0 displayName: Install dependencies - bash: cibuildwheel --output-dir wheelhouse . displayName: Build wheels @@ -20,7 +20,7 @@ jobs: - bash: | set -o errexit python3 -m pip install --upgrade pip - python3 -m pip install cibuildwheel==2.15.0 + python3 -m pip install cibuildwheel==2.16.0 displayName: Install dependencies - bash: cibuildwheel --output-dir wheelhouse . displayName: Build wheels @@ -34,7 +34,7 @@ jobs: - bash: | set -o errexit python -m pip install --upgrade pip - pip install cibuildwheel==2.15.0 + pip install cibuildwheel==2.16.0 displayName: Install dependencies - bash: cibuildwheel --output-dir wheelhouse . displayName: Build wheels diff --git a/examples/circleci-minimal.yml b/examples/circleci-minimal.yml index 015aa548..8dcbbf70 100644 --- a/examples/circleci-minimal.yml +++ b/examples/circleci-minimal.yml @@ -11,7 +11,7 @@ jobs: - run: name: Build the Linux wheels. command: | - pip3 install --user cibuildwheel==2.15.0 + pip3 install --user cibuildwheel==2.16.0 cibuildwheel --output-dir wheelhouse - store_artifacts: path: wheelhouse/ @@ -28,7 +28,7 @@ jobs: - run: name: Build the Linux aarch64 wheels. command: | - python3 -m pip install --user cibuildwheel==2.15.0 + python3 -m pip install --user cibuildwheel==2.16.0 python3 -m cibuildwheel --output-dir wheelhouse - store_artifacts: path: wheelhouse/ @@ -42,7 +42,7 @@ jobs: - run: name: Build the OS X wheels. command: | - pip3 install cibuildwheel==2.15.0 + pip3 install cibuildwheel==2.16.0 cibuildwheel --output-dir wheelhouse - store_artifacts: path: wheelhouse/ diff --git a/examples/cirrus-ci-intel-mac.yml b/examples/cirrus-ci-intel-mac.yml index ab0b69c4..0e66c8e4 100644 --- a/examples/cirrus-ci-intel-mac.yml +++ b/examples/cirrus-ci-intel-mac.yml @@ -1,6 +1,6 @@ build_and_store_wheels: &BUILD_AND_STORE_WHEELS install_cibuildwheel_script: - - python -m pip install cibuildwheel==2.15.0 + - python -m pip install cibuildwheel==2.16.0 run_cibuildwheel_script: - cibuildwheel wheels_artifacts: diff --git a/examples/cirrus-ci-minimal.yml b/examples/cirrus-ci-minimal.yml index 176726c9..f38b09ab 100644 --- a/examples/cirrus-ci-minimal.yml +++ b/examples/cirrus-ci-minimal.yml @@ -1,6 +1,6 @@ build_and_store_wheels: &BUILD_AND_STORE_WHEELS install_cibuildwheel_script: - - python -m pip install cibuildwheel==2.15.0 + - python -m pip install cibuildwheel==2.16.0 run_cibuildwheel_script: - cibuildwheel wheels_artifacts: diff --git a/examples/github-apple-silicon.yml b/examples/github-apple-silicon.yml index 233785e6..bc1547fb 100644 --- a/examples/github-apple-silicon.yml +++ b/examples/github-apple-silicon.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v4 - name: Build wheels - uses: pypa/cibuildwheel@v2.15.0 + uses: pypa/cibuildwheel@v2.16.0 env: CIBW_ARCHS_MACOS: x86_64 arm64 diff --git a/examples/github-deploy.yml b/examples/github-deploy.yml index 4b376e7b..68eaeb87 100644 --- a/examples/github-deploy.yml +++ b/examples/github-deploy.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v4 - name: Build wheels - uses: pypa/cibuildwheel@v2.15.0 + uses: pypa/cibuildwheel@v2.16.0 - uses: actions/upload-artifact@v3 with: diff --git a/examples/github-minimal.yml b/examples/github-minimal.yml index 21524657..79493c70 100644 --- a/examples/github-minimal.yml +++ b/examples/github-minimal.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - name: Build wheels - uses: pypa/cibuildwheel@v2.15.0 + uses: pypa/cibuildwheel@v2.16.0 # env: # CIBW_SOME_OPTION: value # ... diff --git a/examples/github-with-qemu.yml b/examples/github-with-qemu.yml index f26c71e4..c5472d01 100644 --- a/examples/github-with-qemu.yml +++ b/examples/github-with-qemu.yml @@ -20,7 +20,7 @@ jobs: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.15.0 + uses: pypa/cibuildwheel@v2.16.0 env: # configure cibuildwheel to build native archs ('auto'), and some # emulated ones diff --git a/examples/gitlab-minimal.yml b/examples/gitlab-minimal.yml index 454c5d27..4af8dd86 100644 --- a/examples/gitlab-minimal.yml +++ b/examples/gitlab-minimal.yml @@ -12,7 +12,7 @@ linux: DOCKER_TLS_CERTDIR: "" script: - curl -sSL https://get.docker.com/ | sh - - python -m pip install cibuildwheel==2.15.0 + - python -m pip install cibuildwheel==2.16.0 - cibuildwheel --output-dir wheelhouse artifacts: paths: @@ -23,7 +23,7 @@ windows: before_script: - choco install python -y --version 3.8.6 - choco install git.install -y - - py -m pip install cibuildwheel==2.15.0 + - py -m pip install cibuildwheel==2.16.0 script: - py -m cibuildwheel --output-dir wheelhouse --platform windows artifacts: diff --git a/examples/gitlab-with-qemu.yml b/examples/gitlab-with-qemu.yml index 6dd3f757..495479bf 100644 --- a/examples/gitlab-with-qemu.yml +++ b/examples/gitlab-with-qemu.yml @@ -14,7 +14,7 @@ linux: - curl -sSL https://get.docker.com/ | sh # Warning: This is extremely slow, be careful with how many wheels you build - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - - python -m pip install cibuildwheel==2.15.0 + - python -m pip install cibuildwheel==2.16.0 # Assuming your CI runner's default architecture is x86_64... - cibuildwheel --output-dir wheelhouse --platform linux --archs aarch64 artifacts: diff --git a/examples/travis-ci-deploy.yml b/examples/travis-ci-deploy.yml index a0c0ee7d..28e1822b 100644 --- a/examples/travis-ci-deploy.yml +++ b/examples/travis-ci-deploy.yml @@ -20,7 +20,7 @@ jobs: - ln -s /c/Python38/python.exe /c/Python38/python3.exe install: - - python3 -m pip install cibuildwheel==2.15.0 + - python3 -m pip install cibuildwheel==2.16.0 script: # build the wheels, put them into './dist' diff --git a/examples/travis-ci-minimal.yml b/examples/travis-ci-minimal.yml index 34c06fed..ca251d4b 100644 --- a/examples/travis-ci-minimal.yml +++ b/examples/travis-ci-minimal.yml @@ -26,7 +26,7 @@ jobs: - ln -s /c/Python38/python.exe /c/Python38/python3.exe install: - - python3 -m pip install cibuildwheel==2.15.0 + - python3 -m pip install cibuildwheel==2.16.0 script: # build the wheels, put them into './wheelhouse' diff --git a/examples/travis-ci-test-and-deploy.yml b/examples/travis-ci-test-and-deploy.yml index 32ac81e6..d748953d 100644 --- a/examples/travis-ci-test-and-deploy.yml +++ b/examples/travis-ci-test-and-deploy.yml @@ -54,7 +54,7 @@ jobs: - stage: deploy name: Build and deploy Linux wheels services: docker - install: python3 -m pip install cibuildwheel==2.15.0 twine + install: python3 -m pip install cibuildwheel==2.16.0 twine script: python3 -m cibuildwheel --output-dir wheelhouse after_success: python3 -m twine upload --skip-existing wheelhouse/*.whl # Deploy on windows @@ -62,7 +62,7 @@ jobs: name: Build and deploy Windows wheels os: windows language: shell - install: python3 -m pip install cibuildwheel==2.15.0 twine + install: python3 -m pip install cibuildwheel==2.16.0 twine script: python3 -m cibuildwheel --output-dir wheelhouse after_success: python3 -m twine upload --skip-existing wheelhouse/*.whl diff --git a/setup.cfg b/setup.cfg index e9e0c7cf..c84f6cd9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = cibuildwheel -version = 2.15.0 +version = 2.16.0 description = Build Python wheels on CI with minimal configuration. long_description = file: README.md long_description_content_type = text/markdown From 1646b086591dc1ffba87c11c17b684fb6d82c458 Mon Sep 17 00:00:00 2001 From: mayeut Date: Sun, 27 Aug 2023 13:13:34 +0200 Subject: [PATCH 10/17] fix: do not use `linux32` when unnecessary --- cibuildwheel/oci_container.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index 1e1a19a8..984748cd 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -17,7 +17,7 @@ from types import TracebackType from typing import IO, Dict, Literal from .typing import PathOrStr, PopenBytes -from .util import CIProvider, detect_ci_provider, parse_key_value_string +from .util import CIProvider, call, detect_ci_provider, parse_key_value_string ContainerEngineName = Literal["docker", "podman"] @@ -110,7 +110,16 @@ class OCIContainer: if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == "ppc64le": network_args = ["--network=host"] - shell_args = ["linux32", "/bin/bash"] if self.simulate_32_bit else ["/bin/bash"] + simulate_32_bit = self.simulate_32_bit + container_machine = call( + self.engine.name, "run", "--rm", self.image, "uname", "-m", capture_stdout=True + ).strip() + if container_machine not in {"x86_64", "aarch64"}: + # either the architecture running the image is already the right one + # or the image entrypoint took care of this + simulate_32_bit = False + + shell_args = ["linux32", "/bin/bash"] if simulate_32_bit else ["/bin/bash"] subprocess.run( [ From 137dc1a2cf2ae09bc79cf8159a4fb2c2e99cc772 Mon Sep 17 00:00:00 2001 From: mayeut Date: Thu, 7 Sep 2023 19:05:58 +0200 Subject: [PATCH 11/17] Rename `simulate_32_bit` to `enforce_32_bit` --- cibuildwheel/linux.py | 2 +- cibuildwheel/oci_container.py | 6 +++--- unit_test/option_prepare_test.py | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cibuildwheel/linux.py b/cibuildwheel/linux.py index 0c6184ef..32223d80 100644 --- a/cibuildwheel/linux.py +++ b/cibuildwheel/linux.py @@ -423,7 +423,7 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001 with OCIContainer( image=build_step.container_image, - simulate_32_bit=build_step.platform_tag.endswith("i686"), + enforce_32_bit=build_step.platform_tag.endswith("i686"), cwd=container_project_path, engine=options.globals.container_engine, ) as container: diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index 984748cd..f1ab3452 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -85,7 +85,7 @@ class OCIContainer: self, *, image: str, - simulate_32_bit: bool = False, + enforce_32_bit: bool = False, cwd: PathOrStr | None = None, engine: OCIContainerEngineConfig = DEFAULT_ENGINE, ): @@ -94,7 +94,7 @@ class OCIContainer: raise ValueError(msg) self.image = image - self.simulate_32_bit = simulate_32_bit + self.enforce_32_bit = enforce_32_bit self.cwd = cwd self.name: str | None = None self.engine = engine @@ -110,7 +110,7 @@ class OCIContainer: if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == "ppc64le": network_args = ["--network=host"] - simulate_32_bit = self.simulate_32_bit + simulate_32_bit = self.enforce_32_bit container_machine = call( self.engine.name, "run", "--rm", self.image, "uname", "-m", capture_stdout=True ).strip() diff --git a/unit_test/option_prepare_test.py b/unit_test/option_prepare_test.py index fdf77c53..180ef015 100644 --- a/unit_test/option_prepare_test.py +++ b/unit_test/option_prepare_test.py @@ -72,7 +72,7 @@ def test_build_default_launches(monkeypatch): kwargs = build_in_container.call_args_list[0][1] assert "quay.io/pypa/manylinux2014_x86_64" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert not kwargs["container"]["simulate_32_bit"] + assert not kwargs["container"]["enforce_32_bit"] identifiers = {x.identifier for x in kwargs["platform_configs"]} assert identifiers == {f"{x}-manylinux_x86_64" for x in ALL_IDS} @@ -80,7 +80,7 @@ def test_build_default_launches(monkeypatch): kwargs = build_in_container.call_args_list[1][1] assert "quay.io/pypa/manylinux2014_i686" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert kwargs["container"]["simulate_32_bit"] + assert kwargs["container"]["enforce_32_bit"] identifiers = {x.identifier for x in kwargs["platform_configs"]} assert identifiers == {f"{x}-manylinux_i686" for x in ALL_IDS} @@ -88,7 +88,7 @@ def test_build_default_launches(monkeypatch): kwargs = build_in_container.call_args_list[2][1] assert "quay.io/pypa/musllinux_1_1_x86_64" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert not kwargs["container"]["simulate_32_bit"] + assert not kwargs["container"]["enforce_32_bit"] identifiers = {x.identifier for x in kwargs["platform_configs"]} assert identifiers == { @@ -98,7 +98,7 @@ def test_build_default_launches(monkeypatch): kwargs = build_in_container.call_args_list[3][1] assert "quay.io/pypa/musllinux_1_1_i686" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert kwargs["container"]["simulate_32_bit"] + assert kwargs["container"]["enforce_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} @@ -141,7 +141,7 @@ before-all = "true" kwargs = build_in_container.call_args_list[0][1] assert "quay.io/pypa/manylinux2014_x86_64" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert not kwargs["container"]["simulate_32_bit"] + assert not kwargs["container"]["enforce_32_bit"] identifiers = {x.identifier for x in kwargs["platform_configs"]} assert identifiers == {"cp36-manylinux_x86_64"} @@ -150,7 +150,7 @@ before-all = "true" kwargs = build_in_container.call_args_list[1][1] assert "quay.io/pypa/manylinux2014_x86_64" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert not kwargs["container"]["simulate_32_bit"] + assert not kwargs["container"]["enforce_32_bit"] identifiers = {x.identifier for x in kwargs["platform_configs"]} assert identifiers == { @@ -162,7 +162,7 @@ before-all = "true" kwargs = build_in_container.call_args_list[2][1] assert "quay.io/pypa/manylinux_2_28_x86_64" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert not kwargs["container"]["simulate_32_bit"] + assert not kwargs["container"]["enforce_32_bit"] identifiers = {x.identifier for x in kwargs["platform_configs"]} assert identifiers == { f"{x}-manylinux_x86_64" @@ -172,7 +172,7 @@ before-all = "true" kwargs = build_in_container.call_args_list[3][1] assert "quay.io/pypa/manylinux2014_i686" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert kwargs["container"]["simulate_32_bit"] + assert kwargs["container"]["enforce_32_bit"] identifiers = {x.identifier for x in kwargs["platform_configs"]} assert identifiers == {f"{x}-manylinux_i686" for x in ALL_IDS} @@ -180,7 +180,7 @@ before-all = "true" kwargs = build_in_container.call_args_list[4][1] assert "quay.io/pypa/musllinux_1_1_x86_64" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert not kwargs["container"]["simulate_32_bit"] + assert not kwargs["container"]["enforce_32_bit"] identifiers = {x.identifier for x in kwargs["platform_configs"]} assert identifiers == { @@ -190,7 +190,7 @@ before-all = "true" kwargs = build_in_container.call_args_list[5][1] assert "quay.io/pypa/musllinux_1_2_x86_64" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert not kwargs["container"]["simulate_32_bit"] + assert not kwargs["container"]["enforce_32_bit"] identifiers = {x.identifier for x in kwargs["platform_configs"]} assert identifiers == { f"{x}-musllinux_x86_64" for x in ALL_IDS - {"cp36", "cp37", "cp38", "cp39"} if "pp" not in x @@ -199,7 +199,7 @@ before-all = "true" kwargs = build_in_container.call_args_list[6][1] assert "quay.io/pypa/musllinux_1_1_i686" in kwargs["container"]["image"] assert kwargs["container"]["cwd"] == PurePosixPath("/project") - assert kwargs["container"]["simulate_32_bit"] + assert kwargs["container"]["enforce_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} From cf7586ce7e05f3279be7d39b203e4faa4564d035 Mon Sep 17 00:00:00 2001 From: mayeut Date: Thu, 7 Sep 2023 19:12:48 +0200 Subject: [PATCH 12/17] check i686 directly --- cibuildwheel/oci_container.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index f1ab3452..bfb3ff19 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -111,13 +111,14 @@ class OCIContainer: network_args = ["--network=host"] simulate_32_bit = self.enforce_32_bit - container_machine = call( - self.engine.name, "run", "--rm", self.image, "uname", "-m", capture_stdout=True - ).strip() - if container_machine not in {"x86_64", "aarch64"}: - # either the architecture running the image is already the right one - # or the image entrypoint took care of this - simulate_32_bit = False + if self.enforce_32_bit: + # If the architecture running the image is already the right one + # or the image entrypoint takes care of enforcing this, then we don't need to + # simulate this + container_machine = call( + self.engine.name, "run", "--rm", self.image, "uname", "-m", capture_stdout=True + ).strip() + simulate_32_bit = container_machine != "i686" shell_args = ["linux32", "/bin/bash"] if simulate_32_bit else ["/bin/bash"] From 6d0890e7e04f37bf026d12e72031430f4f85b06c Mon Sep 17 00:00:00 2001 From: mayeut Date: Thu, 7 Sep 2023 20:30:22 +0200 Subject: [PATCH 13/17] add tests --- unit_test/oci_container_test.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/unit_test/oci_container_test.py b/unit_test/oci_container_test.py index a4b200d9..82bfe629 100644 --- a/unit_test/oci_container_test.py +++ b/unit_test/oci_container_test.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json import os import platform import random @@ -18,15 +19,12 @@ from cibuildwheel.oci_container import OCIContainer, OCIContainerEngineConfig # for these tests we use manylinux2014 images, because they're available on # multi architectures and include python3.8 +DEFAULT_IMAGE_TEMPLATE = "quay.io/pypa/manylinux2014_{machine}:2023-09-04-0828984" pm = platform.machine() -if pm == "x86_64": - DEFAULT_IMAGE = "quay.io/pypa/manylinux2014_x86_64:2020-05-17-2f8ac3b" +if pm in {"x86_64", "ppc64le", "s390x"}: + DEFAULT_IMAGE = DEFAULT_IMAGE_TEMPLATE.format(machine=pm) elif pm in {"aarch64", "arm64"}: - DEFAULT_IMAGE = "quay.io/pypa/manylinux2014_aarch64:2020-05-17-2f8ac3b" -elif pm == "ppc64le": - DEFAULT_IMAGE = "quay.io/pypa/manylinux2014_ppc64le:2020-05-17-2f8ac3b" -elif pm == "s390x": - DEFAULT_IMAGE = "quay.io/pypa/manylinux2014_s390x:2020-05-17-2f8ac3b" + DEFAULT_IMAGE = DEFAULT_IMAGE_TEMPLATE.format(machine="aarch64") else: DEFAULT_IMAGE = "" @@ -378,3 +376,24 @@ def test_parse_engine_config(config, name, create_args): engine_config = OCIContainerEngineConfig.from_config_string(config) assert engine_config.name == name assert engine_config.create_args == create_args + + +@pytest.mark.skipif(pm != "x86_64", reason="Only runs on x86_64") +@pytest.mark.parametrize( + ("image", "shell_args"), + [ + (DEFAULT_IMAGE_TEMPLATE.format(machine="i686"), ["/bin/bash"]), + (DEFAULT_IMAGE_TEMPLATE.format(machine="x86_64"), ["linux32", "/bin/bash"]), + ], +) +def test_enforce_32_bit(container_engine, image, shell_args): + with OCIContainer(engine=container_engine, image=image, enforce_32_bit=True) as container: + assert container.call(["uname", "-m"], capture_output=True).strip() == "i686" + container_args = subprocess.run( + f"{container.engine.name} inspect -f '{{{{json .Args }}}}' {container.name}", + shell=True, + check=True, + stdout=subprocess.PIPE, + text=True, + ).stdout + assert json.loads(container_args) == shell_args From ba11212a135469b1b3c878756cba85d5258ba9c9 Mon Sep 17 00:00:00 2001 From: mayeut Date: Fri, 8 Sep 2023 11:02:04 +0200 Subject: [PATCH 14/17] use fixture in oci_container_test.py to clean-up images after tests --- test/conftest.py | 8 ++------ unit_test/oci_container_test.py | 36 ++++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 16114868..06a4f242 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -34,6 +34,8 @@ def build_frontend_env(request) -> dict[str, str]: @pytest.fixture() def docker_cleanup() -> Generator[None, None, None]: def get_images() -> set[str]: + if detect_ci_provider() is None or platform != "linux": + return set() images = subprocess.run( ["docker", "image", "ls", "--format", "{{json .ID}}"], text=True, @@ -42,12 +44,6 @@ def docker_cleanup() -> Generator[None, None, None]: ).stdout return {json.loads(image.strip()) for image in images.splitlines() if image.strip()} - if detect_ci_provider() is None or platform != "linux": - try: - yield - finally: - pass - return images_before = get_images() try: yield diff --git a/unit_test/oci_container_test.py b/unit_test/oci_container_test.py index 82bfe629..c342892d 100644 --- a/unit_test/oci_container_test.py +++ b/unit_test/oci_container_test.py @@ -14,6 +14,7 @@ import tomli_w from cibuildwheel.environment import EnvironmentAssignmentBash from cibuildwheel.oci_container import OCIContainer, OCIContainerEngineConfig +from cibuildwheel.util import detect_ci_provider # Test utilities @@ -31,13 +32,31 @@ else: PODMAN = OCIContainerEngineConfig(name="podman") -@pytest.fixture(params=["docker", "podman"]) +@pytest.fixture(params=["docker", "podman"], scope="module") def container_engine(request): if request.param == "docker" and not request.config.getoption("--run-docker"): pytest.skip("need --run-docker option to run") if request.param == "podman" and not request.config.getoption("--run-podman"): pytest.skip("need --run-podman option to run") - return OCIContainerEngineConfig(name=request.param) + + def get_images() -> set[str]: + if detect_ci_provider() is None: + return set() + images = subprocess.run( + [request.param, "image", "ls", "--format", "{{json .ID}}"], + text=True, + check=True, + stdout=subprocess.PIPE, + ).stdout + return {json.loads(image.strip()) for image in images.splitlines() if image.strip()} + + images_before = get_images() + try: + yield OCIContainerEngineConfig(name=request.param) + finally: + images_after = get_images() + for image in images_after - images_before: + subprocess.run([request.param, "rmi", image], check=False) # Tests @@ -230,10 +249,9 @@ def test_environment_executor(container_engine): assert assignment.evaluated_value({}, container.environment_executor) == "42" -def test_podman_vfs(tmp_path: Path, monkeypatch, request): - # Tests podman VFS, for the podman in docker use-case - if not request.config.getoption("--run-podman"): - pytest.skip("need --run-podman option to run") +def test_podman_vfs(tmp_path: Path, monkeypatch, container_engine): + if container_engine.name != "podman": + pytest.skip("only runs with podman") # create the VFS configuration vfs_path = tmp_path / "podman_vfs" @@ -309,9 +327,9 @@ def test_podman_vfs(tmp_path: Path, monkeypatch, request): subprocess.run(["podman", "unshare", "rm", "-rf", vfs_path], check=True) -def test_create_args_volume(tmp_path: Path, request): - if not request.config.getoption("--run-docker"): - pytest.skip("need --run-docker option to run") +def test_create_args_volume(tmp_path: Path, container_engine): + if container_engine.name != "docker": + pytest.skip("only runs with docker") if "CIRCLECI" in os.environ or "GITLAB_CI" in os.environ: pytest.skip( From 0ccf1dc016d1bb88e118ce6549475e82b21217bd Mon Sep 17 00:00:00 2001 From: mayeut Date: Fri, 8 Sep 2023 11:19:01 +0200 Subject: [PATCH 15/17] remove GHA runner cached docker images This frees up some space preventing reaching GHA disk space limits. --- .github/workflows/test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5297c386..6609baf8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,13 @@ jobs: sudo apt-get update sudo apt-get -y install podman + # free some space to prevent reaching GHA disk space limits + - name: Clean docker images + if: runner.os == 'Linux' + run: | + docker system prune -a -f + df -h + - name: Install dependencies run: | python -m pip install ".[test]" From 7a8b8012ffe79448b50852f8b7c327292ed61f48 Mon Sep 17 00:00:00 2001 From: Matthieu Darbois Date: Mon, 18 Sep 2023 21:30:22 +0200 Subject: [PATCH 16/17] clearer simulate_32_bit initialization Co-authored-by: Joe Rickerby --- cibuildwheel/oci_container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cibuildwheel/oci_container.py b/cibuildwheel/oci_container.py index bfb3ff19..9b2d1751 100644 --- a/cibuildwheel/oci_container.py +++ b/cibuildwheel/oci_container.py @@ -110,7 +110,7 @@ class OCIContainer: if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == "ppc64le": network_args = ["--network=host"] - simulate_32_bit = self.enforce_32_bit + simulate_32_bit = False if self.enforce_32_bit: # If the architecture running the image is already the right one # or the image entrypoint takes care of enforcing this, then we don't need to From 72222654f48f70f792df979f05e0c1dea470ae35 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 08:28:07 +0200 Subject: [PATCH 17/17] [pre-commit.ci] pre-commit autoupdate (#1619) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.288 → v0.0.290](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.288...v0.0.290) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d17b785..dd76c966 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.288 + rev: v0.0.290 hooks: - id: ruff args: ["--fix", "--show-fixes"]