#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0+ import datetime import lzma import apt_pkg apt_pkg.init() version_compare = apt_pkg.version_compare import flask import flask_sqlalchemy import jinja2 import sqlalchemy import werkzeug import werkzeug.security app = flask.Flask("crossqa") app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = flask_sqlalchemy.SQLAlchemy(app) index_template = """
source | version | build architecture | host architecture | started | result log | bugs |
---|---|---|---|---|---|---|
{{- build.source|e -}} | {{ build.version|e }} | {{ build.buildarch|e }} | {{ build.hostarch|e }} | {{- build.starttime|sqltimestamp|formatts -}} | log xz | {%- if build.buglvl == 2 -%} patch reported {%- elif build.buglvl == 1 -%} bug reported {%- endif -%} |
state | architectures |
---|---|
{{ reason|e }} | {{ archs|archpairs_format }} |
ok | {{ okarchs|archpairs_format }} |
started | version | build architecture | host architecture | result log |
---|---|---|---|---|
{{- build.starttime|sqltimestamp|formatts -}} | {{ build.version|e }} | {{ build.buildarch|e }} | {{ build.hostarch|e }} | {{- "ok" if build.success else "failed" -}} xz |
No build performed yet.
{%- endif -%}Scheduled a build of {{ request.form["source"]|e }} {%- if buildarch or hostarch %} for {{ (buildarch|default("any"), hostarch|default("any"))|archpair_format -}} {%- endif %}.
"""
@app.template_filter("sqltimestamp")
def sqltimestamp_filter(s):
strptime = datetime.datetime.strptime
try:
return strptime(s, "%Y-%m-%d %H:%M:%S.%f").replace(microsecond=0)
except ValueError:
return strptime(s, "%Y-%m-%d %H:%M:%S")
def formatts(ts):
assert isinstance(ts, datetime.datetime)
dt = datetime.datetime.utcnow() - ts
if dt < datetime.timedelta(seconds=1):
return "now"
if dt < datetime.timedelta(seconds=100):
return "%d s" % dt.seconds
if dt < datetime.timedelta(minutes=100):
return "%d m" % (dt.seconds // 60)
if dt < datetime.timedelta(days=1):
return "%d h" % (dt.seconds // (60 * 60))
return "%d d" % dt.days
@app.template_filter("formatts")
def formatts_filter(ts):
return jinja2.utils.markupsafe.Markup(
'' % (ts, ts, formatts(ts))
)
@app.template_filter("archpair_format")
def archpair_format_filter(archpair):
return jinja2.utils.markupsafe.Markup(
"%s → %s" % tuple(map(jinja2.utils.markupsafe.escape, archpair))
)
def group_pairs(pairs):
result = {}
for v, w in pairs:
result.setdefault(v, set()).add(w)
return result
def render_archset(subset, all_archs):
if len(subset) == 1:
return next(iter(subset))
if subset == all_archs:
return "any"
return "{%s}" % ", ".join(
map(jinja2.utils.markupsafe.escape, sorted(subset))
)
@app.template_filter('archpairs_format')
@jinja2.pass_context
def archpairs_format_filter(context, some_archs):
architectures = group_pairs(context["architectures"])
fwdmap = {} # build architecture -> host architecture set representation
for buildarch, hostarchs in group_pairs(some_archs).items():
fwdmap[buildarch] = render_archset(hostarchs, architectures[buildarch])
allbuildarchs = set(architectures.keys())
# host architecture set representation -> build architecture set
flippedit = group_pairs((v, k) for (k, v) in fwdmap.items()).items()
maps = ("%s → %s" % (render_archset(buildarchs, allbuildarchs),
hostarchrep)
for hostarchrep, buildarchs in flippedit)
return jinja2.utils.markupsafe.Markup("; ".join(sorted(maps)))
def collect_depstate(conn, source):
version = None
depstate = None
query = sqlalchemy.text("""
SELECT version, buildarch, hostarch, satisfiable, reason
FROM depstate WHERE source = :source;""")
for row in conn.execute(query, source=source):
if version is None or version_compare(version, row.version) > 0:
version = row.version
depstate = {}
depstate[row.buildarch, row.hostarch] = \
None if row.satisfiable else row.reason
if version is None:
raise werkzeug.exceptions.NotFound()
depresult = {}
for archpair, reason in depstate.items():
depresult.setdefault(reason, set()).add(archpair)
return version, depresult
@app.route("/")
def show_index():
with db.engine.connect() as conn:
builds = list(conn.execute("""
SELECT source, version, buildarch, hostarch, starttime, filename,
ifnull((SELECT max(patched + 1) FROM bugs
WHERE affects = source),
0) AS buglvl
FROM builds
WHERE success = 0
ORDER BY starttime
DESC LIMIT 10;"""))
return flask.render_template_string(index_template, builds=builds)
@app.route("/src/