Merge remote-tracking branch 'origin/main' into env-order
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "2.15.0"
|
||||
__version__ = "2.16.0"
|
||||
|
||||
@@ -16,9 +16,9 @@ from .options import Options
|
||||
from .typing import PathOrStr
|
||||
from .util import (
|
||||
AlreadyBuiltWheelError,
|
||||
BuildFrontendConfig,
|
||||
BuildSelector,
|
||||
NonPlatformWheelError,
|
||||
build_frontend_or_default,
|
||||
find_compatible_wheel,
|
||||
get_build_verbosity_extra_flags,
|
||||
prepare_command,
|
||||
@@ -177,7 +177,7 @@ def build_in_container(
|
||||
for config in platform_configs:
|
||||
log.build_start(config.identifier)
|
||||
build_options = options.build_options(config.identifier)
|
||||
build_frontend = build_frontend_or_default(build_options.build_frontend)
|
||||
build_frontend = build_options.build_frontend or BuildFrontendConfig("pip")
|
||||
|
||||
dependency_constraint_flags: list[PathOrStr] = []
|
||||
|
||||
@@ -243,9 +243,10 @@ def build_in_container(
|
||||
container.call(["rm", "-rf", built_wheel_dir])
|
||||
container.call(["mkdir", "-p", built_wheel_dir])
|
||||
|
||||
extra_flags = split_config_settings(build_options.config_settings, build_frontend)
|
||||
extra_flags = split_config_settings(build_options.config_settings, build_frontend.name)
|
||||
extra_flags += build_frontend.args
|
||||
|
||||
if build_frontend == "pip":
|
||||
if build_frontend.name == "pip":
|
||||
extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity)
|
||||
container.call(
|
||||
[
|
||||
@@ -260,7 +261,7 @@ def build_in_container(
|
||||
],
|
||||
env=env,
|
||||
)
|
||||
elif build_frontend == "build":
|
||||
elif build_frontend.name == "build":
|
||||
if not 0 <= build_options.build_verbosity < 2:
|
||||
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
|
||||
log.warning(msg)
|
||||
@@ -422,7 +423,7 @@ def build(options: Options, tmp_path: Path) -> None: # noqa: ARG001
|
||||
|
||||
with OCIContainer(
|
||||
image=build_step.container_image,
|
||||
simulate_32_bit=build_step.platform_tag.endswith("i686"),
|
||||
enforce_32_bit=build_step.platform_tag.endswith("i686"),
|
||||
cwd=container_project_path,
|
||||
engine=options.globals.container_engine,
|
||||
) as container:
|
||||
|
||||
+11
-8
@@ -25,10 +25,10 @@ from .typing import PathOrStr
|
||||
from .util import (
|
||||
CIBW_CACHE_PATH,
|
||||
AlreadyBuiltWheelError,
|
||||
BuildFrontend,
|
||||
BuildFrontendConfig,
|
||||
BuildFrontendName,
|
||||
BuildSelector,
|
||||
NonPlatformWheelError,
|
||||
build_frontend_or_default,
|
||||
call,
|
||||
detect_ci_provider,
|
||||
download,
|
||||
@@ -165,7 +165,7 @@ def setup_python(
|
||||
python_configuration: PythonConfiguration,
|
||||
dependency_constraint_flags: Sequence[PathOrStr],
|
||||
environment: ParsedEnvironment,
|
||||
build_frontend: BuildFrontend,
|
||||
build_frontend: BuildFrontendName,
|
||||
) -> dict[str, str]:
|
||||
tmp.mkdir()
|
||||
implementation_id = python_configuration.identifier.split("-")[0]
|
||||
@@ -334,7 +334,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
|
||||
for config in python_configurations:
|
||||
build_options = options.build_options(config.identifier)
|
||||
build_frontend = build_frontend_or_default(build_options.build_frontend)
|
||||
build_frontend = build_options.build_frontend or BuildFrontendConfig("pip")
|
||||
log.build_start(config.identifier)
|
||||
|
||||
identifier_tmp_dir = tmp_path / config.identifier
|
||||
@@ -357,7 +357,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
config,
|
||||
dependency_constraint_flags,
|
||||
build_options.environment,
|
||||
build_frontend,
|
||||
build_frontend.name,
|
||||
)
|
||||
|
||||
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
|
||||
@@ -378,9 +378,12 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
log.step("Building wheel...")
|
||||
built_wheel_dir.mkdir()
|
||||
|
||||
extra_flags = split_config_settings(build_options.config_settings, build_frontend)
|
||||
extra_flags = split_config_settings(
|
||||
build_options.config_settings, build_frontend.name
|
||||
)
|
||||
extra_flags += build_frontend.args
|
||||
|
||||
if build_frontend == "pip":
|
||||
if build_frontend.name == "pip":
|
||||
extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity)
|
||||
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
|
||||
# see https://github.com/pypa/cibuildwheel/pull/369
|
||||
@@ -395,7 +398,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
*extra_flags,
|
||||
env=env,
|
||||
)
|
||||
elif build_frontend == "build":
|
||||
elif build_frontend.name == "build":
|
||||
if not 0 <= build_options.build_verbosity < 2:
|
||||
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
|
||||
log.warning(msg)
|
||||
|
||||
@@ -17,7 +17,7 @@ from types import TracebackType
|
||||
from typing import IO, Dict, Literal
|
||||
|
||||
from .typing import PathOrStr, PopenBytes
|
||||
from .util import CIProvider, detect_ci_provider, parse_key_value_string
|
||||
from .util import CIProvider, call, detect_ci_provider, parse_key_value_string
|
||||
|
||||
ContainerEngineName = Literal["docker", "podman"]
|
||||
|
||||
@@ -29,7 +29,9 @@ class OCIContainerEngineConfig:
|
||||
|
||||
@staticmethod
|
||||
def from_config_string(config_string: str) -> OCIContainerEngineConfig:
|
||||
config_dict = parse_key_value_string(config_string, ["name"])
|
||||
config_dict = parse_key_value_string(
|
||||
config_string, ["name"], ["create_args", "create-args"]
|
||||
)
|
||||
name = " ".join(config_dict["name"])
|
||||
if name not in {"docker", "podman"}:
|
||||
msg = f"unknown container engine {name}"
|
||||
@@ -83,7 +85,7 @@ class OCIContainer:
|
||||
self,
|
||||
*,
|
||||
image: str,
|
||||
simulate_32_bit: bool = False,
|
||||
enforce_32_bit: bool = False,
|
||||
cwd: PathOrStr | None = None,
|
||||
engine: OCIContainerEngineConfig = DEFAULT_ENGINE,
|
||||
):
|
||||
@@ -92,7 +94,7 @@ class OCIContainer:
|
||||
raise ValueError(msg)
|
||||
|
||||
self.image = image
|
||||
self.simulate_32_bit = simulate_32_bit
|
||||
self.enforce_32_bit = enforce_32_bit
|
||||
self.cwd = cwd
|
||||
self.name: str | None = None
|
||||
self.engine = engine
|
||||
@@ -108,13 +110,24 @@ class OCIContainer:
|
||||
if detect_ci_provider() == CIProvider.travis_ci and platform.machine() == "ppc64le":
|
||||
network_args = ["--network=host"]
|
||||
|
||||
shell_args = ["linux32", "/bin/bash"] if self.simulate_32_bit else ["/bin/bash"]
|
||||
simulate_32_bit = False
|
||||
if self.enforce_32_bit:
|
||||
# If the architecture running the image is already the right one
|
||||
# or the image entrypoint takes care of enforcing this, then we don't need to
|
||||
# simulate this
|
||||
container_machine = call(
|
||||
self.engine.name, "run", "--rm", self.image, "uname", "-m", capture_stdout=True
|
||||
).strip()
|
||||
simulate_32_bit = container_machine != "i686"
|
||||
|
||||
shell_args = ["linux32", "/bin/bash"] if simulate_32_bit else ["/bin/bash"]
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
self.engine.name,
|
||||
"create",
|
||||
"--env=CIBUILDWHEEL",
|
||||
"--env=SOURCE_DATE_EPOCH",
|
||||
f"--name={self.name}",
|
||||
"--interactive",
|
||||
"--volume=/:/host", # ignored on CircleCI
|
||||
|
||||
+15
-13
@@ -27,7 +27,7 @@ from .typing import PLATFORMS, PlatformName
|
||||
from .util import (
|
||||
MANYLINUX_ARCHS,
|
||||
MUSLLINUX_ARCHS,
|
||||
BuildFrontend,
|
||||
BuildFrontendConfig,
|
||||
BuildSelector,
|
||||
DependencyConstraints,
|
||||
TestSelector,
|
||||
@@ -92,7 +92,7 @@ class BuildOptions:
|
||||
test_requires: list[str]
|
||||
test_extras: str
|
||||
build_verbosity: int
|
||||
build_frontend: BuildFrontend | Literal["default"]
|
||||
build_frontend: BuildFrontendConfig | None
|
||||
config_settings: str
|
||||
|
||||
@property
|
||||
@@ -488,7 +488,6 @@ class Options:
|
||||
with self.reader.identifier(identifier):
|
||||
before_all = self.reader.get("before-all", sep=" && ")
|
||||
|
||||
build_frontend_str = self.reader.get("build-frontend", env_plat=False)
|
||||
environment_config = self.reader.get(
|
||||
"environment", table={"item": '{k}="{v}"', "sep": " "}
|
||||
)
|
||||
@@ -506,17 +505,20 @@ class Options:
|
||||
test_extras = self.reader.get("test-extras", sep=",")
|
||||
build_verbosity_str = self.reader.get("build-verbosity")
|
||||
|
||||
build_frontend: BuildFrontend | Literal["default"]
|
||||
if build_frontend_str == "build":
|
||||
build_frontend = "build"
|
||||
elif build_frontend_str == "pip":
|
||||
build_frontend = "pip"
|
||||
elif build_frontend_str == "default":
|
||||
build_frontend = "default"
|
||||
build_frontend_str = self.reader.get(
|
||||
"build-frontend",
|
||||
env_plat=False,
|
||||
table={"item": "{k}:{v}", "sep": "; ", "quote": shlex.quote},
|
||||
)
|
||||
build_frontend: BuildFrontendConfig | None
|
||||
if not build_frontend_str or build_frontend_str == "default":
|
||||
build_frontend = None
|
||||
else:
|
||||
msg = f"cibuildwheel: Unrecognised build frontend {build_frontend_str!r}, only 'pip' and 'build' are supported"
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
try:
|
||||
build_frontend = BuildFrontendConfig.from_config_string(build_frontend_str)
|
||||
except ValueError as e:
|
||||
print(f"cibuildwheel: {e}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
environment = parse_environment(environment_config)
|
||||
|
||||
@@ -8,6 +8,43 @@ from pathlib import Path
|
||||
from ._compat import tomllib
|
||||
|
||||
|
||||
def get_parent(node: ast.AST | None, depth: int = 1) -> ast.AST | None:
|
||||
for _ in range(depth):
|
||||
node = getattr(node, "parent", None)
|
||||
return node
|
||||
|
||||
|
||||
def is_main(parent: ast.AST | None) -> bool:
|
||||
if parent is None:
|
||||
return False
|
||||
|
||||
# This would be much nicer with 3.10's pattern matching!
|
||||
if not isinstance(parent, ast.If):
|
||||
return False
|
||||
if not isinstance(parent.test, ast.Compare):
|
||||
return False
|
||||
|
||||
try:
|
||||
(op,) = parent.test.ops
|
||||
(comp,) = parent.test.comparators
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
if not isinstance(op, ast.Eq):
|
||||
return False
|
||||
|
||||
values = {comp, parent.test.left}
|
||||
|
||||
mains = {x for x in values if isinstance(x, ast.Constant) and x.value == "__main__"}
|
||||
if len(mains) != 1:
|
||||
return False
|
||||
consts = {x for x in values if isinstance(x, ast.Name) and x.id == "__name__"}
|
||||
if len(consts) != 1:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Analyzer(ast.NodeVisitor):
|
||||
def __init__(self) -> None:
|
||||
self.requires_python: str | None = None
|
||||
@@ -19,13 +56,22 @@ class Analyzer(ast.NodeVisitor):
|
||||
super().visit(node)
|
||||
|
||||
def visit_keyword(self, node: ast.keyword) -> None:
|
||||
# Must not be nested except for if __name__ == "__main__"
|
||||
|
||||
self.generic_visit(node)
|
||||
# Must not be nested in an if or other structure
|
||||
# This will be Module -> Expr -> Call -> keyword
|
||||
parent = get_parent(node, 4)
|
||||
unnested = parent is None
|
||||
|
||||
# This will be Module -> If -> Expr -> Call -> keyword
|
||||
name_main_unnested = (
|
||||
parent is not None and get_parent(parent) is None and is_main(get_parent(node, 3))
|
||||
)
|
||||
|
||||
if (
|
||||
node.arg == "python_requires"
|
||||
and not hasattr(node.parent.parent.parent, "parent") # type: ignore[attr-defined]
|
||||
and isinstance(node.value, ast.Constant)
|
||||
and (unnested or name_main_unnested)
|
||||
):
|
||||
self.requires_python = node.value.value
|
||||
|
||||
|
||||
+38
-11
@@ -57,16 +57,6 @@ install_certifi_script: Final[Path] = resources_dir / "install_certifi.py"
|
||||
|
||||
test_fail_cwd_file: Final[Path] = resources_dir / "testing_temp_dir_file.py"
|
||||
|
||||
BuildFrontend = Literal["pip", "build"]
|
||||
|
||||
|
||||
def build_frontend_or_default(
|
||||
setting: BuildFrontend | Literal["default"], default: BuildFrontend = "pip"
|
||||
) -> BuildFrontend:
|
||||
if setting == "default":
|
||||
return default
|
||||
return setting
|
||||
|
||||
|
||||
MANYLINUX_ARCHS: Final[tuple[str, ...]] = (
|
||||
"x86_64",
|
||||
@@ -376,6 +366,34 @@ class DependencyConstraints:
|
||||
return self.base_file_path.name
|
||||
|
||||
|
||||
BuildFrontendName = Literal["pip", "build"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BuildFrontendConfig:
|
||||
name: BuildFrontendName
|
||||
args: Sequence[str] = ()
|
||||
|
||||
@staticmethod
|
||||
def from_config_string(config_string: str) -> BuildFrontendConfig:
|
||||
config_dict = parse_key_value_string(config_string, ["name"], ["args"])
|
||||
name = " ".join(config_dict["name"])
|
||||
if name not in {"pip", "build"}:
|
||||
msg = f"Unrecognised build frontend {name}, only 'pip' and 'build' are supported"
|
||||
raise ValueError(msg)
|
||||
|
||||
name = typing.cast(BuildFrontendName, name)
|
||||
|
||||
args = config_dict.get("args") or []
|
||||
return BuildFrontendConfig(name=name, args=args)
|
||||
|
||||
def options_summary(self) -> str | dict[str, str]:
|
||||
if not self.args:
|
||||
return self.name
|
||||
else:
|
||||
return {"name": self.name, "args": repr(self.args)}
|
||||
|
||||
|
||||
class NonPlatformWheelError(Exception):
|
||||
def __init__(self) -> None:
|
||||
message = textwrap.dedent(
|
||||
@@ -699,13 +717,19 @@ def fix_ansi_codes_for_github_actions(text: str) -> str:
|
||||
|
||||
|
||||
def parse_key_value_string(
|
||||
key_value_string: str, positional_arg_names: list[str] | None = None
|
||||
key_value_string: str,
|
||||
positional_arg_names: Sequence[str] | None = None,
|
||||
kw_arg_names: Sequence[str] | None = None,
|
||||
) -> dict[str, list[str]]:
|
||||
"""
|
||||
Parses a string like "docker; create_args: --some-option=value another-option"
|
||||
"""
|
||||
if positional_arg_names is None:
|
||||
positional_arg_names = []
|
||||
if kw_arg_names is None:
|
||||
kw_arg_names = []
|
||||
|
||||
all_field_names = [*positional_arg_names, *kw_arg_names]
|
||||
|
||||
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";:")
|
||||
shlexer.commenters = ""
|
||||
@@ -721,6 +745,9 @@ def parse_key_value_string(
|
||||
if len(field) > 1 and field[1] == ":":
|
||||
field_name = field[0]
|
||||
values = field[2:]
|
||||
if field_name not in all_field_names:
|
||||
msg = f"Failed to parse {key_value_string!r}. Unknown field name {field_name!r}"
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
try:
|
||||
field_name = positional_arg_names[field_i]
|
||||
|
||||
+11
-8
@@ -25,10 +25,10 @@ from .typing import PathOrStr
|
||||
from .util import (
|
||||
CIBW_CACHE_PATH,
|
||||
AlreadyBuiltWheelError,
|
||||
BuildFrontend,
|
||||
BuildFrontendConfig,
|
||||
BuildFrontendName,
|
||||
BuildSelector,
|
||||
NonPlatformWheelError,
|
||||
build_frontend_or_default,
|
||||
call,
|
||||
download,
|
||||
find_compatible_wheel,
|
||||
@@ -216,7 +216,7 @@ def setup_python(
|
||||
python_configuration: PythonConfiguration,
|
||||
dependency_constraint_flags: Sequence[PathOrStr],
|
||||
environment: ParsedEnvironment,
|
||||
build_frontend: BuildFrontend,
|
||||
build_frontend: BuildFrontendName,
|
||||
) -> dict[str, str]:
|
||||
tmp.mkdir()
|
||||
implementation_id = python_configuration.identifier.split("-")[0]
|
||||
@@ -369,7 +369,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
|
||||
for config in python_configurations:
|
||||
build_options = options.build_options(config.identifier)
|
||||
build_frontend = build_frontend_or_default(build_options.build_frontend)
|
||||
build_frontend = build_options.build_frontend or BuildFrontendConfig("pip")
|
||||
log.build_start(config.identifier)
|
||||
|
||||
identifier_tmp_dir = tmp_path / config.identifier
|
||||
@@ -390,7 +390,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
config,
|
||||
dependency_constraint_flags,
|
||||
build_options.environment,
|
||||
build_frontend,
|
||||
build_frontend.name,
|
||||
)
|
||||
|
||||
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
|
||||
@@ -414,9 +414,12 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
log.step("Building wheel...")
|
||||
built_wheel_dir.mkdir()
|
||||
|
||||
extra_flags = split_config_settings(build_options.config_settings, build_frontend)
|
||||
extra_flags = split_config_settings(
|
||||
build_options.config_settings, build_frontend.name
|
||||
)
|
||||
extra_flags += build_frontend.args
|
||||
|
||||
if build_frontend == "pip":
|
||||
if build_frontend.name == "pip":
|
||||
extra_flags += get_build_verbosity_extra_flags(build_options.build_verbosity)
|
||||
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
|
||||
# see https://github.com/pypa/cibuildwheel/pull/369
|
||||
@@ -431,7 +434,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
||||
*extra_flags,
|
||||
env=env,
|
||||
)
|
||||
elif build_frontend == "build":
|
||||
elif build_frontend.name == "build":
|
||||
if not 0 <= build_options.build_verbosity < 2:
|
||||
msg = f"build_verbosity {build_options.build_verbosity} is not supported for build frontend. Ignoring."
|
||||
log.warning(msg)
|
||||
|
||||
Reference in New Issue
Block a user