X-Git-Url: https://git.subdivi.de/?p=~helmut%2Fcrossqa.git;a=blobdiff_plain;f=webapp.py;h=cc9de3715279aa6c87c5968ac24eb82ef5c2de68;hp=d37dbda8615c411a233dbfe552463c5a1db93178;hb=a52d12012b1befdf1f00d0fcd5101306c52271ff;hpb=09e5bba31b864da3e86b2747da9bc6cfebe90cbe diff --git a/webapp.py b/webapp.py index d37dbda..cc9de37 100644 --- a/webapp.py +++ b/webapp.py @@ -19,7 +19,83 @@ app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = flask_sqlalchemy.SQLAlchemy(app) -src_template = """ +index_template = """ + + + Debian cross build quality assurance + + +
+

Debian cross build quality assurance

+
+
+

Recently failed builds

+ + + + + + + + + + + + + + {%- for build in builds|sort(attribute='starttime', reverse=true) -%} + + + + + + + + + + {%- endfor -%} + +
sourceversionbuild architecturehost architecturestartedresult logbugs
+ + {{- 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 -%} +
+
+ + + +""" + +src_template = """ +{%- macro render_bug(bugobj) -%} + #{{ bugobj.bugnum }} + {%- if bugobj.patched %} + [+] + {%- endif -%}: + {% if bugobj.package != "src:" + bugobj.affects and + not bugobj.title.startswith(bugobj.affects + ":") -%} + {{- bugobj.package|e }}: + {% endif -%} + {{- bugobj.title|e -}} +{%- endmacro -%} + {{ sourcepackage|e }} - Debian cross build @@ -33,13 +109,13 @@ tr.dep.tempbad td:nth-child(1) { tr.dep.good td:nth-child(1) { background-color: #afa; } -tr.build.bad td:nth-child(4) { +tr.build.bad td:nth-child(5) { background-color: #faa; } -tr.build.tempbad td:nth-child(4) { +tr.build.tempbad td:nth-child(5) { background-color: #ffa; } -tr.build.good td:nth-child(4) { +tr.build.good td:nth-child(5) { background-color: #afa; } th { @@ -67,8 +143,30 @@ footer { + {%- if bugs.ftbfs -%} +
+

Reported FTBFS bugs

+ +
+ {%- endif -%}

Cross build dependency satisfiability

+ {%- if bugs.bdsat -%} +
Reported satisfiability problems
+ + {%- endif -%} @@ -81,38 +179,51 @@ footer { {%- for reason, archs in depresult.items()|sort -%} - + {%- endfor -%} {%- if okarchs -%} - + {%- endif -%}
{{ reason|e }}{{ archs|arch_format }}{{ archs|archpairs_format }}
ok{{ okarchs|arch_format }}{{ okarchs|archpairs_format }}
-
See also
- + {%- endif -%}

Cross builds

+ {%- if bugs.ftcbfs -%} +
Reported cross build failures
+ + {%- endif -%} {%- if builds -%} - + + @@ -123,7 +234,8 @@ footer { {{- build.starttime|sqltimestamp|formatts -}} - + +
started versionarchitecturebuild architecturehost architecture result log
{{ build.version|e }}{{ build.architecture|e }}{{ build.buildarch|e }}{{ build.hostarch|e }} {{- "ok" if build.success else "failed" -}} @@ -141,10 +253,12 @@ footer { {{ sourcepackage|e }} for - + + {%- for buildarch, hostarch in architectures|sort -%} + {%- endfor -%} @@ -200,46 +314,99 @@ 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: +@app.template_filter("archpair_format") +def archpair_format_filter(archpair): + return jinja2.Markup("%s → %s" % tuple(map(jinja2.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 ", ".join(sorted(some_archs)) + return "{%s}" % ", ".join(map(jinja2.escape, sorted(subset))) + +@app.template_filter('archpairs_format') +@jinja2.contextfilter +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.Markup("; ".join(sorted(maps))) def collect_depstate(conn, source): version = None depstate = None query = sqlalchemy.text(""" - SELECT version, architecture, satisfiable, reason + SELECT version, 'amd64' AS buildarch, architecture AS 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.architecture] = None if row.satisfiable else row.reason + depstate[row.buildarch, row.hostarch] = \ + 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) + 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, 'amd64' AS buildarch, + architecture AS 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/") 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)) + query = sqlalchemy.text("""SELECT 'amd64' AS buildarch, + architecture AS hostarch + FROM depcheck;""") + context["architectures"] = set(map(tuple, conn.execute(query))) context["version"], context["depresult"] = collect_depstate(conn, source) query = sqlalchemy.text(""" - SELECT version, architecture, success, starttime, filename + SELECT version, 'amd64' AS buildarch, architecture AS hostarch, + success, starttime, filename FROM builds WHERE source = :source;""") context["builds"] = list(conn.execute(query, source=source)) + query = sqlalchemy.text(""" + SELECT bugnum, kind, title, package, patched, affects + FROM bugs WHERE affects = :affects;""") + context["bugs"] = {} + for bug in conn.execute(query, affects=source): + context["bugs"].setdefault(bug.kind, []).append(bug) context["show_bootstrapdn"] = \ any(reason and not reason.startswith("skew ") for reason in context["depresult"].keys()) + context["show_debcheck"] = \ + any(context["depresult"].keys()) return flask.render_template_string(src_template, **context) @app.route("/build/") @@ -258,21 +425,23 @@ def show_log(filename): @app.route("/schedule", methods=["POST"]) def request_schedule(): source = flask.request.form["source"] - architecture = flask.request.form["architecture"] + buildarch, hostarch = flask.request.form["archpair"].split("_") + if buildarch not in ("any", "amd64"): + raise werkzeug.exceptions.BadRequest() with db.engine.connect() as conn: query = sqlalchemy.text(""" SELECT 1 FROM depstate WHERE source = :source;""") if not conn.execute(query, source=source).first(): raise werkzeug.exceptions.BadRequest() - if architecture == "any": - architecture = None + if hostarch == "any": + hostarch = None else: query = sqlalchemy.text(""" - SELECT 1 FROM depcheck WHERE architecture = :architecture;""") - if not conn.execute(query, architecture=architecture).first(): + SELECT 1 FROM depcheck WHERE architecture = :hostarch;""") + if not conn.execute(query, hostarch=hostarch).first(): raise werkzeug.exceptions.BadRequest() query = sqlalchemy.text(""" INSERT INTO buildrequests (source, architecture, requesttime) - VALUES (:source, :architecture, datetime('now'));""") - conn.execute(query, source=source, architecture=architecture) + VALUES (:source, :hostarch, datetime('now'));""") + conn.execute(query, source=source, hostarch=hostarch) return flask.render_template_string(schedule_template)