#!/usr/bin/python3
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'
db = flask_sqlalchemy.SQLAlchemy(app)
src_template = """
{{ sourcepackage|e }} - Debian cross build
{{ sourcepackage|e }}
Cross satisfiability
state |
architectures |
{%- set okarchs = depresult.pop(None, None) -%}
{%- for reason, archs in depresult.items()|sort -%}
{{ reason|e }} |
{{ archs|arch_format }} |
{%- endfor -%}
{%- if okarchs -%}
ok |
{{ okarchs|arch_format }} |
{%- endif -%}
See also
{%- if builds -%}
Cross builds
started |
version |
architecture |
result |
{%- for build in builds|sort(attribute='starttime', reverse=true) -%}
{{- build.starttime|sqltimestamp|formatts -}}
|
{{ build.version|e }} |
{{ build.architecture|e }} |
{{- "ok" if build.success else "failed" -}}
xz
|
{%- endfor -%}
{%- endif -%}
"""
@app.template_filter("sqltimestamp")
def sqltimestamp_filter(s):
return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S.%f")
@app.template_filter("formatts")
def formatts_filter(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('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("/src/")
def show_source(source):
context = dict(sourcepackage=source)
with db.engine.connect() as conn:
query = sqlalchemy.text("SELECT architecture FROM depcheck;")
context["architectures"] = set(row[0] for row in conn.execute(query))
context["version"], context["depresult"] = collect_depstate(conn,
source)
query = sqlalchemy.text("""
SELECT version, architecture, success, starttime, filename
FROM builds WHERE source = :source;""")
context["builds"] = list(conn.execute(query, source=source))
return flask.render_template_string(src_template, **context)
@app.route("/build/")
def show_log(filename):
if filename.endswith(".xz"):
return flask.send_from_directory("logs", filename,
mimetype="application/octet-stream")
filename += ".xz"
return flask.send_file(lzma.open(flask.safe_join("logs", filename), "rb"),
mimetype="text/plain")