diff options
author | Ondřej Surý <ondrej@sury.org> | 2012-01-30 15:38:19 +0100 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2012-01-30 15:38:19 +0100 |
commit | 4cecda6c347bd6902b960c6a35a967add7070b0d (patch) | |
tree | a462e224ff41ec9f3eb1a0b6e815806f9e8804ad /src/cmd/goinstall/main.go | |
parent | 6c7ca6e4d4e26e4c8cbe0d183966011b3b088a0a (diff) | |
download | golang-4cecda6c347bd6902b960c6a35a967add7070b0d.tar.gz |
Imported Upstream version 2012.01.27upstream-weekly/2012.01.27
Diffstat (limited to 'src/cmd/goinstall/main.go')
-rw-r--r-- | src/cmd/goinstall/main.go | 289 |
1 files changed, 177 insertions, 112 deletions
diff --git a/src/cmd/goinstall/main.go b/src/cmd/goinstall/main.go index 478266357..bbc4b6b76 100644 --- a/src/cmd/goinstall/main.go +++ b/src/cmd/goinstall/main.go @@ -6,13 +6,14 @@ package main import ( "bytes" - "exec" + "errors" "flag" "fmt" "go/build" "go/token" "io/ioutil" "os" + "os/exec" "path/filepath" // use for file system paths "regexp" "runtime" @@ -31,7 +32,6 @@ const logfile = "goinstall.log" var ( fset = token.NewFileSet() argv0 = os.Args[0] - errors = false parents = make(map[string]string) visit = make(map[string]status) installedPkgs = make(map[string]map[string]bool) @@ -40,10 +40,11 @@ var ( allpkg = flag.Bool("a", false, "install all previously installed packages") reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL) update = flag.Bool("u", false, "update already-downloaded packages") + doGofix = flag.Bool("fix", false, "gofix each package before building it") doInstall = flag.Bool("install", true, "build and install") clean = flag.Bool("clean", false, "clean the package directory before installing") nuke = flag.Bool("nuke", false, "clean the package directory and target before installing") - useMake = flag.Bool("make", true, "use make to build and install") + useMake = flag.Bool("make", true, "use make to build and install (obsolete, always true)") verbose = flag.Bool("v", false, "verbose") ) @@ -54,6 +55,51 @@ const ( done ) +type PackageError struct { + pkg string + err error +} + +func (e *PackageError) Error() string { + return fmt.Sprintf("%s: %v", e.pkg, e.err) +} + +type DownloadError struct { + pkg string + goroot bool + err error +} + +func (e *DownloadError) Error() string { + s := fmt.Sprintf("%s: download failed: %v", e.pkg, e.err) + if e.goroot && os.Getenv("GOPATH") == "" { + s += " ($GOPATH is not set)" + } + return s +} + +type DependencyError PackageError + +func (e *DependencyError) Error() string { + return fmt.Sprintf("%s: depends on failing packages:\n\t%v", e.pkg, e.err) +} + +type BuildError PackageError + +func (e *BuildError) Error() string { + return fmt.Sprintf("%s: build failed: %v", e.pkg, e.err) +} + +type RunError struct { + cmd, dir string + out []byte + err error +} + +func (e *RunError) Error() string { + return fmt.Sprintf("%v\ncd %q && %q\n%s", e.err, e.dir, e.cmd, e.out) +} + func logf(format string, args ...interface{}) { format = "%s: " + format args = append([]interface{}{argv0}, args...) @@ -66,18 +112,6 @@ func printf(format string, args ...interface{}) { } } -func errorf(format string, args ...interface{}) { - errors = true - logf(format, args...) -} - -func terrorf(tree *build.Tree, format string, args ...interface{}) { - if tree != nil && tree.Goroot && os.Getenv("GOPATH") == "" { - format = strings.TrimRight(format, "\n") + " ($GOPATH not set)\n" - } - errorf(format, args...) -} - func main() { flag.Usage = usage flag.Parse() @@ -111,15 +145,14 @@ func main() { if len(args) == 0 { usage() } + errs := false for _, path := range args { - if s := schemeRe.FindString(path); s != "" { - errorf("%q used in import path, try %q\n", s, path[len(s):]) - continue + if err := install(path, ""); err != nil { + errs = true + fmt.Fprintln(os.Stderr, err) } - - install(path, "") } - if errors { + if errs { os.Exit(1) } } @@ -136,7 +169,7 @@ func printDeps(pkg string) { } // readPackageList reads the list of installed packages from the -// goinstall.log files in GOROOT and the GOPATHs and initalizes +// goinstall.log files in GOROOT and the GOPATHs and initializes // the installedPkgs variable. func readPackageList() { for _, t := range build.Path { @@ -163,7 +196,7 @@ func logPackage(pkg string, tree *build.Tree) (logged bool) { name := filepath.Join(tree.Path, logfile) fout, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { - terrorf(tree, "package log: %s\n", err) + printf("package log: %s\n", err) return false } fmt.Fprintf(fout, "%s\n", pkg) @@ -172,11 +205,19 @@ func logPackage(pkg string, tree *build.Tree) (logged bool) { } // install installs the package named by path, which is needed by parent. -func install(pkg, parent string) { +func install(pkg, parent string) error { + // Basic validation of import path string. + if s := schemeRe.FindString(pkg); s != "" { + return fmt.Errorf("%q used in import path, try %q\n", s, pkg[len(s):]) + } + if strings.HasSuffix(pkg, "/") { + return fmt.Errorf("%q should not have trailing '/'\n", pkg) + } + // Make sure we're not already trying to install pkg. switch visit[pkg] { case done: - return + return nil case visiting: fmt.Fprintf(os.Stderr, "%s: package dependency cycle\n", argv0) printDeps(parent) @@ -189,24 +230,17 @@ func install(pkg, parent string) { visit[pkg] = done }() - // Don't allow trailing '/' - if strings.HasSuffix(pkg, "/") { - errorf("%s should not have trailing '/'\n", pkg) - return - } - // Check whether package is local or remote. // If remote, download or update it. tree, pkg, err := build.FindTree(pkg) // Don't build the standard library. if err == nil && tree.Goroot && isStandardPath(pkg) { if parent == "" { - errorf("%s: can not goinstall the standard library\n", pkg) - } else { - printf("%s: skipping standard library\n", pkg) + return &PackageError{pkg, errors.New("cannot goinstall the standard library")} } - return + return nil } + // Download remote packages if not found or forced with -u flag. remote, public := isRemote(pkg), false if remote { @@ -214,85 +248,118 @@ func install(pkg, parent string) { // Download remote package. printf("%s: download\n", pkg) public, err = download(pkg, tree.SrcDir()) + if err != nil { + // only suggest -fix if the bad import was not on the command line + if e, ok := err.(*errOldGoogleRepo); ok && parent != "" { + err = fmt.Errorf("%v\nRun goinstall with -fix to gofix the code.", e) + } + return &DownloadError{pkg, tree.Goroot, err} + } } else { // Test if this is a public repository // (for reporting to dashboard). - m, _ := findPublicRepo(pkg) - public = m != nil + repo, e := findPublicRepo(pkg) + public = repo != nil + err = e } } if err != nil { - terrorf(tree, "%s: %v\n", pkg, err) - return + return &PackageError{pkg, err} } - dir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg)) - // Install prerequisites. + // Install the package and its dependencies. + if err := installPackage(pkg, parent, tree, false); err != nil { + return err + } + + if remote { + // mark package as installed in goinstall.log + logged := logPackage(pkg, tree) + + // report installation to the dashboard if this is the first + // install from a public repository. + if logged && public { + maybeReportToDashboard(pkg) + } + } + + return nil +} + +// installPackage installs the specified package and its dependencies. +func installPackage(pkg, parent string, tree *build.Tree, retry bool) (installErr error) { + printf("%s: install\n", pkg) + + // Read package information. + dir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg)) dirInfo, err := build.ScanDir(dir) if err != nil { - terrorf(tree, "%s: %v\n", pkg, err) - return + return &PackageError{pkg, err} } + // We reserve package main to identify commands. if parent != "" && dirInfo.Package == "main" { - terrorf(tree, "%s: found only package main in %s; cannot import", pkg, dir) - return - } - for _, p := range dirInfo.Imports { - if p != "C" { - install(p, pkg) - } - } - if errors { - return + return &PackageError{pkg, fmt.Errorf("found only package main in %s; cannot import", dir)} } - // Install this package. - if *useMake { - err := domake(dir, pkg, tree, dirInfo.IsCommand()) - if err != nil { - terrorf(tree, "%s: install: %v\n", pkg, err) + // Run gofix if we fail to build and -fix is set. + defer func() { + if retry || installErr == nil || !*doGofix { return } - } else { - script, err := build.Build(tree, pkg, dirInfo) - if err != nil { - terrorf(tree, "%s: install: %v\n", pkg, err) - return + if e, ok := (installErr).(*DependencyError); ok { + // If this package failed to build due to a + // DependencyError, only attempt to gofix it if its + // dependency failed for some reason other than a + // DependencyError or BuildError. + // (If a dep or one of its deps doesn't build there's + // no way that gofixing this package can help.) + switch e.err.(type) { + case *DependencyError: + return + case *BuildError: + return + } } - if *nuke { - printf("%s: nuke\n", pkg) - script.Nuke() - } else if *clean { - printf("%s: clean\n", pkg) - script.Clean() + gofix(pkg, dir, dirInfo) + installErr = installPackage(pkg, parent, tree, true) // retry + }() + + // Install prerequisites. + for _, p := range dirInfo.Imports { + if p == "C" { + continue } - if *doInstall { - if script.Stale() { - printf("%s: install\n", pkg) - if err := script.Run(); err != nil { - terrorf(tree, "%s: install: %v\n", pkg, err) - return - } - } else { - printf("%s: up-to-date\n", pkg) - } + if err := install(p, pkg); err != nil { + return &DependencyError{pkg, err} } } - if remote { - // mark package as installed in goinstall.log - logged := logPackage(pkg, tree) + // Install this package. + err = domake(dir, pkg, tree, dirInfo.IsCommand()) + if err != nil { + return &BuildError{pkg, err} + } + return nil +} - // report installation to the dashboard if this is the first - // install from a public repository. - if logged && public { - maybeReportToDashboard(pkg) - } +// gofix runs gofix against the GoFiles and CgoFiles of dirInfo in dir. +func gofix(pkg, dir string, dirInfo *build.DirInfo) { + printf("%s: gofix\n", pkg) + files := append([]string{}, dirInfo.GoFiles...) + files = append(files, dirInfo.CgoFiles...) + for i, file := range files { + files[i] = filepath.Join(dir, file) + } + cmd := exec.Command("gofix", files...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + logf("%s: gofix: %v", pkg, err) } } -// Is this a standard package path? strings container/vector etc. +// Is this a standard package path? strings container/list etc. // Assume that if the first element has a dot, it's a domain name // and is not the standard package path. func isStandardPath(s string) bool { @@ -302,34 +369,32 @@ func isStandardPath(s string) bool { } // run runs the command cmd in directory dir with standard input stdin. -// If the command fails, run prints the command and output on standard error -// in addition to returning a non-nil os.Error. -func run(dir string, stdin []byte, cmd ...string) os.Error { - return genRun(dir, stdin, cmd, false) -} - -// quietRun is like run but prints nothing on failure unless -v is used. -func quietRun(dir string, stdin []byte, cmd ...string) os.Error { - return genRun(dir, stdin, cmd, true) -} - -// genRun implements run and quietRun. -func genRun(dir string, stdin []byte, arg []string, quiet bool) os.Error { +// If verbose is set and the command fails it prints the output to stderr. +func run(dir string, stdin []byte, arg ...string) error { cmd := exec.Command(arg[0], arg[1:]...) cmd.Stdin = bytes.NewBuffer(stdin) cmd.Dir = dir - printf("%s: %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " ")) - out, err := cmd.CombinedOutput() - if err != nil { - if !quiet || *verbose { - if dir != "" { - dir = "cd " + dir + "; " - } - fmt.Fprintf(os.Stderr, "%s: === %s%s\n", cmd.Path, dir, strings.Join(cmd.Args, " ")) - os.Stderr.Write(out) - fmt.Fprintf(os.Stderr, "--- %s\n", err) + printf("cd %s && %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " ")) + if out, err := cmd.CombinedOutput(); err != nil { + if *verbose { + fmt.Fprintf(os.Stderr, "%v\n%s\n", err, out) } - return os.NewError("running " + arg[0] + ": " + err.String()) + return &RunError{strings.Join(arg, " "), dir, out, err} } return nil } + +// isRemote returns true if the first part of the package name looks like a +// hostname - i.e. contains at least one '.' and the last part is at least 2 +// characters. +func isRemote(pkg string) bool { + parts := strings.SplitN(pkg, "/", 2) + if len(parts) != 2 { + return false + } + parts = strings.Split(parts[0], ".") + if len(parts) < 2 || len(parts[len(parts)-1]) < 2 { + return false + } + return true +} |