1
0
mirror of https://github.com/golang/go synced 2024-11-18 18:04:46 -07:00

go.tools/dashboard: implement dashboard using vcs package.

To make the dashboard more flexible with different VCS, the Repo now depends on the VCS package for running commands.

* Exported Repo.Master
* Modified RemoteRepo to return a repo and an error
* Reimplemented all Repo methods using vcs package
* Removed hgCmd
* Removed repoURL
* Removed scheme from hgUrl since vcs.RepoRootForImportPath decides the scheme.
* Changed waitWithTimeout into timeout
* Added waitForFuncWithTimeout to wrap vcs commands with a timeout.

R=adg
CC=golang-dev
https://golang.org/cl/13201043
This commit is contained in:
Chris Manghane 2013-08-27 21:52:18 -07:00
parent bc5f637240
commit 1d214a6a09
3 changed files with 138 additions and 76 deletions

View File

@ -15,7 +15,7 @@ import (
)
// run is a simple wrapper for exec.Run/Close
func run(timeout time.Duration, envv []string, dir string, argv ...string) error {
func run(d time.Duration, envv []string, dir string, argv ...string) error {
if *verbose {
log.Println("run", argv)
}
@ -26,7 +26,15 @@ func run(timeout time.Duration, envv []string, dir string, argv ...string) error
if err := cmd.Start(); err != nil {
return err
}
return waitWithTimeout(timeout, cmd)
return timeout(d, func() error {
if err := cmd.Wait(); err != nil {
if _, ok := err.(TimeoutErr); ok {
cmd.Process.Kill()
}
return err
}
return nil
})
}
// runLog runs a process and returns the combined stdout/stderr. It returns
@ -42,7 +50,7 @@ func runLog(timeout time.Duration, envv []string, dir string, argv ...string) (s
// runOutput runs a process and directs any output to the supplied writer.
// It returns exit status and error. The error returned is nil, if process
// is started successfully, even if exit status is not successful.
func runOutput(timeout time.Duration, envv []string, out io.Writer, dir string, argv ...string) (bool, error) {
func runOutput(d time.Duration, envv []string, out io.Writer, dir string, argv ...string) (bool, error) {
if *verbose {
log.Println("runOutput", argv)
}
@ -57,23 +65,40 @@ func runOutput(timeout time.Duration, envv []string, out io.Writer, dir string,
if startErr != nil {
return false, startErr
}
if err := waitWithTimeout(timeout, cmd); err != nil {
if err := timeout(d, func() error {
if err := cmd.Wait(); err != nil {
if _, ok := err.(TimeoutErr); ok {
cmd.Process.Kill()
}
return err
}
return nil
}); err != nil {
return false, err
}
return true, nil
}
func waitWithTimeout(timeout time.Duration, cmd *exec.Cmd) error {
errc := make(chan error, 1)
// timeout runs f and returns its error value, or if the function does not
// complete before the provided duration it returns a timeout error.
func timeout(d time.Duration, f func() error) error {
errc := make(chan error)
go func() {
errc <- cmd.Wait()
errc <- f()
}()
var err error
t := time.NewTimer(d)
defer t.Stop()
select {
case <-time.After(timeout):
cmd.Process.Kill()
err = fmt.Errorf("timed out after %v", timeout)
case err = <-errc:
}
case <-t.C:
return fmt.Errorf("timed out after %v", d)
case err := <-errc:
return err
}
}
type TimeoutErr time.Duration
func (e TimeoutErr) Error() string {
return fmt.Sprintf("timed out after %v", time.Duration(e))
}

View File

@ -17,12 +17,14 @@ import (
"runtime"
"strings"
"time"
"code.google.com/p/go.tools/go/vcs"
)
const (
codeProject = "go"
codePyScript = "misc/dashboard/googlecode_upload.py"
hgUrl = "https://code.google.com/p/go/"
hgUrl = "code.google.com/p/go"
mkdirPerm = 0750
waitInterval = 30 * time.Second // time to wait before checking for new revs
pkgBuildInterval = 24 * time.Hour // rebuild packages every 24 hours
@ -85,8 +87,18 @@ func main() {
if len(flag.Args()) == 0 {
flag.Usage()
}
vcs.ShowCmd = *verbose
vcs.Verbose = *verbose
rr, err := vcs.RepoRootForImportPath(hgUrl, *verbose)
if err != nil {
log.Fatal("Error finding repository:", err)
}
rootPath := filepath.Join(*buildroot, "goroot")
goroot := &Repo{
Path: filepath.Join(*buildroot, "goroot"),
Path: rootPath,
Master: rr,
}
// set up work environment, use existing enviroment if possible
@ -100,7 +112,12 @@ func main() {
log.Fatalf("Error making build root (%s): %s", *buildroot, err)
}
var err error
goroot, err = RemoteRepo(hgUrl).Clone(goroot.Path, "tip")
goroot, err = RemoteRepo(hgUrl, rootPath)
if err != nil {
log.Fatalf("Error creating repository with url (%s): %s", hgUrl, err)
}
goroot, err = goroot.Clone(goroot.Path, "tip")
if err != nil {
log.Fatal("Error cloning repository:", err)
}
@ -253,13 +270,13 @@ func (b *Builder) buildHash(hash string) error {
// create place in which to do work
workpath := filepath.Join(*buildroot, b.name+"-"+hash[:12])
if err := os.Mkdir(workpath, mkdirPerm); err != nil {
return err
//return err
}
defer os.RemoveAll(workpath)
// pull before cloning to ensure we have the revision
if err := b.goroot.Pull(); err != nil {
return err
// return err
}
// clone repo at specified revision
@ -499,8 +516,13 @@ func commitWatcher(goroot *Repo) {
commitPoll(goroot, "", key)
// Go sub-repositories.
for _, pkg := range dashboardPackages("subrepo") {
pkgmaster, err := vcs.RepoRootForImportPath(pkg, *verbose)
if err != nil {
log.Fatalf("Error finding subrepo (%s): %s", pkg, err)
}
pkgroot := &Repo{
Path: filepath.Join(*buildroot, pkg),
Master: pkgmaster,
}
commitPoll(pkgroot, pkg, key)
}
@ -518,9 +540,15 @@ var logByHash = map[string]*HgLog{}
// commitPoll pulls any new revisions from the hg server
// and tells the server about them.
func commitPoll(repo *Repo, pkg, key string) {
pkgPath := filepath.Join(*buildroot, repo.Master.Root)
if !repo.Exists() {
var err error
repo, err = RemoteRepo(repoURL(pkg)).Clone(repo.Path, "tip")
repo, err = RemoteRepo(pkg, pkgPath)
if err != nil {
log.Printf("Error cloning package (%s): %s", pkg, err)
}
repo, err = repo.Clone(repo.Path, "tip")
if err != nil {
log.Printf("%s: hg clone failed: %v", pkg, err)
if err := os.RemoveAll(repo.Path); err != nil {
@ -600,18 +628,6 @@ func addCommit(pkg, hash, key string) bool {
return true
}
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]
}
// defaultSuffix returns file extension used for command files in
// current os environment.
func defaultSuffix() string {

View File

@ -7,25 +7,31 @@ package main
import (
"encoding/xml"
"fmt"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"code.google.com/p/go.tools/go/vcs"
)
// Repo represents a mercurial repository.
type Repo struct {
Path string
Master *vcs.RepoRoot
sync.Mutex
}
// RemoteRepo constructs a *Repo representing a remote repository.
func RemoteRepo(url string) *Repo {
return &Repo{
Path: url,
func RemoteRepo(url, path string) (*Repo, error) {
rr, err := vcs.RepoRootForImportPath(url, *verbose)
if err != nil {
return nil, err
}
return &Repo{
Path: path,
Master: rr,
}, nil
}
// Clone clones the current Repo to a new destination
@ -33,11 +39,20 @@ func RemoteRepo(url string) *Repo {
func (r *Repo) Clone(path, rev string) (*Repo, error) {
r.Lock()
defer r.Unlock()
if err := run(*cmdTimeout, nil, *buildroot, r.hgCmd("clone", "-r", rev, r.Path, path)...); err != nil {
err := timeout(*cmdTimeout, func() error {
err := r.Master.VCS.CreateAtRev(path, r.Master.Repo, rev)
if err != nil {
return err
}
return r.Master.VCS.TagSync(path, "")
})
if err != nil {
return nil, err
}
return &Repo{
Path: path,
Master: r.Master,
}, nil
}
@ -46,12 +61,15 @@ func (r *Repo) Clone(path, rev string) (*Repo, error) {
func (r *Repo) UpdateTo(hash string) error {
r.Lock()
defer r.Unlock()
return run(*cmdTimeout, nil, r.Path, r.hgCmd("update", hash)...)
return timeout(*cmdTimeout, func() error {
return r.Master.VCS.TagSync(r.Path, hash)
})
}
// Exists reports whether this Repo represents a valid Mecurial repository.
func (r *Repo) Exists() bool {
fi, err := os.Stat(filepath.Join(r.Path, ".hg"))
fi, err := os.Stat(filepath.Join(r.Path, "."+r.Master.VCS.Cmd))
if err != nil {
return false
}
@ -63,7 +81,10 @@ func (r *Repo) Exists() bool {
func (r *Repo) Pull() error {
r.Lock()
defer r.Unlock()
return run(*cmdTimeout, nil, r.Path, r.hgCmd("pull")...)
return timeout(*cmdTimeout, func() error {
return r.Master.VCS.Download(r.Path)
})
}
// Log returns the changelog for this repository.
@ -71,25 +92,25 @@ func (r *Repo) Log() ([]HgLog, error) {
if err := r.Pull(); err != nil {
return nil, err
}
const N = 50 // how many revisions to grab
r.Lock()
defer r.Unlock()
data, _, err := runLog(*cmdTimeout, nil, r.Path, r.hgCmd("log",
"--encoding=utf-8",
"--limit="+strconv.Itoa(N),
"--template="+xmlLogTemplate)...,
)
if err != nil {
return nil, err
}
var logStruct struct {
Log []HgLog
}
err = xml.Unmarshal([]byte("<Top>"+data+"</Top>"), &logStruct)
err := timeout(*cmdTimeout, func() error {
data, err := r.Master.VCS.Log(r.Path, xmlLogTemplate)
if err != nil {
return err
}
err = xml.Unmarshal([]byte("<Top>"+string(data)+"</Top>"), &logStruct)
if err != nil {
return fmt.Errorf("unmarshal %s log: %v", r.Master.VCS, err)
}
return nil
})
if err != nil {
log.Printf("unmarshal hg log: %v", err)
return nil, err
}
return logStruct.Log, nil
@ -99,28 +120,28 @@ func (r *Repo) Log() ([]HgLog, error) {
func (r *Repo) FullHash(rev string) (string, error) {
r.Lock()
defer r.Unlock()
s, _, err := runLog(*cmdTimeout, nil, r.Path,
r.hgCmd("log",
"--encoding=utf-8",
"--rev="+rev,
"--limit=1",
"--template={node}")...,
)
var hash string
err := timeout(*cmdTimeout, func() error {
data, err := r.Master.VCS.LogAtRev(r.Path, rev, "{node}")
if err != nil {
return "", nil
}
s = strings.TrimSpace(s)
if s == "" {
return "", fmt.Errorf("cannot find revision")
}
if len(s) != 40 {
return "", fmt.Errorf("hg returned invalid hash " + s)
}
return s, nil
return err
}
func (r *Repo) hgCmd(args ...string) []string {
return append([]string{"hg", "--config", "extensions.codereview=!"}, args...)
s := strings.TrimSpace(string(data))
if s == "" {
return fmt.Errorf("cannot find revision")
}
if len(s) != 40 {
return fmt.Errorf("%s returned invalid hash: %s", r.Master.VCS, s)
}
hash = s
return nil
})
if err != nil {
return "", err
}
return hash, nil
}
// HgLog represents a single Mercurial revision.