#!/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 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 | architecture | started | result log | bugs |
---|---|---|---|---|---|
{{- build.source|e -}} | {{ build.version|e }} | {{ build.architecture|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|arch_format }} |
ok | {{ okarchs|arch_format }} |
started | version | architecture | result log |
---|---|---|---|
{{- build.starttime|sqltimestamp|formatts -}} | {{ build.version|e }} | {{ build.architecture|e }} | {{- "ok" if build.success else "failed" -}} xz |
No build performed yet.
{%- endif -%}Scheduled a build of {{ request.form["source"]|e }} {%- if request.form["architecture"] != "any" %} for {{ request.form["architecture"]|e -}} {%- 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.Markup('' %
(ts, ts, formatts(ts)))
@app.template_filter('arch_format')
@jinja2.contextfilter
def arch_format_filter(context, some_archs):
if context["architectures"] == some_archs:
return "any"
return ", ".join(sorted(some_archs))
def collect_depstate(conn, source):
version = None
depstate = None
query = sqlalchemy.text("""
SELECT version, architecture, 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.architecture] = None if row.satisfiable else row.reason
if version is None:
raise werkzeug.exceptions.NotFound()
depresult = {}
for arch, reason in depstate.items():
depresult.setdefault(reason, set()).add(arch)
return version, depresult
@app.route("/")
def show_index():
with db.engine.connect() as conn:
builds = list(conn.execute("""
SELECT source, version, architecture, 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/