diff options
| author | Nick Brassel <nick@tzarc.org> | 2024-07-11 23:43:26 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-11 14:43:26 +0100 |
| commit | 1b8b6801d4f896409a765d302e7e0d50ff089692 (patch) | |
| tree | a0669cf4858248d05678a422c700d4c90fcd446c /util | |
| parent | cc62eb503d8e0b1cb1be869597a5a972ebbca5f6 (diff) | |
[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 @@ | |||
| 1 | #!/usr/bin/env python3 | ||
| 2 | |||
| 3 | import os | ||
| 4 | import re | ||
| 5 | import shlex | ||
| 6 | import subprocess | ||
| 7 | from pathlib import Path | ||
| 8 | |||
| 9 | from ansi2html import Ansi2HTMLConverter | ||
| 10 | from jinja2 import Environment, FileSystemLoader, select_autoescape | ||
| 11 | |||
| 12 | orig_cwd = os.getcwd() | ||
| 13 | qmk_firmware_dir = Path(os.path.realpath(__file__)).parents[2] | ||
| 14 | build_dir = qmk_firmware_dir / ".build" | ||
| 15 | |||
| 16 | KEYBOARD_PATTERN = re.compile("CI Metadata: KEYBOARD=(?P<keyboard>.*)\r?\n") | ||
| 17 | KEYMAP_PATTERN = re.compile("CI Metadata: KEYMAP=(?P<keymap>.*)\r?\n") | ||
| 18 | |||
| 19 | env = Environment( | ||
| 20 | loader=FileSystemLoader(Path(os.path.realpath(__file__)).parent / "templates"), | ||
| 21 | autoescape=select_autoescape(), | ||
| 22 | ) | ||
| 23 | |||
| 24 | |||
| 25 | def _run(command, capture_output=True, combined_output=False, text=True, **kwargs): | ||
| 26 | if isinstance(command, str): | ||
| 27 | command = shlex.split(command) | ||
| 28 | if capture_output: | ||
| 29 | kwargs["stdout"] = subprocess.PIPE | ||
| 30 | kwargs["stderr"] = subprocess.PIPE | ||
| 31 | if combined_output: | ||
| 32 | kwargs["stderr"] = subprocess.STDOUT | ||
| 33 | if "stdin" in kwargs and kwargs["stdin"] is None: | ||
| 34 | del kwargs["stdin"] | ||
| 35 | if text: | ||
| 36 | kwargs["universal_newlines"] = True | ||
| 37 | return subprocess.run(command, **kwargs) | ||
| 38 | |||
| 39 | |||
| 40 | def _ansi2html(value): | ||
| 41 | return Ansi2HTMLConverter().convert(value, full=False) | ||
| 42 | |||
| 43 | |||
| 44 | def _ansi2html_styles(): | ||
| 45 | from ansi2html.style import get_styles | ||
| 46 | |||
| 47 | styles = get_styles(scheme="dracula") | ||
| 48 | return "\n".join([str(s) for s in styles]) | ||
| 49 | |||
| 50 | |||
| 51 | def _git_log(count = 4): | ||
| 52 | os.chdir(qmk_firmware_dir) | ||
| 53 | ret = _run(f"git log -n {count} --color=always --no-merges --topo-order --stat").stdout.strip() | ||
| 54 | os.chdir(orig_cwd) | ||
| 55 | return ret | ||
| 56 | |||
| 57 | def _git_describe(): | ||
| 58 | os.chdir(qmk_firmware_dir) | ||
| 59 | ret = _run("git describe --tags --always --dirty").stdout.strip() | ||
| 60 | os.chdir(orig_cwd) | ||
| 61 | return ret | ||
| 62 | |||
| 63 | def _git_revision(): | ||
| 64 | os.chdir(qmk_firmware_dir) | ||
| 65 | ret = _run("git rev-parse HEAD").stdout.strip() | ||
| 66 | os.chdir(orig_cwd) | ||
| 67 | return ret | ||
| 68 | |||
| 69 | |||
| 70 | env.filters["ansi2html"] = _ansi2html | ||
| 71 | |||
| 72 | binaries = [] | ||
| 73 | binaries.extend(qmk_firmware_dir.glob("*.bin")) | ||
| 74 | binaries.extend(qmk_firmware_dir.glob("*.hex")) | ||
| 75 | binaries.extend(qmk_firmware_dir.glob("*.uf2")) | ||
| 76 | binaries = list(sorted(binaries)) | ||
| 77 | |||
| 78 | failures = [] | ||
| 79 | for mdfile in list(sorted(build_dir.glob("failed.log.*"))): | ||
| 80 | txt = Path(mdfile).read_text() | ||
| 81 | |||
| 82 | m_kb = KEYBOARD_PATTERN.search(txt) | ||
| 83 | if not m_kb: | ||
| 84 | raise Exception("Couldn't determine the keyboard from the failure output") | ||
| 85 | m_km = KEYMAP_PATTERN.search(txt) | ||
| 86 | if not m_km: | ||
| 87 | raise Exception("Couldn't determine the keymap from the failure output") | ||
| 88 | |||
| 89 | txt = KEYBOARD_PATTERN.sub("", KEYMAP_PATTERN.sub("", txt)).strip() | ||
| 90 | |||
| 91 | failures.append( | ||
| 92 | { | ||
| 93 | "stdout": txt, | ||
| 94 | "keyboard": m_kb.group("keyboard"), | ||
| 95 | "keymap": m_km.group("keymap"), | ||
| 96 | } | ||
| 97 | ) | ||
| 98 | |||
| 99 | template = env.get_template("index.html.j2") | ||
| 100 | print( | ||
| 101 | template.render( | ||
| 102 | ansi2html_styles=_ansi2html_styles(), | ||
| 103 | git_log=_git_log(), | ||
| 104 | git_describe=_git_describe(), | ||
| 105 | git_revision=_git_revision(), | ||
| 106 | binaries=binaries, | ||
| 107 | failures=failures, | ||
| 108 | ) | ||
| 109 | ) | ||
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 @@ | |||
| 1 | discord-webhook | 1 | discord-webhook |
| 2 | Jinja2 | ||
| 3 | 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 @@ | |||
| 1 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | |||
| 4 | <head> | ||
| 5 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | ||
| 6 | <style type="text/css"> | ||
| 7 | {{ ansi2html_styles }} | ||
| 8 | |||
| 9 | body { | ||
| 10 | color: #FFF; | ||
| 11 | background-color: #111; | ||
| 12 | } | ||
| 13 | |||
| 14 | h3 { | ||
| 15 | font-family: sans-serif; | ||
| 16 | margin-top: 0; | ||
| 17 | } | ||
| 18 | |||
| 19 | a { | ||
| 20 | color: #00e1ff; | ||
| 21 | text-decoration: none; | ||
| 22 | } | ||
| 23 | |||
| 24 | a:hover { | ||
| 25 | color: #f700ff; | ||
| 26 | text-decoration: underline; | ||
| 27 | } | ||
| 28 | |||
| 29 | a:visited { | ||
| 30 | color: #00e1ff; | ||
| 31 | } | ||
| 32 | |||
| 33 | .build-target { | ||
| 34 | background-color: #333; | ||
| 35 | font-family: monospace; | ||
| 36 | padding-left: 0.3em; | ||
| 37 | padding-right: 0.3em; | ||
| 38 | } | ||
| 39 | |||
| 40 | .binary-link { | ||
| 41 | margin: 0; | ||
| 42 | margin-top: 0.25em; | ||
| 43 | } | ||
| 44 | |||
| 45 | .container { | ||
| 46 | background-color: #222; | ||
| 47 | padding: 2.0em; | ||
| 48 | margin: 2.0em; | ||
| 49 | border-radius: 2.0em; | ||
| 50 | } | ||
| 51 | |||
| 52 | .header-container { | ||
| 53 | display: table-cell; | ||
| 54 | padding-left: 1.5em; | ||
| 55 | vertical-align: middle; | ||
| 56 | font-family: sans-serif; | ||
| 57 | } | ||
| 58 | |||
| 59 | .header-container div:not(:first-child) { | ||
| 60 | margin-top: 0.25em; | ||
| 61 | } | ||
| 62 | </style> | ||
| 63 | </head> | ||
| 64 | |||
| 65 | <body class="" style="font-size: normal;"> | ||
| 66 | <div style="float: left"> | ||
| 67 | <div class="container"> | ||
| 68 | <div style="display: table-cell; vertical-align: middle;"> | ||
| 69 | <a href="https://qmk.fm/"><img src="https://qmk.fm/assets/images/badge-community-dark.svg" style="width: 30em;" /></a> | ||
| 70 | </div> | ||
| 71 | <div class="header-container"> | ||
| 72 | <div> | ||
| 73 | <span style="font-size: 175%; font-weight: bold;">CI Build {% if failures | length > 0 %}❌{% else %}✅{% endif %}</span> | ||
| 74 | </div> | ||
| 75 | <div> | ||
| 76 | <span style="font-size: 100%; font-family: monospace;">{{ git_describe }}</span> | ||
| 77 | </div> | ||
| 78 | <div> | ||
| 79 | <span style="font-size: 100%; font-family: monospace;">{{ git_revision }}</span> | ||
| 80 | </div> | ||
| 81 | <div> | ||
| 82 | <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> | ||
| 83 | </div> | ||
| 84 | <div> | ||
| 85 | <span style="font-size: 80%"> | ||
| 86 | {% if binaries | length > 0 %}<a href="#firmwares">Firmwares</a> | {% endif %} | ||
| 87 | <a href="#commit_info">Commit info</a> | ||
| 88 | {% if failures | length > 0 %} | <a href="#failure_logs">Failure Logs</a>{% endif %} | ||
| 89 | </span> | ||
| 90 | </div> | ||
| 91 | </div> | ||
| 92 | </div> | ||
| 93 | <a name="commit_info"></a> | ||
| 94 | <div class="container"> | ||
| 95 | <h3>Git commit</h3> | ||
| 96 | <div class="body_foreground body_background" | ||
| 97 | style="display: table-cell; padding: 1.0em; border-radius: 1.0em;"> | ||
| 98 | <pre class="ansi2html-content">{{ git_log | ansi2html }}</pre> | ||
| 99 | </div> | ||
| 100 | </div> | ||
| 101 | {% if failures | length > 0 %} | ||
| 102 | <a name="failure_logs"></a> | ||
| 103 | <div class="container"> | ||
| 104 | <h3>Build failure logs</h3> | ||
| 105 | <ul> | ||
| 106 | {% for failure in failures %} | ||
| 107 | <li><a style="font-family: monospace;" href="#build_failure_{{ failure.keyboard }}_{{ failure.keymap }}">{{ failure.keyboard }}:{{ failure.keymap }}</a></li> | ||
| 108 | {% endfor %} | ||
| 109 | </ul> | ||
| 110 | </div> | ||
| 111 | {% for failure in failures %} | ||
| 112 | <a name="build_failure_{{ failure.keyboard }}_{{ failure.keymap }}"></a> | ||
| 113 | <div class="container"> | ||
| 114 | <h3>Build failure — <span class="build-target">{{ failure.keyboard }}:{{ failure.keymap }}</span></h3> | ||
| 115 | <div class="body_foreground body_background" | ||
| 116 | style="display: table-cell; padding: 1.0em; border-radius: 1.0em;"> | ||
| 117 | <pre class="ansi2html-content">{{ failure.stdout | ansi2html }}</pre> | ||
| 118 | </div> | ||
| 119 | </div> | ||
| 120 | {% endfor %} | ||
| 121 | {% endif %} | ||
| 122 | </div> | ||
| 123 | {% if binaries | length > 0 %} | ||
| 124 | <div style="float: right"> | ||
| 125 | <a name="firmwares"></a> | ||
| 126 | <div class="container"> | ||
| 127 | <h3>Firmware downloads</h3> | ||
| 128 | <div class="body_foreground body_background" | ||
| 129 | style="display: table-cell; padding: 1.0em; border-radius: 1.0em;"> | ||
| 130 | {% for binary in binaries %} | ||
| 131 | <p class="binary-link" style="font-family: monospace;"><a href="{{ binary.name }}">{{ binary.name }}</a></p> | ||
| 132 | {%- endfor %} | ||
| 133 | </div> | ||
| 134 | </div> | ||
| 135 | </div> | ||
| 136 | {% endif %} | ||
| 137 | </body> | ||
| 138 | |||
| 139 | </html> | ||
