new file mode 100644
--- /dev/null
+++ b/warning-ui/build.html
@@ -0,0 +1,54 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="common.inc" />
+
+ <head>
+ <title>Warning for build ${id}</title>
+ </head>
+ <body class="build">
+ <h1>Build ${id}</h1>
+
+ <div class="h2block">
+ <h2>Revision:</h2>
+ <a href="http://hg.mozilla.org/mozilla-central/pushloghtml?changeset=${rev}">${rev}</a>
+ </div>
+ <div class="h2block" py:if="previd is not None and prevrev != rev">
+ <h2>Changes since <a href="/build?id=${previd}">last build</a>:</h2>
+ <a href="http://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=${prevrev}&tochange=${rev}">Log</a>
+ </div>
+ <div class="h2block">
+ <h2>Unique warnings:</h2> ${unique}
+ </div>
+ <div class="h2block">
+ <h2>Total warnings:</h2> ${total}
+ </div>
+
+ <py:if test="previd is not None">
+ <div py:if="len(newwarnings)">
+ <h3>New:</h3>
+
+ <ul>
+ <li py:for="signature, file, lineno, msg in newwarnings">
+ <a href="${genlink('warning', signature=signature)}">${file}:${lineno} - ${msg}</a>
+ </li>
+ </ul>
+ </div>
+
+ <div py:if="len(fixedwarnings)">
+ <h3>Fixed:</h3>
+
+ <ul>
+ <li py:for="signature, file, lineno, msg in fixedwarnings">
+ <a href="${genlink('warning', signature=signature)}">${file}:${lineno} - ${msg}</a>
+ </li>
+ </ul>
+ </div>
+ </py:if>
+
+ <h2>Search warnings:</h2>
+
+ ${searchform('', '', 'unused variable%')}
+
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/warning-ui/common.inc
@@ -0,0 +1,28 @@
+<!-- -*- Mode: XML -*- -->
+<?python from urllib import quote, urlencode ?>
+<div xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ py:strip="True">
+ <py:def function="searchform(user, path, msg)">
+ <form action="/search">
+ User LIKE: <input type="text" name="user" size="25" value="${user}"/><br />
+ Directory LIKE: <input type="text" name="path" size="25" value="${path}"/><br />
+ Warning message LIKE: <input type="text" name="msg" size="30" value="${msg}"/><br />
+ <input type="hidden" name="id" value="${id}" />
+ <input type="submit" value="Search" />
+ </form>
+ </py:def>
+
+ <py:def function="genlink(path, **params)">/${quote(path)}<py:if test="len(params)">?${urlencode(params)}</py:if></py:def>
+
+ <head py:match="head">
+ ${select("*|text()")}
+ <link type="text/css" rel="stylesheet" href="/static/warnings.css" />
+ </head>
+ <body py:match="body">
+ <div class="header">
+ <a href="/">Compiler Warnings Tracker: mozilla-central</a>
+ </div>
+ ${select("*|text()")}
+ </body>
+</div>
new file mode 100644
--- /dev/null
+++ b/warning-ui/index.html
@@ -0,0 +1,107 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="common.inc" />
+
+ <head>
+ <title>Warnings</title>
+ </head>
+ <body class="index">
+ <svg xmlns="http://www.w3.org/2000/svg" id="graph" preserveAspectRatio="none"
+ width="500" height="200" style="border: 1px solid black;" />
+
+ <ul>
+ <li py:for="buildnumber, rev, unique in builds"><a href="build?id=${buildnumber}">${rev}</a></li>
+ </ul>
+
+ <script type="application/x-javascript">
+ var kWidth, kHeight, min, max, g, i, x, y, s, points, txt, a, hit, gline;
+
+ kWidth = 500;
+ kHeight = 200;
+
+ gData = [
+ <py:for each="buildnumber, rev, unique in builds">
+ [${buildnumber}, "${rev}", ${unique}],
+ </py:for>
+ ];
+
+ // Oh, my kingdom for a .reduce, but I want this to work in Safari
+ min = null;
+ max = null;
+ for (i = 0; i < gData.length; ++i) {
+ if (min == null || gData[i][2] < min)
+ min = gData[i][2];
+ if (max == null || gData[i][2] > max)
+ max = gData[i][2];
+ }
+
+ g = document.getElementById("graph");
+
+ for (i = min - min % 25; i < max; i += 25) {
+ y = kHeight - ((i - min) / (max - min) * (kHeight - 10)) - 5;
+ s = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+ s.setAttribute('class', 'scale');
+ s.x1.baseVal.value = 40;
+ s.x2.baseVal.value = 490;
+ s.y1.baseVal.value = y;
+ s.y2.baseVal.value = y;
+ g.appendChild(s);
+
+ s = document.createElementNS('http://www.w3.org/2000/svg', 'text');
+ s.setAttribute('class', 'scaleText');
+ s.textContent = i;
+ s.setAttribute('x', 38);
+ s.setAttribute('y', y);
+ g.appendChild(s);
+ }
+
+ points = [];
+
+ for (i = gData.length - 1; i >= 0; --i) {
+ id = gData[i][0];
+ rev = gData[i][1];
+ t = gData[i][2];
+
+ a = document.createElementNS('http://www.w3.org/2000/svg', 'a');
+ a.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/build?id=' + id);
+ g.appendChild(a);
+
+ txt = document.createElementNS('http://www.w3.org/2000/svg', 'text');
+ txt.setAttribute('class', 'overlay');
+ txt.textContent = "Warnings: " + t;
+ txt.setAttribute('x', kWidth / 2);
+ txt.setAttribute('y', 20);
+ a.appendChild(txt);
+
+ txt = document.createElementNS('http://www.w3.org/2000/svg', 'text');
+ txt.setAttribute('class', 'overlay');
+ txt.textContent = 'Rev: ' + rev;
+ txt.setAttribute('x', kWidth / 2);
+ txt.setAttribute('y', 40);
+ a.appendChild(txt);
+
+ x = (gData.length - i) / gData.length * (kWidth - 50) + 40;
+ y = kHeight - ((t - min) / (max - min) * (kHeight - 10)) - 5;
+
+ points.push(x, y);
+
+ if (isNaN(y))
+ throw new Error("isNaN: " + [i, t, x, y, max, min]);
+
+ hit = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
+ hit.setAttribute('class', 'mark');
+ hit.cx.baseVal.value = x;
+ hit.cy.baseVal.value = y;
+ hit.r.baseVal.value = 3;
+ a.appendChild(hit);
+ }
+
+ gline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
+ gline.setAttribute('id', 'gline');
+ gline.setAttribute('points', points.join(' '));
+
+ g.insertBefore(gline, g.childNodes[0]);
+ </script>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/warning-ui/search.html
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="common.inc" />
+
+ <head>
+ <title>Warning for build ${id}</title>
+ </head>
+ <body>
+ <h1>Warnings in <a href="/build?id=${id}">build ${id}</a></h1>
+
+ <ul>
+ <li py:if="user != ''">User LIKE '${user}'</li>
+ <li py:if="path != ''">Path LIKE '${path}'</li>
+ </ul>
+
+ <h2>Results</h2>
+
+ <p py:if="len(results) == 0">No warnings found!</p>
+
+ <ul py:if="len(results)">
+ <li py:for="signature, file, lineno, msg in results">
+ <a href="${genlink('warning', signature=signature)}">${file}:${lineno} - ${msg}</a>
+ </li>
+ </ul>
+
+ <h2>Search again:</h2>
+ ${searchform(user, path, msg)}
+
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/warning-ui/static/warnings.css
@@ -0,0 +1,63 @@
+body {
+ font-family: sans-serif;
+}
+
+h1 {
+ font-size: 180%;
+ font-weight: bold;
+}
+
+h2 {
+ font-size: 150%;
+ font-weight: bold;
+}
+
+.h2block {
+ font-size: 150%;
+}
+
+.h2block h2 {
+ display: inline;
+ font-size: 100%;
+}
+
+.mark {
+ fill: rgb(200, 120, 12);
+ fill-opacity: 0.5;
+}
+#gline {
+ stroke: rgb(250, 160, 20);
+ stroke-width: 2px;
+ fill: none;
+}
+.overlay {
+ font-family: sans-serif;
+ font-size: 18px;
+ font-weight: bold;
+ fill: black;
+ fill-opacity: 0.7;
+ display: none;
+ text-anchor: middle;
+}
+a:hover .overlay {
+ display: inline;
+}
+.scale {
+ stroke: #AAA;
+ stroke-width: 1px;
+}
+.scaleText {
+ font-family: sans-serif;
+ font-size: 12px;
+ fill: #666;
+ text-anchor: end;
+ dominant-baseline: middle;
+}
+
+.header {
+ background-color: rgb(250, 160, 20);
+ color: black;
+ font-size: 150%;
+ padding: 5px;
+ margin-bottom: 1em;
+}
new file mode 100644
--- /dev/null
+++ b/warning-ui/ui.py
@@ -0,0 +1,222 @@
+import cherrypy, os, sys, re, sqlite3
+from genshi.template import TemplateLoader
+
+class Root(object):
+ def __init__(self):
+ self.loader = TemplateLoader(cherrypy.config['tools.staticdir.root'],
+ auto_reload=True)
+
+ def dbcursor(self):
+ return sqlite3.connect(cherrypy.request.app.config['database']['path']).cursor()
+
+ def render(self, tmpl, **kwargs):
+ cherrypy.response.headers['Content-Type'] = 'application/xhtml+xml'
+ tmpl = self.loader.load(tmpl)
+ return tmpl.generate(**kwargs).render('xhtml', doctype='xhtml')
+
+ @cherrypy.expose
+ def index(self):
+ try:
+ cur = self.dbcursor()
+ cur.execute('''SELECT builds.buildnumber, builds.rev, uwarnings.ucount
+ FROM builds LEFT OUTER JOIN
+ (SELECT buildnumber, count(*) AS ucount
+ FROM warnings
+ GROUP BY buildnumber) AS uwarnings
+ ON builds.buildnumber = uwarnings.buildnumber
+ ORDER BY builds.buildnumber DESC''')
+ return self.render('index.html', builds=cur.fetchall())
+ finally:
+ cur.close()
+
+ @cherrypy.expose
+ def search(self, id, user="", path="", msg=""):
+ id = int(id)
+ user = user.strip()
+ path = path.strip()
+ msg = msg.strip()
+
+ bindp = [id]
+
+ subclauses = []
+ if user != '':
+ subclauses.append('AND wl2.blamewho LIKE ?')
+ bindp.append(user)
+
+ if path != '':
+ subclauses.append('AND wl2.file LIKE ?')
+ bindp.append(path)
+
+ if msg != '':
+ subclauses.append('AND wl2.msg LIKE ?')
+ bindp.append(msg)
+
+ if len(subclauses):
+ clause = '''AND EXISTS (SELECT *
+ FROM wlines AS wl2
+ WHERE
+ wl2.buildnumber = warnings.buildnumber AND
+ wl2.signature = warnings.signature %s)''' % ' '.join(subclauses)
+ else:
+ clause = ''
+
+ try:
+ cur = self.dbcursor()
+ q = '''SELECT warnings.signature, file, lineno, msg
+ FROM warnings, wlines
+ WHERE
+ warnings.buildnumber = wlines.buildnumber AND
+ warnings.signature = wlines.signature AND
+ wlines.wline = 0 AND
+ warnings.buildnumber = ? %s''' % (clause, )
+ print "Query: %s\nBind: %s" % (q, bindp)
+
+ cur.execute(q, bindp)
+
+ return self.render('search.html',
+ id=id,
+ user=user,
+ path=path,
+ msg=msg,
+ results=cur.fetchall())
+ finally:
+ cur.close()
+
+ @cherrypy.expose
+ def build(self, id):
+ id = int(id)
+ try:
+ cur = self.dbcursor()
+ cur.execute('''SELECT rev
+ FROM builds
+ WHERE buildnumber = ?''', (id,))
+ rev, = cur.fetchone()
+
+ cur.execute('''SELECT count(*) as ucount, sum(count) AS tcount
+ FROM warnings
+ WHERE buildnumber = ?''', (id,))
+ unique, total = cur.fetchone()
+
+ cur.execute('''SELECT buildnumber, rev
+ FROM builds
+ WHERE
+ buildnumber < ?
+ ORDER BY buildnumber DESC
+ LIMIT 1''', (id,))
+ prev = cur.fetchone()
+ if prev is None:
+ previd = None
+ prevrev = None
+ newwarnings = None
+ fixedwarnings = None
+ else:
+ previd, prevrev = prev
+
+ cur.execute('''SELECT warnings.signature, file, lineno, msg
+ FROM warnings, wlines
+ WHERE
+ warnings.buildnumber = wlines.buildnumber AND
+ warnings.signature = wlines.signature AND
+ wlines.wline = 0 AND
+ warnings.buildnumber = ? AND
+ NOT EXISTS (SELECT *
+ FROM warnings AS oldwarnings
+ WHERE oldwarnings.signature = warnings.signature AND
+ oldwarnings.buildnumber = ?)''', (id, previd))
+ newwarnings = cur.fetchall()
+
+ cur.execute('''SELECT warnings.signature, file, lineno, msg
+ FROM warnings, wlines
+ WHERE
+ warnings.buildnumber = wlines.buildnumber AND
+ warnings.signature = wlines.signature AND
+ wlines.wline = 0 AND
+ warnings.buildnumber = ? AND
+ NOT EXISTS (SELECT *
+ FROM warnings AS oldwarnings
+ WHERE oldwarnings.signature = warnings.signature AND
+ oldwarnings.buildnumber = ?)''', (previd, id))
+ fixedwarnings = cur.fetchall()
+
+ return self.render('build.html',
+ rev=rev,
+ id=id,
+ unique=unique,
+ total=total,
+ previd=previd,
+ prevrev=prevrev,
+ newwarnings=newwarnings,
+ fixedwarnings=fixedwarnings)
+ finally:
+ cur.close()
+
+ @cherrypy.expose
+ def warning(self, signature):
+ try:
+ cur = self.dbcursor()
+ cur.execute('''SELECT buildnumber, rev
+ FROM builds
+ WHERE
+ EXISTS (SELECT *
+ FROM warnings
+ WHERE
+ warnings.buildnumber = builds.buildnumber AND
+ warnings.signature = ?)
+ ORDER BY buildnumber ASC LIMIT 1''', (signature,))
+
+ firstid, firstrev = cur.fetchone()
+
+ cur.execute('''SELECT buildnumber, rev
+ FROM builds
+ WHERE
+ NOT EXISTS (SELECT *
+ FROM warnings
+ WHERE
+ warnings.buildnumber = builds.buildnumber AND
+ warnings.signature = ?) AND
+ buildnumber > ?
+ ORDER BY buildnumber ASC LIMIT 1''', (signature, firstid))
+
+ lastid, lastrev = cur.fetchone() or (None, None)
+
+ cur.execute('''SELECT file, lineno, msg, blametype, blamefile, blamerev, blameline, blamewho
+ FROM wlines
+ WHERE
+ buildnumber = ? AND
+ signature = ?
+ ORDER BY wline ASC''', (firstid, signature))
+
+ wlines = cur.fetchall()
+
+ return self.render('warning.html',
+ firstid=firstid, firstrev=firstrev,
+ lastid=lastid, lastrev=lastrev,
+ wlines=wlines)
+ finally:
+ cur.close()
+
+def main(configfiles):
+ for cf in configfiles:
+ cherrypy.config.update(cf)
+ if 'tools.staticdir.root' not in cherrypy.config:
+ thisdir = os.path.abspath(os.path.dirname(__file__))
+ cherrypy.config.update({'tools.staticdir.root': thisdir})
+
+ app = cherrypy.tree.mount(Root(), '/', {
+ 'global': {
+ 'tools.encode.on': True,
+ 'tools.encode.encoding': 'utf-8',
+ },
+ '/static': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': 'static',
+ },
+ })
+ for cf in configfiles:
+ app.merge(cf)
+
+ cherrypy.server.quickstart()
+ cherrypy.engine.start()
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
new file mode 100644
--- /dev/null
+++ b/warning-ui/warning.html
@@ -0,0 +1,43 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="common.inc" />
+
+ <head>
+ <title>Warning: ${wlines[0][0]}:${wlines[0][1]}: ${wlines[0][2]}</title>
+ <link type="text/css" rel="stylesheet" href="/static/warnings.css" />
+ </head>
+ <body class="warning">
+ <h1>Warning: ${wlines[0][0]}:${wlines[0][1]}: ${wlines[0][2]}</h1>
+
+ <div class="h2block">
+ <h2>First appeared:</h2>
+ <a href="${genlink('build', id=firstid)}">${firstrev}</a>
+ </div>
+ <div class="h2block" py:if="lastid is not None">
+ <h2>Fixed in:</h2>
+ <a href="${genlink('build', id=lastid)}">${lastrev}</a>
+ </div>
+
+ <dl>
+ <py:for each="file, lineno, msg, blametype, blamefile, blamerev, blameline, blamewho in wlines">
+ <dt>
+ <a py:strip="blametype is None"
+ href="http://hg.mozilla.org/mozilla-central/file/${firstrev}/${file}#l${lineno}">
+ ${file}:${lineno}</a>:
+ ${msg}
+ </dt>
+ <dd py:if="blametype is not None">
+ <py:choose test="blametype">
+ <py:when test="'hg'">
+ Blamed on ${blamewho}: <a href="http://hg.mozilla.org/mozilla-central/annotate/${blamerev}/${blamefile}/#l${blameline}">${blamefile}:${blameline}</a>, revision <a href="http://hg.mozilla.org/mozilla-central/rev/${blamerev}">${blamerev}</a>
+ </py:when>
+ <py:when test="'cvs'">
+ Blamed in ${blamewho}: <a href="http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/${blamefile}&rev=HG_REPO_INITIAL_IMPORT&mark=${blameline}#${blameline}">${blamefile}:${blameline}, revision ${blamerev}</a>
+ </py:when>
+ </py:choose>
+ </dd>
+ </py:for>
+ </dl>
+ </body>
+</html>