diff options
Diffstat (limited to 'src/cmd/goinstall/main.go')
-rw-r--r-- | src/cmd/goinstall/main.go | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/src/cmd/goinstall/main.go b/src/cmd/goinstall/main.go new file mode 100644 index 000000000..1be2bd600 --- /dev/null +++ b/src/cmd/goinstall/main.go @@ -0,0 +1,213 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Experimental Go package installer; see doc.go. + +package main + +import ( + "bytes" + "exec" + "flag" + "fmt" + "io" + "os" + "path" + "strings" +) + +func usage() { + fmt.Fprint(os.Stderr, "usage: goinstall importpath...\n") + flag.PrintDefaults() + os.Exit(2) +} + +var ( + argv0 = os.Args[0] + errors = false + gobin = os.Getenv("GOBIN") + parents = make(map[string]string) + root = os.Getenv("GOROOT") + visit = make(map[string]status) + + reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL) + update = flag.Bool("u", false, "update already-downloaded packages") + verbose = flag.Bool("v", false, "verbose") +) + +type status int // status for visited map +const ( + unvisited status = iota + visiting + done +) + +func main() { + flag.Usage = usage + flag.Parse() + if root == "" { + fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0) + os.Exit(1) + } + root += "/src/pkg/" + if gobin == "" { + gobin = os.Getenv("HOME") + "/bin" + } + + // special case - "unsafe" is already installed + visit["unsafe"] = done + + // install command line arguments + args := flag.Args() + if len(args) == 0 { + usage() + } + for _, path := range args { + install(path, "") + } + if errors { + os.Exit(1) + } +} + +// printDeps prints the dependency path that leads to pkg. +func printDeps(pkg string) { + if pkg == "" { + return + } + if visit[pkg] != done { + printDeps(parents[pkg]) + } + fmt.Fprintf(os.Stderr, "\t%s ->\n", pkg) +} + +// install installs the package named by path, which is needed by parent. +func install(pkg, parent string) { + // Make sure we're not already trying to install pkg. + switch v, _ := visit[pkg]; v { + case done: + return + case visiting: + fmt.Fprintf(os.Stderr, "%s: package dependency cycle\n", argv0) + printDeps(parent) + fmt.Fprintf(os.Stderr, "\t%s\n", pkg) + os.Exit(2) + } + visit[pkg] = visiting + parents[pkg] = parent + if *verbose { + fmt.Println(pkg) + } + + // Check whether package is local or remote. + // If remote, download or update it. + var dir string + local := false + if isLocalPath(pkg) { + dir = pkg + local = true + } else if isStandardPath(pkg) { + dir = path.Join(root, pkg) + local = true + } else { + var err os.Error + dir, err = download(pkg) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %s: %s\n", argv0, pkg, err) + errors = true + visit[pkg] = done + return + } + } + + // Install prerequisites. + files, m, err := goFiles(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %s: %s\n", argv0, pkg, err) + errors = true + visit[pkg] = done + return + } + if len(files) == 0 { + fmt.Fprintf(os.Stderr, "%s: %s: package has no files\n", argv0, pkg) + errors = true + visit[pkg] = done + return + } + for p := range m { + install(p, pkg) + } + + // Install this package. + if !errors { + if err := domake(dir, pkg, local); err != nil { + fmt.Fprintf(os.Stderr, "%s: installing %s: %s\n", argv0, pkg, err) + errors = true + } + } + + visit[pkg] = done +} + +// Is this a local path? /foo ./foo ../foo . .. +func isLocalPath(s string) bool { + return strings.HasPrefix(s, "/") || strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") || s == "." || s == ".." +} + +// Is this a standard package path? strings container/vector 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 { + dot := strings.Index(s, ".") + slash := strings.Index(s, "/") + return dot < 0 || 0 < slash && slash < dot +} + +// 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 tryRun. +func genRun(dir string, stdin []byte, cmd []string, quiet bool) os.Error { + bin, err := exec.LookPath(cmd[0]) + if err != nil { + return err + } + p, err := exec.Run(bin, cmd, os.Environ(), dir, exec.Pipe, exec.Pipe, exec.MergeWithStdout) + if *verbose { + fmt.Fprintf(os.Stderr, "%s: %s; %s %s\n", argv0, dir, bin, strings.Join(cmd[1:], " ")) + } + if err != nil { + return err + } + go func() { + p.Stdin.Write(stdin) + p.Stdin.Close() + }() + var buf bytes.Buffer + io.Copy(&buf, p.Stdout) + io.Copy(&buf, p.Stdout) + w, err := p.Wait(0) + p.Close() + if !w.Exited() || w.ExitStatus() != 0 { + if !quiet || *verbose { + if dir != "" { + dir = "cd " + dir + "; " + } + fmt.Fprintf(os.Stderr, "%s: === %s%s\n", argv0, dir, strings.Join(cmd, " ")) + os.Stderr.Write(buf.Bytes()) + fmt.Fprintf(os.Stderr, "--- %s\n", w) + } + return os.ErrorString("running " + cmd[0] + ": " + w.String()) + } + return nil +} |