Skip to content

Commit 4d0d9f6

Browse files
authored
ci: warm-up docker images (#2099)
* ci: warm-up docker images * don't cache when running emulation tests * skip graalpy cache * add comment about the single worker case * enforce docker warm-up succeeds in CI * skip warm-up on architectures where it does not do any good
1 parent 34dbe0e commit 4d0d9f6

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ docs = [
8484
]
8585
test = [
8686
"build",
87+
"filelock",
8788
"jinja2",
8889
"pytest-timeout",
8990
"pytest-xdist",

test/conftest.py

+81
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
from typing import Generator
66

77
import pytest
8+
from filelock import FileLock
89

10+
from cibuildwheel.architecture import Architecture
11+
from cibuildwheel.options import CommandLineArguments, Options
912
from cibuildwheel.util import detect_ci_provider, find_uv
1013

1114
from .utils import EMULATED_ARCHS, platform
@@ -28,6 +31,84 @@ def pytest_addoption(parser: pytest.Parser) -> None:
2831
)
2932

3033

34+
def docker_warmup(request: pytest.FixtureRequest) -> None:
35+
machine = request.config.getoption("--run-emulation", default=None)
36+
if machine is None:
37+
archs = {arch.value for arch in Architecture.auto_archs("linux")}
38+
elif machine == "all":
39+
archs = set(EMULATED_ARCHS)
40+
else:
41+
archs = {machine}
42+
43+
# Only include architectures where there are missing pre-installed interpreters
44+
archs &= {"x86_64", "i686", "aarch64"}
45+
if not archs:
46+
return
47+
48+
options = Options(
49+
platform="linux",
50+
command_line_arguments=CommandLineArguments.defaults(),
51+
env={},
52+
defaults=True,
53+
)
54+
build_options = options.build_options(None)
55+
assert build_options.manylinux_images is not None
56+
assert build_options.musllinux_images is not None
57+
images = [build_options.manylinux_images[arch] for arch in archs] + [
58+
build_options.musllinux_images[arch] for arch in archs
59+
]
60+
# exclude GraalPy as it's not a target for cibuildwheel
61+
command = (
62+
"manylinux-interpreters ensure $(manylinux-interpreters list 2>/dev/null | grep -v graalpy) &&"
63+
"cpython3.13 -m pip download -d /tmp setuptools wheel pytest"
64+
)
65+
for image in images:
66+
container_id = subprocess.run(
67+
["docker", "create", image, "bash", "-c", command],
68+
text=True,
69+
check=True,
70+
stdout=subprocess.PIPE,
71+
).stdout.strip()
72+
try:
73+
subprocess.run(["docker", "start", container_id], check=True, stdout=subprocess.DEVNULL)
74+
exit_code = subprocess.run(
75+
["docker", "wait", container_id], text=True, check=True, stdout=subprocess.PIPE
76+
).stdout.strip()
77+
assert exit_code == "0"
78+
subprocess.run(
79+
["docker", "commit", container_id, image], check=True, stdout=subprocess.DEVNULL
80+
)
81+
finally:
82+
subprocess.run(["docker", "rm", container_id], check=True, stdout=subprocess.DEVNULL)
83+
84+
85+
@pytest.fixture(scope="session", autouse=True)
86+
def docker_warmup_fixture(
87+
request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str
88+
) -> None:
89+
# if we're in CI testing linux, let's warm-up docker images
90+
if detect_ci_provider() is None or platform != "linux":
91+
return None
92+
if request.config.getoption("--run-emulation", default=None) is not None:
93+
# emulation tests only run one test in CI, caching the image only slows down the test
94+
return None
95+
96+
if worker_id == "master":
97+
# not executing with multiple workers
98+
# it might be unsafe to write to tmp_path_factory.getbasetemp().parent
99+
return docker_warmup(request)
100+
101+
# get the temp directory shared by all workers
102+
root_tmp_dir = tmp_path_factory.getbasetemp().parent
103+
104+
fn = root_tmp_dir / "warmup.done"
105+
with FileLock(str(fn) + ".lock"):
106+
if not fn.is_file():
107+
docker_warmup(request)
108+
fn.write_text("done")
109+
return None
110+
111+
31112
@pytest.fixture(params=["pip", "build"])
32113
def build_frontend_env_nouv(request: pytest.FixtureRequest) -> dict[str, str]:
33114
frontend = request.param

0 commit comments

Comments
 (0)