mirror of
https://github.com/golang/go
synced 2024-10-01 01:28:32 -06: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:
parent
bc5f637240
commit
1d214a6a09
@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// run is a simple wrapper for exec.Run/Close
|
// 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 {
|
if *verbose {
|
||||||
log.Println("run", argv)
|
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 {
|
if err := cmd.Start(); err != nil {
|
||||||
return err
|
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
|
// 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.
|
// 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
|
// It returns exit status and error. The error returned is nil, if process
|
||||||
// is started successfully, even if exit status is not successful.
|
// 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 {
|
if *verbose {
|
||||||
log.Println("runOutput", argv)
|
log.Println("runOutput", argv)
|
||||||
}
|
}
|
||||||
@ -57,23 +65,40 @@ func runOutput(timeout time.Duration, envv []string, out io.Writer, dir string,
|
|||||||
if startErr != nil {
|
if startErr != nil {
|
||||||
return false, startErr
|
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 false, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitWithTimeout(timeout time.Duration, cmd *exec.Cmd) error {
|
// timeout runs f and returns its error value, or if the function does not
|
||||||
errc := make(chan error, 1)
|
// 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() {
|
go func() {
|
||||||
errc <- cmd.Wait()
|
errc <- f()
|
||||||
}()
|
}()
|
||||||
var err error
|
t := time.NewTimer(d)
|
||||||
|
defer t.Stop()
|
||||||
select {
|
select {
|
||||||
case <-time.After(timeout):
|
case <-t.C:
|
||||||
cmd.Process.Kill()
|
return fmt.Errorf("timed out after %v", d)
|
||||||
err = fmt.Errorf("timed out after %v", timeout)
|
case err := <-errc:
|
||||||
case err = <-errc:
|
return err
|
||||||
}
|
}
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
type TimeoutErr time.Duration
|
||||||
|
|
||||||
|
func (e TimeoutErr) Error() string {
|
||||||
|
return fmt.Sprintf("timed out after %v", time.Duration(e))
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,14 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/go/vcs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
codeProject = "go"
|
codeProject = "go"
|
||||||
codePyScript = "misc/dashboard/googlecode_upload.py"
|
codePyScript = "misc/dashboard/googlecode_upload.py"
|
||||||
hgUrl = "https://code.google.com/p/go/"
|
hgUrl = "code.google.com/p/go"
|
||||||
mkdirPerm = 0750
|
mkdirPerm = 0750
|
||||||
waitInterval = 30 * time.Second // time to wait before checking for new revs
|
waitInterval = 30 * time.Second // time to wait before checking for new revs
|
||||||
pkgBuildInterval = 24 * time.Hour // rebuild packages every 24 hours
|
pkgBuildInterval = 24 * time.Hour // rebuild packages every 24 hours
|
||||||
@ -85,8 +87,18 @@ func main() {
|
|||||||
if len(flag.Args()) == 0 {
|
if len(flag.Args()) == 0 {
|
||||||
flag.Usage()
|
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{
|
goroot := &Repo{
|
||||||
Path: filepath.Join(*buildroot, "goroot"),
|
Path: rootPath,
|
||||||
|
Master: rr,
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up work environment, use existing enviroment if possible
|
// 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)
|
log.Fatalf("Error making build root (%s): %s", *buildroot, err)
|
||||||
}
|
}
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
log.Fatal("Error cloning repository:", err)
|
log.Fatal("Error cloning repository:", err)
|
||||||
}
|
}
|
||||||
@ -253,13 +270,13 @@ func (b *Builder) buildHash(hash string) error {
|
|||||||
// create place in which to do work
|
// create place in which to do work
|
||||||
workpath := filepath.Join(*buildroot, b.name+"-"+hash[:12])
|
workpath := filepath.Join(*buildroot, b.name+"-"+hash[:12])
|
||||||
if err := os.Mkdir(workpath, mkdirPerm); err != nil {
|
if err := os.Mkdir(workpath, mkdirPerm); err != nil {
|
||||||
return err
|
//return err
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(workpath)
|
defer os.RemoveAll(workpath)
|
||||||
|
|
||||||
// pull before cloning to ensure we have the revision
|
// pull before cloning to ensure we have the revision
|
||||||
if err := b.goroot.Pull(); err != nil {
|
if err := b.goroot.Pull(); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone repo at specified revision
|
// clone repo at specified revision
|
||||||
@ -499,8 +516,13 @@ func commitWatcher(goroot *Repo) {
|
|||||||
commitPoll(goroot, "", key)
|
commitPoll(goroot, "", key)
|
||||||
// Go sub-repositories.
|
// Go sub-repositories.
|
||||||
for _, pkg := range dashboardPackages("subrepo") {
|
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{
|
pkgroot := &Repo{
|
||||||
Path: filepath.Join(*buildroot, pkg),
|
Path: filepath.Join(*buildroot, pkg),
|
||||||
|
Master: pkgmaster,
|
||||||
}
|
}
|
||||||
commitPoll(pkgroot, pkg, key)
|
commitPoll(pkgroot, pkg, key)
|
||||||
}
|
}
|
||||||
@ -518,9 +540,15 @@ var logByHash = map[string]*HgLog{}
|
|||||||
// 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(repo *Repo, pkg, key string) {
|
func commitPoll(repo *Repo, pkg, key string) {
|
||||||
|
pkgPath := filepath.Join(*buildroot, repo.Master.Root)
|
||||||
if !repo.Exists() {
|
if !repo.Exists() {
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
log.Printf("%s: hg clone failed: %v", pkg, err)
|
log.Printf("%s: hg clone failed: %v", pkg, err)
|
||||||
if err := os.RemoveAll(repo.Path); err != nil {
|
if err := os.RemoveAll(repo.Path); err != nil {
|
||||||
@ -600,18 +628,6 @@ func addCommit(pkg, hash, key string) bool {
|
|||||||
return true
|
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
|
// defaultSuffix returns file extension used for command files in
|
||||||
// current os environment.
|
// current os environment.
|
||||||
func defaultSuffix() string {
|
func defaultSuffix() string {
|
||||||
|
@ -7,25 +7,31 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/go/vcs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repo represents a mercurial repository.
|
// Repo represents a mercurial repository.
|
||||||
type Repo struct {
|
type Repo struct {
|
||||||
Path string
|
Path string
|
||||||
|
Master *vcs.RepoRoot
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoteRepo constructs a *Repo representing a remote repository.
|
// RemoteRepo constructs a *Repo representing a remote repository.
|
||||||
func RemoteRepo(url string) *Repo {
|
func RemoteRepo(url, path string) (*Repo, error) {
|
||||||
return &Repo{
|
rr, err := vcs.RepoRootForImportPath(url, *verbose)
|
||||||
Path: url,
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return &Repo{
|
||||||
|
Path: path,
|
||||||
|
Master: rr,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone clones the current Repo to a new destination
|
// 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) {
|
func (r *Repo) Clone(path, rev string) (*Repo, error) {
|
||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &Repo{
|
return &Repo{
|
||||||
Path: path,
|
Path: path,
|
||||||
|
Master: r.Master,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,12 +61,15 @@ func (r *Repo) Clone(path, rev string) (*Repo, error) {
|
|||||||
func (r *Repo) UpdateTo(hash string) error {
|
func (r *Repo) UpdateTo(hash string) error {
|
||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
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.
|
// Exists reports whether this Repo represents a valid Mecurial repository.
|
||||||
func (r *Repo) Exists() bool {
|
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 {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -63,7 +81,10 @@ func (r *Repo) Exists() bool {
|
|||||||
func (r *Repo) Pull() error {
|
func (r *Repo) Pull() error {
|
||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
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.
|
// Log returns the changelog for this repository.
|
||||||
@ -71,25 +92,25 @@ func (r *Repo) Log() ([]HgLog, error) {
|
|||||||
if err := r.Pull(); err != nil {
|
if err := r.Pull(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
const N = 50 // how many revisions to grab
|
|
||||||
|
|
||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
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 {
|
var logStruct struct {
|
||||||
Log []HgLog
|
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 {
|
if err != nil {
|
||||||
log.Printf("unmarshal hg log: %v", err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return logStruct.Log, nil
|
return logStruct.Log, nil
|
||||||
@ -99,28 +120,28 @@ func (r *Repo) Log() ([]HgLog, error) {
|
|||||||
func (r *Repo) FullHash(rev string) (string, error) {
|
func (r *Repo) FullHash(rev string) (string, error) {
|
||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
defer r.Unlock()
|
||||||
s, _, err := runLog(*cmdTimeout, nil, r.Path,
|
|
||||||
r.hgCmd("log",
|
|
||||||
"--encoding=utf-8",
|
|
||||||
"--rev="+rev,
|
|
||||||
"--limit=1",
|
|
||||||
"--template={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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) hgCmd(args ...string) []string {
|
var hash string
|
||||||
return append([]string{"hg", "--config", "extensions.codereview=!"}, args...)
|
err := timeout(*cmdTimeout, func() error {
|
||||||
|
data, err := r.Master.VCS.LogAtRev(r.Path, rev, "{node}")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
// HgLog represents a single Mercurial revision.
|
||||||
|
Loading…
Reference in New Issue
Block a user