Files
cibuildwheel/cibuildwheel/__main__.py
T

388 lines
13 KiB
Python
Raw Normal View History

from __future__ import annotations
2019-11-12 23:51:27 +00:00
import argparse
import os
import shutil
2019-11-12 23:51:27 +00:00
import sys
2022-04-26 22:21:27 -04:00
import tarfile
2019-11-12 23:51:27 +00:00
import textwrap
2022-09-09 08:34:47 -04:00
import typing
from pathlib import Path
from tempfile import mkdtemp
2021-01-31 17:14:15 -05:00
2017-04-13 15:02:04 +01:00
import cibuildwheel
2019-11-12 23:51:27 +00:00
import cibuildwheel.linux
import cibuildwheel.macos
2021-02-04 14:27:54 -05:00
import cibuildwheel.util
2019-11-12 23:51:27 +00:00
import cibuildwheel.windows
2021-01-22 09:33:22 -05:00
from cibuildwheel.architecture import Architecture, allowed_architectures_check
from cibuildwheel.logger import log
2021-10-12 02:05:47 +01:00
from cibuildwheel.options import CommandLineArguments, Options, compute_options
2021-01-09 15:40:40 -05:00
from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
from cibuildwheel.util import (
CIBW_CACHE_PATH,
BuildSelector,
2022-12-04 13:51:56 +00:00
CIProvider,
Unbuffered,
2022-04-26 22:21:27 -04:00
chdir,
detect_ci_provider,
2022-12-04 13:51:56 +00:00
fix_ansi_codes_for_github_actions,
)
def main() -> None:
2017-03-19 20:57:51 +00:00
parser = argparse.ArgumentParser(
2021-05-03 11:45:43 -04:00
description="Build wheels for all the platforms.",
epilog="""
2021-06-21 12:26:46 -04:00
Most options are supplied via environment variables or in
--config-file (pyproject.toml usually). See
https://github.com/pypa/cibuildwheel#options for info.
2021-05-03 11:45:43 -04:00
""",
2021-04-30 17:56:34 -04:00
)
2017-03-19 20:57:51 +00:00
2021-04-30 17:56:34 -04:00
parser.add_argument(
2021-05-03 11:45:43 -04:00
"--platform",
choices=["auto", "linux", "macos", "windows"],
2022-09-09 08:34:47 -04:00
default=None,
2021-05-03 11:45:43 -04:00
help="""
Platform to build for. Use this option to override the
auto-detected platform or to run cibuildwheel on your development
machine. Specifying "macos" or "windows" only works on that
operating system, but "linux" works on all three, as long as
Docker/Podman is installed. Default: auto.
2021-05-03 11:45:43 -04:00
""",
2021-04-30 17:56:34 -04:00
)
2020-12-16 23:17:51 +00:00
2021-04-29 20:21:42 -04:00
arch_list_str = ", ".join(a.name for a in Architecture)
2021-04-30 17:56:34 -04:00
parser.add_argument(
2021-05-03 11:45:43 -04:00
"--archs",
2021-04-30 17:56:34 -04:00
default=None,
2021-05-03 11:45:43 -04:00
help=f"""
2021-05-02 16:37:38 +02:00
Comma-separated list of CPU architectures to build for.
When set to 'auto', builds the architectures natively supported
on this machine. Set this option to build an architecture
via emulation, for example, using binfmt_misc and QEMU.
Default: auto.
Choices: auto, auto64, auto32, native, all, {arch_list_str}
2021-05-03 11:45:43 -04:00
""",
2021-04-30 17:56:34 -04:00
)
2021-04-29 20:21:42 -04:00
2022-09-09 08:34:47 -04:00
parser.add_argument(
"--only",
default=None,
help="""
Force a single wheel build when given an identifier. Overrides
CIBW_BUILD/CIBW_SKIP. --platform and --arch cannot be specified
if this is given.
""",
)
2021-04-30 17:56:34 -04:00
parser.add_argument(
2021-05-03 11:45:43 -04:00
"--output-dir",
2022-04-26 22:21:27 -04:00
type=Path,
2022-04-27 17:08:46 -04:00
default=Path(os.environ.get("CIBW_OUTPUT_DIR", "wheelhouse")),
help="Destination folder for the wheels. Default: wheelhouse.",
2021-04-30 17:56:34 -04:00
)
2021-04-29 20:21:42 -04:00
2021-06-21 12:26:46 -04:00
parser.add_argument(
"--config-file",
default="",
2021-06-21 12:26:46 -04:00
help="""
2022-04-27 12:31:42 -04:00
TOML config file. Default: "", meaning {package}/pyproject.toml, if
it exists. To refer to a project inside your project, use {package};
this matters if you build from an SDist.
2021-06-21 12:26:46 -04:00
""",
)
2021-04-30 17:56:34 -04:00
parser.add_argument(
2021-05-03 11:45:43 -04:00
"package_dir",
2022-04-27 17:08:46 -04:00
metavar="PACKAGE",
2022-04-26 22:21:27 -04:00
default=Path("."),
type=Path,
2021-05-03 11:45:43 -04:00
nargs="?",
help="""
2022-04-27 17:08:46 -04:00
Path to the package that you want wheels for. Default: the working
directory. Can be a directory inside the working directory, or an
sdist. When set to a directory, the working directory is still
considered the 'project' and is copied into the build container
2022-04-27 17:08:46 -04:00
on Linux. When set to a tar.gz sdist file, --config-file
and --output-dir are relative to the current directory, and other
paths are relative to the expanded SDist directory.
2021-05-03 11:45:43 -04:00
""",
2021-04-30 17:56:34 -04:00
)
2017-03-19 20:57:51 +00:00
2021-04-30 17:56:34 -04:00
parser.add_argument(
2021-05-03 11:45:43 -04:00
"--print-build-identifiers",
action="store_true",
help="Print the build identifiers matched by the current invocation and exit.",
2021-04-30 17:56:34 -04:00
)
2021-04-29 20:21:42 -04:00
2021-04-30 17:56:34 -04:00
parser.add_argument(
2021-05-03 11:45:43 -04:00
"--allow-empty",
action="store_true",
help="Do not report an error code if the build does not match any wheels.",
2021-04-30 17:56:34 -04:00
)
parser.add_argument(
"--prerelease-pythons",
action="store_true",
help="Enable pre-release Python versions if available.",
)
2022-04-28 09:19:27 -04:00
args = CommandLineArguments(**vars(parser.parse_args()))
2017-03-19 20:57:51 +00:00
2022-04-26 22:21:27 -04:00
args.package_dir = args.package_dir.resolve()
2022-04-27 12:31:42 -04:00
# This are always relative to the base directory, even in SDist builds
2022-04-27 17:08:46 -04:00
args.output_dir = args.output_dir.resolve()
2022-04-26 22:21:27 -04:00
# Standard builds if a directory or non-existent path is given
if not args.package_dir.is_file() and not args.package_dir.name.endswith("tar.gz"):
build_in_directory(args)
return
# Tarfile builds require extraction and changing the directory
2022-09-03 11:21:23 +02:00
temp_dir = Path(mkdtemp(prefix="cibw-sdist-")).resolve(strict=True)
try:
2022-04-26 22:21:27 -04:00
with tarfile.open(args.package_dir) as tar:
tar.extractall(path=temp_dir)
# The extract directory is now the project dir
try:
(project_dir,) = temp_dir.iterdir()
except ValueError:
2022-09-05 13:11:46 -04:00
msg = "invalid sdist: didn't contain a single dir"
raise SystemExit(msg) from None
2022-04-26 22:21:27 -04:00
2022-04-27 12:31:42 -04:00
# This is now the new package dir
2022-04-26 22:21:27 -04:00
args.package_dir = project_dir.resolve()
with chdir(temp_dir):
build_in_directory(args)
2022-09-03 11:21:23 +02:00
finally:
# avoid https://github.com/python/cpython/issues/86962 by performing
# cleanup manually
shutil.rmtree(temp_dir, ignore_errors=sys.platform.startswith("win"))
if temp_dir.exists():
2022-10-07 08:47:31 -04:00
log.warning(f"Can't delete temporary folder '{temp_dir}'")
2022-04-26 22:21:27 -04:00
def build_in_directory(args: CommandLineArguments) -> None:
2022-09-09 08:34:47 -04:00
platform_option_value = args.platform or os.environ.get("CIBW_PLATFORM", "auto")
2022-04-27 12:31:42 -04:00
platform: PlatformName
2022-09-09 08:34:47 -04:00
if args.only:
if "linux_" in args.only:
platform = "linux"
elif "macosx_" in args.only:
platform = "macos"
2022-09-24 23:12:32 +01:00
elif "win_" in args.only or "win32" in args.only:
2022-09-09 08:34:47 -04:00
platform = "windows"
else:
print(
f"Invalid --only='{args.only}', must be a build selector with a known platform",
file=sys.stderr,
)
sys.exit(2)
if args.platform is not None:
print(
"--platform cannot be specified with --only, it is computed from --only",
file=sys.stderr,
)
sys.exit(2)
if args.archs is not None:
print(
"--arch cannot be specified with --only, it is computed from --only",
file=sys.stderr,
)
sys.exit(2)
elif platform_option_value != "auto":
if platform_option_value not in PLATFORMS:
print(f"cibuildwheel: Unsupported platform: {platform_option_value}", file=sys.stderr)
sys.exit(2)
platform = typing.cast(PlatformName, platform_option_value)
else:
2020-11-23 21:00:22 +00:00
ci_provider = detect_ci_provider()
if ci_provider is None:
2021-04-30 17:56:34 -04:00
print(
textwrap.dedent(
2021-05-03 11:45:43 -04:00
"""
cibuildwheel: Unable to detect platform. cibuildwheel should run on your CI server;
2022-07-24 01:17:22 +02:00
Travis CI, AppVeyor, Azure Pipelines, GitHub Actions, CircleCI, Gitlab, and Cirrus CI
are supported. You can run on your development machine or other CI providers
using the --platform argument. Check --help output for more information.
2021-05-03 11:45:43 -04:00
"""
2021-04-30 17:56:34 -04:00
),
file=sys.stderr,
)
2021-01-17 14:13:26 -05:00
sys.exit(2)
2021-05-03 11:45:43 -04:00
if sys.platform.startswith("linux"):
platform = "linux"
elif sys.platform == "darwin":
platform = "macos"
elif sys.platform == "win32":
platform = "windows"
else:
2021-04-30 17:56:34 -04:00
print(
'cibuildwheel: Unable to detect platform from "sys.platform" in a CI environment. You can run '
2021-05-03 11:45:43 -04:00
"cibuildwheel using the --platform argument. Check --help output for more information.",
2021-04-30 17:56:34 -04:00
file=sys.stderr,
)
2021-01-17 14:13:26 -05:00
sys.exit(2)
2022-11-26 15:54:08 +00:00
options = compute_options(platform=platform, command_line_arguments=args, environ=os.environ)
2021-10-12 02:05:47 +01:00
package_dir = options.globals.package_dir
package_files = {"setup.py", "setup.cfg", "pyproject.toml"}
if not any(package_dir.joinpath(name).exists() for name in package_files):
names = ", ".join(sorted(package_files, reverse=True))
msg = f"cibuildwheel: Could not find any of {{{names}}} at root of package"
print(msg, file=sys.stderr)
sys.exit(2)
identifiers = get_build_identifiers(
2021-10-12 02:05:47 +01:00
platform=platform,
build_selector=options.globals.build_selector,
architectures=options.globals.architectures,
)
2021-01-18 00:09:11 -05:00
if args.print_build_identifiers:
2021-01-18 00:09:11 -05:00
for identifier in identifiers:
print(identifier)
2021-01-17 14:13:26 -05:00
sys.exit(0)
2021-09-19 00:19:28 -04:00
# Add CIBUILDWHEEL environment variable
os.environ["CIBUILDWHEEL"] = "1"
2017-03-19 20:57:51 +00:00
# Python is buffering by default when running on the CI platforms, giving problems interleaving subprocess call output with unflushed calls to 'print'
sys.stdout = Unbuffered(sys.stdout) # type: ignore[assignment]
# create the cache dir before it gets printed & builds performed
CIBW_CACHE_PATH.mkdir(parents=True, exist_ok=True)
2021-10-12 02:05:47 +01:00
print_preamble(platform=platform, options=options, identifiers=identifiers)
2017-04-13 15:02:04 +01:00
2021-01-22 09:23:52 -05:00
try:
2021-10-12 02:05:47 +01:00
options.check_for_invalid_configuration(identifiers)
allowed_architectures_check(platform, options.globals.architectures)
2021-01-22 09:23:52 -05:00
except ValueError as err:
print("cibuildwheel:", *err.args, file=sys.stderr)
sys.exit(4)
2021-01-22 08:54:19 -05:00
if not identifiers:
print(
2021-10-12 02:05:47 +01:00
f"cibuildwheel: No build identifiers selected: {options.globals.build_selector}",
file=sys.stderr,
)
2021-01-22 08:54:19 -05:00
if not args.allow_empty:
sys.exit(3)
2021-10-12 02:05:47 +01:00
output_dir = options.globals.output_dir
2020-06-15 01:53:31 +02:00
if not output_dir.exists():
output_dir.mkdir(parents=True)
2017-07-02 18:02:27 -05:00
tmp_path = Path(mkdtemp(prefix="cibw-run-")).resolve(strict=True)
try:
with cibuildwheel.util.print_new_wheels(
"\n{n} wheels produced in {m:.0f} minutes:", output_dir
):
if platform == "linux":
cibuildwheel.linux.build(options, tmp_path)
elif platform == "windows":
cibuildwheel.windows.build(options, tmp_path)
elif platform == "macos":
cibuildwheel.macos.build(options, tmp_path)
else:
assert_never(platform)
finally:
2022-09-03 11:21:23 +02:00
# avoid https://github.com/python/cpython/issues/86962 by performing
# cleanup manually
shutil.rmtree(tmp_path, ignore_errors=sys.platform.startswith("win"))
if tmp_path.exists():
2022-10-07 08:47:31 -04:00
log.warning(f"Can't delete temporary folder '{tmp_path}'")
2017-03-19 20:57:51 +00:00
def print_preamble(platform: str, options: Options, identifiers: list[str]) -> None:
2021-04-30 17:56:34 -04:00
print(
textwrap.dedent(
2021-05-03 11:45:43 -04:00
"""
2021-05-02 16:37:38 +02:00
_ _ _ _ _ _ _
___|_| |_ _ _|_| |_| |_ _ _| |_ ___ ___| |
| _| | . | | | | | . | | | | | -_| -_| |
|___|_|___|___|_|_|___|_____|_|_|___|___|_|
2021-05-03 11:45:43 -04:00
"""
2021-04-30 17:56:34 -04:00
)
)
2017-04-13 15:02:04 +01:00
2021-05-03 11:45:43 -04:00
print(f"cibuildwheel version {cibuildwheel.__version__}\n")
2017-04-13 15:02:04 +01:00
2021-05-03 11:45:43 -04:00
print("Build options:")
2022-11-26 15:54:08 +00:00
print(f" platform: {platform}")
2022-12-04 13:51:56 +00:00
options_summary = textwrap.indent(options.summary(identifiers), " ")
if detect_ci_provider() == CIProvider.github_actions:
options_summary = fix_ansi_codes_for_github_actions(options_summary)
print(options_summary)
2017-04-13 15:02:04 +01:00
2022-11-26 15:54:08 +00:00
print()
print(f"Cache folder: {CIBW_CACHE_PATH}")
warnings = detect_warnings(options=options, identifiers=identifiers)
if warnings:
2021-05-03 11:45:43 -04:00
print("\nWarnings:")
for warning in warnings:
2021-05-03 11:45:43 -04:00
print(" " + warning)
2021-05-03 11:45:43 -04:00
print("\nHere we go!\n")
2017-04-13 15:02:04 +01:00
2021-01-18 00:09:11 -05:00
def get_build_identifiers(
platform: PlatformName, build_selector: BuildSelector, architectures: set[Architecture]
) -> list[str]:
python_configurations: (
list[cibuildwheel.linux.PythonConfiguration]
| list[cibuildwheel.windows.PythonConfiguration]
| list[cibuildwheel.macos.PythonConfiguration]
)
2021-01-31 17:14:15 -05:00
2021-05-03 11:45:43 -04:00
if platform == "linux":
2021-04-30 17:56:34 -04:00
python_configurations = cibuildwheel.linux.get_python_configurations(
build_selector, architectures
)
2021-05-03 11:45:43 -04:00
elif platform == "windows":
2021-04-30 17:56:34 -04:00
python_configurations = cibuildwheel.windows.get_python_configurations(
build_selector, architectures
)
2021-05-03 11:45:43 -04:00
elif platform == "macos":
2021-04-30 17:56:34 -04:00
python_configurations = cibuildwheel.macos.get_python_configurations(
build_selector, architectures
)
2021-01-18 00:09:11 -05:00
else:
assert_never(platform)
2021-01-18 00:09:11 -05:00
return [config.identifier for config in python_configurations]
def detect_warnings(*, options: Options, identifiers: list[str]) -> list[str]:
warnings = []
# warn about deprecated {python} and {pip}
2021-10-12 02:05:47 +01:00
for option_name in ["test_command", "before_build"]:
option_values = [getattr(options.build_options(i), option_name) for i in identifiers]
2021-10-12 02:05:47 +01:00
if any(o and ("{python}" in o or "{pip}" in o) for o in option_values):
# Reminder: in an f-string, double braces means literal single brace
msg = (
f"{option_name}: '{{python}}' and '{{pip}}' are no longer needed, "
"and will be removed in a future release. Simply use 'python' or 'pip' instead."
)
warnings.append(msg)
return warnings
2021-05-03 11:45:43 -04:00
if __name__ == "__main__":
2017-03-19 20:57:51 +00:00
main()