|
1 | 1 | from __future__ import annotations
|
2 | 2 |
|
| 3 | +import collections |
3 | 4 | import configparser
|
4 | 5 | import contextlib
|
5 | 6 | import dataclasses
|
6 | 7 | import difflib
|
7 | 8 | import functools
|
8 |
| -import os |
9 | 9 | import shlex
|
10 | 10 | import sys
|
| 11 | +import textwrap |
11 | 12 | import traceback
|
12 | 13 | from pathlib import Path
|
13 | 14 | from typing import Any, Callable, Dict, Generator, Iterator, List, Mapping, Union, cast
|
|
21 | 22 |
|
22 | 23 | from .architecture import Architecture
|
23 | 24 | from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
|
| 25 | +from .logger import log |
24 | 26 | from .oci_container import ContainerEngine
|
25 | 27 | from .projectfiles import get_requires_python_str
|
26 | 28 | from .typing import PLATFORMS, Literal, NotRequired, PlatformName, TypedDict
|
@@ -52,6 +54,20 @@ class CommandLineArguments:
|
52 | 54 | allow_empty: bool
|
53 | 55 | prerelease_pythons: bool
|
54 | 56 |
|
| 57 | + @staticmethod |
| 58 | + def defaults() -> CommandLineArguments: |
| 59 | + return CommandLineArguments( |
| 60 | + platform="auto", |
| 61 | + allow_empty=False, |
| 62 | + archs=None, |
| 63 | + only=None, |
| 64 | + config_file="", |
| 65 | + output_dir=Path("wheelhouse"), |
| 66 | + package_dir=Path("."), |
| 67 | + prerelease_pythons=False, |
| 68 | + print_build_identifiers=False, |
| 69 | + ) |
| 70 | + |
55 | 71 |
|
56 | 72 | @dataclasses.dataclass(frozen=True)
|
57 | 73 | class GlobalOptions:
|
@@ -176,9 +192,11 @@ def __init__(
|
176 | 192 | config_file_path: Path | None = None,
|
177 | 193 | *,
|
178 | 194 | platform: PlatformName,
|
| 195 | + env: Mapping[str, str], |
179 | 196 | disallow: dict[str, set[str]] | None = None,
|
180 | 197 | ) -> None:
|
181 | 198 | self.platform = platform
|
| 199 | + self.env = env |
182 | 200 | self.disallow = disallow or {}
|
183 | 201 |
|
184 | 202 | # Open defaults.toml, loading both global and platform sections
|
@@ -319,8 +337,8 @@ def get(
|
319 | 337 | # get the option from the environment, then the config file, then finally the default.
|
320 | 338 | # platform-specific options are preferred, if they're allowed.
|
321 | 339 | result = _dig_first(
|
322 |
| - (os.environ if env_plat else {}, plat_envvar), # type: ignore[arg-type] |
323 |
| - (os.environ, envvar), |
| 340 | + (self.env if env_plat else {}, plat_envvar), |
| 341 | + (self.env, envvar), |
324 | 342 | *[(o.options, name) for o in active_config_overrides],
|
325 | 343 | (self.config_platform_options, name),
|
326 | 344 | (self.config_options, name),
|
@@ -362,13 +380,21 @@ def _inner_fmt(k: str, v: Any, table: TableFmt) -> Iterator[str]:
|
362 | 380 |
|
363 | 381 |
|
364 | 382 | class Options:
|
365 |
| - def __init__(self, platform: PlatformName, command_line_arguments: CommandLineArguments): |
| 383 | + def __init__( |
| 384 | + self, |
| 385 | + platform: PlatformName, |
| 386 | + command_line_arguments: CommandLineArguments, |
| 387 | + env: Mapping[str, str], |
| 388 | + read_config_file: bool = True, |
| 389 | + ): |
366 | 390 | self.platform = platform
|
367 | 391 | self.command_line_arguments = command_line_arguments
|
| 392 | + self.env = env |
368 | 393 |
|
369 | 394 | self.reader = OptionsReader(
|
370 |
| - self.config_file_path, |
| 395 | + self.config_file_path if read_config_file else None, |
371 | 396 | platform=platform,
|
| 397 | + env=env, |
372 | 398 | disallow=DISALLOWED_OPTIONS,
|
373 | 399 | )
|
374 | 400 |
|
@@ -402,13 +428,13 @@ def globals(self) -> GlobalOptions:
|
402 | 428 | test_skip = self.reader.get("test-skip", env_plat=False, sep=" ")
|
403 | 429 |
|
404 | 430 | prerelease_pythons = args.prerelease_pythons or strtobool(
|
405 |
| - os.environ.get("CIBW_PRERELEASE_PYTHONS", "0") |
| 431 | + self.env.get("CIBW_PRERELEASE_PYTHONS", "0") |
406 | 432 | )
|
407 | 433 |
|
408 | 434 | # This is not supported in tool.cibuildwheel, as it comes from a standard location.
|
409 | 435 | # Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py
|
410 | 436 | requires_python_str: str | None = (
|
411 |
| - os.environ.get("CIBW_PROJECT_REQUIRES_PYTHON") or self.package_requires_python_str |
| 437 | + self.env.get("CIBW_PROJECT_REQUIRES_PYTHON") or self.package_requires_python_str |
412 | 438 | )
|
413 | 439 | requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
|
414 | 440 |
|
@@ -497,7 +523,7 @@ def build_options(self, identifier: str | None) -> BuildOptions:
|
497 | 523 | if self.platform == "linux":
|
498 | 524 | for env_var_name in environment_pass:
|
499 | 525 | with contextlib.suppress(KeyError):
|
500 |
| - environment.add(env_var_name, os.environ[env_var_name]) |
| 526 | + environment.add(env_var_name, self.env[env_var_name]) |
501 | 527 |
|
502 | 528 | if dependency_versions == "pinned":
|
503 | 529 | dependency_constraints: None | (
|
@@ -594,37 +620,119 @@ def check_for_deprecated_options(self) -> None:
|
594 | 620 | deprecated_selectors("CIBW_SKIP", build_selector.skip_config)
|
595 | 621 | deprecated_selectors("CIBW_TEST_SKIP", test_selector.skip_config)
|
596 | 622 |
|
| 623 | + @cached_property |
| 624 | + def defaults(self) -> Options: |
| 625 | + return Options( |
| 626 | + platform=self.platform, |
| 627 | + command_line_arguments=CommandLineArguments.defaults(), |
| 628 | + env={}, |
| 629 | + read_config_file=False, |
| 630 | + ) |
| 631 | + |
597 | 632 | def summary(self, identifiers: list[str]) -> str:
|
598 |
| - lines = [ |
599 |
| - f"{option_name}: {option_value!r}" |
600 |
| - for option_name, option_value in sorted(dataclasses.asdict(self.globals).items()) |
601 |
| - ] |
| 633 | + lines = [] |
| 634 | + global_option_names = sorted(f.name for f in dataclasses.fields(self.globals)) |
602 | 635 |
|
603 |
| - build_option_defaults = self.build_options(identifier=None) |
| 636 | + for option_name in global_option_names: |
| 637 | + option_value = getattr(self.globals, option_name) |
| 638 | + default_value = getattr(self.defaults.globals, option_name) |
| 639 | + lines.append(self.option_summary(option_name, option_value, default_value)) |
| 640 | + |
| 641 | + build_options = self.build_options(identifier=None) |
| 642 | + build_options_defaults = self.defaults.build_options(identifier=None) |
604 | 643 | build_options_for_identifier = {
|
605 | 644 | identifier: self.build_options(identifier) for identifier in identifiers
|
606 | 645 | }
|
607 | 646 |
|
608 |
| - for option_name, default_value in sorted(dataclasses.asdict(build_option_defaults).items()): |
| 647 | + build_option_names = sorted(f.name for f in dataclasses.fields(build_options)) |
| 648 | + |
| 649 | + for option_name in build_option_names: |
609 | 650 | if option_name == "globals":
|
610 | 651 | continue
|
611 | 652 |
|
612 |
| - lines.append(f"{option_name}: {default_value!r}") |
| 653 | + option_value = getattr(build_options, option_name) |
| 654 | + default_value = getattr(build_options_defaults, option_name) |
| 655 | + overrides = { |
| 656 | + i: getattr(build_options_for_identifier[i], option_name) for i in identifiers |
| 657 | + } |
613 | 658 |
|
614 |
| - # if any identifiers have an overridden value, print that too |
615 |
| - for identifier in identifiers: |
616 |
| - option_value = getattr(build_options_for_identifier[identifier], option_name) |
617 |
| - if option_value != default_value: |
618 |
| - lines.append(f" {identifier}: {option_value!r}") |
| 659 | + lines.append( |
| 660 | + self.option_summary(option_name, option_value, default_value, overrides=overrides) |
| 661 | + ) |
619 | 662 |
|
620 | 663 | return "\n".join(lines)
|
621 | 664 |
|
| 665 | + def option_summary( |
| 666 | + self, |
| 667 | + option_name: str, |
| 668 | + option_value: Any, |
| 669 | + default_value: Any, |
| 670 | + overrides: dict[str, Any] | None = None, |
| 671 | + ) -> str: |
| 672 | + """ |
| 673 | + Return a summary of the option value, including any overrides, with |
| 674 | + ANSI 'dim' color if it's the default. |
| 675 | + """ |
| 676 | + value_str = self.option_summary_value(option_value) |
| 677 | + default_value_str = self.option_summary_value(default_value) |
| 678 | + overrides_value_strs = { |
| 679 | + k: self.option_summary_value(v) for k, v in (overrides or {}).items() |
| 680 | + } |
| 681 | + # if the override value is the same as the non-overridden value, don't print it |
| 682 | + overrides_value_strs = {k: v for k, v in overrides_value_strs.items() if v != value_str} |
| 683 | + |
| 684 | + has_been_set = (value_str != default_value_str) or overrides_value_strs |
| 685 | + c = log.colors |
| 686 | + |
| 687 | + result = c.gray if not has_been_set else "" |
| 688 | + result += f"{option_name}: " |
| 689 | + |
| 690 | + if overrides_value_strs: |
| 691 | + overrides_groups = collections.defaultdict(list) |
| 692 | + for k, v in overrides_value_strs.items(): |
| 693 | + overrides_groups[v].append(k) |
| 694 | + |
| 695 | + result += "\n *: " |
| 696 | + result += self.indent_if_multiline(value_str, " ") |
| 697 | + |
| 698 | + for override_value_str, identifiers in overrides_groups.items(): |
| 699 | + result += f"\n {', '.join(identifiers)}: " |
| 700 | + result += self.indent_if_multiline(override_value_str, " ") |
| 701 | + else: |
| 702 | + result += self.indent_if_multiline(value_str, " ") |
| 703 | + |
| 704 | + result += c.end |
| 705 | + |
| 706 | + return result |
| 707 | + |
| 708 | + def indent_if_multiline(self, value: str, indent: str) -> str: |
| 709 | + if "\n" in value: |
| 710 | + return "\n" + textwrap.indent(value.strip(), indent) |
| 711 | + else: |
| 712 | + return value |
| 713 | + |
| 714 | + def option_summary_value(self, option_value: Any) -> str: |
| 715 | + if hasattr(option_value, "options_summary"): |
| 716 | + option_value = option_value.options_summary() |
| 717 | + |
| 718 | + if isinstance(option_value, list): |
| 719 | + return "".join(f"{el}\n" for el in option_value) |
| 720 | + |
| 721 | + if isinstance(option_value, set): |
| 722 | + return ", ".join(str(el) for el in sorted(option_value)) |
| 723 | + |
| 724 | + if isinstance(option_value, dict): |
| 725 | + return "".join(f"{k}: {v}\n" for k, v in option_value.items()) |
| 726 | + |
| 727 | + return str(option_value) |
| 728 | + |
622 | 729 |
|
623 | 730 | def compute_options(
|
624 | 731 | platform: PlatformName,
|
625 | 732 | command_line_arguments: CommandLineArguments,
|
| 733 | + env: Mapping[str, str], |
626 | 734 | ) -> Options:
|
627 |
| - options = Options(platform=platform, command_line_arguments=command_line_arguments) |
| 735 | + options = Options(platform=platform, command_line_arguments=command_line_arguments, env=env) |
628 | 736 | options.check_for_deprecated_options()
|
629 | 737 | return options
|
630 | 738 |
|
|
0 commit comments