summaryrefslogtreecommitdiff
path: root/src/cmd/goinstall/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/goinstall/main.go')
-rw-r--r--src/cmd/goinstall/main.go289
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
+}