]> Cypherpunks repositories - gostls13.git/commitdiff
gobuilder: use new dashboard protocol
authorAndrew Gerrand <adg@golang.org>
Mon, 5 Dec 2011 05:44:10 +0000 (16:44 +1100)
committerAndrew Gerrand <adg@golang.org>
Mon, 5 Dec 2011 05:44:10 +0000 (16:44 +1100)
gobuilder: -commit mode for packages
gobuilder: cripple -package mode temporarily

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5450092

misc/dashboard/builder/http.go
misc/dashboard/builder/main.go

index 3e2217f5412ffd77a303a796e65636978dfa8fe1..9de54a36947d25373a9e85c7704783635b8e819d 100644 (file)
@@ -8,96 +8,108 @@ import (
        "bytes"
        "encoding/json"
        "errors"
-       "fmt"
+       "io"
        "log"
        "net/http"
        "net/url"
-       "strconv"
 )
 
-type param map[string]string
+type obj map[string]interface{}
 
 // dash runs the given method and command on the dashboard.
-// If args is not nil, it is the query or post parameters.
-// If resp is not nil, dash unmarshals the body as JSON into resp.
-func dash(meth, cmd string, resp interface{}, args param) error {
+// If args is non-nil it is encoded as the URL query string.
+// If req is non-nil it is JSON-encoded and passed as the body of the HTTP POST.
+// 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 err error
        if *verbose {
-               log.Println("dash", cmd, args)
+               log.Println("dash", meth, cmd, args, req)
        }
        cmd = "http://" + *dashboard + "/" + cmd
-       vals := make(url.Values)
-       for k, v := range args {
-               vals.Add(k, v)
+       if len(args) > 0 {
+               cmd += "?" + args.Encode()
        }
        switch meth {
        case "GET":
-               if q := vals.Encode(); q != "" {
-                       cmd += "?" + q
+               if req != nil {
+                       log.Panicf("%s to %s with req", meth, cmd)
                }
                r, err = http.Get(cmd)
        case "POST":
-               r, err = http.PostForm(cmd, vals)
+               var body io.Reader
+               if req != nil {
+                       b, err := json.Marshal(req)
+                       if err != nil {
+                               return err
+                       }
+                       body = bytes.NewBuffer(b)
+               }
+               r, err = http.Post(cmd, "text/json", body)
        default:
-               return fmt.Errorf("unknown method %q", meth)
+               log.Panicf("%s: invalid method %q", cmd, meth)
+               panic("invalid method: " + meth)
        }
        if err != nil {
                return err
        }
+
        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
-               }
+       body := new(bytes.Buffer)
+       if _, err := body.ReadFrom(r.Body); err != nil {
+               return err
        }
-       return nil
-}
 
-func dashStatus(meth, cmd string, args param) error {
-       var resp struct {
-               Status string
-               Error  string
+       // 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,
        }
-       err := dash(meth, cmd, &resp, args)
-       if err != nil {
+       if err = json.Unmarshal(body.Bytes(), &result); err != nil {
+               log.Printf("json unmarshal %#q: %s\n", body.Bytes(), err)
                return err
        }
-       if resp.Status != "OK" {
-               return errors.New("/build: " + resp.Error)
+       if result.Error != "" {
+               return errors.New(result.Error)
        }
+
        return nil
 }
 
 // todo returns the next hash to build.
 func (b *Builder) todo() (rev string, err error) {
-       var resp []struct {
-               Hash string
-       }
-       if err = dash("GET", "todo", &resp, param{"builder": b.name}); err != nil {
+       // TODO(adg): handle packages
+       args := url.Values{"builder": {b.name}}
+       var resp string
+       if err = dash("GET", "todo", args, nil, &resp); err != nil {
                return
        }
-       if len(resp) > 0 {
-               rev = resp[0].Hash
+       if resp != "" {
+               rev = resp
        }
        return
 }
 
 // recordResult sends build results to the dashboard
 func (b *Builder) recordResult(buildLog string, hash string) error {
-       return dash("POST", "build", nil, param{
-               "builder": b.name,
-               "key":     b.key,
-               "node":    hash,
-               "log":     buildLog,
-       })
+       // TODO(adg): handle packages
+       return dash("POST", "result", url.Values{"key": {b.key}}, obj{
+               "Builder": b.name,
+               "Hash":    hash,
+               "Log":     buildLog,
+       }, nil)
 }
 
 // packages fetches a list of package paths from the dashboard
 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 {
                Packages []struct {
                        Path string
@@ -111,10 +123,13 @@ func packages() (pkgs []string, err error) {
                pkgs = append(pkgs, p.Path)
        }
        return
+       */
 }
 
 // updatePackage sends package build results and info dashboard
 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{
                "builder": b.name,
                "key":     b.key,
@@ -123,26 +138,44 @@ func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) erro
                "log":     buildLog,
                "info":    info,
        })
+       */
 }
 
-// postCommit informs the dashboard of a new commit
-func postCommit(key string, l *HgLog) error {
-       return dashStatus("POST", "commit", param{
-               "key":    key,
-               "node":   l.Hash,
-               "date":   l.Date,
-               "user":   l.Author,
-               "parent": l.Parent,
-               "desc":   l.Desc,
-       })
-}
-
-// dashboardCommit returns true if the dashboard knows about hash.
-func dashboardCommit(hash string) bool {
-       err := dashStatus("GET", "commit", param{"node": hash})
+func postCommit(key, pkg string, l *HgLog) bool {
+       err := dash("POST", "commit", url.Values{"key": {key}}, obj{
+               "PackagePath": pkg,
+               "Hash":        l.Hash,
+               "ParentHash":  l.Parent,
+               // TODO(adg): l.Date as int64 unix epoch secs in Time field
+               "User": l.Author,
+               "Desc": l.Desc,
+       }, 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 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
+}
index 101b77ed7671c0299d65af2d98e44e659c880633..aaeedcfb60832f0e83a9218d8da9910b4d561ed6 100644 (file)
@@ -13,6 +13,7 @@ import (
        "log"
        "os"
        "path"
+       "path/filepath"
        "regexp"
        "runtime"
        "strconv"
@@ -93,7 +94,7 @@ func main() {
        if err := os.Mkdir(*buildroot, mkdirPerm); err != nil {
                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)
        }
 
@@ -107,7 +108,7 @@ func main() {
 
        // if specified, build revision and return
        if *buildRevision != "" {
-               hash, err := fullHash(*buildRevision)
+               hash, err := fullHash(goroot, *buildRevision)
                if err != nil {
                        log.Fatal("Error finding revision: ", err)
                }
@@ -246,7 +247,7 @@ func (b *Builder) build() bool {
        }
        // 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.
                if err := run(nil, goroot, "hg", "pull"); err != nil {
                        log.Println("hg pull failed:", err)
@@ -425,11 +426,16 @@ func commitWatcher() {
        if err != nil {
                log.Fatal(err)
        }
+       key := b.key
+
        for {
                if *verbose {
                        log.Printf("poll...")
                }
-               commitPoll(b.key)
+               commitPoll(key, "")
+               for _, pkg := range dashboardPackages() {
+                       commitPoll(key, pkg)
+               }
                if *verbose {
                        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.
 type HgLog struct {
        Hash   string
@@ -467,7 +485,7 @@ const xmlLogTemplate = `
 
 // commitPoll pulls any new revisions from the hg server
 // and tells the server about them.
-func commitPoll(key string) {
+func commitPoll(key, pkg string) {
        // Catch unexpected panics.
        defer func() {
                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)
                return
        }
 
        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",
                "--limit="+strconv.Itoa(N),
                "--template="+xmlLogTemplate,
@@ -511,14 +544,11 @@ func commitPoll(key string) {
                if l.Parent == "" && i+1 < len(logs) {
                        l.Parent = logs[i+1].Hash
                } 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 l.Parent == "" {
-                       // Can't create node without parent.
-                       continue
+               if *verbose {
+                       log.Printf("hg log %s: %s < %s\n", pkg, l.Hash, l.Parent)
                }
-
                if logByHash[l.Hash] == nil {
                        // Make copy to avoid pinning entire slice when only one entry is new.
                        t := *l
@@ -528,17 +558,14 @@ func commitPoll(key string) {
 
        for i := range logs {
                l := &logs[i]
-               if l.Parent == "" {
-                       continue
-               }
-               addCommit(l.Hash, key)
+               addCommit(pkg, l.Hash, key)
        }
 }
 
 // addCommit adds the commit with the named hash to the dashboard.
 // key is the secret key for authentication to the dashboard.
 // It avoids duplicate effort.
-func addCommit(hash, key string) bool {
+func addCommit(pkg, hash, key string) bool {
        l := logByHash[hash]
        if l == nil {
                return false
@@ -548,7 +575,7 @@ func addCommit(hash, key string) bool {
        }
 
        // Check for already added, perhaps in an earlier run.
-       if dashboardCommit(hash) {
+       if dashboardCommit(pkg, hash) {
                log.Printf("%s already on dashboard\n", hash)
                // Record that this hash is on the dashboard,
                // 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.
-       if !addCommit(l.Parent, key) {
-               return false
+       if l.Parent != "" {
+               if !addCommit(pkg, l.Parent, key) {
+                       return false
+               }
        }
 
        // Create commit.
-       if err := postCommit(key, l); err != nil {
-               log.Printf("failed to add %s to dashboard: %v", key, err)
-               return false
-       }
-       return true
+       return postCommit(key, pkg, l)
 }
 
 // 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() {
                if err != nil {
                        err = fmt.Errorf("fullHash: %s: %s", rev, err)
                }
        }()
-       s, _, err := runLog(nil, "", goroot,
+       s, _, err := runLog(nil, "", root,
                "hg", "log",
                "--encoding=utf-8",
                "--rev="+rev,
@@ -617,9 +642,21 @@ func firstTag(re *regexp.Regexp) (hash string, tag string, err error) {
                        continue
                }
                tag = s[1]
-               hash, err = fullHash(s[2])
+               hash, err = fullHash(goroot, s[2])
                return
        }
        err = errors.New("no matching tag found")
        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]
+}