]> Cypherpunks repositories - gostls13.git/commitdiff
dashboard: show build state and package comments on dashboard
authorAndrew Gerrand <adg@golang.org>
Sat, 2 Jul 2011 04:02:42 +0000 (14:02 +1000)
committerAndrew Gerrand <adg@golang.org>
Sat, 2 Jul 2011 04:02:42 +0000 (14:02 +1000)
This permits full URLs to be shown on the dashboard,
not just the repository roots.

This has been tested.

R=rsc, mattn.jp
CC=golang-dev
https://golang.org/cl/4627081

misc/dashboard/builder/http.go
misc/dashboard/builder/main.go
misc/dashboard/builder/package.go
misc/dashboard/godashboard/auth.py [new file with mode: 0644]
misc/dashboard/godashboard/gobuild.py
misc/dashboard/godashboard/index.yaml
misc/dashboard/godashboard/package.html
misc/dashboard/godashboard/package.py
misc/dashboard/godashboard/static/style.css

index 5e1da0c878d45a8293ab5ddba59f6f86904120af..98400c51a81972cbef8b2c512b1dd1bbd527bb8f 100644 (file)
@@ -112,16 +112,15 @@ func packages() (pkgs []string, err os.Error) {
        return
 }
 
-// updatePackage sends package build results and info to the dashboard
-func (b *Builder) updatePackage(pkg string, state bool, buildLog, info string, hash string) os.Error {
+// updatePackage sends package build results and info dashboard
+func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) os.Error {
        return dash("POST", "package", nil, param{
                "builder": b.name,
                "key":     b.key,
                "path":    pkg,
-               "state":   strconv.Btoa(state),
+               "ok":      strconv.Btoa(ok),
                "log":     buildLog,
                "info":    info,
-               "go_rev":  hash[:12],
        })
 }
 
index bee663d6cff24a234b494102cc70fb535252c7e3..989965bc41b73ef36ea4801f7e23dd906b40d423 100644 (file)
@@ -60,8 +60,9 @@ var (
 )
 
 var (
-       goroot        string
-       releaseRegexp = regexp.MustCompile(`^(release|weekly)\.[0-9\-.]+`)
+       goroot      string
+       binaryTagRe = regexp.MustCompile(`^(release\.r|weekly\.)[0-9\-.]+`)
+       releaseRe   = regexp.MustCompile(`^release\.r[0-9\-.]+`)
 )
 
 func main() {
@@ -200,7 +201,7 @@ func (b *Builder) buildExternal() {
                        log.Println("hg pull failed:", err)
                        continue
                }
-               hash, tag, err := firstTag(releaseRegexp)
+               hash, tag, err := firstTag(releaseRe)
                if err != nil {
                        log.Println(err)
                        continue
@@ -321,7 +322,7 @@ func (b *Builder) buildHash(hash string) (err os.Error) {
        }
 
        // if this is a release, create tgz and upload to google code
-       releaseHash, release, err := firstTag(releaseRegexp)
+       releaseHash, release, err := firstTag(binaryTagRe)
        if hash == releaseHash {
                // clean out build state
                err = run(b.envv(), srcDir, "./clean.bash", "--nopkg")
@@ -591,7 +592,7 @@ func fullHash(rev string) (hash string, err os.Error) {
        if s == "" {
                return "", fmt.Errorf("cannot find revision")
        }
-       if len(s) != 20 {
+       if len(s) != 40 {
                return "", fmt.Errorf("hg returned invalid hash " + s)
        }
        return s, nil
@@ -615,7 +616,7 @@ func firstTag(re *regexp.Regexp) (hash string, tag string, err os.Error) {
                        continue
                }
                tag = s[1]
-               hash, err = fullHash(s[3])
+               hash, err = fullHash(s[2])
                return
        }
        err = os.NewError("no matching tag found")
index dd18e3af5fa9237243eb9309730bddbd0afa1688..b6674428dacd8805f658b9f4986bc20ee90c50d1 100644 (file)
@@ -14,6 +14,8 @@ import (
        "strings"
 )
 
+const MaxCommentLength = 500 // App Engine won't store more in a StringProperty.
+
 func (b *Builder) buildPackages(workpath string, hash string) os.Error {
        pkgs, err := packages()
        if err != nil {
@@ -21,25 +23,34 @@ func (b *Builder) buildPackages(workpath string, hash string) os.Error {
        }
        for _, p := range pkgs {
                goroot := filepath.Join(workpath, "go")
-               goinstall := filepath.Join(goroot, "bin", "goinstall")
+               gobin := filepath.Join(goroot, "bin")
+               goinstall := filepath.Join(gobin, "goinstall")
                envv := append(b.envv(), "GOROOT="+goroot)
 
+               // add GOBIN to path
+               for i, v := range envv {
+                       if strings.HasPrefix(v, "PATH=") {
+                               p := filepath.SplitList(v[5:])
+                               p = append([]string{gobin}, p...)
+                               s := strings.Join(p, string(filepath.ListSeparator))
+                               envv[i] = "PATH=" + s
+                       }
+               }
+
                // goinstall
-               buildLog, code, err := runLog(envv, "", goroot, goinstall, p)
+               buildLog, code, err := runLog(envv, "", goroot, goinstall, "-log=false", p)
                if err != nil {
                        log.Printf("goinstall %v: %v", p, err)
-                       continue
                }
-               built := code == 0
 
                // get doc comment from package source
-               info, err := packageComment(p, filepath.Join(goroot, "pkg", p))
+               info, err := packageComment(p, filepath.Join(goroot, "src", "pkg", p))
                if err != nil {
-                       log.Printf("goinstall %v: %v", p, err)
+                       log.Printf("packageComment %v: %v", p, err)
                }
 
                // update dashboard with build state + info
-               err = b.updatePackage(p, built, buildLog, info, hash)
+               err = b.updatePackage(p, code == 0, buildLog, info)
                if err != nil {
                        log.Printf("updatePackage %v: %v", p, err)
                }
@@ -69,5 +80,15 @@ func packageComment(pkg, pkgpath string) (info string, err os.Error) {
                pdoc := doc.NewPackageDoc(pkgs[name], pkg)
                info = pdoc.Doc
        }
+       // grab only first paragraph
+       if parts := strings.SplitN(info, "\n\n", 2); len(parts) > 1 {
+               info = parts[0]
+       }
+       // replace newlines with spaces
+       info = strings.Replace(info, "\n", " ", -1)
+       // truncate
+       if len(info) > MaxCommentLength {
+               info = info[:MaxCommentLength]
+       }
        return
 }
diff --git a/misc/dashboard/godashboard/auth.py b/misc/dashboard/godashboard/auth.py
new file mode 100644 (file)
index 0000000..73a54c0
--- /dev/null
@@ -0,0 +1,13 @@
+# Copyright 2011 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+import hmac
+
+# local imports
+import key
+
+def auth(req):
+    k = req.get('key')
+    return k == hmac.new(key.accessKey, req.get('builder')).hexdigest() or k == key.accessKey
+
index 5678f2e1b05658058dc1e606a5ea6eaf82c7bccb..685dc83a9bf93cc38e1396efddad0004d3c399c0 100644 (file)
@@ -14,14 +14,13 @@ from google.appengine.ext.webapp import template
 from google.appengine.ext.webapp.util import run_wsgi_app
 import datetime
 import hashlib
-import hmac
 import logging
 import os
 import re
 import bz2
 
 # local imports
-import key
+from auth import auth
 import const
 
 # The majority of our state are commit objects. One of these exists for each of
@@ -142,10 +141,6 @@ class DashboardHandler(webapp.RequestHandler):
         simplejson.dump(obj, self.response.out)
         return
 
-def auth(req):
-    k = req.get('key')
-    return k == hmac.new(key.accessKey, req.get('builder')).hexdigest() or k == key.accessKey
-
 # Todo serves /todo.  It tells the builder which commits need to be built.
 class Todo(DashboardHandler):
     def get(self):
index 4a00c4a6fe8fbe797b2ca6eca3364baea6a299ee..f39299d5dca08693d49512ae97e316c1847b9f45 100644 (file)
@@ -49,4 +49,3 @@ indexes:
 # manually, move them above the marker line.  The index.yaml file is
 # automatically uploaded to the admin console when you next deploy
 # your application using appcfg.py.
-
index 9332b5a792797aed7ce85a99e36143080887553d..043080b5bfd5b7ac807ed18c4c34573ff1e56aa2 100644 (file)
     Packages listed on this page are written by third parties and 
     may or may not build or be safe to use.
     </p>
+
+    <p>
+    An "ok" in the <b>build</b> column indicates that the package is
+    <a href="http://golang.org/cmd/goinstall/">goinstallable</a>
+    with the latest
+    <a href="http://golang.org/doc/devel/release.html">release</a> of Go.
+    </p>
+
+    <p>
+    The <b>info</b> column shows the first paragraph from the
+    <a href="http://blog.golang.org/2011/03/godoc-documenting-go-code.html">package doc comment</a>.
+    </p>
     
     <h2>Recently Installed Packages</h2>
     <table class="alternate" cellpadding="0" cellspacing="0">
-      <tr><th>last install</th><th>count</th><th>path</th><th>project</th></tr>
+      <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr>
       {% for r in by_time %}
         <tr>
           <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
           <td class="count">{{r.count}}</td>
+          <td class="ok">{% if r.ok %}<a title="{{r.last_ok|date:"Y-M-d H:i"}}">ok</a>{% else %}&nbsp;{% endif %}</td>
           <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
-         <td class="project">
-         {% for p in r.project_set %}
-               <a href="{{p.web_url}}">{{p.name}}</a> - {{p.descr}}
-         {% endfor %}
-         </td>
+             <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td>
         </tr>
       {% endfor %}
     </table>
 
     <h2>Most Installed Packages</h2>
     <table class="alternate" cellpadding="0" cellspacing="0">
-      <tr><th>last install</th><th>count</th><th>path</th><th>project</th></tr>
+      <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr>
       {% for r in by_count %}
         <tr>
           <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
           <td class="count">{{r.count}}</td>
+          <td class="ok">{% if r.ok %}<a title="{{r.last_ok|date:"Y-M-d H:i"}}">ok</a>{% else %}&nbsp;{% endif %}</td>
           <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
-         <td class="project">
-         {% for p in r.project_set %}
-               <a href="{{p.web_url}}">{{p.name}}</a> - {{p.descr}}
-         {% endfor %}
-         </td>
+             <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td>
         </tr>
       {% endfor %}
     </table>
index dd09593ac1fcff29ac96e495c1256ae9215ccfd2..316f3867f0e9e0d19a54cb0f51c9e0c2469f25a7 100644 (file)
@@ -23,6 +23,7 @@ import sets
 # local imports
 import toutf8
 import const
+from auth import auth
 
 template.register_template_library('toutf8')
 
@@ -34,6 +35,11 @@ class Package(db.Model):
     count = db.IntegerProperty()
     last_install = db.DateTimeProperty()
 
+    # data contributed by gobuilder
+    info = db.StringProperty()  
+    ok = db.BooleanProperty()
+    last_ok = db.DateTimeProperty()
+
 class Project(db.Model):
     name = db.StringProperty(indexed=True)
     descr = db.StringProperty()
@@ -43,22 +49,25 @@ class Project(db.Model):
     tags = db.ListProperty(str)
     approved = db.BooleanProperty(indexed=True)
 
-re_bitbucket = re.compile(r'^bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$')
-re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg)$')
+re_bitbucket = re.compile(r'^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-zA-Z0-9_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$')
+re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg)(/[a-z0-9A-Z_.\-/]+)?$')
 re_github = re.compile(r'^github\.com/[a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)+$')
 re_launchpad = re.compile(r'^launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$')
 
-
 def vc_to_web(path):
     if re_bitbucket.match(path):
-        check_url = 'http://' + path + '/?cmd=heads'
-        web = 'http://' + path + '/'
+        m = re_bitbucket.match(path)
+        check_url = 'http://' + m.group(1) + '/?cmd=heads'
+        web = 'http://' + m.group(1) + '/'
     elif re_github.match(path):
         m = re_github_web.match(path)
         check_url = 'https://raw.github.com/' + m.group(1) + '/' + m.group(2) + '/master/'
-        web = 'http://github.com/' + m.group(1) + '/' + m.group(2)
+        web = 'http://github.com/' + m.group(1) + '/' + m.group(2) + '/'
     elif re_googlecode.match(path):
+        m = re_googlecode.match(path)
         check_url = 'http://'+path
+        if not m.group(2):  # append / after bare '/hg'
+            check_url += '/'
         web = 'http://code.google.com/p/' + path[:path.index('.')]
     elif re_launchpad.match(path):
         check_url = web = 'https://'+path
@@ -142,8 +151,7 @@ class PackagePage(webapp.RequestHandler):
 
     def can_get_url(self, url):
         try:
-            req = urllib2.Request(url)
-            response = urllib2.urlopen(req)
+            urllib2.urlopen(urllib2.Request(url))
             return True
         except:
             return False
@@ -173,15 +181,23 @@ class PackagePage(webapp.RequestHandler):
                 return False
             p = Package(key_name = key, path = path, count = 0, web_url = web)
 
+        # is this the builder updating package metadata?
+        if auth(self.request):
+            p.info = self.request.get('info')
+            p.ok = self.request.get('ok') == "true"
+            if p.ok:
+                p.last_ok = datetime.datetime.utcnow()
+        else:
+            p.count += 1
+            p.last_install = datetime.datetime.utcnow()
+
         # update package object
-        p.count += 1
-        p.last_install = datetime.datetime.utcnow()
         p.put()
         return True
 
     def post(self):
         path = self.request.get('path')
-        ok = self.record_pkg(path)
+        ok = db.run_in_transaction(self.record_pkg,  path)
         if ok:
             self.response.set_status(200)
             self.response.out.write('ok')
index 481af36d7d60c09c326626884b19034c6f3d8e45..a7e61dda5017372f31dc2d72c819256c5e3f5327 100644 (file)
@@ -52,7 +52,7 @@ table.alternate tr td:last-child {
        padding-right: 0;
 }
 table.alternate tr:nth-child(2n) {
-       background-color: #f8f8f8;
+       background-color: #f0f0f0;
 }
 span.hash {
        font-family: monospace;
@@ -62,10 +62,19 @@ span.hash {
 td.date {
        color: #aaa;
 }
-td.result {
+td.ok { 
        text-align: center;
+       color: #060; 
+       font-weight: bold;
+}
+td.ok a {
+       cursor: help;
+}
+th {
+       text-align: left;
 }
 th.builder {
+       text-align: center;
        font-weight: bold;
 }
 a.fail {