Skip to content

Commit 515e9be

Browse files
henryiiijoerick
andauthored
feat: support pypa build (#521)
* feat: support pypa build fix: update `cibuildwheel.linux.troubleshoot` to know about build Workaround issue with PyPy venv module by installing build[virtualenv] fix: test_dependency_constraints_file when using build fix: test/test_pep518.py fix: use `strtobool` to parse `CIBW_PYPA_BUILD` fix: update `cibuildwheel.linux.troubleshoot` to know about `python -m pip wheel` test: use `build_mode` in `test/test_dependency_versions.py` tests * refactor: use build-backend instead * Apply suggestions from code review Co-authored-by: Joe Rickerby <[email protected]> * refactor: use literal types for build_frontend * refactor(tests): build_mode -> build_frontend_env * Update test/test_before_build.py Co-authored-by: Joe Rickerby <[email protected]>
1 parent 9e1369e commit 515e9be

File tree

13 files changed

+301
-79
lines changed

13 files changed

+301
-79
lines changed

cibuildwheel/__main__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from cibuildwheel.projectfiles import get_requires_python_str
2121
from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
2222
from cibuildwheel.util import (
23+
BuildFrontend,
2324
BuildOptions,
2425
BuildSelector,
2526
DependencyConstraints,
@@ -184,6 +185,7 @@ def main() -> None:
184185

185186
archs_config_str = args.archs or options("archs", sep=" ")
186187

188+
build_frontend_str = options("build-frontend", env_plat=False)
187189
environment_config = options("environment", table={"item": '{k}="{v}"', "sep": " "})
188190
before_all = options("before-all", sep=" && ")
189191
before_build = options("before-build", sep=" && ")
@@ -200,6 +202,16 @@ def main() -> None:
200202
os.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
201203
)
202204

205+
build_frontend: BuildFrontend
206+
if build_frontend_str == "build":
207+
build_frontend = "build"
208+
elif build_frontend_str == "pip":
209+
build_frontend = "pip"
210+
else:
211+
msg = f"cibuildwheel: Unrecognised build frontend '{build_frontend}', only 'pip' and 'build' are supported"
212+
print(msg, file=sys.stderr)
213+
sys.exit(2)
214+
203215
package_files = {"setup.py", "setup.cfg", "pyproject.toml"}
204216

205217
if not any(package_dir.joinpath(name).exists() for name in package_files):
@@ -308,6 +320,7 @@ def main() -> None:
308320
environment=environment,
309321
dependency_constraints=dependency_constraints,
310322
manylinux_images=manylinux_images or None,
323+
build_frontend=build_frontend,
311324
)
312325

313326
# Python is buffering by default when running on the CI platforms, giving problems interleaving subprocess call output with unflushed calls to 'print'

cibuildwheel/linux.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from .architecture import Architecture
88
from .docker_container import DockerContainer
99
from .logger import log
10-
from .typing import PathOrStr
10+
from .typing import PathOrStr, assert_never
1111
from .util import (
1212
BuildOptions,
1313
BuildSelector,
@@ -145,7 +145,7 @@ def build(options: BuildOptions) -> None:
145145
env, executor=docker.environment_executor
146146
)
147147

148-
# check config python and pip are still on PATH
148+
# check config python is still on PATH
149149
which_python = docker.call(
150150
["which", "python"], env=env, capture_output=True
151151
).strip()
@@ -180,18 +180,38 @@ def build(options: BuildOptions) -> None:
180180
docker.call(["rm", "-rf", built_wheel_dir])
181181
docker.call(["mkdir", "-p", built_wheel_dir])
182182

183-
docker.call(
184-
[
185-
"pip",
186-
"wheel",
187-
container_package_dir,
188-
"--wheel-dir",
189-
built_wheel_dir,
190-
"--no-deps",
191-
*get_build_verbosity_extra_flags(options.build_verbosity),
192-
],
193-
env=env,
194-
)
183+
verbosity_flags = get_build_verbosity_extra_flags(options.build_verbosity)
184+
185+
if options.build_frontend == "pip":
186+
docker.call(
187+
[
188+
"python",
189+
"-m",
190+
"pip",
191+
"wheel",
192+
container_package_dir,
193+
f"--wheel-dir={built_wheel_dir}",
194+
"--no-deps",
195+
*verbosity_flags,
196+
],
197+
env=env,
198+
)
199+
elif options.build_frontend == "build":
200+
config_setting = " ".join(verbosity_flags)
201+
docker.call(
202+
[
203+
"python",
204+
"-m",
205+
"build",
206+
container_package_dir,
207+
"--wheel",
208+
f"--outdir={built_wheel_dir}",
209+
f"--config-setting={config_setting}",
210+
],
211+
env=env,
212+
)
213+
else:
214+
assert_never(options.build_frontend)
195215

196216
built_wheel = docker.glob(built_wheel_dir, "*.whl")[0]
197217

@@ -291,8 +311,11 @@ def build(options: BuildOptions) -> None:
291311

292312

293313
def troubleshoot(package_dir: Path, error: Exception) -> None:
294-
if isinstance(error, subprocess.CalledProcessError) and error.cmd[0:2] == ["pip", "wheel"]:
295-
# the 'pip wheel' step failed.
314+
if isinstance(error, subprocess.CalledProcessError) and (
315+
error.cmd[0:4] == ["python", "-m", "pip", "wheel"]
316+
or error.cmd[0:3] == ["python", "-m", "build"]
317+
):
318+
# the wheel build step failed
296319
print("Checking for common errors...")
297320
so_files = list(package_dir.glob("**/*.so"))
298321

cibuildwheel/macos.py

Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
from .architecture import Architecture
1313
from .environment import ParsedEnvironment
1414
from .logger import log
15-
from .typing import Literal, PathOrStr
15+
from .typing import Literal, PathOrStr, assert_never
1616
from .util import (
17+
BuildFrontend,
1718
BuildOptions,
1819
BuildSelector,
1920
NonPlatformWheelError,
2021
download,
2122
get_build_verbosity_extra_flags,
23+
get_pip_version,
2224
install_certifi_script,
2325
prepare_command,
2426
read_python_configs,
@@ -178,7 +180,9 @@ def setup_python(
178180
python_configuration: PythonConfiguration,
179181
dependency_constraint_flags: Sequence[PathOrStr],
180182
environment: ParsedEnvironment,
183+
build_frontend: BuildFrontend,
181184
) -> Dict[str, str]:
185+
182186
implementation_id = python_configuration.identifier.split("-")[0]
183187
log.step(f"Installing Python {implementation_id}...")
184188

@@ -308,18 +312,33 @@ def setup_python(
308312
env.setdefault("SDKROOT", arm64_compatible_sdks[0])
309313

310314
log.step("Installing build tools...")
311-
call(
312-
[
313-
"pip",
314-
"install",
315-
"--upgrade",
316-
"setuptools",
317-
"wheel",
318-
"delocate",
319-
*dependency_constraint_flags,
320-
],
321-
env=env,
322-
)
315+
if build_frontend == "pip":
316+
call(
317+
[
318+
"pip",
319+
"install",
320+
"--upgrade",
321+
"setuptools",
322+
"wheel",
323+
"delocate",
324+
*dependency_constraint_flags,
325+
],
326+
env=env,
327+
)
328+
elif build_frontend == "build":
329+
call(
330+
[
331+
"pip",
332+
"install",
333+
"--upgrade",
334+
"delocate",
335+
"build[virtualenv]",
336+
*dependency_constraint_flags,
337+
],
338+
env=env,
339+
)
340+
else:
341+
assert_never(build_frontend)
323342

324343
return env
325344

@@ -356,7 +375,12 @@ def build(options: BuildOptions) -> None:
356375
options.dependency_constraints.get_for_python_version(config.version),
357376
]
358377

359-
env = setup_python(config, dependency_constraint_flags, options.environment)
378+
env = setup_python(
379+
config,
380+
dependency_constraint_flags,
381+
options.environment,
382+
options.build_frontend,
383+
)
360384

361385
if options.before_build:
362386
log.step("Running before_build...")
@@ -370,20 +394,46 @@ def build(options: BuildOptions) -> None:
370394
shutil.rmtree(built_wheel_dir)
371395
built_wheel_dir.mkdir(parents=True)
372396

373-
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
374-
# see https://github.com/pypa/cibuildwheel/pull/369
375-
call(
376-
[
377-
"pip",
378-
"wheel",
379-
options.package_dir.resolve(),
380-
"--wheel-dir",
381-
built_wheel_dir,
382-
"--no-deps",
383-
*get_build_verbosity_extra_flags(options.build_verbosity),
384-
],
385-
env=env,
386-
)
397+
verbosity_flags = get_build_verbosity_extra_flags(options.build_verbosity)
398+
399+
if options.build_frontend == "pip":
400+
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
401+
# see https://github.com/pypa/cibuildwheel/pull/369
402+
call(
403+
[
404+
"python",
405+
"-m",
406+
"pip",
407+
"wheel",
408+
options.package_dir.resolve(),
409+
f"--wheel-dir={built_wheel_dir}",
410+
"--no-deps",
411+
*verbosity_flags,
412+
],
413+
env=env,
414+
)
415+
elif options.build_frontend == "build":
416+
config_setting = " ".join(verbosity_flags)
417+
build_env = env.copy()
418+
if options.dependency_constraints:
419+
build_env["PIP_CONSTRAINT"] = str(
420+
options.dependency_constraints.get_for_python_version(config.version)
421+
)
422+
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
423+
call(
424+
[
425+
"python",
426+
"-m",
427+
"build",
428+
options.package_dir,
429+
"--wheel",
430+
f"--outdir={built_wheel_dir}",
431+
f"--config-setting={config_setting}",
432+
],
433+
env=build_env,
434+
)
435+
else:
436+
assert_never(options.build_frontend)
387437

388438
built_wheel = next(built_wheel_dir.glob("*.whl"))
389439

cibuildwheel/resources/defaults.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ skip = ""
44
test-skip = ""
55

66
archs = ["auto"]
7+
build-frontend = "pip"
78
dependency-versions = "pinned"
89
environment = {}
910
build-verbosity = ""

cibuildwheel/util.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import os
55
import re
66
import ssl
7+
import subprocess
8+
import sys
79
import textwrap
810
import time
911
import urllib.request
@@ -20,12 +22,14 @@
2022

2123
from .architecture import Architecture
2224
from .environment import ParsedEnvironment
23-
from .typing import PathOrStr, PlatformName
25+
from .typing import Literal, PathOrStr, PlatformName
2426

2527
resources_dir = Path(__file__).parent / "resources"
2628

2729
install_certifi_script = resources_dir / "install_certifi.py"
2830

31+
BuildFrontend = Literal["pip", "build"]
32+
2933

3034
def prepare_command(command: str, **kwargs: PathOrStr) -> str:
3135
"""
@@ -218,6 +222,7 @@ class BuildOptions(NamedTuple):
218222
test_requires: List[str]
219223
test_extras: str
220224
build_verbosity: int
225+
build_frontend: BuildFrontend
221226

222227

223228
class NonPlatformWheelError(Exception):
@@ -300,3 +305,18 @@ def print_new_wheels(msg: str, output_dir: Path) -> Iterator[None]:
300305
s = time.time() - start_time
301306
m = s / 60
302307
print(msg.format(n=n, s=s, m=m), *sorted(f" {f.name}" for f in new_contents), sep="\n")
308+
309+
310+
def get_pip_version(env: Dict[str, str]) -> str:
311+
# we use shell=True here for windows, even though we don't need a shell due to a bug
312+
# https://bugs.python.org/issue8557
313+
shell = sys.platform.startswith("win")
314+
versions_output_text = subprocess.check_output(
315+
["python", "-m", "pip", "freeze", "--all"], universal_newlines=True, shell=shell, env=env
316+
)
317+
(pip_version,) = (
318+
version[5:]
319+
for version in versions_output_text.strip().splitlines()
320+
if version.startswith("pip==")
321+
)
322+
return pip_version

0 commit comments

Comments
 (0)