Skip to content

Commit 1541ce1

Browse files
authored
Automatically enable tox coloring on github actions (#126)
1 parent fb3e7e6 commit 1541ce1

File tree

4 files changed

+82
-7
lines changed

4 files changed

+82
-7
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
repos:
2-
- repo: https://github.com/pre-commit/mirrors-prettier
2+
- repo: https://github.com/rbubley/mirrors-prettier
33
# keep it before markdownlint and eslint
4-
rev: "v4.0.0-alpha.8"
4+
rev: "v3.6.1"
55
hooks:
66
- id: prettier
77
- repo: https://github.com/pre-commit/pre-commit-hooks.git

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,14 @@ system dependencies specific to a single tox environment if they wish.
5656
5757
To disable bindep feature, you can define `TOX_EXTRA_BINDEP=0` in your
5858
environment.
59+
60+
## Improves colored output on CI
61+
62+
Multiple tools, including tox and pre-commit fail to property use coloring
63+
when run in a CI evironment like Github Actions. This plugin will inject
64+
additional environment variables in order to ensure that the tool output
65+
remains colored.
66+
67+
It should be noted that this happens only if these variables are not already
68+
defined by the user. This feature should reduce the need of adding extra
69+
environment variables to your pipelines.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ repository = "https://github.com/tox-dev/tox-extra"
6262
source = ["src"]
6363

6464
[tool.coverage.report]
65+
exclude_also = ["pragma: no cover", "if TYPE_CHECKING:"]
6566
fail_under = 92.0
6667
include = ["src/*"]
6768
omit = [".tox/*/lib/python*/site-packages/*", "src/*/_version.py"]
@@ -205,7 +206,7 @@ commands = [
205206
description = "Run tests"
206207
extras = ["test"]
207208
package = "editable"
208-
pass_env = ["SSH_AUTH_SOCK"]
209+
pass_env = ["ANSIBLE_FORCE_COLOR", "CI", "FORCE_COLOR", "GITHUB_*", "MYPY_*", "PRE_COMMIT*", "PY_COLORS", "SSH_AUTH_SOCK", "TERM"]
209210
requires = ["tox-uv"]
210211
set_env = {COVERAGE_FILE = "{env:COVERAGE_FILE:{toxworkdir}/.coverage.{envname}}", COVERAGE_PROCESS_START = "{toxinidir}/pyproject.toml", GIT_AUTHOR_EMAIL = "[email protected]", GIT_AUTHOR_NAME = "John Doe", GIT_COMMITTER_EMAIL = "[email protected]", GIT_COMMITTER_NAME = "John Doe", PIP_DISABLE_PIP_VERSION_CHECK = "1", PYTHONWARNINGS = "error"}
211212
skip_install = false

src/tox_extra/hooks.py

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pathlib
88
import shutil
99
import sys
10-
from typing import TYPE_CHECKING, Any
10+
from typing import TYPE_CHECKING, Any, TextIO
1111

1212
import git
1313
from tox.plugin import impl
@@ -33,9 +33,54 @@
3333
"::error title=tox-extra detected git dirty status:: " + WARNING_MSG_GIT_DIRTY
3434
)
3535

36-
# Change the color of stderr from default red to a dimmed grey
37-
if "TOX_STDERR_COLOR" not in os.environ:
38-
os.environ["TOX_STDERR_COLOR"] = "LIGHTBLACK_EX"
36+
37+
# Based on Ansible implementation
38+
def to_bool(value: str | bool | None) -> bool: # pragma: no cover # noqa: FBT001
39+
"""Return a bool for the arg."""
40+
if value is None or isinstance(value, bool):
41+
return bool(value)
42+
if isinstance(value, str):
43+
value = value.lower()
44+
return value in ("yes", "on", "1", "true", 1)
45+
46+
47+
def should_do_markup(stream: TextIO = sys.stdout) -> bool: # pragma: no cover
48+
"""Decide about use of ANSI colors."""
49+
py_colors = None
50+
51+
# https://xkcd.com/927/
52+
for env_var in [
53+
"PY_COLORS",
54+
"CLICOLOR",
55+
"FORCE_COLOR",
56+
"TOX_COLORED",
57+
"GITHUB_ACTIONS", # they support ANSI
58+
]:
59+
value = os.environ.get(env_var, None)
60+
if value is not None:
61+
py_colors = to_bool(value)
62+
break
63+
64+
# If deliberately disabled colors
65+
if os.environ.get("NO_COLOR", None):
66+
return False
67+
68+
# User configuration requested colors
69+
if py_colors is not None:
70+
return to_bool(py_colors)
71+
72+
term = os.environ.get("TERM", "")
73+
if "xterm" in term:
74+
return True
75+
76+
if term == "dumb":
77+
return False
78+
79+
# Use tty detection logic as last resort because there are numerous
80+
# factors that can make isatty return a misleading value, including:
81+
# - stdin.isatty() is the only one returning true, even on a real terminal
82+
# - stderr returning false if user uses a error stream coloring solution
83+
return stream.isatty()
3984

4085

4186
def is_git_dirty(path: str) -> bool:
@@ -109,3 +154,21 @@ def tox_after_run_commands(
109154
if os.environ.get("CI") == "true":
110155
raise Fail(ERROR_MSG_GIT_DIRTY)
111156
logger.warning(WARNING_MSG_GIT_DIRTY)
157+
158+
159+
if should_do_markup():
160+
# Workaround for tools that do not naturally detect colors in CI system
161+
# like Github Actions. Still, when already defined we will not add them.
162+
overrides = {
163+
"ANSIBLE_FORCE_COLOR": "1",
164+
"COLOR": "yes",
165+
"FORCE_COLOR": "1",
166+
"MYPY_FORCE_COLOR": "1",
167+
"PRE_COMMIT_COLOR": "always",
168+
"PY_COLORS": "1",
169+
"TOX_COLORED": "yes",
170+
"TOX_STDERR_COLOR": "LIGHTBLACK_EX", # stderr in grey instead of red
171+
}
172+
for k, v in overrides.items():
173+
if k not in os.environ:
174+
os.environ[k] = v

0 commit comments

Comments
 (0)