// 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" "go/token" "io" "io/ioutil" "os" "path" "runtime" "strings" ) func usage() { fmt.Fprint(os.Stderr, "usage: goinstall importpath...\n") fmt.Fprintf(os.Stderr, "\tgoinstall -a\n") flag.PrintDefaults() os.Exit(2) } var ( fset = token.NewFileSet() argv0 = os.Args[0] errors = false parents = make(map[string]string) root = runtime.GOROOT() visit = make(map[string]status) logfile = path.Join(root, "goinstall.log") installedPkgs = make(map[string]bool) allpkg = flag.Bool("a", false, "install all previously installed packages") reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL) logPkgs = flag.Bool("log", true, "log installed packages to $GOROOT/goinstall.log for use by -a") update = flag.Bool("u", false, "update already-downloaded packages") clean = flag.Bool("clean", false, "clean the package directory before installing") 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/" // special case - "unsafe" is already installed visit["unsafe"] = done args := flag.Args() if *allpkg || *logPkgs { readPackageList() } if *allpkg { if len(args) != 0 { usage() // -a and package list both provided } // install all packages that were ever installed if len(installedPkgs) == 0 { fmt.Fprintf(os.Stderr, "%s: no installed packages\n", argv0) os.Exit(1) } args = make([]string, len(installedPkgs), len(installedPkgs)) i := 0 for pkg := range installedPkgs { args[i] = pkg i++ } } 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) } // readPackageList reads the list of installed packages from goinstall.log func readPackageList() { pkglistdata, _ := ioutil.ReadFile(logfile) pkglist := strings.Fields(string(pkglistdata)) for _, pkg := range pkglist { installedPkgs[pkg] = true } } // logPackage logs the named package as installed in goinstall.log, if the package is not found in there func logPackage(pkg string) { if installedPkgs[pkg] { return } fout, err := os.Open(logfile, os.O_WRONLY|os.O_APPEND|os.O_CREAT, 0644) if err != nil { fmt.Fprintf(os.Stderr, "%s: %s\n", argv0, err) return } fmt.Fprintf(fout, "%s\n", pkg) fout.Close() } // 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) } 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 strings.HasPrefix(pkg, "http://") { fmt.Fprintf(os.Stderr, "%s: %s: 'http://' used in remote path, try '%s'\n", argv0, pkg, pkg[7:]) errors = true return } 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. dirInfo, err := scanDir(dir, parent == "") if err != nil { fmt.Fprintf(os.Stderr, "%s: %s: %s\n", argv0, pkg, err) errors = true visit[pkg] = done return } if len(dirInfo.goFiles) == 0 { fmt.Fprintf(os.Stderr, "%s: %s: package has no files\n", argv0, pkg) errors = true visit[pkg] = done return } for _, p := range dirInfo.imports { if p != "C" { install(p, pkg) } } if dirInfo.pkgName == "main" { if !errors { fmt.Fprintf(os.Stderr, "%s: %s's dependencies are installed.\n", argv0, pkg) } errors = true visit[pkg] = done return } // 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 } else if !local && *logPkgs { // mark this package as installed in $GOROOT/goinstall.log logPackage(pkg) } } 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 quietRun. 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 err != nil { return err } 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 }