On Wed, Feb 15, 2017 at 04:53:14PM +0100, Mattia Rizzolo wrote:
> (also re-attached the attachment).
... here.
--
regards,
Mattia Rizzolo
GPG Key: 66AE 2B4A FCCF 3F52 DA18 4D18 4B04 3FCD B944 4540 .''`.
more about me: https://mapreri.org : :' :
Launchpad user: https://launchpad.net/~mapreri `. `'`
Debian QA page: https://qa.debian.org/developer.php?login=mattia `-
From 9cf994e38331f895fae15772225925e25d304391 Mon Sep 17 00:00:00 2001
From: Erwan Prioul <erwan@linux.vnet.ibm.com>
Date: Wed, 15 Feb 2017 15:43:32 +0100
Subject: [PATCH] add first version of ftbfs.cgi
script that displays the FTBFS packages on a given architecture, with related bugs if any.
---
web/cgi-bin/ftbfs.cgi | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 247 insertions(+)
create mode 100755 web/cgi-bin/ftbfs.cgi
diff --git a/web/cgi-bin/ftbfs.cgi b/web/cgi-bin/ftbfs.cgi
new file mode 100755
index 0000000..b340e59
--- /dev/null
+++ b/web/cgi-bin/ftbfs.cgi
@@ -0,0 +1,247 @@
+#!/usr/bin/env python
+# Display FTBFS packages on given arch
+# Copyright (C) 2017, Erwan Prioul <erwan@linux.vnet.ibm.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import psycopg2
+import cgi
+import cgitb
+
+DATABASE = {
+ 'database': 'udd',
+ 'port': 5452,
+ 'host': 'localhost',
+ 'user': 'guest'
+}
+
+class AttrDict(dict):
+ def __init__(self, **kwargs):
+ for key, value in kwargs.iteritems():
+ self[key] = value
+
+ def __getattr__(self, name):
+ try:
+ return self[name]
+ except KeyError, e:
+ raise AttributeError(e)
+
+def query(query, cols, *parameters):
+ try:
+ conn = psycopg2.connect(**DATABASE)
+ cursor = conn.cursor()
+ cursor.execute(query, parameters)
+ except:
+ exit(1)
+ for row in cursor.fetchall():
+ yield AttrDict(**dict(zip(cols, row)))
+ cursor.close()
+ conn.close()
+
+def pretty_time_delta(when):
+ seconds = (datetime.datetime.now() - when).total_seconds()
+ days, seconds = divmod(seconds, 86400)
+ hours, seconds = divmod(seconds, 3600)
+ minutes, seconds = divmod(seconds, 60)
+ if days > 0:
+ return '%dd' % (days)
+ elif hours > 0:
+ return '%dh' % (hours)
+ elif minutes > 0:
+ return '%dm' % (minutes)
+ else:
+ return '%ds' % (seconds)
+
+def packageLine(packages, package, pending = False):
+ u = package.replace('+', '%2B')
+ bugs = " "
+ what = 'nopatch'
+ if pending:
+ what = 'patch'
+ if len(packages[package][what]) > 0:
+ bugs = ''.join('<a href="http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%d">#%d</a> - %s<br>' % (x[0], x[0], x[1]) for x in sorted(packages[package][what]))
+ return \
+ '<tr><td class="package">%s_%s</td>' % (package, packages[package]['version']) + \
+ '<td class="links">' + \
+ '<a href="https://buildd.debian.org/status/package.php?p=%s&suite=sid" title="Debian buildd status">%s</a> ' % (u, pretty_time_delta(packages[package]['state_change'])) + \
+ '[<a href="http://bugs.debian.org/cgi-bin/pkgreport.cgi?src=%s" title="Debian bugs in source package">B</a>]' % (u) + \
+ '[<a href="http://buildd.debian.org/status/logs.php?pkg=%s" title="Debian build logs">L</a>]' % (u) + \
+ '[<a href="https://tracker.debian.org/pkg/%s" title="Debian Package Tracker">T</a>]</td>' % (u) + \
+ '<td>%s</td></tr>' % bugs
+
+def generatePending(packages, nb, arch):
+ return \
+ '<div class="claim" style="margin-top:20px;">%d FTBFS packages on <span class="arch">%s</span> with a patch pending</div><table>' % (nb, arch) + \
+ ''.join(packageLine(packages, x, True) for x in sorted(packages.keys()) if len(packages[x]['patch']) > 0) + \
+ '</table>'
+
+def generateFailing(packages, failing, nb, arch):
+ html = '<div class="claim">%d FTBFS packages on <span class="arch">%s</span> without patch</div>' % (nb, arch)
+ for c in sorted(failing.keys()):
+ if c > 2:
+ txt = "and %d other architectures" % (c - 1)
+ elif c == 2:
+ txt = "and 1 other architecture"
+ else:
+ txt = ""
+ html += \
+ '<div class="banner">%d packages are failing on <span class="arch">%s</span> %s</div><table>' % (len(failing[c]), arch, txt) + \
+ ''.join(packageLine(packages, x) for x in sorted(failing[c])) + \
+ '</table>'
+ return html
+
+def getPackages(arch):
+ # Get all FTBFS packages on given architecture
+ q = """
+select distinct source, version, state_change
+from wannabuild
+where architecture = '%s' and
+ distribution = 'sid' and
+ state in ('Failed', 'Build-Attempted') and
+ vancouvered = 'f'
+;
+""" % arch
+ packages = {x['s']: { 'version': x['version'], 'state_change': x['state_change'], 'patch': [], 'nopatch': [] } for x in query(q, ('s', 'version', 'state_change'))}
+ # Get opened bugs with the given architecture as tag
+ q = """
+select distinct source, id, title
+from bugs inner join bugs_usertags using (id)
+where bugs_usertags.tag = '%s' and status != 'done' and source in (%s);
+""" % (arch, ", ".join("'"+x+"'" for x in packages.keys()))
+ bugs = {x['id']: [x['source'], x['title']] for x in query(q, ('source', 'id', 'title'))}
+ # Which bugs have a patch?
+ q = """
+select id
+from bugs_tags
+where tag = 'patch' and id in (%s);
+""" % ", ".join("'"+str(x)+"'" for x in bugs.keys())
+ patched = [x['i'] for x in query(q, ('id'))]
+ for b in bugs.keys():
+ where = 'nopatch'
+ if b in patched:
+ where = 'patch'
+ packages[bugs[b][0]][where].append([b, bugs[b][1]])
+ l = [x for x in packages.keys() if len(packages[x]['patch']) <= 0]
+ countFailing = len(l)
+ countPending = len(packages.keys()) - countFailing
+ # For a FTBFS package, get the number of architectures it also fails on
+ q = """
+select source, count(*) as c
+from wannabuild
+where distribution = 'sid' and state in ('Failed', 'Build-Attempted') and vancouvered = 'f' and source in (%s)
+group by source;
+""" % ", ".join("'"+x+"'" for x in l)
+ failing = {}
+ for r in query(q, ('source', 'c')):
+ if not failing.has_key(r['c']):
+ failing[r['c']] = []
+ failing[r['c']].append(r['source'])
+ return """
+<!doctype html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>FTBFS packages on %s</title>
+<style>
+table {
+ width: 100%%;
+ border-collapse: collapse;
+ table-layout: fixed;
+}
+tr:nth-child(2n+1) {
+ background-color: #fbfbfb;
+}
+td {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis !important;
+ vertical-align: top;
+ padding-left: 4px;
+}
+.package {
+ width: 375px;
+}
+.links {
+ width: 150px;
+ text-align: right;
+}
+.claim {
+ background-color: #dddddd;
+ padding: 10px;
+ padding-left: 4px !important;
+ font-size: 110%%;
+}
+.banner {
+ background-color: #eeeeee;
+ padding: 4px;
+}
+.arch {
+ font-style: oblique;
+}
+</style>
+</head>
+<body>
+""" % arch + generateFailing(packages, failing, countFailing, arch) + generatePending(packages, countPending, arch) + '</body></html>'
+
+def getArchs():
+ q = """
+select distinct(architecture) as a
+from wannabuild
+where distribution = 'sid' and
+ state in ('Failed', 'Build-Attempted') and
+ vancouvered = 'f'
+order by a;
+"""
+ return [x['a'] for x in query(q, ('a'))]
+
+def getForm(archs):
+ return """
+<!doctype html>
+<html>
+<head>
+<title>FTBFS packages on given architecture</title>
+<style>
+.claim {
+ background-color: #dddddd;
+ padding: 10px;
+ padding-left: 4px !important;
+ font-size: 110%%;
+ margin-bottom: 5px;
+}
+</style>
+</head>
+<body>
+<div class="claim">FTBFS packages</div>
+<form action="?" accept-charset="UTF-8">
+Architecture: <select name="arch" id="arch"><option value=""></option>%s</select> <input type="submit" value="Show packages">
+</form>
+</body>
+</html>
+""" % ''.join('<option value="%s">%s</option>' % (x, x) for x in archs)
+
+def main():
+ print 'Content-Type: text/html\n'
+ archs = getArchs()
+ cgitb.enable()
+ form = cgi.FieldStorage()
+ if 'arch' in form:
+ arch = form.getfirst('arch', '')
+ if arch in archs:
+ print getPackages(arch)
+ else:
+ print getForm(archs)
+ else:
+ print getForm(archs)
+
+if __name__ == '__main__':
+ main()
--
2.7.4
Attachment:
signature.asc
Description: PGP signature