diff options
author | Nick Brassel <[email protected]> | 2024-07-11 23:43:26 +1000 |
---|---|---|
committer | GitHub <[email protected]> | 2024-07-11 14:43:26 +0100 |
commit | 1b8b6801d4f896409a765d302e7e0d50ff089692 (patch) | |
tree | a0669cf4858248d05678a422c700d4c90fcd446c /util | |
parent | cc62eb503d8e0b1cb1be869597a5a972ebbca5f6 (diff) | |
download | qmk_firmware-1b8b6801d4f896409a765d302e7e0d50ff089692.tar.gz qmk_firmware-1b8b6801d4f896409a765d302e7e0d50ff089692.zip |
[CI] Add index page generator. (#23737)
Diffstat (limited to 'util')
-rwxr-xr-x | util/ci/index_generator.py | 109 | ||||
-rw-r--r-- | util/ci/requirements.txt | 2 | ||||
-rw-r--r-- | util/ci/templates/index.html.j2 | 139 |
3 files changed, 250 insertions, 0 deletions
diff --git a/util/ci/index_generator.py b/util/ci/index_generator.py new file mode 100755 index 0000000000..b6440bbffb --- /dev/null +++ b/util/ci/index_generator.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 + +import os +import re +import shlex +import subprocess +from pathlib import Path + +from ansi2html import Ansi2HTMLConverter +from jinja2 import Environment, FileSystemLoader, select_autoescape + +orig_cwd = os.getcwd() +qmk_firmware_dir = Path(os.path.realpath(__file__)).parents[2] +build_dir = qmk_firmware_dir / ".build" + +KEYBOARD_PATTERN = re.compile("CI Metadata: KEYBOARD=(?P<keyboard>.*)\r?\n") +KEYMAP_PATTERN = re.compile("CI Metadata: KEYMAP=(?P<keymap>.*)\r?\n") + +env = Environment( + loader=FileSystemLoader(Path(os.path.realpath(__file__)).parent / "templates"), + autoescape=select_autoescape(), +) + + +def _run(command, capture_output=True, combined_output=False, text=True, **kwargs): + if isinstance(command, str): + command = shlex.split(command) + if capture_output: + kwargs["stdout"] = subprocess.PIPE + kwargs["stderr"] = subprocess.PIPE + if combined_output: + kwargs["stderr"] = subprocess.STDOUT + if "stdin" in kwargs and kwargs["stdin"] is None: + del kwargs["stdin"] + if text: + kwargs["universal_newlines"] = True + return subprocess.run(command, **kwargs) + + +def _ansi2html(value): + return Ansi2HTMLConverter().convert(value, full=False) + + +def _ansi2html_styles(): + from ansi2html.style import get_styles + + styles = get_styles(scheme="dracula") + return "\n".join([str(s) for s in styles]) + + +def _git_log(count = 4): + os.chdir(qmk_firmware_dir) + ret = _run(f"git log -n {count} --color=always --no-merges --topo-order --stat").stdout.strip() + os.chdir(orig_cwd) + return ret + +def _git_describe(): + os.chdir(qmk_firmware_dir) + ret = _run("git describe --tags --always --dirty").stdout.strip() + os.chdir(orig_cwd) + return ret + +def _git_revision(): + os.chdir(qmk_firmware_dir) + ret = _run("git rev-parse HEAD").stdout.strip() + os.chdir(orig_cwd) + return ret + + +env.filters["ansi2html"] = _ansi2html + +binaries = [] +binaries.extend(qmk_firmware_dir.glob("*.bin")) +binaries.extend(qmk_firmware_dir.glob("*.hex")) +binaries.extend(qmk_firmware_dir.glob("*.uf2")) +binaries = list(sorted(binaries)) + +failures = [] +for mdfile in list(sorted(build_dir.glob("failed.log.*"))): + txt = Path(mdfile).read_text() + + m_kb = KEYBOARD_PATTERN.search(txt) + if not m_kb: + raise Exception("Couldn't determine the keyboard from the failure output") + m_km = KEYMAP_PATTERN.search(txt) + if not m_km: + raise Exception("Couldn't determine the keymap from the failure output") + + txt = KEYBOARD_PATTERN.sub("", KEYMAP_PATTERN.sub("", txt)).strip() + + failures.append( + { + "stdout": txt, + "keyboard": m_kb.group("keyboard"), + "keymap": m_km.group("keymap"), + } + ) + +template = env.get_template("index.html.j2") +print( + template.render( + ansi2html_styles=_ansi2html_styles(), + git_log=_git_log(), + git_describe=_git_describe(), + git_revision=_git_revision(), + binaries=binaries, + failures=failures, + ) +) diff --git a/util/ci/requirements.txt b/util/ci/requirements.txt index 3196568e1a..47acf85644 100644 --- a/util/ci/requirements.txt +++ b/util/ci/requirements.txt @@ -1 +1,3 @@ discord-webhook +Jinja2 +ansi2html diff --git a/util/ci/templates/index.html.j2 b/util/ci/templates/index.html.j2 new file mode 100644 index 0000000000..18086987db --- /dev/null +++ b/util/ci/templates/index.html.j2 @@ -0,0 +1,139 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <style type="text/css"> + {{ ansi2html_styles }} + + body { + color: #FFF; + background-color: #111; + } + + h3 { + font-family: sans-serif; + margin-top: 0; + } + + a { + color: #00e1ff; + text-decoration: none; + } + + a:hover { + color: #f700ff; + text-decoration: underline; + } + + a:visited { + color: #00e1ff; + } + + .build-target { + background-color: #333; + font-family: monospace; + padding-left: 0.3em; + padding-right: 0.3em; + } + + .binary-link { + margin: 0; + margin-top: 0.25em; + } + + .container { + background-color: #222; + padding: 2.0em; + margin: 2.0em; + border-radius: 2.0em; + } + + .header-container { + display: table-cell; + padding-left: 1.5em; + vertical-align: middle; + font-family: sans-serif; + } + + .header-container div:not(:first-child) { + margin-top: 0.25em; + } + </style> +</head> + +<body class="" style="font-size: normal;"> + <div style="float: left"> + <div class="container"> + <div style="display: table-cell; vertical-align: middle;"> + <a href="https://qmk.fm/"><img src="https://qmk.fm/assets/images/badge-community-dark.svg" style="width: 30em;" /></a> + </div> + <div class="header-container"> + <div> + <span style="font-size: 175%; font-weight: bold;">CI Build {% if failures | length > 0 %}❌{% else %}✅{% endif %}</span> + </div> + <div> + <span style="font-size: 100%; font-family: monospace;">{{ git_describe }}</span> + </div> + <div> + <span style="font-size: 100%; font-family: monospace;">{{ git_revision }}</span> + </div> + <div> + <span style="font-size: 80%; font-weight: bold; color: {% if failures | length > 0 %}#F00{% else %}#0F0{% endif %};">{{ failures | length }} failure{% if failures | length != 1 %}s{% endif %}</span> + </div> + <div> + <span style="font-size: 80%"> + {% if binaries | length > 0 %}<a href="#firmwares">Firmwares</a> | {% endif %} + <a href="#commit_info">Commit info</a> + {% if failures | length > 0 %} | <a href="#failure_logs">Failure Logs</a>{% endif %} + </span> + </div> + </div> + </div> + <a name="commit_info"></a> + <div class="container"> + <h3>Git commit</h3> + <div class="body_foreground body_background" + style="display: table-cell; padding: 1.0em; border-radius: 1.0em;"> + <pre class="ansi2html-content">{{ git_log | ansi2html }}</pre> + </div> + </div> + {% if failures | length > 0 %} + <a name="failure_logs"></a> + <div class="container"> + <h3>Build failure logs</h3> + <ul> + {% for failure in failures %} + <li><a style="font-family: monospace;" href="#build_failure_{{ failure.keyboard }}_{{ failure.keymap }}">{{ failure.keyboard }}:{{ failure.keymap }}</a></li> + {% endfor %} + </ul> + </div> + {% for failure in failures %} + <a name="build_failure_{{ failure.keyboard }}_{{ failure.keymap }}"></a> + <div class="container"> + <h3>Build failure — <span class="build-target">{{ failure.keyboard }}:{{ failure.keymap }}</span></h3> + <div class="body_foreground body_background" + style="display: table-cell; padding: 1.0em; border-radius: 1.0em;"> + <pre class="ansi2html-content">{{ failure.stdout | ansi2html }}</pre> + </div> + </div> + {% endfor %} + {% endif %} + </div> + {% if binaries | length > 0 %} + <div style="float: right"> + <a name="firmwares"></a> + <div class="container"> + <h3>Firmware downloads</h3> + <div class="body_foreground body_background" + style="display: table-cell; padding: 1.0em; border-radius: 1.0em;"> + {% for binary in binaries %} + <p class="binary-link" style="font-family: monospace;"><a href="{{ binary.name }}">{{ binary.name }}</a></p> + {%- endfor %} + </div> + </div> + </div> + {% endif %} +</body> + +</html> |