Improve the formatting of the preamble

This commit is contained in:
Joe Rickerby
2022-11-26 17:16:54 +00:00
parent 9c6416d4ac
commit 8d05681491
9 changed files with 223 additions and 98 deletions
+3 -2
View File
@@ -229,7 +229,7 @@ def build_in_directory(args: CommandLineArguments) -> None:
)
sys.exit(2)
options = compute_options(platform=platform, command_line_arguments=args)
options = compute_options(platform=platform, command_line_arguments=args, environ=os.environ)
package_dir = options.globals.package_dir
package_files = {"setup.py", "setup.cfg", "pyproject.toml"}
@@ -318,9 +318,10 @@ def print_preamble(platform: str, options: Options, identifiers: list[str]) -> N
print(f"cibuildwheel version {cibuildwheel.__version__}\n")
print("Build options:")
print(f" platform: {platform!r}")
print(f" platform: {platform}")
print(textwrap.indent(options.summary(identifiers), " "))
print()
print(f"Cache folder: {CIBW_CACHE_PATH}")
warnings = detect_warnings(options=options, identifiers=identifiers)
+3
View File
@@ -43,6 +43,9 @@ class Architecture(Enum):
def __lt__(self, other: Architecture) -> bool:
return self.value < other.value
def __str__(self) -> str:
return self.name
@staticmethod
def parse_config(config: str, platform: PlatformName) -> set[Architecture]:
result = set()
+4 -1
View File
@@ -70,7 +70,7 @@ class EnvironmentAssignmentRaw:
self.value = value
def __repr__(self) -> str:
return f"{self.name}: {self.value}"
return f"{self.name}={self.value}"
def evaluated_value(self, **_: Any) -> str:
return self.value
@@ -131,6 +131,9 @@ class ParsedEnvironment:
def __repr__(self) -> str:
return f"{self.__class__.__name__}({[repr(a) for a in self.assignments]!r})"
def options_summary(self) -> Any:
return self.assignments
def parse_environment(env_string: str) -> ParsedEnvironment:
env_items = split_env_items(env_string)
+129 -22
View File
@@ -1,13 +1,14 @@
from __future__ import annotations
import collections
import configparser
import contextlib
import dataclasses
import difflib
import functools
import os
import shlex
import sys
import textwrap
import traceback
from pathlib import Path
from typing import Any, Callable, Dict, Generator, Iterator, List, Mapping, Union, cast
@@ -21,6 +22,7 @@ from packaging.specifiers import SpecifierSet
from .architecture import Architecture
from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
from .logger import log
from .oci_container import ContainerEngine
from .projectfiles import get_requires_python_str
from .typing import PLATFORMS, Literal, NotRequired, PlatformName, TypedDict
@@ -52,6 +54,20 @@ class CommandLineArguments:
allow_empty: bool
prerelease_pythons: bool
@staticmethod
def defaults() -> CommandLineArguments:
return CommandLineArguments(
platform="auto",
allow_empty=False,
archs=None,
only=None,
config_file="",
output_dir=Path("wheelhouse"),
package_dir=Path("."),
prerelease_pythons=False,
print_build_identifiers=False,
)
@dataclasses.dataclass(frozen=True)
class GlobalOptions:
@@ -176,9 +192,11 @@ class OptionsReader:
config_file_path: Path | None = None,
*,
platform: PlatformName,
environ: Mapping[str, str],
disallow: dict[str, set[str]] | None = None,
) -> None:
self.platform = platform
self.environ = environ
self.disallow = disallow or {}
# Open defaults.toml, loading both global and platform sections
@@ -319,8 +337,8 @@ class OptionsReader:
# get the option from the environment, then the config file, then finally the default.
# platform-specific options are preferred, if they're allowed.
result = _dig_first(
(os.environ if env_plat else {}, plat_envvar), # type: ignore[arg-type]
(os.environ, envvar),
(self.environ if env_plat else {}, plat_envvar),
(self.environ, envvar),
*[(o.options, name) for o in active_config_overrides],
(self.config_platform_options, name),
(self.config_options, name),
@@ -362,13 +380,21 @@ def _inner_fmt(k: str, v: Any, table: TableFmt) -> Iterator[str]:
class Options:
def __init__(self, platform: PlatformName, command_line_arguments: CommandLineArguments):
def __init__(
self,
platform: PlatformName,
command_line_arguments: CommandLineArguments,
environ: Mapping[str, str],
read_config_file: bool = True,
):
self.platform = platform
self.command_line_arguments = command_line_arguments
self.environ = environ
self.reader = OptionsReader(
self.config_file_path,
self.config_file_path if read_config_file else None,
platform=platform,
environ=environ,
disallow=DISALLOWED_OPTIONS,
)
@@ -402,13 +428,13 @@ class Options:
test_skip = self.reader.get("test-skip", env_plat=False, sep=" ")
prerelease_pythons = args.prerelease_pythons or strtobool(
os.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
self.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
)
# This is not supported in tool.cibuildwheel, as it comes from a standard location.
# Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py
requires_python_str: str | None = (
os.environ.get("CIBW_PROJECT_REQUIRES_PYTHON") or self.package_requires_python_str
self.environ.get("CIBW_PROJECT_REQUIRES_PYTHON") or self.package_requires_python_str
)
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
@@ -497,7 +523,7 @@ class Options:
if self.platform == "linux":
for env_var_name in environment_pass:
with contextlib.suppress(KeyError):
environment.add(env_var_name, os.environ[env_var_name])
environment.add(env_var_name, self.environ[env_var_name])
if dependency_versions == "pinned":
dependency_constraints: None | (
@@ -594,37 +620,118 @@ class Options:
deprecated_selectors("CIBW_SKIP", build_selector.skip_config)
deprecated_selectors("CIBW_TEST_SKIP", test_selector.skip_config)
def summary(self, identifiers: list[str]) -> str:
lines = [
f"{option_name}: {option_value!r}"
for option_name, option_value in sorted(dataclasses.asdict(self.globals).items())
]
@cached_property
def defaults(self) -> Options:
return Options(
platform=self.platform,
command_line_arguments=CommandLineArguments.defaults(),
environ={},
read_config_file=False,
)
build_option_defaults = self.build_options(identifier=None)
def summary(self, identifiers: list[str]) -> str:
lines = []
global_option_names = sorted(f.name for f in dataclasses.fields(self.globals))
for option_name in global_option_names:
option_value = getattr(self.globals, option_name)
default_value = getattr(self.defaults.globals, option_name)
lines.append(self.option_summary(option_name, option_value, default_value))
build_options = self.build_options(identifier=None)
build_options_defaults = self.defaults.build_options(identifier=None)
build_options_for_identifier = {
identifier: self.build_options(identifier) for identifier in identifiers
}
for option_name, default_value in sorted(dataclasses.asdict(build_option_defaults).items()):
build_option_names = sorted(f.name for f in dataclasses.fields(build_options))
for option_name in build_option_names:
if option_name == "globals":
continue
lines.append(f"{option_name}: {default_value!r}")
option_value = getattr(build_options, option_name)
default_value = getattr(build_options_defaults, option_name)
overrides = {
i: getattr(build_options_for_identifier[i], option_name) for i in identifiers
}
# if any identifiers have an overridden value, print that too
for identifier in identifiers:
option_value = getattr(build_options_for_identifier[identifier], option_name)
if option_value != default_value:
lines.append(f" {identifier}: {option_value!r}")
lines.append(
self.option_summary(option_name, option_value, default_value, overrides=overrides)
)
return "\n".join(lines)
def option_summary(
self,
option_name: str,
option_value: Any,
default_value: Any,
overrides: dict[str, Any] | None = None,
) -> str:
value_str = self.option_summary_value(option_value)
default_value_str = self.option_summary_value(default_value)
overrides_value_strs = {
k: self.option_summary_value(v) for k, v in (overrides or {}).items()
}
# if the override value is the same as the non-overridden value, don't print it
overrides_value_strs = {k: v for k, v in overrides_value_strs.items() if v != value_str}
has_been_set = (value_str != default_value_str) or overrides_value_strs
c = log.colors
result = ""
result += c.faint if not has_been_set else ""
result += f"{option_name}: "
if overrides_value_strs:
overrides_groups = collections.defaultdict(list)
for k, v in overrides_value_strs.items():
overrides_groups[v].append(k)
result += "\n *: "
result += self.indent_if_multiline(value_str, " ")
for override_value_str, identifiers in overrides_groups.items():
result += f"\n {', '.join(identifiers)}: "
result += self.indent_if_multiline(override_value_str, " ")
else:
result += self.indent_if_multiline(value_str, " ")
result += c.end
return result
def indent_if_multiline(self, value: str, indent: str) -> str:
if "\n" in value:
return "\n" + textwrap.indent(value.strip(), indent)
else:
return value
def option_summary_value(self, option_value: Any) -> str:
if hasattr(option_value, "options_summary"):
option_value = option_value.options_summary()
if isinstance(option_value, list):
return "".join(f"{el}\n" for el in option_value)
if isinstance(option_value, set):
return ", ".join(str(el) for el in sorted(option_value))
if isinstance(option_value, dict):
return "".join(f"{k}: {v}\n" for k, v in option_value.items())
return str(option_value)
def compute_options(
platform: PlatformName,
command_line_arguments: CommandLineArguments,
environ: Mapping[str, str],
) -> Options:
options = Options(platform=platform, command_line_arguments=command_line_arguments)
options = Options(
platform=platform, command_line_arguments=command_line_arguments, environ=environ
)
options.check_for_deprecated_options()
return options
+17
View File
@@ -270,6 +270,14 @@ class BuildSelector:
return should_build and not should_skip
def options_summary(self) -> Any:
return {
"build_config": self.build_config,
"skip_config": self.skip_config,
"requires_python": str(self.requires_python),
"prerelease_pythons": self.prerelease_pythons,
}
@dataclass(frozen=True)
class TestSelector:
@@ -283,6 +291,9 @@ class TestSelector:
should_skip = selector_matches(self.skip_config, build_id)
return not should_skip
def options_summary(self) -> Any:
return {"skip_config": self.skip_config}
# Taken from https://stackoverflow.com/a/107717
class Unbuffered:
@@ -356,6 +367,12 @@ class DependencyConstraints:
return self.base_file_path == o.base_file_path
def options_summary(self) -> Any:
if self == DependencyConstraints.with_defaults():
return "pinned"
else:
return self.base_file_path.name
class NonPlatformWheelError(Exception):
def __init__(self) -> None: