Merge pull request #1352 from pypa/improve-preamble
Improve the formatting of the preamble
This commit is contained in:
@@ -22,9 +22,11 @@ from cibuildwheel.typing import PLATFORMS, PlatformName, assert_never
|
|||||||
from cibuildwheel.util import (
|
from cibuildwheel.util import (
|
||||||
CIBW_CACHE_PATH,
|
CIBW_CACHE_PATH,
|
||||||
BuildSelector,
|
BuildSelector,
|
||||||
|
CIProvider,
|
||||||
Unbuffered,
|
Unbuffered,
|
||||||
chdir,
|
chdir,
|
||||||
detect_ci_provider,
|
detect_ci_provider,
|
||||||
|
fix_ansi_codes_for_github_actions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -229,7 +231,7 @@ def build_in_directory(args: CommandLineArguments) -> None:
|
|||||||
)
|
)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
options = compute_options(platform=platform, command_line_arguments=args)
|
options = compute_options(platform=platform, command_line_arguments=args, env=os.environ)
|
||||||
|
|
||||||
package_dir = options.globals.package_dir
|
package_dir = options.globals.package_dir
|
||||||
package_files = {"setup.py", "setup.cfg", "pyproject.toml"}
|
package_files = {"setup.py", "setup.cfg", "pyproject.toml"}
|
||||||
@@ -318,9 +320,13 @@ def print_preamble(platform: str, options: Options, identifiers: list[str]) -> N
|
|||||||
print(f"cibuildwheel version {cibuildwheel.__version__}\n")
|
print(f"cibuildwheel version {cibuildwheel.__version__}\n")
|
||||||
|
|
||||||
print("Build options:")
|
print("Build options:")
|
||||||
print(f" platform: {platform!r}")
|
print(f" platform: {platform}")
|
||||||
print(textwrap.indent(options.summary(identifiers), " "))
|
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)
|
||||||
|
|
||||||
|
print()
|
||||||
print(f"Cache folder: {CIBW_CACHE_PATH}")
|
print(f"Cache folder: {CIBW_CACHE_PATH}")
|
||||||
|
|
||||||
warnings = detect_warnings(options=options, identifiers=identifiers)
|
warnings = detect_warnings(options=options, identifiers=identifiers)
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ class Architecture(Enum):
|
|||||||
def __lt__(self, other: Architecture) -> bool:
|
def __lt__(self, other: Architecture) -> bool:
|
||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_config(config: str, platform: PlatformName) -> set[Architecture]:
|
def parse_config(config: str, platform: PlatformName) -> set[Architecture]:
|
||||||
result = set()
|
result = set()
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class EnvironmentAssignmentRaw:
|
|||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self.name}: {self.value}"
|
return f"{self.name}={self.value}"
|
||||||
|
|
||||||
def evaluated_value(self, **_: Any) -> str:
|
def evaluated_value(self, **_: Any) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
@@ -131,6 +131,9 @@ class ParsedEnvironment:
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self.__class__.__name__}({[repr(a) for a in self.assignments]!r})"
|
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:
|
def parse_environment(env_string: str) -> ParsedEnvironment:
|
||||||
env_items = split_env_items(env_string)
|
env_items = split_env_items(env_string)
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ class Colors:
|
|||||||
self.bright_red = "\033[91m" if enabled else ""
|
self.bright_red = "\033[91m" if enabled else ""
|
||||||
self.bright_green = "\033[92m" if enabled else ""
|
self.bright_green = "\033[92m" if enabled else ""
|
||||||
self.white = "\033[37m\033[97m" if enabled else ""
|
self.white = "\033[37m\033[97m" if enabled else ""
|
||||||
|
self.gray = "\033[38;5;244m" if enabled else ""
|
||||||
|
|
||||||
self.bg_grey = "\033[48;5;235m" if enabled else ""
|
self.bg_grey = "\033[48;5;235m" if enabled else ""
|
||||||
|
|
||||||
|
|||||||
+130
-22
@@ -1,13 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections
|
||||||
import configparser
|
import configparser
|
||||||
import contextlib
|
import contextlib
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import difflib
|
import difflib
|
||||||
import functools
|
import functools
|
||||||
import os
|
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Dict, Generator, Iterator, List, Mapping, Union, cast
|
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 .architecture import Architecture
|
||||||
from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
|
from .environment import EnvironmentParseError, ParsedEnvironment, parse_environment
|
||||||
|
from .logger import log
|
||||||
from .oci_container import ContainerEngine
|
from .oci_container import ContainerEngine
|
||||||
from .projectfiles import get_requires_python_str
|
from .projectfiles import get_requires_python_str
|
||||||
from .typing import PLATFORMS, Literal, NotRequired, PlatformName, TypedDict
|
from .typing import PLATFORMS, Literal, NotRequired, PlatformName, TypedDict
|
||||||
@@ -52,6 +54,20 @@ class CommandLineArguments:
|
|||||||
allow_empty: bool
|
allow_empty: bool
|
||||||
prerelease_pythons: 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)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class GlobalOptions:
|
class GlobalOptions:
|
||||||
@@ -176,9 +192,11 @@ class OptionsReader:
|
|||||||
config_file_path: Path | None = None,
|
config_file_path: Path | None = None,
|
||||||
*,
|
*,
|
||||||
platform: PlatformName,
|
platform: PlatformName,
|
||||||
|
env: Mapping[str, str],
|
||||||
disallow: dict[str, set[str]] | None = None,
|
disallow: dict[str, set[str]] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.platform = platform
|
self.platform = platform
|
||||||
|
self.env = env
|
||||||
self.disallow = disallow or {}
|
self.disallow = disallow or {}
|
||||||
|
|
||||||
# Open defaults.toml, loading both global and platform sections
|
# 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.
|
# get the option from the environment, then the config file, then finally the default.
|
||||||
# platform-specific options are preferred, if they're allowed.
|
# platform-specific options are preferred, if they're allowed.
|
||||||
result = _dig_first(
|
result = _dig_first(
|
||||||
(os.environ if env_plat else {}, plat_envvar), # type: ignore[arg-type]
|
(self.env if env_plat else {}, plat_envvar),
|
||||||
(os.environ, envvar),
|
(self.env, envvar),
|
||||||
*[(o.options, name) for o in active_config_overrides],
|
*[(o.options, name) for o in active_config_overrides],
|
||||||
(self.config_platform_options, name),
|
(self.config_platform_options, name),
|
||||||
(self.config_options, name),
|
(self.config_options, name),
|
||||||
@@ -362,13 +380,21 @@ def _inner_fmt(k: str, v: Any, table: TableFmt) -> Iterator[str]:
|
|||||||
|
|
||||||
|
|
||||||
class Options:
|
class Options:
|
||||||
def __init__(self, platform: PlatformName, command_line_arguments: CommandLineArguments):
|
def __init__(
|
||||||
|
self,
|
||||||
|
platform: PlatformName,
|
||||||
|
command_line_arguments: CommandLineArguments,
|
||||||
|
env: Mapping[str, str],
|
||||||
|
read_config_file: bool = True,
|
||||||
|
):
|
||||||
self.platform = platform
|
self.platform = platform
|
||||||
self.command_line_arguments = command_line_arguments
|
self.command_line_arguments = command_line_arguments
|
||||||
|
self.env = env
|
||||||
|
|
||||||
self.reader = OptionsReader(
|
self.reader = OptionsReader(
|
||||||
self.config_file_path,
|
self.config_file_path if read_config_file else None,
|
||||||
platform=platform,
|
platform=platform,
|
||||||
|
env=env,
|
||||||
disallow=DISALLOWED_OPTIONS,
|
disallow=DISALLOWED_OPTIONS,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -402,13 +428,13 @@ class Options:
|
|||||||
test_skip = self.reader.get("test-skip", env_plat=False, sep=" ")
|
test_skip = self.reader.get("test-skip", env_plat=False, sep=" ")
|
||||||
|
|
||||||
prerelease_pythons = args.prerelease_pythons or strtobool(
|
prerelease_pythons = args.prerelease_pythons or strtobool(
|
||||||
os.environ.get("CIBW_PRERELEASE_PYTHONS", "0")
|
self.env.get("CIBW_PRERELEASE_PYTHONS", "0")
|
||||||
)
|
)
|
||||||
|
|
||||||
# This is not supported in tool.cibuildwheel, as it comes from a standard location.
|
# 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
|
# Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py
|
||||||
requires_python_str: str | None = (
|
requires_python_str: str | None = (
|
||||||
os.environ.get("CIBW_PROJECT_REQUIRES_PYTHON") or self.package_requires_python_str
|
self.env.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)
|
requires_python = None if requires_python_str is None else SpecifierSet(requires_python_str)
|
||||||
|
|
||||||
@@ -497,7 +523,7 @@ class Options:
|
|||||||
if self.platform == "linux":
|
if self.platform == "linux":
|
||||||
for env_var_name in environment_pass:
|
for env_var_name in environment_pass:
|
||||||
with contextlib.suppress(KeyError):
|
with contextlib.suppress(KeyError):
|
||||||
environment.add(env_var_name, os.environ[env_var_name])
|
environment.add(env_var_name, self.env[env_var_name])
|
||||||
|
|
||||||
if dependency_versions == "pinned":
|
if dependency_versions == "pinned":
|
||||||
dependency_constraints: None | (
|
dependency_constraints: None | (
|
||||||
@@ -594,37 +620,119 @@ class Options:
|
|||||||
deprecated_selectors("CIBW_SKIP", build_selector.skip_config)
|
deprecated_selectors("CIBW_SKIP", build_selector.skip_config)
|
||||||
deprecated_selectors("CIBW_TEST_SKIP", test_selector.skip_config)
|
deprecated_selectors("CIBW_TEST_SKIP", test_selector.skip_config)
|
||||||
|
|
||||||
def summary(self, identifiers: list[str]) -> str:
|
@cached_property
|
||||||
lines = [
|
def defaults(self) -> Options:
|
||||||
f"{option_name}: {option_value!r}"
|
return Options(
|
||||||
for option_name, option_value in sorted(dataclasses.asdict(self.globals).items())
|
platform=self.platform,
|
||||||
]
|
command_line_arguments=CommandLineArguments.defaults(),
|
||||||
|
env={},
|
||||||
|
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 = {
|
build_options_for_identifier = {
|
||||||
identifier: self.build_options(identifier) for identifier in identifiers
|
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":
|
if option_name == "globals":
|
||||||
continue
|
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
|
lines.append(
|
||||||
for identifier in identifiers:
|
self.option_summary(option_name, option_value, default_value, overrides=overrides)
|
||||||
option_value = getattr(build_options_for_identifier[identifier], option_name)
|
)
|
||||||
if option_value != default_value:
|
|
||||||
lines.append(f" {identifier}: {option_value!r}")
|
|
||||||
|
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def option_summary(
|
||||||
|
self,
|
||||||
|
option_name: str,
|
||||||
|
option_value: Any,
|
||||||
|
default_value: Any,
|
||||||
|
overrides: dict[str, Any] | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Return a summary of the option value, including any overrides, with
|
||||||
|
ANSI 'dim' color if it's the default.
|
||||||
|
"""
|
||||||
|
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 = c.gray 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(
|
def compute_options(
|
||||||
platform: PlatformName,
|
platform: PlatformName,
|
||||||
command_line_arguments: CommandLineArguments,
|
command_line_arguments: CommandLineArguments,
|
||||||
|
env: Mapping[str, str],
|
||||||
) -> Options:
|
) -> Options:
|
||||||
options = Options(platform=platform, command_line_arguments=command_line_arguments)
|
options = Options(platform=platform, command_line_arguments=command_line_arguments, env=env)
|
||||||
options.check_for_deprecated_options()
|
options.check_for_deprecated_options()
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
|||||||
@@ -272,6 +272,14 @@ class BuildSelector:
|
|||||||
|
|
||||||
return should_build and not should_skip
|
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)
|
@dataclass(frozen=True)
|
||||||
class TestSelector:
|
class TestSelector:
|
||||||
@@ -285,6 +293,9 @@ class TestSelector:
|
|||||||
should_skip = selector_matches(self.skip_config, build_id)
|
should_skip = selector_matches(self.skip_config, build_id)
|
||||||
return not should_skip
|
return not should_skip
|
||||||
|
|
||||||
|
def options_summary(self) -> Any:
|
||||||
|
return {"skip_config": self.skip_config}
|
||||||
|
|
||||||
|
|
||||||
# Taken from https://stackoverflow.com/a/107717
|
# Taken from https://stackoverflow.com/a/107717
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
@@ -358,6 +369,12 @@ class DependencyConstraints:
|
|||||||
|
|
||||||
return self.base_file_path == o.base_file_path
|
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):
|
class NonPlatformWheelError(Exception):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -657,3 +674,31 @@ def chdir(new_path: Path | str) -> Generator[None, None, None]:
|
|||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
|
|
||||||
|
def fix_ansi_codes_for_github_actions(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Github Actions forgets the current ANSI style on every new line. This
|
||||||
|
function repeats the current ANSI style on every new line.
|
||||||
|
"""
|
||||||
|
ansi_code_regex = re.compile(r"(\033\[[0-9;]*m)")
|
||||||
|
ansi_codes: list[str] = []
|
||||||
|
output = ""
|
||||||
|
|
||||||
|
for line in text.splitlines(keepends=True):
|
||||||
|
# add the current ANSI codes to the beginning of the line
|
||||||
|
output += "".join(ansi_codes) + line
|
||||||
|
|
||||||
|
# split the line at each ANSI code
|
||||||
|
parts = ansi_code_regex.split(line)
|
||||||
|
# if there are any ANSI codes, save them
|
||||||
|
if len(parts) > 1:
|
||||||
|
# iterate over the ANSI codes in this line
|
||||||
|
for code in parts[1::2]:
|
||||||
|
if code == "\033[0m":
|
||||||
|
# reset the list of ANSI codes when the clear code is found
|
||||||
|
ansi_codes = []
|
||||||
|
else:
|
||||||
|
ansi_codes.append(code)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ from pprint import pprint
|
|||||||
|
|
||||||
import cibuildwheel.linux
|
import cibuildwheel.linux
|
||||||
import cibuildwheel.oci_container
|
import cibuildwheel.oci_container
|
||||||
from cibuildwheel.options import Options
|
from cibuildwheel.options import CommandLineArguments, Options
|
||||||
|
|
||||||
from .utils import get_default_command_line_arguments
|
|
||||||
|
|
||||||
|
|
||||||
def test_linux_container_split(tmp_path: Path, monkeypatch):
|
def test_linux_container_split(tmp_path: Path, monkeypatch):
|
||||||
@@ -16,7 +14,7 @@ def test_linux_container_split(tmp_path: Path, monkeypatch):
|
|||||||
Tests splitting linux builds by container image and before_all
|
Tests splitting linux builds by container image and before_all
|
||||||
"""
|
"""
|
||||||
|
|
||||||
args = get_default_command_line_arguments()
|
args = CommandLineArguments.defaults()
|
||||||
args.platform = "linux"
|
args.platform = "linux"
|
||||||
|
|
||||||
(tmp_path / "pyproject.toml").write_text(
|
(tmp_path / "pyproject.toml").write_text(
|
||||||
@@ -42,7 +40,7 @@ def test_linux_container_split(tmp_path: Path, monkeypatch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
monkeypatch.chdir(tmp_path)
|
monkeypatch.chdir(tmp_path)
|
||||||
options = Options("linux", command_line_arguments=args)
|
options = Options("linux", command_line_arguments=args, env={})
|
||||||
|
|
||||||
python_configurations = cibuildwheel.linux.get_python_configurations(
|
python_configurations = cibuildwheel.linux.get_python_configurations(
|
||||||
options.globals.build_selector, options.globals.architectures
|
options.globals.build_selector, options.globals.architectures
|
||||||
|
|||||||
+22
-19
@@ -10,13 +10,16 @@ import pytest
|
|||||||
from cibuildwheel.__main__ import get_build_identifiers
|
from cibuildwheel.__main__ import get_build_identifiers
|
||||||
from cibuildwheel.bashlex_eval import local_environment_executor
|
from cibuildwheel.bashlex_eval import local_environment_executor
|
||||||
from cibuildwheel.environment import parse_environment
|
from cibuildwheel.environment import parse_environment
|
||||||
from cibuildwheel.options import Options, _get_pinned_container_images
|
from cibuildwheel.options import (
|
||||||
|
CommandLineArguments,
|
||||||
from .utils import get_default_command_line_arguments
|
Options,
|
||||||
|
_get_pinned_container_images,
|
||||||
|
)
|
||||||
|
|
||||||
PYPROJECT_1 = """
|
PYPROJECT_1 = """
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
build = ["cp38*", "cp37*"]
|
build = ["cp38*", "cp37*"]
|
||||||
|
skip = ["*musllinux*"]
|
||||||
environment = {FOO="BAR"}
|
environment = {FOO="BAR"}
|
||||||
|
|
||||||
test-command = "pyproject"
|
test-command = "pyproject"
|
||||||
@@ -39,12 +42,12 @@ def test_options_1(tmp_path, monkeypatch):
|
|||||||
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
||||||
f.write(PYPROJECT_1)
|
f.write(PYPROJECT_1)
|
||||||
|
|
||||||
args = get_default_command_line_arguments()
|
args = CommandLineArguments.defaults()
|
||||||
args.package_dir = tmp_path
|
args.package_dir = tmp_path
|
||||||
|
|
||||||
monkeypatch.setattr(platform_module, "machine", lambda: "x86_64")
|
monkeypatch.setattr(platform_module, "machine", lambda: "x86_64")
|
||||||
|
|
||||||
options = Options(platform="linux", command_line_arguments=args)
|
options = Options(platform="linux", command_line_arguments=args, env={})
|
||||||
|
|
||||||
identifiers = get_build_identifiers(
|
identifiers = get_build_identifiers(
|
||||||
platform="linux",
|
platform="linux",
|
||||||
@@ -53,9 +56,8 @@ def test_options_1(tmp_path, monkeypatch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
override_display = """\
|
override_display = """\
|
||||||
test_command: 'pyproject'
|
*: pyproject
|
||||||
cp37-manylinux_x86_64: 'pyproject-override'"""
|
cp37-manylinux_x86_64, cp37-manylinux_i686: pyproject-override"""
|
||||||
|
|
||||||
print(options.summary(identifiers))
|
print(options.summary(identifiers))
|
||||||
|
|
||||||
assert override_display in options.summary(identifiers)
|
assert override_display in options.summary(identifiers)
|
||||||
@@ -82,13 +84,12 @@ def test_passthrough(tmp_path, monkeypatch):
|
|||||||
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
with tmp_path.joinpath("pyproject.toml").open("w") as f:
|
||||||
f.write(PYPROJECT_1)
|
f.write(PYPROJECT_1)
|
||||||
|
|
||||||
args = get_default_command_line_arguments()
|
args = CommandLineArguments.defaults()
|
||||||
args.package_dir = tmp_path
|
args.package_dir = tmp_path
|
||||||
|
|
||||||
monkeypatch.setattr(platform_module, "machine", lambda: "x86_64")
|
monkeypatch.setattr(platform_module, "machine", lambda: "x86_64")
|
||||||
monkeypatch.setenv("EXAMPLE_ENV", "ONE")
|
|
||||||
|
|
||||||
options = Options(platform="linux", command_line_arguments=args)
|
options = Options(platform="linux", command_line_arguments=args, env={"EXAMPLE_ENV": "ONE"})
|
||||||
|
|
||||||
default_build_options = options.build_options(identifier=None)
|
default_build_options = options.build_options(identifier=None)
|
||||||
|
|
||||||
@@ -110,14 +111,16 @@ def test_passthrough(tmp_path, monkeypatch):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_passthrough_evil(tmp_path, monkeypatch, env_var_value):
|
def test_passthrough_evil(tmp_path, monkeypatch, env_var_value):
|
||||||
args = get_default_command_line_arguments()
|
args = CommandLineArguments.defaults()
|
||||||
args.package_dir = tmp_path
|
args.package_dir = tmp_path
|
||||||
|
|
||||||
monkeypatch.setattr(platform_module, "machine", lambda: "x86_64")
|
monkeypatch.setattr(platform_module, "machine", lambda: "x86_64")
|
||||||
monkeypatch.setenv("CIBW_ENVIRONMENT_PASS_LINUX", "ENV_VAR")
|
options = Options(
|
||||||
options = Options(platform="linux", command_line_arguments=args)
|
platform="linux",
|
||||||
|
command_line_arguments=args,
|
||||||
|
env={"CIBW_ENVIRONMENT_PASS_LINUX": "ENV_VAR", "ENV_VAR": env_var_value},
|
||||||
|
)
|
||||||
|
|
||||||
monkeypatch.setenv("ENV_VAR", env_var_value)
|
|
||||||
parsed_environment = options.build_options(identifier=None).environment
|
parsed_environment = options.build_options(identifier=None).environment
|
||||||
assert parsed_environment.as_dictionary(prev_environment={}) == {"ENV_VAR": env_var_value}
|
assert parsed_environment.as_dictionary(prev_environment={}) == {"ENV_VAR": env_var_value}
|
||||||
|
|
||||||
@@ -138,7 +141,7 @@ xfail_env_parse = pytest.mark.xfail(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_toml_environment_evil(tmp_path, monkeypatch, env_var_value):
|
def test_toml_environment_evil(tmp_path, monkeypatch, env_var_value):
|
||||||
args = get_default_command_line_arguments()
|
args = CommandLineArguments.defaults()
|
||||||
args.package_dir = tmp_path
|
args.package_dir = tmp_path
|
||||||
|
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
tmp_path.joinpath("pyproject.toml").write_text(
|
||||||
@@ -150,7 +153,7 @@ def test_toml_environment_evil(tmp_path, monkeypatch, env_var_value):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
options = Options(platform="linux", command_line_arguments=args)
|
options = Options(platform="linux", command_line_arguments=args, env={})
|
||||||
parsed_environment = options.build_options(identifier=None).environment
|
parsed_environment = options.build_options(identifier=None).environment
|
||||||
assert parsed_environment.as_dictionary(prev_environment={}) == {"EXAMPLE": env_var_value}
|
assert parsed_environment.as_dictionary(prev_environment={}) == {"EXAMPLE": env_var_value}
|
||||||
|
|
||||||
@@ -174,7 +177,7 @@ def test_toml_environment_evil(tmp_path, monkeypatch, env_var_value):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_toml_environment_quoting(tmp_path: Path, toml_assignment, result_value):
|
def test_toml_environment_quoting(tmp_path: Path, toml_assignment, result_value):
|
||||||
args = get_default_command_line_arguments()
|
args = CommandLineArguments.defaults()
|
||||||
args.package_dir = tmp_path
|
args.package_dir = tmp_path
|
||||||
|
|
||||||
tmp_path.joinpath("pyproject.toml").write_text(
|
tmp_path.joinpath("pyproject.toml").write_text(
|
||||||
@@ -186,7 +189,7 @@ def test_toml_environment_quoting(tmp_path: Path, toml_assignment, result_value)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
options = Options(platform="linux", command_line_arguments=args)
|
options = Options(platform="linux", command_line_arguments=args, env={})
|
||||||
parsed_environment = options.build_options(identifier=None).environment
|
parsed_environment = options.build_options(identifier=None).environment
|
||||||
environment_values = parsed_environment.as_dictionary(
|
environment_values = parsed_environment.as_dictionary(
|
||||||
prev_environment={**os.environ, "PARAM": "spam"},
|
prev_environment={**os.environ, "PARAM": "spam"},
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ def test_simple_settings(tmp_path, platform, fname):
|
|||||||
config_file_path: Path = tmp_path / fname
|
config_file_path: Path = tmp_path / fname
|
||||||
config_file_path.write_text(PYPROJECT_1)
|
config_file_path.write_text(PYPROJECT_1)
|
||||||
|
|
||||||
options_reader = OptionsReader(config_file_path, platform=platform)
|
options_reader = OptionsReader(config_file_path, platform=platform, env={})
|
||||||
|
|
||||||
assert options_reader.get("build", env_plat=False, sep=" ") == "cp39*"
|
assert options_reader.get("build", env_plat=False, sep=" ") == "cp39*"
|
||||||
|
|
||||||
@@ -72,16 +72,20 @@ def test_simple_settings(tmp_path, platform, fname):
|
|||||||
|
|
||||||
|
|
||||||
def test_envvar_override(tmp_path, platform, monkeypatch):
|
def test_envvar_override(tmp_path, platform, monkeypatch):
|
||||||
monkeypatch.setenv("CIBW_BUILD", "cp38*")
|
|
||||||
monkeypatch.setenv("CIBW_MANYLINUX_X86_64_IMAGE", "manylinux_2_24")
|
|
||||||
monkeypatch.setenv("CIBW_TEST_COMMAND", "mytest")
|
|
||||||
monkeypatch.setenv("CIBW_TEST_REQUIRES", "docs")
|
|
||||||
monkeypatch.setenv("CIBW_TEST_REQUIRES_LINUX", "scod")
|
|
||||||
|
|
||||||
config_file_path: Path = tmp_path / "pyproject.toml"
|
config_file_path: Path = tmp_path / "pyproject.toml"
|
||||||
config_file_path.write_text(PYPROJECT_1)
|
config_file_path.write_text(PYPROJECT_1)
|
||||||
|
|
||||||
options_reader = OptionsReader(config_file_path, platform=platform)
|
options_reader = OptionsReader(
|
||||||
|
config_file_path,
|
||||||
|
platform=platform,
|
||||||
|
env={
|
||||||
|
"CIBW_BUILD": "cp38*",
|
||||||
|
"CIBW_MANYLINUX_X86_64_IMAGE": "manylinux_2_24",
|
||||||
|
"CIBW_TEST_COMMAND": "mytest",
|
||||||
|
"CIBW_TEST_REQUIRES": "docs",
|
||||||
|
"CIBW_TEST_REQUIRES_LINUX": "scod",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
assert options_reader.get("archs", sep=" ") == "auto"
|
assert options_reader.get("archs", sep=" ") == "auto"
|
||||||
|
|
||||||
@@ -104,18 +108,18 @@ def test_project_global_override_default_platform(tmp_path, platform):
|
|||||||
repair-wheel-command = "repair-project-global"
|
repair-wheel-command = "repair-project-global"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
options_reader = OptionsReader(pyproject_toml, platform=platform)
|
options_reader = OptionsReader(pyproject_toml, platform=platform, env={})
|
||||||
assert options_reader.get("repair-wheel-command") == "repair-project-global"
|
assert options_reader.get("repair-wheel-command") == "repair-project-global"
|
||||||
|
|
||||||
|
|
||||||
def test_env_global_override_default_platform(tmp_path, platform, monkeypatch):
|
def test_env_global_override_default_platform(tmp_path, platform, monkeypatch):
|
||||||
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
|
options_reader = OptionsReader(
|
||||||
options_reader = OptionsReader(platform=platform)
|
platform=platform, env={"CIBW_REPAIR_WHEEL_COMMAND": "repair-env-global"}
|
||||||
|
)
|
||||||
assert options_reader.get("repair-wheel-command") == "repair-env-global"
|
assert options_reader.get("repair-wheel-command") == "repair-env-global"
|
||||||
|
|
||||||
|
|
||||||
def test_env_global_override_project_platform(tmp_path, platform, monkeypatch):
|
def test_env_global_override_project_platform(tmp_path, platform, monkeypatch):
|
||||||
monkeypatch.setenv("CIBW_REPAIR_WHEEL_COMMAND", "repair-env-global")
|
|
||||||
pyproject_toml = tmp_path / "pyproject.toml"
|
pyproject_toml = tmp_path / "pyproject.toml"
|
||||||
pyproject_toml.write_text(
|
pyproject_toml.write_text(
|
||||||
"""
|
"""
|
||||||
@@ -127,7 +131,13 @@ repair-wheel-command = "repair-project-windows"
|
|||||||
repair-wheel-command = "repair-project-macos"
|
repair-wheel-command = "repair-project-macos"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
options_reader = OptionsReader(pyproject_toml, platform=platform)
|
options_reader = OptionsReader(
|
||||||
|
pyproject_toml,
|
||||||
|
platform=platform,
|
||||||
|
env={
|
||||||
|
"CIBW_REPAIR_WHEEL_COMMAND": "repair-env-global",
|
||||||
|
},
|
||||||
|
)
|
||||||
assert options_reader.get("repair-wheel-command") == "repair-env-global"
|
assert options_reader.get("repair-wheel-command") == "repair-env-global"
|
||||||
|
|
||||||
|
|
||||||
@@ -145,7 +155,7 @@ repair-wheel-command = "repair-project-macos"
|
|||||||
repair-wheel-command = "repair-project-global"
|
repair-wheel-command = "repair-project-global"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
options_reader = OptionsReader(pyproject_toml, platform=platform)
|
options_reader = OptionsReader(pyproject_toml, platform=platform, env={})
|
||||||
assert options_reader.get("repair-wheel-command") == f"repair-project-{platform}"
|
assert options_reader.get("repair-wheel-command") == f"repair-project-{platform}"
|
||||||
|
|
||||||
|
|
||||||
@@ -161,7 +171,7 @@ repairs-wheel-command = "repair-project-linux"
|
|||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ConfigOptionError) as excinfo:
|
with pytest.raises(ConfigOptionError) as excinfo:
|
||||||
OptionsReader(pyproject_toml, platform="linux")
|
OptionsReader(pyproject_toml, platform="linux", env={})
|
||||||
|
|
||||||
assert "repair-wheel-command" in str(excinfo.value)
|
assert "repair-wheel-command" in str(excinfo.value)
|
||||||
|
|
||||||
@@ -178,7 +188,7 @@ repair_wheel_command = "repair-project-linux"
|
|||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ConfigOptionError) as excinfo:
|
with pytest.raises(ConfigOptionError) as excinfo:
|
||||||
OptionsReader(pyproject_toml, platform="linux")
|
OptionsReader(pyproject_toml, platform="linux", env={})
|
||||||
|
|
||||||
assert "repair-wheel-command" in str(excinfo.value)
|
assert "repair-wheel-command" in str(excinfo.value)
|
||||||
|
|
||||||
@@ -192,7 +202,7 @@ repair-wheel-command = "repair-project-linux"
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
OptionsReader(pyproject_toml, platform="linux")
|
OptionsReader(pyproject_toml, platform="linux", env={})
|
||||||
|
|
||||||
|
|
||||||
def test_unsupported_join(tmp_path):
|
def test_unsupported_join(tmp_path):
|
||||||
@@ -203,7 +213,7 @@ def test_unsupported_join(tmp_path):
|
|||||||
build = ["1", "2"]
|
build = ["1", "2"]
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
options_reader = OptionsReader(pyproject_toml, platform="linux")
|
options_reader = OptionsReader(pyproject_toml, platform="linux", env={})
|
||||||
|
|
||||||
assert "1, 2" == options_reader.get("build", sep=", ")
|
assert "1, 2" == options_reader.get("build", sep=", ")
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
@@ -219,9 +229,9 @@ manylinux-x86_64-image = "manylinux1"
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
disallow = {"windows": {"manylinux-x86_64-image"}}
|
disallow = {"windows": {"manylinux-x86_64-image"}}
|
||||||
OptionsReader(pyproject_toml, platform="linux", disallow=disallow)
|
OptionsReader(pyproject_toml, platform="linux", disallow=disallow, env={})
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
OptionsReader(pyproject_toml, platform="windows", disallow=disallow)
|
OptionsReader(pyproject_toml, platform="windows", disallow=disallow, env={})
|
||||||
|
|
||||||
|
|
||||||
def test_environment_override_empty(tmp_path, monkeypatch):
|
def test_environment_override_empty(tmp_path, monkeypatch):
|
||||||
@@ -234,10 +244,14 @@ manylinux-x86_64-image = ""
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
monkeypatch.setenv("CIBW_MANYLINUX_I686_IMAGE", "")
|
options_reader = OptionsReader(
|
||||||
monkeypatch.setenv("CIBW_MANYLINUX_AARCH64_IMAGE", "manylinux1")
|
pyproject_toml,
|
||||||
|
platform="linux",
|
||||||
options_reader = OptionsReader(pyproject_toml, platform="linux")
|
env={
|
||||||
|
"CIBW_MANYLINUX_I686_IMAGE": "",
|
||||||
|
"CIBW_MANYLINUX_AARCH64_IMAGE": "manylinux1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
assert options_reader.get("manylinux-x86_64-image") == ""
|
assert options_reader.get("manylinux-x86_64-image") == ""
|
||||||
assert options_reader.get("manylinux-i686-image") == ""
|
assert options_reader.get("manylinux-i686-image") == ""
|
||||||
@@ -306,7 +320,7 @@ def test_pyproject_2(tmp_path, platform):
|
|||||||
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||||
pyproject_toml.write_text(PYPROJECT_2)
|
pyproject_toml.write_text(PYPROJECT_2)
|
||||||
|
|
||||||
options_reader = OptionsReader(config_file_path=pyproject_toml, platform=platform)
|
options_reader = OptionsReader(config_file_path=pyproject_toml, platform=platform, env={})
|
||||||
assert options_reader.get("test-command") == "pyproject"
|
assert options_reader.get("test-command") == "pyproject"
|
||||||
|
|
||||||
with options_reader.identifier("random"):
|
with options_reader.identifier("random"):
|
||||||
@@ -330,7 +344,7 @@ test-command = "pyproject-override"
|
|||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ConfigOptionError):
|
with pytest.raises(ConfigOptionError):
|
||||||
OptionsReader(config_file_path=pyproject_toml, platform=platform)
|
OptionsReader(config_file_path=pyproject_toml, platform=platform, env={})
|
||||||
|
|
||||||
|
|
||||||
def test_config_settings(tmp_path):
|
def test_config_settings(tmp_path):
|
||||||
@@ -343,7 +357,7 @@ other = ["two", "three"]
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
options_reader = OptionsReader(config_file_path=pyproject_toml, platform="linux")
|
options_reader = OptionsReader(config_file_path=pyproject_toml, platform="linux", env={})
|
||||||
assert (
|
assert (
|
||||||
options_reader.get("config-settings", table={"item": '{k}="{v}"', "sep": " "})
|
options_reader.get("config-settings", table={"item": '{k}="{v}"', "sep": " "})
|
||||||
== 'example="one" other="two" other="three"'
|
== 'example="one" other="two" other="three"'
|
||||||
@@ -359,7 +373,7 @@ def test_pip_config_settings(tmp_path):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
options_reader = OptionsReader(config_file_path=pyproject_toml, platform="linux")
|
options_reader = OptionsReader(config_file_path=pyproject_toml, platform="linux", env={})
|
||||||
assert (
|
assert (
|
||||||
options_reader.get(
|
options_reader.get(
|
||||||
"config-settings", table={"item": "--config-settings='{k}=\"{v}\"'", "sep": " "}
|
"config-settings", table={"item": "--config-settings='{k}=\"{v}\"'", "sep": " "}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from cibuildwheel.options import CommandLineArguments
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_command_line_arguments() -> CommandLineArguments:
|
|
||||||
defaults = 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,
|
|
||||||
)
|
|
||||||
|
|
||||||
return defaults
|
|
||||||
+35
-1
@@ -1,10 +1,16 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import textwrap
|
||||||
from pathlib import PurePath
|
from pathlib import PurePath
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from cibuildwheel.util import find_compatible_wheel, format_safe, prepare_command
|
from cibuildwheel.util import (
|
||||||
|
find_compatible_wheel,
|
||||||
|
fix_ansi_codes_for_github_actions,
|
||||||
|
format_safe,
|
||||||
|
prepare_command,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_format_safe():
|
def test_format_safe():
|
||||||
@@ -90,3 +96,31 @@ def test_find_compatible_wheel_found(wheel: str, identifier: str):
|
|||||||
)
|
)
|
||||||
def test_find_compatible_wheel_not_found(wheel: str, identifier: str):
|
def test_find_compatible_wheel_not_found(wheel: str, identifier: str):
|
||||||
assert find_compatible_wheel([PurePath(wheel)], identifier) is None
|
assert find_compatible_wheel([PurePath(wheel)], identifier) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_fix_ansi_codes_for_github_actions():
|
||||||
|
input = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
This line is normal
|
||||||
|
\033[1mThis line is bold
|
||||||
|
This line is also bold
|
||||||
|
\033[31m this line is red and bold
|
||||||
|
This line is red and bold, too\033[0m
|
||||||
|
This line is normal again
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
expected = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
This line is normal
|
||||||
|
\033[1mThis line is bold
|
||||||
|
\033[1mThis line is also bold
|
||||||
|
\033[1m\033[31m this line is red and bold
|
||||||
|
\033[1m\033[31mThis line is red and bold, too\033[0m
|
||||||
|
This line is normal again
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
output = fix_ansi_codes_for_github_actions(input)
|
||||||
|
|
||||||
|
assert output == expected
|
||||||
|
|||||||
Reference in New Issue
Block a user