1
0
mirror of https://github.com/golang/go synced 2024-11-21 21:04:41 -07:00

dashboard: show build state and package comments on dashboard

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
This commit is contained in:
Andrew Gerrand 2011-07-02 14:02:42 +10:00
parent acc284d847
commit 6a2e2432c9
9 changed files with 108 additions and 49 deletions

View File

@ -112,16 +112,15 @@ func packages() (pkgs []string, err os.Error) {
return return
} }
// updatePackage sends package build results and info to the dashboard // updatePackage sends package build results and info dashboard
func (b *Builder) updatePackage(pkg string, state bool, buildLog, info string, hash string) os.Error { func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) os.Error {
return dash("POST", "package", nil, param{ return dash("POST", "package", nil, param{
"builder": b.name, "builder": b.name,
"key": b.key, "key": b.key,
"path": pkg, "path": pkg,
"state": strconv.Btoa(state), "ok": strconv.Btoa(ok),
"log": buildLog, "log": buildLog,
"info": info, "info": info,
"go_rev": hash[:12],
}) })
} }

View File

@ -61,7 +61,8 @@ var (
var ( var (
goroot string goroot string
releaseRegexp = regexp.MustCompile(`^(release|weekly)\.[0-9\-.]+`) binaryTagRe = regexp.MustCompile(`^(release\.r|weekly\.)[0-9\-.]+`)
releaseRe = regexp.MustCompile(`^release\.r[0-9\-.]+`)
) )
func main() { func main() {
@ -200,7 +201,7 @@ func (b *Builder) buildExternal() {
log.Println("hg pull failed:", err) log.Println("hg pull failed:", err)
continue continue
} }
hash, tag, err := firstTag(releaseRegexp) hash, tag, err := firstTag(releaseRe)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
continue 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 // 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 { if hash == releaseHash {
// clean out build state // clean out build state
err = run(b.envv(), srcDir, "./clean.bash", "--nopkg") err = run(b.envv(), srcDir, "./clean.bash", "--nopkg")
@ -591,7 +592,7 @@ func fullHash(rev string) (hash string, err os.Error) {
if s == "" { if s == "" {
return "", fmt.Errorf("cannot find revision") return "", fmt.Errorf("cannot find revision")
} }
if len(s) != 20 { if len(s) != 40 {
return "", fmt.Errorf("hg returned invalid hash " + s) return "", fmt.Errorf("hg returned invalid hash " + s)
} }
return s, nil return s, nil
@ -615,7 +616,7 @@ func firstTag(re *regexp.Regexp) (hash string, tag string, err os.Error) {
continue continue
} }
tag = s[1] tag = s[1]
hash, err = fullHash(s[3]) hash, err = fullHash(s[2])
return return
} }
err = os.NewError("no matching tag found") err = os.NewError("no matching tag found")

View File

@ -14,6 +14,8 @@ import (
"strings" "strings"
) )
const MaxCommentLength = 500 // App Engine won't store more in a StringProperty.
func (b *Builder) buildPackages(workpath string, hash string) os.Error { func (b *Builder) buildPackages(workpath string, hash string) os.Error {
pkgs, err := packages() pkgs, err := packages()
if err != nil { if err != nil {
@ -21,25 +23,34 @@ func (b *Builder) buildPackages(workpath string, hash string) os.Error {
} }
for _, p := range pkgs { for _, p := range pkgs {
goroot := filepath.Join(workpath, "go") 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) 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 // goinstall
buildLog, code, err := runLog(envv, "", goroot, goinstall, p) buildLog, code, err := runLog(envv, "", goroot, goinstall, "-log=false", p)
if err != nil { if err != nil {
log.Printf("goinstall %v: %v", p, err) log.Printf("goinstall %v: %v", p, err)
continue
} }
built := code == 0
// get doc comment from package source // 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 { if err != nil {
log.Printf("goinstall %v: %v", p, err) log.Printf("packageComment %v: %v", p, err)
} }
// update dashboard with build state + info // 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 { if err != nil {
log.Printf("updatePackage %v: %v", p, err) 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) pdoc := doc.NewPackageDoc(pkgs[name], pkg)
info = pdoc.Doc 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 return
} }

View File

@ -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

View File

@ -14,14 +14,13 @@ from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app from google.appengine.ext.webapp.util import run_wsgi_app
import datetime import datetime
import hashlib import hashlib
import hmac
import logging import logging
import os import os
import re import re
import bz2 import bz2
# local imports # local imports
import key from auth import auth
import const import const
# The majority of our state are commit objects. One of these exists for each of # 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) simplejson.dump(obj, self.response.out)
return 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. # Todo serves /todo. It tells the builder which commits need to be built.
class Todo(DashboardHandler): class Todo(DashboardHandler):
def get(self): def get(self):

View File

@ -49,4 +49,3 @@ indexes:
# manually, move them above the marker line. The index.yaml file is # manually, move them above the marker line. The index.yaml file is
# automatically uploaded to the admin console when you next deploy # automatically uploaded to the admin console when you next deploy
# your application using appcfg.py. # your application using appcfg.py.

View File

@ -20,36 +20,42 @@
may or may not build or be safe to use. may or may not build or be safe to use.
</p> </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> <h2>Recently Installed Packages</h2>
<table class="alternate" cellpadding="0" cellspacing="0"> <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 %} {% for r in by_time %}
<tr> <tr>
<td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td> <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
<td class="count">{{r.count}}</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="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
<td class="project"> <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td>
{% for p in r.project_set %}
<a href="{{p.web_url}}">{{p.name}}</a> - {{p.descr}}
{% endfor %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
<h2>Most Installed Packages</h2> <h2>Most Installed Packages</h2>
<table class="alternate" cellpadding="0" cellspacing="0"> <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 %} {% for r in by_count %}
<tr> <tr>
<td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td> <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
<td class="count">{{r.count}}</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="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
<td class="project"> <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td>
{% for p in r.project_set %}
<a href="{{p.web_url}}">{{p.name}}</a> - {{p.descr}}
{% endfor %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@ -23,6 +23,7 @@ import sets
# local imports # local imports
import toutf8 import toutf8
import const import const
from auth import auth
template.register_template_library('toutf8') template.register_template_library('toutf8')
@ -34,6 +35,11 @@ class Package(db.Model):
count = db.IntegerProperty() count = db.IntegerProperty()
last_install = db.DateTimeProperty() last_install = db.DateTimeProperty()
# data contributed by gobuilder
info = db.StringProperty()
ok = db.BooleanProperty()
last_ok = db.DateTimeProperty()
class Project(db.Model): class Project(db.Model):
name = db.StringProperty(indexed=True) name = db.StringProperty(indexed=True)
descr = db.StringProperty() descr = db.StringProperty()
@ -43,22 +49,25 @@ class Project(db.Model):
tags = db.ListProperty(str) tags = db.ListProperty(str)
approved = db.BooleanProperty(indexed=True) approved = db.BooleanProperty(indexed=True)
re_bitbucket = re.compile(r'^bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$') 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)$') 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_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_.\-/]+)?$') 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): def vc_to_web(path):
if re_bitbucket.match(path): if re_bitbucket.match(path):
check_url = 'http://' + path + '/?cmd=heads' m = re_bitbucket.match(path)
web = 'http://' + path + '/' check_url = 'http://' + m.group(1) + '/?cmd=heads'
web = 'http://' + m.group(1) + '/'
elif re_github.match(path): elif re_github.match(path):
m = re_github_web.match(path) m = re_github_web.match(path)
check_url = 'https://raw.github.com/' + m.group(1) + '/' + m.group(2) + '/master/' 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): elif re_googlecode.match(path):
m = re_googlecode.match(path)
check_url = 'http://'+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('.')] web = 'http://code.google.com/p/' + path[:path.index('.')]
elif re_launchpad.match(path): elif re_launchpad.match(path):
check_url = web = 'https://'+path check_url = web = 'https://'+path
@ -142,8 +151,7 @@ class PackagePage(webapp.RequestHandler):
def can_get_url(self, url): def can_get_url(self, url):
try: try:
req = urllib2.Request(url) urllib2.urlopen(urllib2.Request(url))
response = urllib2.urlopen(req)
return True return True
except: except:
return False return False
@ -173,15 +181,23 @@ class PackagePage(webapp.RequestHandler):
return False return False
p = Package(key_name = key, path = path, count = 0, web_url = web) p = Package(key_name = key, path = path, count = 0, web_url = web)
# update package object # 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.count += 1
p.last_install = datetime.datetime.utcnow() p.last_install = datetime.datetime.utcnow()
# update package object
p.put() p.put()
return True return True
def post(self): def post(self):
path = self.request.get('path') path = self.request.get('path')
ok = self.record_pkg(path) ok = db.run_in_transaction(self.record_pkg, path)
if ok: if ok:
self.response.set_status(200) self.response.set_status(200)
self.response.out.write('ok') self.response.out.write('ok')

View File

@ -52,7 +52,7 @@ table.alternate tr td:last-child {
padding-right: 0; padding-right: 0;
} }
table.alternate tr:nth-child(2n) { table.alternate tr:nth-child(2n) {
background-color: #f8f8f8; background-color: #f0f0f0;
} }
span.hash { span.hash {
font-family: monospace; font-family: monospace;
@ -62,10 +62,19 @@ span.hash {
td.date { td.date {
color: #aaa; color: #aaa;
} }
td.result { td.ok {
text-align: center; text-align: center;
color: #060;
font-weight: bold;
}
td.ok a {
cursor: help;
}
th {
text-align: left;
} }
th.builder { th.builder {
text-align: center;
font-weight: bold; font-weight: bold;
} }
a.fail { a.fail {