| // 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 |
| } |