[CI] Add index page generator. (#23737)
This commit is contained in:
parent
cc62eb503d
commit
1b8b6801d4
5 changed files with 269 additions and 6 deletions
24
.github/workflows/ci_build_major_branch.yml
vendored
24
.github/workflows/ci_build_major_branch.yml
vendored
|
@ -77,44 +77,56 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Disable safe.directory check
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
|
||||||
|
- name: Checkout QMK Firmware
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download firmwares
|
- name: Download firmwares
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
pattern: firmware-*
|
pattern: firmware-*
|
||||||
path: firmwares
|
path: .
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Generate index page
|
||||||
|
run: |
|
||||||
|
python3 -m pip install -r ./util/ci/requirements.txt
|
||||||
|
./util/ci/index_generator.py > index.html
|
||||||
|
|
||||||
- name: Upload to https://ci.qmk.fm/${{ inputs.branch || github.ref_name }}/${{ github.sha }}
|
- name: Upload to https://ci.qmk.fm/${{ inputs.branch || github.ref_name }}/${{ github.sha }}
|
||||||
uses: jakejarvis/s3-sync-action@master
|
uses: jakejarvis/s3-sync-action@master
|
||||||
with:
|
with:
|
||||||
args: --acl public-read --follow-symlinks --delete
|
args: --acl public-read --follow-symlinks --delete --exclude '*' --include 'index.html' --include '*.hex' --include '*.bin' --include '*.uf2'
|
||||||
env:
|
env:
|
||||||
AWS_S3_BUCKET: ${{ vars.CI_QMK_FM_SPACES_BUCKET }}
|
AWS_S3_BUCKET: ${{ vars.CI_QMK_FM_SPACES_BUCKET }}
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CI_QMK_FM_SPACES_KEY }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.CI_QMK_FM_SPACES_KEY }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CI_QMK_FM_SPACES_SECRET }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.CI_QMK_FM_SPACES_SECRET }}
|
||||||
AWS_REGION: ${{ vars.CI_QMK_FM_SPACES_REGION }}
|
AWS_REGION: ${{ vars.CI_QMK_FM_SPACES_REGION }}
|
||||||
AWS_S3_ENDPOINT: ${{ vars.CI_QMK_FM_SPACES_ENDPOINT }}
|
AWS_S3_ENDPOINT: ${{ vars.CI_QMK_FM_SPACES_ENDPOINT }}
|
||||||
SOURCE_DIR: firmwares
|
SOURCE_DIR: .
|
||||||
DEST_DIR: ${{ inputs.branch || github.ref_name }}/${{ github.sha }}
|
DEST_DIR: ${{ inputs.branch || github.ref_name }}/${{ github.sha }}
|
||||||
|
|
||||||
- name: Upload to https://ci.qmk.fm/${{ inputs.branch || github.ref_name }}/latest
|
- name: Upload to https://ci.qmk.fm/${{ inputs.branch || github.ref_name }}/latest
|
||||||
uses: jakejarvis/s3-sync-action@master
|
uses: jakejarvis/s3-sync-action@master
|
||||||
with:
|
with:
|
||||||
args: --acl public-read --follow-symlinks --delete
|
args: --acl public-read --follow-symlinks --delete --exclude '*' --include 'index.html' --include '*.hex' --include '*.bin' --include '*.uf2'
|
||||||
env:
|
env:
|
||||||
AWS_S3_BUCKET: ${{ vars.CI_QMK_FM_SPACES_BUCKET }}
|
AWS_S3_BUCKET: ${{ vars.CI_QMK_FM_SPACES_BUCKET }}
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CI_QMK_FM_SPACES_KEY }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.CI_QMK_FM_SPACES_KEY }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CI_QMK_FM_SPACES_SECRET }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.CI_QMK_FM_SPACES_SECRET }}
|
||||||
AWS_REGION: ${{ vars.CI_QMK_FM_SPACES_REGION }}
|
AWS_REGION: ${{ vars.CI_QMK_FM_SPACES_REGION }}
|
||||||
AWS_S3_ENDPOINT: ${{ vars.CI_QMK_FM_SPACES_ENDPOINT }}
|
AWS_S3_ENDPOINT: ${{ vars.CI_QMK_FM_SPACES_ENDPOINT }}
|
||||||
SOURCE_DIR: firmwares
|
SOURCE_DIR: .
|
||||||
DEST_DIR: ${{ inputs.branch || github.ref_name }}/latest
|
DEST_DIR: ${{ inputs.branch || github.ref_name }}/latest
|
||||||
|
|
||||||
- name: Check if failure marker file exists
|
- name: Check if failure marker file exists
|
||||||
id: check_failure_marker
|
id: check_failure_marker
|
||||||
uses: andstor/file-existence-action@v3
|
uses: andstor/file-existence-action@v3
|
||||||
with:
|
with:
|
||||||
files: firmwares/.failed
|
files: ./.failed
|
||||||
|
|
||||||
- name: Fail build if needed
|
- name: Fail build if needed
|
||||||
if: steps.check_failure_marker.outputs.files_exists == 'true'
|
if: steps.check_failure_marker.outputs.files_exists == 'true'
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -25,6 +25,7 @@
|
||||||
*.la
|
*.la
|
||||||
*.stackdump
|
*.stackdump
|
||||||
*.sym
|
*.sym
|
||||||
|
index.html
|
||||||
|
|
||||||
# QMK-specific
|
# QMK-specific
|
||||||
api_data/v1
|
api_data/v1
|
||||||
|
|
109
util/ci/index_generator.py
Executable file
109
util/ci/index_generator.py
Executable file
|
@ -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,
|
||||||
|
)
|
||||||
|
)
|
|
@ -1 +1,3 @@
|
||||||
discord-webhook
|
discord-webhook
|
||||||
|
Jinja2
|
||||||
|
ansi2html
|
||||||
|
|
139
util/ci/templates/index.html.j2
Normal file
139
util/ci/templates/index.html.j2
Normal file
|
@ -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>
|
Loading…
Reference in a new issue