Skip to content

Commit e3ff027

Browse files
committed
feature: add support for ABI3 wheels
1 parent de07370 commit e3ff027

File tree

7 files changed

+341
-203
lines changed

7 files changed

+341
-203
lines changed

cibuildwheel/linux.py

Lines changed: 79 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .util import (
1313
BuildSelector,
1414
NonPlatformWheelError,
15+
find_compatible_abi3_wheel,
1516
get_build_verbosity_extra_flags,
1617
prepare_command,
1718
read_python_configs,
@@ -132,6 +133,8 @@ def build_on_docker(
132133
)
133134
docker.call(["sh", "-c", before_all_prepared], env=env)
134135

136+
built_wheels: List[PurePath] = []
137+
135138
for config in platform_configs:
136139
log.build_start(config.identifier)
137140
build_options = options.build_options(config.identifier)
@@ -174,74 +177,83 @@ def build_on_docker(
174177
)
175178
sys.exit(1)
176179

177-
if build_options.before_build:
178-
log.step("Running before_build...")
179-
before_build_prepared = prepare_command(
180-
build_options.before_build,
181-
project=container_project_path,
182-
package=container_package_dir,
183-
)
184-
docker.call(["sh", "-c", before_build_prepared], env=env)
185-
186-
log.step("Building wheel...")
187-
188-
temp_dir = PurePath("/tmp/cibuildwheel")
189-
built_wheel_dir = temp_dir / "built_wheel"
190-
docker.call(["rm", "-rf", built_wheel_dir])
191-
docker.call(["mkdir", "-p", built_wheel_dir])
192-
193-
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
194-
195-
if build_options.build_frontend == "pip":
196-
docker.call(
197-
[
198-
"python",
199-
"-m",
200-
"pip",
201-
"wheel",
202-
container_package_dir,
203-
f"--wheel-dir={built_wheel_dir}",
204-
"--no-deps",
205-
*verbosity_flags,
206-
],
207-
env=env,
208-
)
209-
elif build_options.build_frontend == "build":
210-
config_setting = " ".join(verbosity_flags)
211-
docker.call(
212-
[
213-
"python",
214-
"-m",
215-
"build",
216-
container_package_dir,
217-
"--wheel",
218-
f"--outdir={built_wheel_dir}",
219-
f"--config-setting={config_setting}",
220-
],
221-
env=env,
180+
abi3_wheel = find_compatible_abi3_wheel(built_wheels, config.identifier)
181+
if abi3_wheel:
182+
log.step_end()
183+
print(
184+
f"Found previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..."
222185
)
186+
repaired_wheels = [abi3_wheel]
223187
else:
224-
assert_never(build_options.build_frontend)
225188

226-
built_wheel = docker.glob(built_wheel_dir, "*.whl")[0]
189+
if build_options.before_build:
190+
log.step("Running before_build...")
191+
before_build_prepared = prepare_command(
192+
build_options.before_build,
193+
project=container_project_path,
194+
package=container_package_dir,
195+
)
196+
docker.call(["sh", "-c", before_build_prepared], env=env)
197+
198+
log.step("Building wheel...")
199+
200+
temp_dir = PurePath("/tmp/cibuildwheel")
201+
built_wheel_dir = temp_dir / "built_wheel"
202+
docker.call(["rm", "-rf", built_wheel_dir])
203+
docker.call(["mkdir", "-p", built_wheel_dir])
204+
205+
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
206+
207+
if build_options.build_frontend == "pip":
208+
docker.call(
209+
[
210+
"python",
211+
"-m",
212+
"pip",
213+
"wheel",
214+
container_package_dir,
215+
f"--wheel-dir={built_wheel_dir}",
216+
"--no-deps",
217+
*verbosity_flags,
218+
],
219+
env=env,
220+
)
221+
elif build_options.build_frontend == "build":
222+
config_setting = " ".join(verbosity_flags)
223+
docker.call(
224+
[
225+
"python",
226+
"-m",
227+
"build",
228+
container_package_dir,
229+
"--wheel",
230+
f"--outdir={built_wheel_dir}",
231+
f"--config-setting={config_setting}",
232+
],
233+
env=env,
234+
)
235+
else:
236+
assert_never(build_options.build_frontend)
227237

228-
repaired_wheel_dir = temp_dir / "repaired_wheel"
229-
docker.call(["rm", "-rf", repaired_wheel_dir])
230-
docker.call(["mkdir", "-p", repaired_wheel_dir])
238+
built_wheel = docker.glob(built_wheel_dir, "*.whl")[0]
231239

232-
if built_wheel.name.endswith("none-any.whl"):
233-
raise NonPlatformWheelError()
240+
repaired_wheel_dir = temp_dir / "repaired_wheel"
241+
docker.call(["rm", "-rf", repaired_wheel_dir])
242+
docker.call(["mkdir", "-p", repaired_wheel_dir])
234243

235-
if build_options.repair_command:
236-
log.step("Repairing wheel...")
237-
repair_command_prepared = prepare_command(
238-
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
239-
)
240-
docker.call(["sh", "-c", repair_command_prepared], env=env)
241-
else:
242-
docker.call(["mv", built_wheel, repaired_wheel_dir])
244+
if built_wheel.name.endswith("none-any.whl"):
245+
raise NonPlatformWheelError()
243246

244-
repaired_wheels = docker.glob(repaired_wheel_dir, "*.whl")
247+
if build_options.repair_command:
248+
log.step("Repairing wheel...")
249+
repair_command_prepared = prepare_command(
250+
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
251+
)
252+
docker.call(["sh", "-c", repair_command_prepared], env=env)
253+
else:
254+
docker.call(["mv", built_wheel, repaired_wheel_dir])
255+
256+
repaired_wheels = docker.glob(repaired_wheel_dir, "*.whl")
245257

246258
if build_options.test_command and build_options.test_selector(config.identifier):
247259
log.step("Testing wheel...")
@@ -292,8 +304,12 @@ def build_on_docker(
292304
docker.call(["rm", "-rf", venv_dir])
293305

294306
# move repaired wheels to output
295-
docker.call(["mkdir", "-p", container_output_dir])
296-
docker.call(["mv", *repaired_wheels, container_output_dir])
307+
if abi3_wheel is None:
308+
docker.call(["mkdir", "-p", container_output_dir])
309+
docker.call(["mv", *repaired_wheels, container_output_dir])
310+
built_wheels.extend(
311+
container_output_dir / repaired_wheel.name for repaired_wheel in repaired_wheels
312+
)
297313

298314
log.build_end()
299315

cibuildwheel/macos.py

Lines changed: 84 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
call,
2424
detect_ci_provider,
2525
download,
26+
find_compatible_abi3_wheel,
2627
get_build_verbosity_extra_flags,
2728
get_pip_version,
2829
install_certifi_script,
@@ -291,6 +292,8 @@ def build(options: Options, tmp_path: Path) -> None:
291292
)
292293
shell(before_all_prepared, env=env)
293294

295+
built_wheels: List[Path] = []
296+
294297
for config in python_configurations:
295298
build_options = options.build_options(config.identifier)
296299
log.build_start(config.identifier)
@@ -318,84 +321,94 @@ def build(options: Options, tmp_path: Path) -> None:
318321
build_options.build_frontend,
319322
)
320323

321-
if build_options.before_build:
322-
log.step("Running before_build...")
323-
before_build_prepared = prepare_command(
324-
build_options.before_build, project=".", package=build_options.package_dir
325-
)
326-
shell(before_build_prepared, env=env)
327-
328-
log.step("Building wheel...")
329-
built_wheel_dir.mkdir()
330-
331-
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
332-
333-
if build_options.build_frontend == "pip":
334-
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
335-
# see https://github.com/pypa/cibuildwheel/pull/369
336-
call(
337-
"python",
338-
"-m",
339-
"pip",
340-
"wheel",
341-
build_options.package_dir.resolve(),
342-
f"--wheel-dir={built_wheel_dir}",
343-
"--no-deps",
344-
*verbosity_flags,
345-
env=env,
346-
)
347-
elif build_options.build_frontend == "build":
348-
config_setting = " ".join(verbosity_flags)
349-
build_env = env.copy()
350-
if build_options.dependency_constraints:
351-
constraint_path = build_options.dependency_constraints.get_for_python_version(
352-
config.version
353-
)
354-
build_env["PIP_CONSTRAINT"] = constraint_path.as_uri()
355-
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
356-
call(
357-
"python",
358-
"-m",
359-
"build",
360-
build_options.package_dir,
361-
"--wheel",
362-
f"--outdir={built_wheel_dir}",
363-
f"--config-setting={config_setting}",
364-
env=build_env,
324+
abi3_wheel = find_compatible_abi3_wheel(built_wheels, config.identifier)
325+
if abi3_wheel:
326+
log.step_end()
327+
print(
328+
f"Found previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..."
365329
)
330+
repaired_wheel = abi3_wheel
366331
else:
367-
assert_never(build_options.build_frontend)
332+
if build_options.before_build:
333+
log.step("Running before_build...")
334+
before_build_prepared = prepare_command(
335+
build_options.before_build, project=".", package=build_options.package_dir
336+
)
337+
shell(before_build_prepared, env=env)
338+
339+
log.step("Building wheel...")
340+
built_wheel_dir.mkdir()
341+
342+
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
343+
344+
if build_options.build_frontend == "pip":
345+
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
346+
# see https://github.com/pypa/cibuildwheel/pull/369
347+
call(
348+
"python",
349+
"-m",
350+
"pip",
351+
"wheel",
352+
build_options.package_dir.resolve(),
353+
f"--wheel-dir={built_wheel_dir}",
354+
"--no-deps",
355+
*verbosity_flags,
356+
env=env,
357+
)
358+
elif build_options.build_frontend == "build":
359+
config_setting = " ".join(verbosity_flags)
360+
build_env = env.copy()
361+
if build_options.dependency_constraints:
362+
constraint_path = (
363+
build_options.dependency_constraints.get_for_python_version(
364+
config.version
365+
)
366+
)
367+
build_env["PIP_CONSTRAINT"] = constraint_path.as_uri()
368+
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
369+
call(
370+
"python",
371+
"-m",
372+
"build",
373+
build_options.package_dir,
374+
"--wheel",
375+
f"--outdir={built_wheel_dir}",
376+
f"--config-setting={config_setting}",
377+
env=build_env,
378+
)
379+
else:
380+
assert_never(build_options.build_frontend)
368381

369-
built_wheel = next(built_wheel_dir.glob("*.whl"))
382+
built_wheel = next(built_wheel_dir.glob("*.whl"))
370383

371-
repaired_wheel_dir.mkdir()
384+
repaired_wheel_dir.mkdir()
372385

373-
if built_wheel.name.endswith("none-any.whl"):
374-
raise NonPlatformWheelError()
386+
if built_wheel.name.endswith("none-any.whl"):
387+
raise NonPlatformWheelError()
375388

376-
if build_options.repair_command:
377-
log.step("Repairing wheel...")
389+
if build_options.repair_command:
390+
log.step("Repairing wheel...")
378391

379-
if config_is_universal2:
380-
delocate_archs = "x86_64,arm64"
381-
elif config_is_arm64:
382-
delocate_archs = "arm64"
392+
if config_is_universal2:
393+
delocate_archs = "x86_64,arm64"
394+
elif config_is_arm64:
395+
delocate_archs = "arm64"
396+
else:
397+
delocate_archs = "x86_64"
398+
399+
repair_command_prepared = prepare_command(
400+
build_options.repair_command,
401+
wheel=built_wheel,
402+
dest_dir=repaired_wheel_dir,
403+
delocate_archs=delocate_archs,
404+
)
405+
shell(repair_command_prepared, env=env)
383406
else:
384-
delocate_archs = "x86_64"
385-
386-
repair_command_prepared = prepare_command(
387-
build_options.repair_command,
388-
wheel=built_wheel,
389-
dest_dir=repaired_wheel_dir,
390-
delocate_archs=delocate_archs,
391-
)
392-
shell(repair_command_prepared, env=env)
393-
else:
394-
shutil.move(str(built_wheel), repaired_wheel_dir)
407+
shutil.move(str(built_wheel), repaired_wheel_dir)
395408

396-
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
409+
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
397410

398-
log.step_end()
411+
log.step_end()
399412

400413
if build_options.test_command and build_options.test_selector(config.identifier):
401414
machine_arch = platform.machine()
@@ -521,7 +534,9 @@ def build(options: Options, tmp_path: Path) -> None:
521534
)
522535

523536
# we're all done here; move it to output (overwrite existing)
524-
shutil.move(str(repaired_wheel), build_options.output_dir)
537+
if abi3_wheel is None:
538+
shutil.move(str(repaired_wheel), build_options.output_dir)
539+
built_wheels.append(build_options.output_dir / repaired_wheel.name)
525540

526541
# clean up
527542
shutil.rmtree(identifier_tmp_dir)

0 commit comments

Comments
 (0)