summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
authorNick Brassel <nick@tzarc.org>2024-07-11 23:43:26 +1000
committerGitHub <noreply@github.com>2024-07-11 14:43:26 +0100
commit1b8b6801d4f896409a765d302e7e0d50ff089692 (patch)
treea0669cf4858248d05678a422c700d4c90fcd446c /util
parentcc62eb503d8e0b1cb1be869597a5a972ebbca5f6 (diff)
[CI] Add index page generator. (#23737)
Diffstat (limited to 'util')
-rwxr-xr-xutil/ci/index_generator.py109
-rw-r--r--util/ci/requirements.txt2
-rw-r--r--util/ci/templates/index.html.j2139
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
3import os
4import re
5import shlex
6import subprocess
7from pathlib import Path
8
9from ansi2html import Ansi2HTMLConverter
10from jinja2 import Environment, FileSystemLoader, select_autoescape
11
12orig_cwd = os.getcwd()
13qmk_firmware_dir = Path(os.path.realpath(__file__)).parents[2]
14build_dir = qmk_firmware_dir / ".build"
15
16KEYBOARD_PATTERN = re.compile("CI Metadata: KEYBOARD=(?P<keyboard>.*)\r?\n")
17KEYMAP_PATTERN = re.compile("CI Metadata: KEYMAP=(?P<keymap>.*)\r?\n")
18
19env = Environment(
20 loader=FileSystemLoader(Path(os.path.realpath(__file__)).parent / "templates"),
21 autoescape=select_autoescape(),
22)
23
24
25def _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
40def _ansi2html(value):
41 return Ansi2HTMLConverter().convert(value, full=False)
42
43
44def _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
51def _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
57def _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
63def _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
70env.filters["ansi2html"] = _ansi2html
71
72binaries = []
73binaries.extend(qmk_firmware_dir.glob("*.bin"))
74binaries.extend(qmk_firmware_dir.glob("*.hex"))
75binaries.extend(qmk_firmware_dir.glob("*.uf2"))
76binaries = list(sorted(binaries))
77
78failures = []
79for 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
99template = env.get_template("index.html.j2")
100print(
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 @@
1discord-webhook 1discord-webhook
2Jinja2
3ansi2html
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 %}&#10060;{% else %}&#9989;{% 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 &mdash; <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>