mirror of
https://github.com/golang/go
synced 2024-11-20 06:44:40 -07:00
494e52fe1c
R=golang-dev, dsymonds CC=golang-dev https://golang.org/cl/5503056
254 lines
5.5 KiB
Go
254 lines
5.5 KiB
Go
// 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.
|
|
|
|
// TODO(adg): packages at weekly/release
|
|
// TODO(adg): some means to register new packages
|
|
|
|
package build
|
|
|
|
import (
|
|
"appengine"
|
|
"appengine/datastore"
|
|
"appengine/memcache"
|
|
"bytes"
|
|
"exp/template/html"
|
|
"http"
|
|
"os"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"template"
|
|
)
|
|
|
|
const (
|
|
uiCacheKey = "build-ui"
|
|
uiCacheExpiry = 10 * 60 // 10 minutes in seconds
|
|
)
|
|
|
|
func init() {
|
|
http.HandleFunc("/", uiHandler)
|
|
html.Escape(uiTemplate)
|
|
}
|
|
|
|
// uiHandler draws the build status page.
|
|
func uiHandler(w http.ResponseWriter, r *http.Request) {
|
|
c := appengine.NewContext(r)
|
|
|
|
page, _ := strconv.Atoi(r.FormValue("page"))
|
|
if page < 0 {
|
|
page = 0
|
|
}
|
|
|
|
// Used cached version of front page, if available.
|
|
if page == 0 {
|
|
t, err := memcache.Get(c, uiCacheKey)
|
|
if err == nil {
|
|
w.Write(t.Value)
|
|
return
|
|
}
|
|
if err != memcache.ErrCacheMiss {
|
|
c.Errorf("get ui cache: %v", err)
|
|
}
|
|
}
|
|
|
|
commits, err := goCommits(c, page)
|
|
if err != nil {
|
|
logErr(w, r, err)
|
|
return
|
|
}
|
|
builders := commitBuilders(commits)
|
|
|
|
tipState, err := TagState(c, "tip")
|
|
if err != nil {
|
|
logErr(w, r, err)
|
|
return
|
|
}
|
|
|
|
p := &Pagination{}
|
|
if len(commits) == commitsPerPage {
|
|
p.Next = page + 1
|
|
}
|
|
if page > 0 {
|
|
p.Prev = page - 1
|
|
p.HasPrev = true
|
|
}
|
|
data := &uiTemplateData{commits, builders, tipState, p}
|
|
|
|
var buf bytes.Buffer
|
|
if err := uiTemplate.Execute(&buf, data); err != nil {
|
|
logErr(w, r, err)
|
|
return
|
|
}
|
|
|
|
// Cache the front page.
|
|
if page == 0 {
|
|
t := &memcache.Item{
|
|
Key: uiCacheKey,
|
|
Value: buf.Bytes(),
|
|
Expiration: uiCacheExpiry,
|
|
}
|
|
if err := memcache.Set(c, t); err != nil {
|
|
c.Errorf("set ui cache: %v", err)
|
|
}
|
|
}
|
|
|
|
buf.WriteTo(w)
|
|
}
|
|
|
|
type Pagination struct {
|
|
Next, Prev int
|
|
HasPrev bool
|
|
}
|
|
|
|
// goCommits gets a slice of the latest Commits to the Go repository.
|
|
// If page > 0 it paginates by commitsPerPage.
|
|
func goCommits(c appengine.Context, page int) ([]*Commit, os.Error) {
|
|
q := datastore.NewQuery("Commit").
|
|
Ancestor((&Package{}).Key(c)).
|
|
Order("-Time").
|
|
Limit(commitsPerPage).
|
|
Offset(page * commitsPerPage)
|
|
var commits []*Commit
|
|
_, err := q.GetAll(c, &commits)
|
|
return commits, err
|
|
}
|
|
|
|
// commitBuilders returns the names of the builders that provided
|
|
// Results for the provided commits.
|
|
func commitBuilders(commits []*Commit) []string {
|
|
builders := make(map[string]bool)
|
|
for _, commit := range commits {
|
|
for _, r := range commit.Results("") {
|
|
builders[r.Builder] = true
|
|
}
|
|
}
|
|
return keys(builders)
|
|
}
|
|
|
|
func keys(m map[string]bool) (s []string) {
|
|
for k := range m {
|
|
s = append(s, k)
|
|
}
|
|
sort.Strings(s)
|
|
return
|
|
}
|
|
|
|
// PackageState represents the state of a Package at a tag.
|
|
type PackageState struct {
|
|
*Package
|
|
*Commit
|
|
Results []*Result
|
|
OK bool
|
|
}
|
|
|
|
// TagState fetches the results for all non-Go packages at the specified tag.
|
|
func TagState(c appengine.Context, name string) ([]*PackageState, os.Error) {
|
|
tag, err := GetTag(c, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pkgs, err := Packages(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var states []*PackageState
|
|
for _, pkg := range pkgs {
|
|
commit, err := pkg.LastCommit(c)
|
|
if err != nil {
|
|
c.Errorf("no Commit found: %v", pkg)
|
|
continue
|
|
}
|
|
results := commit.Results(tag.Hash)
|
|
ok := len(results) > 0
|
|
for _, r := range results {
|
|
ok = ok && r.OK
|
|
}
|
|
states = append(states, &PackageState{
|
|
pkg, commit, results, ok,
|
|
})
|
|
}
|
|
return states, nil
|
|
}
|
|
|
|
type uiTemplateData struct {
|
|
Commits []*Commit
|
|
Builders []string
|
|
TipState []*PackageState
|
|
Pagination *Pagination
|
|
}
|
|
|
|
var uiTemplate = template.Must(
|
|
template.New("ui").Funcs(tmplFuncs).ParseFile("build/ui.html"),
|
|
)
|
|
|
|
var tmplFuncs = template.FuncMap{
|
|
"builderTitle": builderTitle,
|
|
"repoURL": repoURL,
|
|
"shortDesc": shortDesc,
|
|
"shortHash": shortHash,
|
|
"shortUser": shortUser,
|
|
"tail": tail,
|
|
}
|
|
|
|
// builderTitle formats "linux-amd64-foo" as "linux amd64 foo".
|
|
func builderTitle(s string) string {
|
|
return strings.Replace(s, "-", " ", -1)
|
|
}
|
|
|
|
// shortDesc returns the first line of a description.
|
|
func shortDesc(desc string) string {
|
|
if i := strings.Index(desc, "\n"); i != -1 {
|
|
desc = desc[:i]
|
|
}
|
|
return desc
|
|
}
|
|
|
|
// shortHash returns a short version of a hash.
|
|
func shortHash(hash string) string {
|
|
if len(hash) > 12 {
|
|
hash = hash[:12]
|
|
}
|
|
return hash
|
|
}
|
|
|
|
// shortUser returns a shortened version of a user string.
|
|
func shortUser(user string) string {
|
|
if i, j := strings.Index(user, "<"), strings.Index(user, ">"); i != -1 && j > i {
|
|
user = user[i+1 : j]
|
|
if k := strings.Index(user, "@golang.org"); k != -1 {
|
|
user = user[:k]
|
|
}
|
|
}
|
|
return user
|
|
}
|
|
|
|
// repoRe matches Google Code repositories and subrepositories (without paths).
|
|
var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+)(\.[a-z0-9\-]+)?$`)
|
|
|
|
// repoURL returns the URL of a change at a Google Code repository or subrepo.
|
|
func repoURL(hash, packagePath string) (string, os.Error) {
|
|
if packagePath == "" {
|
|
return "https://code.google.com/p/go/source/detail?r=" + hash, nil
|
|
}
|
|
m := repoRe.FindStringSubmatch(packagePath)
|
|
if m == nil {
|
|
return "", os.NewError("unrecognized package: " + packagePath)
|
|
}
|
|
url := "https://code.google.com/p/" + m[1] + "/source/detail?r=" + hash
|
|
if len(m) > 2 {
|
|
url += "&repo=" + m[2][1:]
|
|
}
|
|
return url, nil
|
|
}
|
|
|
|
// tail returns the trailing n lines of s.
|
|
func tail(n int, s string) string {
|
|
lines := strings.Split(s, "\n")
|
|
if len(lines) < n {
|
|
return s
|
|
}
|
|
return strings.Join(lines[len(lines)-n:], "\n")
|
|
}
|