1
0
mirror of https://github.com/golang/go synced 2024-11-21 16:24:40 -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

@ -60,8 +60,9 @@ 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

@ -19,37 +19,43 @@
Packages listed on this page are written by third parties and Packages listed on this page are written by third parties and
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)
# 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 # update package object
p.count += 1
p.last_install = datetime.datetime.utcnow()
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 {