mirror of
https://github.com/golang/go
synced 2024-11-21 23:34:42 -07:00
gobuilder: use new dashboard protocol
gobuilder: -commit mode for packages gobuilder: cripple -package mode temporarily R=golang-dev, dsymonds CC=golang-dev https://golang.org/cl/5450092
This commit is contained in:
parent
2c0072217a
commit
263c955f2f
@ -8,96 +8,108 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type param map[string]string
|
type obj map[string]interface{}
|
||||||
|
|
||||||
// dash runs the given method and command on the dashboard.
|
// dash runs the given method and command on the dashboard.
|
||||||
// If args is not nil, it is the query or post parameters.
|
// If args is non-nil it is encoded as the URL query string.
|
||||||
// If resp is not nil, dash unmarshals the body as JSON into resp.
|
// If req is non-nil it is JSON-encoded and passed as the body of the HTTP POST.
|
||||||
func dash(meth, cmd string, resp interface{}, args param) error {
|
// If resp is non-nil the server's response is decoded into the value pointed
|
||||||
|
// to by resp (resp must be a pointer).
|
||||||
|
func dash(meth, cmd string, args url.Values, req, resp interface{}) error {
|
||||||
var r *http.Response
|
var r *http.Response
|
||||||
var err error
|
var err error
|
||||||
if *verbose {
|
if *verbose {
|
||||||
log.Println("dash", cmd, args)
|
log.Println("dash", meth, cmd, args, req)
|
||||||
}
|
}
|
||||||
cmd = "http://" + *dashboard + "/" + cmd
|
cmd = "http://" + *dashboard + "/" + cmd
|
||||||
vals := make(url.Values)
|
if len(args) > 0 {
|
||||||
for k, v := range args {
|
cmd += "?" + args.Encode()
|
||||||
vals.Add(k, v)
|
|
||||||
}
|
}
|
||||||
switch meth {
|
switch meth {
|
||||||
case "GET":
|
case "GET":
|
||||||
if q := vals.Encode(); q != "" {
|
if req != nil {
|
||||||
cmd += "?" + q
|
log.Panicf("%s to %s with req", meth, cmd)
|
||||||
}
|
}
|
||||||
r, err = http.Get(cmd)
|
r, err = http.Get(cmd)
|
||||||
case "POST":
|
case "POST":
|
||||||
r, err = http.PostForm(cmd, vals)
|
var body io.Reader
|
||||||
default:
|
if req != nil {
|
||||||
return fmt.Errorf("unknown method %q", meth)
|
b, err := json.Marshal(req)
|
||||||
}
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
body = bytes.NewBuffer(b)
|
||||||
defer r.Body.Close()
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.ReadFrom(r.Body)
|
|
||||||
if resp != nil {
|
|
||||||
if err = json.Unmarshal(buf.Bytes(), resp); err != nil {
|
|
||||||
log.Printf("json unmarshal %#q: %s\n", buf.Bytes(), err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
r, err = http.Post(cmd, "text/json", body)
|
||||||
|
default:
|
||||||
|
log.Panicf("%s: invalid method %q", cmd, meth)
|
||||||
|
panic("invalid method: " + meth)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dashStatus(meth, cmd string, args param) error {
|
|
||||||
var resp struct {
|
|
||||||
Status string
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
err := dash(meth, cmd, &resp, args)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.Status != "OK" {
|
|
||||||
return errors.New("/build: " + resp.Error)
|
defer r.Body.Close()
|
||||||
|
body := new(bytes.Buffer)
|
||||||
|
if _, err := body.ReadFrom(r.Body); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read JSON-encoded Response into provided resp
|
||||||
|
// and return an error if present.
|
||||||
|
var result = struct {
|
||||||
|
Response interface{}
|
||||||
|
Error string
|
||||||
|
}{
|
||||||
|
// Put the provided resp in here as it can be a pointer to
|
||||||
|
// some value we should unmarshal into.
|
||||||
|
Response: resp,
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(body.Bytes(), &result); err != nil {
|
||||||
|
log.Printf("json unmarshal %#q: %s\n", body.Bytes(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if result.Error != "" {
|
||||||
|
return errors.New(result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo returns the next hash to build.
|
// todo returns the next hash to build.
|
||||||
func (b *Builder) todo() (rev string, err error) {
|
func (b *Builder) todo() (rev string, err error) {
|
||||||
var resp []struct {
|
// TODO(adg): handle packages
|
||||||
Hash string
|
args := url.Values{"builder": {b.name}}
|
||||||
}
|
var resp string
|
||||||
if err = dash("GET", "todo", &resp, param{"builder": b.name}); err != nil {
|
if err = dash("GET", "todo", args, nil, &resp); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(resp) > 0 {
|
if resp != "" {
|
||||||
rev = resp[0].Hash
|
rev = resp
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// recordResult sends build results to the dashboard
|
// recordResult sends build results to the dashboard
|
||||||
func (b *Builder) recordResult(buildLog string, hash string) error {
|
func (b *Builder) recordResult(buildLog string, hash string) error {
|
||||||
return dash("POST", "build", nil, param{
|
// TODO(adg): handle packages
|
||||||
"builder": b.name,
|
return dash("POST", "result", url.Values{"key": {b.key}}, obj{
|
||||||
"key": b.key,
|
"Builder": b.name,
|
||||||
"node": hash,
|
"Hash": hash,
|
||||||
"log": buildLog,
|
"Log": buildLog,
|
||||||
})
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// packages fetches a list of package paths from the dashboard
|
// packages fetches a list of package paths from the dashboard
|
||||||
func packages() (pkgs []string, err error) {
|
func packages() (pkgs []string, err error) {
|
||||||
|
return nil, nil
|
||||||
|
/* TODO(adg): un-stub this once the new package builder design is done
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Packages []struct {
|
Packages []struct {
|
||||||
Path string
|
Path string
|
||||||
@ -111,10 +123,13 @@ func packages() (pkgs []string, err error) {
|
|||||||
pkgs = append(pkgs, p.Path)
|
pkgs = append(pkgs, p.Path)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// updatePackage sends package build results and info dashboard
|
// updatePackage sends package build results and info dashboard
|
||||||
func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) error {
|
func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) error {
|
||||||
|
return nil
|
||||||
|
/* TODO(adg): un-stub this once the new package builder design is done
|
||||||
return dash("POST", "package", nil, param{
|
return dash("POST", "package", nil, param{
|
||||||
"builder": b.name,
|
"builder": b.name,
|
||||||
"key": b.key,
|
"key": b.key,
|
||||||
@ -123,26 +138,44 @@ func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) erro
|
|||||||
"log": buildLog,
|
"log": buildLog,
|
||||||
"info": info,
|
"info": info,
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// postCommit informs the dashboard of a new commit
|
func postCommit(key, pkg string, l *HgLog) bool {
|
||||||
func postCommit(key string, l *HgLog) error {
|
err := dash("POST", "commit", url.Values{"key": {key}}, obj{
|
||||||
return dashStatus("POST", "commit", param{
|
"PackagePath": pkg,
|
||||||
"key": key,
|
"Hash": l.Hash,
|
||||||
"node": l.Hash,
|
"ParentHash": l.Parent,
|
||||||
"date": l.Date,
|
// TODO(adg): l.Date as int64 unix epoch secs in Time field
|
||||||
"user": l.Author,
|
"User": l.Author,
|
||||||
"parent": l.Parent,
|
"Desc": l.Desc,
|
||||||
"desc": l.Desc,
|
}, nil)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// dashboardCommit returns true if the dashboard knows about hash.
|
|
||||||
func dashboardCommit(hash string) bool {
|
|
||||||
err := dashStatus("GET", "commit", param{"node": hash})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("check %s: %s", hash, err)
|
log.Printf("failed to add %s to dashboard: %v", key, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dashboardCommit(pkg, hash string) bool {
|
||||||
|
err := dash("GET", "commit", url.Values{
|
||||||
|
"packagePath": {pkg},
|
||||||
|
"hash": {hash},
|
||||||
|
}, nil, nil)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dashboardPackages() []string {
|
||||||
|
var resp []struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
if err := dash("GET", "packages", nil, nil, &resp); err != nil {
|
||||||
|
log.Println("dashboardPackages:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var pkgs []string
|
||||||
|
for _, r := range resp {
|
||||||
|
pkgs = append(pkgs, r.Path)
|
||||||
|
}
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -93,7 +94,7 @@ func main() {
|
|||||||
if err := os.Mkdir(*buildroot, mkdirPerm); err != nil {
|
if err := os.Mkdir(*buildroot, mkdirPerm); err != nil {
|
||||||
log.Fatalf("Error making build root (%s): %s", *buildroot, err)
|
log.Fatalf("Error making build root (%s): %s", *buildroot, err)
|
||||||
}
|
}
|
||||||
if err := run(nil, *buildroot, "hg", "clone", hgUrl, goroot); err != nil {
|
if err := hgClone(hgUrl, goroot); err != nil {
|
||||||
log.Fatal("Error cloning repository:", err)
|
log.Fatal("Error cloning repository:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ func main() {
|
|||||||
|
|
||||||
// if specified, build revision and return
|
// if specified, build revision and return
|
||||||
if *buildRevision != "" {
|
if *buildRevision != "" {
|
||||||
hash, err := fullHash(*buildRevision)
|
hash, err := fullHash(goroot, *buildRevision)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error finding revision: ", err)
|
log.Fatal("Error finding revision: ", err)
|
||||||
}
|
}
|
||||||
@ -246,7 +247,7 @@ func (b *Builder) build() bool {
|
|||||||
}
|
}
|
||||||
// Look for hash locally before running hg pull.
|
// Look for hash locally before running hg pull.
|
||||||
|
|
||||||
if _, err := fullHash(hash[:12]); err != nil {
|
if _, err := fullHash(goroot, hash[:12]); err != nil {
|
||||||
// Don't have hash, so run hg pull.
|
// Don't have hash, so run hg pull.
|
||||||
if err := run(nil, goroot, "hg", "pull"); err != nil {
|
if err := run(nil, goroot, "hg", "pull"); err != nil {
|
||||||
log.Println("hg pull failed:", err)
|
log.Println("hg pull failed:", err)
|
||||||
@ -425,11 +426,16 @@ func commitWatcher() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
key := b.key
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if *verbose {
|
if *verbose {
|
||||||
log.Printf("poll...")
|
log.Printf("poll...")
|
||||||
}
|
}
|
||||||
commitPoll(b.key)
|
commitPoll(key, "")
|
||||||
|
for _, pkg := range dashboardPackages() {
|
||||||
|
commitPoll(key, pkg)
|
||||||
|
}
|
||||||
if *verbose {
|
if *verbose {
|
||||||
log.Printf("sleep...")
|
log.Printf("sleep...")
|
||||||
}
|
}
|
||||||
@ -437,6 +443,18 @@ func commitWatcher() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hgClone(url, path string) error {
|
||||||
|
return run(nil, *buildroot, "hg", "clone", url, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hgRepoExists(path string) bool {
|
||||||
|
fi, err := os.Stat(filepath.Join(path, ".hg"))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
// HgLog represents a single Mercurial revision.
|
// HgLog represents a single Mercurial revision.
|
||||||
type HgLog struct {
|
type HgLog struct {
|
||||||
Hash string
|
Hash string
|
||||||
@ -467,7 +485,7 @@ const xmlLogTemplate = `
|
|||||||
|
|
||||||
// commitPoll pulls any new revisions from the hg server
|
// commitPoll pulls any new revisions from the hg server
|
||||||
// and tells the server about them.
|
// and tells the server about them.
|
||||||
func commitPoll(key string) {
|
func commitPoll(key, pkg string) {
|
||||||
// Catch unexpected panics.
|
// Catch unexpected panics.
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
@ -475,14 +493,29 @@ func commitPoll(key string) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := run(nil, goroot, "hg", "pull"); err != nil {
|
pkgRoot := goroot
|
||||||
|
|
||||||
|
if pkg != "" {
|
||||||
|
pkgRoot = path.Join(*buildroot, pkg)
|
||||||
|
if !hgRepoExists(pkgRoot) {
|
||||||
|
if err := hgClone(repoURL(pkg), pkgRoot); err != nil {
|
||||||
|
log.Printf("%s: hg clone failed: %v", pkg, err)
|
||||||
|
if err := os.RemoveAll(pkgRoot); err != nil {
|
||||||
|
log.Printf("%s: %v", pkg, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := run(nil, pkgRoot, "hg", "pull"); err != nil {
|
||||||
log.Printf("hg pull: %v", err)
|
log.Printf("hg pull: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const N = 50 // how many revisions to grab
|
const N = 50 // how many revisions to grab
|
||||||
|
|
||||||
data, _, err := runLog(nil, "", goroot, "hg", "log",
|
data, _, err := runLog(nil, "", pkgRoot, "hg", "log",
|
||||||
"--encoding=utf-8",
|
"--encoding=utf-8",
|
||||||
"--limit="+strconv.Itoa(N),
|
"--limit="+strconv.Itoa(N),
|
||||||
"--template="+xmlLogTemplate,
|
"--template="+xmlLogTemplate,
|
||||||
@ -511,14 +544,11 @@ func commitPoll(key string) {
|
|||||||
if l.Parent == "" && i+1 < len(logs) {
|
if l.Parent == "" && i+1 < len(logs) {
|
||||||
l.Parent = logs[i+1].Hash
|
l.Parent = logs[i+1].Hash
|
||||||
} else if l.Parent != "" {
|
} else if l.Parent != "" {
|
||||||
l.Parent, _ = fullHash(l.Parent)
|
l.Parent, _ = fullHash(pkgRoot, l.Parent)
|
||||||
}
|
}
|
||||||
log.Printf("hg log: %s < %s\n", l.Hash, l.Parent)
|
if *verbose {
|
||||||
if l.Parent == "" {
|
log.Printf("hg log %s: %s < %s\n", pkg, l.Hash, l.Parent)
|
||||||
// Can't create node without parent.
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if logByHash[l.Hash] == nil {
|
if logByHash[l.Hash] == nil {
|
||||||
// Make copy to avoid pinning entire slice when only one entry is new.
|
// Make copy to avoid pinning entire slice when only one entry is new.
|
||||||
t := *l
|
t := *l
|
||||||
@ -528,17 +558,14 @@ func commitPoll(key string) {
|
|||||||
|
|
||||||
for i := range logs {
|
for i := range logs {
|
||||||
l := &logs[i]
|
l := &logs[i]
|
||||||
if l.Parent == "" {
|
addCommit(pkg, l.Hash, key)
|
||||||
continue
|
|
||||||
}
|
|
||||||
addCommit(l.Hash, key)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addCommit adds the commit with the named hash to the dashboard.
|
// addCommit adds the commit with the named hash to the dashboard.
|
||||||
// key is the secret key for authentication to the dashboard.
|
// key is the secret key for authentication to the dashboard.
|
||||||
// It avoids duplicate effort.
|
// It avoids duplicate effort.
|
||||||
func addCommit(hash, key string) bool {
|
func addCommit(pkg, hash, key string) bool {
|
||||||
l := logByHash[hash]
|
l := logByHash[hash]
|
||||||
if l == nil {
|
if l == nil {
|
||||||
return false
|
return false
|
||||||
@ -548,7 +575,7 @@ func addCommit(hash, key string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for already added, perhaps in an earlier run.
|
// Check for already added, perhaps in an earlier run.
|
||||||
if dashboardCommit(hash) {
|
if dashboardCommit(pkg, hash) {
|
||||||
log.Printf("%s already on dashboard\n", hash)
|
log.Printf("%s already on dashboard\n", hash)
|
||||||
// Record that this hash is on the dashboard,
|
// Record that this hash is on the dashboard,
|
||||||
// as must be all its parents.
|
// as must be all its parents.
|
||||||
@ -560,26 +587,24 @@ func addCommit(hash, key string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create parent first, to maintain some semblance of order.
|
// Create parent first, to maintain some semblance of order.
|
||||||
if !addCommit(l.Parent, key) {
|
if l.Parent != "" {
|
||||||
return false
|
if !addCommit(pkg, l.Parent, key) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create commit.
|
// Create commit.
|
||||||
if err := postCommit(key, l); err != nil {
|
return postCommit(key, pkg, l)
|
||||||
log.Printf("failed to add %s to dashboard: %v", key, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fullHash returns the full hash for the given Mercurial revision.
|
// fullHash returns the full hash for the given Mercurial revision.
|
||||||
func fullHash(rev string) (hash string, err error) {
|
func fullHash(root, rev string) (hash string, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("fullHash: %s: %s", rev, err)
|
err = fmt.Errorf("fullHash: %s: %s", rev, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
s, _, err := runLog(nil, "", goroot,
|
s, _, err := runLog(nil, "", root,
|
||||||
"hg", "log",
|
"hg", "log",
|
||||||
"--encoding=utf-8",
|
"--encoding=utf-8",
|
||||||
"--rev="+rev,
|
"--rev="+rev,
|
||||||
@ -617,9 +642,21 @@ func firstTag(re *regexp.Regexp) (hash string, tag string, err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tag = s[1]
|
tag = s[1]
|
||||||
hash, err = fullHash(s[2])
|
hash, err = fullHash(goroot, s[2])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = errors.New("no matching tag found")
|
err = errors.New("no matching tag found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+(\.[a-z0-9\-]+)?)(/[a-z0-9A-Z_.\-/]+)?$`)
|
||||||
|
|
||||||
|
// repoURL returns the repository URL for the supplied import path.
|
||||||
|
func repoURL(importPath string) string {
|
||||||
|
m := repoRe.FindStringSubmatch(importPath)
|
||||||
|
if len(m) < 2 {
|
||||||
|
log.Printf("repoURL: couldn't decipher %q", importPath)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "https://code.google.com/p/" + m[1]
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user