mirror of
https://github.com/golang/go
synced 2024-11-21 21:54: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:
parent
acc284d847
commit
6a2e2432c9
@ -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],
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
13
misc/dashboard/godashboard/auth.py
Normal file
13
misc/dashboard/godashboard/auth.py
Normal 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
|
||||||
|
|
@ -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):
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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 %} {% 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 %} {% 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>
|
||||||
|
@ -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')
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user