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.go334
1 files changed, 334 insertions, 0 deletions
diff --git a/src/cmd/goinstall/main.go b/src/cmd/goinstall/main.go
new file mode 100644
index 000000000..acda6efbb
--- /dev/null
+++ b/src/cmd/goinstall/main.go
@@ -0,0 +1,334 @@
+// 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.
+
+package main
+
+import (
+ "bytes"
+ "exec"
+ "flag"
+ "fmt"
+ "go/build"
+ "go/token"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+)
+
+func usage() {
+ fmt.Fprint(os.Stderr, "usage: goinstall importpath...\n")
+ fmt.Fprintf(os.Stderr, "\tgoinstall -a\n")
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+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)
+ schemeRe = regexp.MustCompile(`^[a-z]+://`)
+
+ 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")
+ 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")
+ verbose = flag.Bool("v", false, "verbose")
+)
+
+type status int // status for visited map
+const (
+ unvisited status = iota
+ visiting
+ done
+)
+
+func logf(format string, args ...interface{}) {
+ format = "%s: " + format
+ args = append([]interface{}{argv0}, args...)
+ fmt.Fprintf(os.Stderr, format, args...)
+}
+
+func printf(format string, args ...interface{}) {
+ if *verbose {
+ logf(format, args...)
+ }
+}
+
+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()
+ if runtime.GOROOT() == "" {
+ fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0)
+ os.Exit(1)
+ }
+ readPackageList()
+
+ // special case - "unsafe" is already installed
+ visit["unsafe"] = done
+
+ args := flag.Args()
+ if *allpkg {
+ if len(args) != 0 {
+ usage() // -a and package list both provided
+ }
+ // install all packages that were ever installed
+ n := 0
+ for _, pkgs := range installedPkgs {
+ for pkg := range pkgs {
+ args = append(args, pkg)
+ n++
+ }
+ }
+ if n == 0 {
+ logf("no installed packages\n")
+ os.Exit(1)
+ }
+ }
+ if len(args) == 0 {
+ usage()
+ }
+ for _, path := range args {
+ if s := schemeRe.FindString(path); s != "" {
+ errorf("%q used in import path, try %q\n", s, path[len(s):])
+ continue
+ }
+
+ 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)
+}
+
+// readPackageList reads the list of installed packages from the
+// goinstall.log files in GOROOT and the GOPATHs and initalizes
+// the installedPkgs variable.
+func readPackageList() {
+ for _, t := range build.Path {
+ installedPkgs[t.Path] = make(map[string]bool)
+ name := filepath.Join(t.Path, logfile)
+ pkglistdata, err := ioutil.ReadFile(name)
+ if err != nil {
+ printf("%s\n", err)
+ continue
+ }
+ pkglist := strings.Fields(string(pkglistdata))
+ for _, pkg := range pkglist {
+ installedPkgs[t.Path][pkg] = true
+ }
+ }
+}
+
+// logPackage logs the named package as installed in the goinstall.log file
+// in the given tree if the package is not already in that file.
+func logPackage(pkg string, tree *build.Tree) (logged bool) {
+ if installedPkgs[tree.Path][pkg] {
+ return false
+ }
+ 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)
+ return false
+ }
+ fmt.Fprintf(fout, "%s\n", pkg)
+ fout.Close()
+ return true
+}
+
+// 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 visit[pkg] {
+ 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)
+ }
+ parents[pkg] = parent
+ visit[pkg] = visiting
+ defer func() {
+ visit[pkg] = done
+ }()
+
+ // Don't allow trailing '/'
+ if _, f := filepath.Split(pkg); f == "" {
+ 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
+ }
+ // Download remote packages if not found or forced with -u flag.
+ remote, public := isRemote(pkg), false
+ if remote {
+ if err == build.ErrNotFound || (err == nil && *update) {
+ // Download remote package.
+ printf("%s: download\n", pkg)
+ public, err = download(pkg, tree.SrcDir())
+ } else {
+ // Test if this is a public repository
+ // (for reporting to dashboard).
+ m, _ := findPublicRepo(pkg)
+ public = m != nil
+ }
+ }
+ if err != nil {
+ terrorf(tree, "%s: %v\n", pkg, err)
+ return
+ }
+ dir := filepath.Join(tree.SrcDir(), pkg)
+
+ // Install prerequisites.
+ dirInfo, err := build.ScanDir(dir, parent == "")
+ if err != nil {
+ terrorf(tree, "%s: %v\n", pkg, err)
+ return
+ }
+ if len(dirInfo.GoFiles)+len(dirInfo.CgoFiles) == 0 {
+ terrorf(tree, "%s: package has no files\n", pkg)
+ return
+ }
+ for _, p := range dirInfo.Imports {
+ if p != "C" {
+ install(p, pkg)
+ }
+ }
+ if errors {
+ return
+ }
+
+ // Install this package.
+ if *useMake {
+ err := domake(dir, pkg, tree, dirInfo.IsCommand())
+ if err != nil {
+ terrorf(tree, "%s: install: %v\n", pkg, err)
+ return
+ }
+ } else {
+ script, err := build.Build(tree, pkg, dirInfo)
+ if err != nil {
+ terrorf(tree, "%s: install: %v\n", pkg, err)
+ return
+ }
+ if *nuke {
+ printf("%s: nuke\n", pkg)
+ script.Nuke()
+ } else if *clean {
+ printf("%s: clean\n", pkg)
+ script.Clean()
+ }
+ 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 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)
+ }
+ }
+}
+
+// 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 quietRun.
+func genRun(dir string, stdin []byte, arg []string, quiet bool) os.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)
+ }
+ return os.NewError("running " + arg[0] + ": " + err.String())
+ }
+ return nil
+}