8 version_compare = apt_pkg.version_compare
11 import flask_sqlalchemy
16 app = flask.Flask("crossqa")
17 app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///db'
18 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
19 db = flask_sqlalchemy.SQLAlchemy(app)
21 src_template = """<!DOCTYPE html>
26 <title>{{ sourcepackage|e }} - Debian cross build</title>
27 <h1>{{ sourcepackage|e }}</h1>
28 <h3>Cross satisfiability</h3>
32 <th>architectures</th>
34 {%- set okarchs = depresult.pop(None, None) -%}
35 {%- for reason, archs in depresult.items()|sort -%}
37 <td>{{ reason|e }}</td>
38 <td>{{ archs|arch_format }}</td>
44 <td>{{ okarchs|arch_format }}</td>
50 {%- if show_bootstrapdn -%}
52 <a href="https://bootstrap.debian.net/cross_all/{{ sourcepackage|e }}.html">bootstrap.debian.net</a>
56 <a href="https://qa.debian.org/dose/debcheck/cross_unstable_main_amd64/">debcheck</a>
68 {%- for build in builds|sort(attribute='starttime', reverse=true) -%}
71 <span title="{{ build.starttime|sqltimestamp }}">
72 {{- build.starttime|sqltimestamp|formatts -}}
75 <td>{{ build.version|e }}</td>
76 <td>{{ build.architecture|e }}</td>
78 <a href="{{ url_for("show_log", filename=build.filename[:-3]) }}">
79 {{- "ok" if build.success else "failed" -}}
81 <a href="{{ url_for("show_log", filename=build.filename) }}">xz</a>
91 @app.template_filter("sqltimestamp")
92 def sqltimestamp_filter(s):
94 return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S.%f")
96 return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
98 @app.template_filter("formatts")
99 def formatts_filter(ts):
100 assert isinstance(ts, datetime.datetime)
101 dt = datetime.datetime.utcnow() - ts
102 if dt < datetime.timedelta(seconds=1):
104 if dt < datetime.timedelta(seconds=100):
105 return "%d s" % dt.seconds
106 if dt < datetime.timedelta(minutes=100):
107 return "%d m" % (dt.seconds // 60)
108 if dt < datetime.timedelta(days=1):
109 return "%d h" % (dt.seconds // (60 * 60))
110 return "%d d" % dt.days
112 @app.template_filter('arch_format')
113 @jinja2.contextfilter
114 def arch_format_filter(context, some_archs):
115 if context["architectures"] == some_archs:
117 return ", ".join(sorted(some_archs))
119 def collect_depstate(conn, source):
122 query = sqlalchemy.text("""
123 SELECT version, architecture, satisfiable, reason
124 FROM depstate WHERE source = :source;""")
125 for row in conn.execute(query, source=source):
126 if version is None or version_compare(version, row.version) > 0:
127 version = row.version
129 depstate[row.architecture] = None if row.satisfiable else row.reason
131 raise werkzeug.exceptions.NotFound()
133 for arch, reason in depstate.items():
134 depresult.setdefault(reason, set()).add(arch)
135 return version, depresult
137 @app.route("/src/<source>")
138 def show_source(source):
139 context = dict(sourcepackage=source)
140 with db.engine.connect() as conn:
141 query = sqlalchemy.text("SELECT architecture FROM depcheck;")
142 context["architectures"] = set(row[0] for row in conn.execute(query))
143 context["version"], context["depresult"] = collect_depstate(conn,
145 query = sqlalchemy.text("""
146 SELECT version, architecture, success, starttime, filename
147 FROM builds WHERE source = :source;""")
148 context["builds"] = list(conn.execute(query, source=source))
149 context["show_bootstrapdn"] = \
150 any(reason and not reason.startswith("skew ")
151 for reason in context["depresult"].keys())
152 return flask.render_template_string(src_template, **context)
154 @app.route("/build/<path:filename>")
155 def show_log(filename):
156 if filename.endswith(".xz"):
157 return flask.send_from_directory("logs", filename,
158 mimetype="application/octet-stream")
160 return flask.send_file(lzma.open(flask.safe_join("logs", filename), "rb"),
161 mimetype="text/plain")