Skip to content

Commit 9d5f5e6

Browse files
authored
Make container-engine a build (non-global) option (#1792)
1 parent 1b354cf commit 9d5f5e6

9 files changed

+135
-75
lines changed

bin/generate_schema.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@
247247
non_global_options = {k: {"$ref": f"#/properties/{k}"} for k in schema["properties"]}
248248
del non_global_options["build"]
249249
del non_global_options["skip"]
250-
del non_global_options["container-engine"]
251250
del non_global_options["test-skip"]
252251

253252
overrides["items"]["properties"]["select"]["oneOf"] = string_array
@@ -258,6 +257,7 @@
258257
not_linux = non_global_options.copy()
259258

260259
del not_linux["environment-pass"]
260+
del not_linux["container-engine"]
261261
for key in list(not_linux):
262262
if "linux-" in key:
263263
del not_linux[key]

cibuildwheel/linux.py

+38-32
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
from ._compat.typing import assert_never
1212
from .architecture import Architecture
1313
from .logger import log
14-
from .oci_container import OCIContainer
15-
from .options import Options
14+
from .oci_container import OCIContainer, OCIContainerEngineConfig
15+
from .options import BuildOptions, Options
1616
from .typing import PathOrStr
1717
from .util import (
1818
AlreadyBuiltWheelError,
@@ -44,6 +44,7 @@ def path(self) -> PurePosixPath:
4444
class BuildStep:
4545
platform_configs: list[PythonConfiguration]
4646
platform_tag: str
47+
container_engine: OCIContainerEngineConfig
4748
container_image: str
4849

4950

@@ -65,8 +66,9 @@ def get_python_configurations(
6566
]
6667

6768

68-
def container_image_for_python_configuration(config: PythonConfiguration, options: Options) -> str:
69-
build_options = options.build_options(config.identifier)
69+
def container_image_for_python_configuration(
70+
config: PythonConfiguration, build_options: BuildOptions
71+
) -> str:
7072
# e.g
7173
# identifier is 'cp310-manylinux_x86_64'
7274
# platform_tag is 'manylinux_x86_64'
@@ -91,22 +93,26 @@ def get_build_steps(
9193
Groups PythonConfigurations into BuildSteps. Each BuildStep represents a
9294
separate container instance.
9395
"""
94-
steps = OrderedDict[Tuple[str, str, str], BuildStep]()
96+
steps = OrderedDict[Tuple[str, str, str, OCIContainerEngineConfig], BuildStep]()
9597

9698
for config in python_configurations:
9799
_, platform_tag = config.identifier.split("-", 1)
98100

99-
before_all = options.build_options(config.identifier).before_all
100-
container_image = container_image_for_python_configuration(config, options)
101+
build_options = options.build_options(config.identifier)
102+
103+
before_all = build_options.before_all
104+
container_image = container_image_for_python_configuration(config, build_options)
105+
container_engine = build_options.container_engine
101106

102-
step_key = (platform_tag, container_image, before_all)
107+
step_key = (platform_tag, container_image, before_all, container_engine)
103108

104109
if step_key in steps:
105110
steps[step_key].platform_configs.append(config)
106111
else:
107112
steps[step_key] = BuildStep(
108113
platform_configs=[config],
109114
platform_tag=platform_tag,
115+
container_engine=container_engine,
110116
container_image=container_image,
111117
)
112118

@@ -388,29 +394,6 @@ def build_in_container(
388394

389395

390396
def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001
391-
try:
392-
# check the container engine is installed
393-
subprocess.run(
394-
[options.globals.container_engine.name, "--version"],
395-
check=True,
396-
stdout=subprocess.DEVNULL,
397-
)
398-
except subprocess.CalledProcessError:
399-
print(
400-
unwrap(
401-
f"""
402-
cibuildwheel: {options.globals.container_engine} not found. An
403-
OCI exe like Docker or Podman is required to run Linux builds.
404-
If you're building on Travis CI, add `services: [docker]` to
405-
your .travis.yml. If you're building on Circle CI in Linux,
406-
add a `setup_remote_docker` step to your .circleci/config.yml.
407-
If you're building on Cirrus CI, use `docker_builder` task.
408-
"""
409-
),
410-
file=sys.stderr,
411-
)
412-
sys.exit(2)
413-
414397
python_configurations = get_python_configurations(
415398
options.globals.build_selector, options.globals.architectures
416399
)
@@ -425,6 +408,29 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001
425408
container_package_dir = container_project_path / abs_package_dir.relative_to(cwd)
426409

427410
for build_step in get_build_steps(options, python_configurations):
411+
try:
412+
# check the container engine is installed
413+
subprocess.run(
414+
[build_step.container_engine.name, "--version"],
415+
check=True,
416+
stdout=subprocess.DEVNULL,
417+
)
418+
except subprocess.CalledProcessError:
419+
print(
420+
unwrap(
421+
f"""
422+
cibuildwheel: {build_step.container_engine.name} not found. An
423+
OCI exe like Docker or Podman is required to run Linux builds.
424+
If you're building on Travis CI, add `services: [docker]` to
425+
your .travis.yml. If you're building on Circle CI in Linux,
426+
add a `setup_remote_docker` step to your .circleci/config.yml.
427+
If you're building on Cirrus CI, use `docker_builder` task.
428+
"""
429+
),
430+
file=sys.stderr,
431+
)
432+
sys.exit(2)
433+
428434
try:
429435
ids_to_build = [x.identifier for x in build_step.platform_configs]
430436
log.step(f"Starting container image {build_step.container_image}...")
@@ -435,7 +441,7 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001
435441
image=build_step.container_image,
436442
enforce_32_bit=build_step.platform_tag.endswith("i686"),
437443
cwd=container_project_path,
438-
engine=options.globals.container_engine,
444+
engine=build_step.container_engine,
439445
) as container:
440446
build_in_container(
441447
options=options,

cibuildwheel/oci_container.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import typing
1212
import uuid
1313
from collections.abc import Mapping, Sequence
14-
from dataclasses import dataclass
14+
from dataclasses import dataclass, field
1515
from pathlib import Path, PurePath, PurePosixPath
1616
from types import TracebackType
1717
from typing import IO, Dict, Literal
@@ -32,7 +32,7 @@
3232
@dataclass(frozen=True)
3333
class OCIContainerEngineConfig:
3434
name: ContainerEngineName
35-
create_args: Sequence[str] = ()
35+
create_args: tuple[str, ...] = field(default_factory=tuple)
3636
disable_host_mount: bool = False
3737

3838
@staticmethod
@@ -58,7 +58,7 @@ def from_config_string(config_string: str) -> OCIContainerEngineConfig:
5858
)
5959

6060
return OCIContainerEngineConfig(
61-
name=name, create_args=create_args, disable_host_mount=disable_host_mount
61+
name=name, create_args=tuple(create_args), disable_host_mount=disable_host_mount
6262
)
6363

6464
def options_summary(self) -> str | dict[str, str]:

cibuildwheel/options.py

+16-14
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ class GlobalOptions:
7575
build_selector: BuildSelector
7676
test_selector: TestSelector
7777
architectures: set[Architecture]
78-
container_engine: OCIContainerEngineConfig
7978

8079

8180
@dataclasses.dataclass(frozen=True)
@@ -95,6 +94,7 @@ class BuildOptions:
9594
build_verbosity: int
9695
build_frontend: BuildFrontendConfig | None
9796
config_settings: str
97+
container_engine: OCIContainerEngineConfig
9898

9999
@property
100100
def package_dir(self) -> Path:
@@ -545,25 +545,12 @@ def globals(self) -> GlobalOptions:
545545
)
546546
test_selector = TestSelector(skip_config=test_skip)
547547

548-
container_engine_str = self.reader.get(
549-
"container-engine",
550-
table_format={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote},
551-
)
552-
553-
try:
554-
container_engine = OCIContainerEngineConfig.from_config_string(container_engine_str)
555-
except ValueError as e:
556-
msg = f"cibuildwheel: Failed to parse container config. {e}"
557-
print(msg, file=sys.stderr)
558-
sys.exit(2)
559-
560548
return GlobalOptions(
561549
package_dir=package_dir,
562550
output_dir=output_dir,
563551
build_selector=build_selector,
564552
test_selector=test_selector,
565553
architectures=architectures,
566-
container_engine=container_engine,
567554
)
568555

569556
def build_options(self, identifier: str | None) -> BuildOptions:
@@ -643,6 +630,8 @@ def build_options(self, identifier: str | None) -> BuildOptions:
643630

644631
manylinux_images: dict[str, str] = {}
645632
musllinux_images: dict[str, str] = {}
633+
container_engine: OCIContainerEngineConfig | None = None
634+
646635
if self.platform == "linux":
647636
all_pinned_container_images = _get_pinned_container_images()
648637

@@ -677,6 +666,18 @@ def build_options(self, identifier: str | None) -> BuildOptions:
677666

678667
musllinux_images[build_platform] = image
679668

669+
container_engine_str = self.reader.get(
670+
"container-engine",
671+
table_format={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote},
672+
)
673+
674+
try:
675+
container_engine = OCIContainerEngineConfig.from_config_string(container_engine_str)
676+
except ValueError as e:
677+
msg = f"cibuildwheel: Failed to parse container config. {e}"
678+
print(msg, file=sys.stderr)
679+
sys.exit(2)
680+
680681
return BuildOptions(
681682
globals=self.globals,
682683
test_command=test_command,
@@ -693,6 +694,7 @@ def build_options(self, identifier: str | None) -> BuildOptions:
693694
musllinux_images=musllinux_images or None,
694695
build_frontend=build_frontend,
695696
config_settings=config_settings,
697+
container_engine=container_engine,
696698
)
697699

698700
def check_for_invalid_configuration(self, identifiers: Iterable[str]) -> None:

cibuildwheel/resources/cibuildwheel.schema.json

+6
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,9 @@
491491
"config-settings": {
492492
"$ref": "#/properties/config-settings"
493493
},
494+
"container-engine": {
495+
"$ref": "#/properties/container-engine"
496+
},
494497
"dependency-versions": {
495498
"$ref": "#/properties/dependency-versions"
496499
},
@@ -579,6 +582,9 @@
579582
"config-settings": {
580583
"$ref": "#/properties/config-settings"
581584
},
585+
"container-engine": {
586+
"$ref": "#/properties/container-engine"
587+
},
582588
"environment": {
583589
"$ref": "#/properties/environment"
584590
},

unit_test/linux_build_steps_test.py

+25-7
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from pprint import pprint
66

77
import cibuildwheel.linux
8-
import cibuildwheel.oci_container
8+
from cibuildwheel.oci_container import OCIContainerEngineConfig
99
from cibuildwheel.options import CommandLineArguments, Options
1010

1111

1212
def test_linux_container_split(tmp_path: Path, monkeypatch):
1313
"""
14-
Tests splitting linux builds by container image and before_all
14+
Tests splitting linux builds by container image, container engine, and before_all
1515
"""
1616

1717
args = CommandLineArguments.defaults()
@@ -28,13 +28,17 @@ def test_linux_container_split(tmp_path: Path, monkeypatch):
2828
archs = "x86_64 i686"
2929
3030
[[tool.cibuildwheel.overrides]]
31-
select = "cp{38,39,310}-*"
31+
select = "cp{37,38,39,310}-*"
3232
manylinux-x86_64-image = "other_container_image"
3333
manylinux-i686-image = "other_container_image"
3434
3535
[[tool.cibuildwheel.overrides]]
3636
select = "cp39-*"
3737
before-all = "echo 'a cp39-only command'"
38+
39+
[[tool.cibuildwheel.overrides]]
40+
select = "cp310-*"
41+
container-engine = "docker; create_args: --privileged"
3842
"""
3943
)
4044
)
@@ -55,21 +59,35 @@ def identifiers(step):
5559
def before_alls(step):
5660
return [options.build_options(c.identifier).before_all for c in step.platform_configs]
5761

62+
def container_engines(step):
63+
return [options.build_options(c.identifier).container_engine for c in step.platform_configs]
64+
5865
pprint(build_steps)
5966

67+
default_container_engine = OCIContainerEngineConfig(name="docker")
68+
6069
assert build_steps[0].container_image == "normal_container_image"
6170
assert identifiers(build_steps[0]) == [
6271
"cp36-manylinux_x86_64",
63-
"cp37-manylinux_x86_64",
6472
"cp311-manylinux_x86_64",
6573
"cp312-manylinux_x86_64",
6674
]
67-
assert before_alls(build_steps[0]) == ["", "", "", ""]
75+
assert before_alls(build_steps[0]) == [""] * 3
76+
assert container_engines(build_steps[0]) == [default_container_engine] * 3
6877

6978
assert build_steps[1].container_image == "other_container_image"
70-
assert identifiers(build_steps[1]) == ["cp38-manylinux_x86_64", "cp310-manylinux_x86_64"]
71-
assert before_alls(build_steps[1]) == ["", ""]
79+
assert identifiers(build_steps[1]) == ["cp37-manylinux_x86_64", "cp38-manylinux_x86_64"]
80+
assert before_alls(build_steps[1]) == [""] * 2
81+
assert container_engines(build_steps[1]) == [default_container_engine] * 2
7282

7383
assert build_steps[2].container_image == "other_container_image"
7484
assert identifiers(build_steps[2]) == ["cp39-manylinux_x86_64"]
7585
assert before_alls(build_steps[2]) == ["echo 'a cp39-only command'"]
86+
assert container_engines(build_steps[2]) == [default_container_engine]
87+
88+
assert build_steps[3].container_image == "other_container_image"
89+
assert identifiers(build_steps[3]) == ["cp310-manylinux_x86_64"]
90+
assert before_alls(build_steps[3]) == [""]
91+
assert container_engines(build_steps[3]) == [
92+
OCIContainerEngineConfig(name="docker", create_args=("--privileged",))
93+
]

0 commit comments

Comments
 (0)