Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 1 | // Copyright 2010 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | // Experimental Go package installer; see doc.go. |
| 6 | |
| 7 | package main |
| 8 | |
| 9 | import ( |
| 10 | "bytes" |
| 11 | "exec" |
| 12 | "flag" |
| 13 | "fmt" |
| 14 | "io" |
| 15 | "os" |
| 16 | "path" |
Russ Cox | da392d9 | 2010-08-18 10:08:49 -0400 | [diff] [blame] | 17 | "runtime" |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 18 | "strings" |
| 19 | ) |
| 20 | |
| 21 | func usage() { |
| 22 | fmt.Fprint(os.Stderr, "usage: goinstall importpath...\n") |
| 23 | flag.PrintDefaults() |
| 24 | os.Exit(2) |
| 25 | } |
| 26 | |
| 27 | var ( |
| 28 | argv0 = os.Args[0] |
| 29 | errors = false |
| 30 | gobin = os.Getenv("GOBIN") |
| 31 | parents = make(map[string]string) |
Russ Cox | da392d9 | 2010-08-18 10:08:49 -0400 | [diff] [blame] | 32 | root = runtime.GOROOT() |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 33 | visit = make(map[string]status) |
| 34 | |
| 35 | reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL) |
| 36 | update = flag.Bool("u", false, "update already-downloaded packages") |
| 37 | verbose = flag.Bool("v", false, "verbose") |
| 38 | ) |
| 39 | |
| 40 | type status int // status for visited map |
| 41 | const ( |
| 42 | unvisited status = iota |
| 43 | visiting |
| 44 | done |
| 45 | ) |
| 46 | |
| 47 | func main() { |
| 48 | flag.Usage = usage |
| 49 | flag.Parse() |
| 50 | if root == "" { |
| 51 | fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0) |
| 52 | os.Exit(1) |
| 53 | } |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 54 | if gobin == "" { |
Russ Cox | aafe474e | 2010-08-24 20:00:33 -0400 | [diff] [blame] | 55 | gobin = root + "/bin" |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 56 | } |
Russ Cox | aafe474e | 2010-08-24 20:00:33 -0400 | [diff] [blame] | 57 | root += "/src/pkg/" |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 58 | |
| 59 | // special case - "unsafe" is already installed |
| 60 | visit["unsafe"] = done |
| 61 | |
| 62 | // install command line arguments |
| 63 | args := flag.Args() |
| 64 | if len(args) == 0 { |
| 65 | usage() |
| 66 | } |
| 67 | for _, path := range args { |
| 68 | install(path, "") |
| 69 | } |
| 70 | if errors { |
| 71 | os.Exit(1) |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | // printDeps prints the dependency path that leads to pkg. |
| 76 | func printDeps(pkg string) { |
| 77 | if pkg == "" { |
| 78 | return |
| 79 | } |
| 80 | if visit[pkg] != done { |
| 81 | printDeps(parents[pkg]) |
| 82 | } |
| 83 | fmt.Fprintf(os.Stderr, "\t%s ->\n", pkg) |
| 84 | } |
| 85 | |
| 86 | // install installs the package named by path, which is needed by parent. |
| 87 | func install(pkg, parent string) { |
| 88 | // Make sure we're not already trying to install pkg. |
Russ Cox | c7122a3 | 2010-03-30 10:51:11 -0700 | [diff] [blame] | 89 | switch visit[pkg] { |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 90 | case done: |
| 91 | return |
| 92 | case visiting: |
| 93 | fmt.Fprintf(os.Stderr, "%s: package dependency cycle\n", argv0) |
| 94 | printDeps(parent) |
| 95 | fmt.Fprintf(os.Stderr, "\t%s\n", pkg) |
| 96 | os.Exit(2) |
| 97 | } |
| 98 | visit[pkg] = visiting |
| 99 | parents[pkg] = parent |
| 100 | if *verbose { |
| 101 | fmt.Println(pkg) |
| 102 | } |
| 103 | |
| 104 | // Check whether package is local or remote. |
| 105 | // If remote, download or update it. |
| 106 | var dir string |
| 107 | local := false |
| 108 | if isLocalPath(pkg) { |
| 109 | dir = pkg |
| 110 | local = true |
| 111 | } else if isStandardPath(pkg) { |
| 112 | dir = path.Join(root, pkg) |
| 113 | local = true |
| 114 | } else { |
| 115 | var err os.Error |
| 116 | dir, err = download(pkg) |
| 117 | if err != nil { |
| 118 | fmt.Fprintf(os.Stderr, "%s: %s: %s\n", argv0, pkg, err) |
| 119 | errors = true |
| 120 | visit[pkg] = done |
| 121 | return |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | // Install prerequisites. |
Roger Peppe | 3ce2938 | 2010-06-21 11:01:20 -0700 | [diff] [blame] | 126 | files, m, pkgname, err := goFiles(dir, parent == "") |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 127 | if err != nil { |
| 128 | fmt.Fprintf(os.Stderr, "%s: %s: %s\n", argv0, pkg, err) |
| 129 | errors = true |
| 130 | visit[pkg] = done |
| 131 | return |
| 132 | } |
| 133 | if len(files) == 0 { |
| 134 | fmt.Fprintf(os.Stderr, "%s: %s: package has no files\n", argv0, pkg) |
| 135 | errors = true |
| 136 | visit[pkg] = done |
| 137 | return |
| 138 | } |
| 139 | for p := range m { |
| 140 | install(p, pkg) |
| 141 | } |
Roger Peppe | 3ce2938 | 2010-06-21 11:01:20 -0700 | [diff] [blame] | 142 | if pkgname == "main" { |
| 143 | if !errors { |
| 144 | fmt.Fprintf(os.Stderr, "%s: %s's dependencies are installed.\n", argv0, pkg) |
| 145 | } |
| 146 | errors = true |
| 147 | visit[pkg] = done |
| 148 | return |
| 149 | } |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 150 | |
| 151 | // Install this package. |
| 152 | if !errors { |
| 153 | if err := domake(dir, pkg, local); err != nil { |
| 154 | fmt.Fprintf(os.Stderr, "%s: installing %s: %s\n", argv0, pkg, err) |
| 155 | errors = true |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | visit[pkg] = done |
| 160 | } |
| 161 | |
| 162 | // Is this a local path? /foo ./foo ../foo . .. |
| 163 | func isLocalPath(s string) bool { |
| 164 | return strings.HasPrefix(s, "/") || strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") || s == "." || s == ".." |
| 165 | } |
| 166 | |
| 167 | // Is this a standard package path? strings container/vector etc. |
| 168 | // Assume that if the first element has a dot, it's a domain name |
| 169 | // and is not the standard package path. |
| 170 | func isStandardPath(s string) bool { |
| 171 | dot := strings.Index(s, ".") |
| 172 | slash := strings.Index(s, "/") |
| 173 | return dot < 0 || 0 < slash && slash < dot |
| 174 | } |
| 175 | |
| 176 | // run runs the command cmd in directory dir with standard input stdin. |
| 177 | // If the command fails, run prints the command and output on standard error |
| 178 | // in addition to returning a non-nil os.Error. |
| 179 | func run(dir string, stdin []byte, cmd ...string) os.Error { |
| 180 | return genRun(dir, stdin, cmd, false) |
| 181 | } |
| 182 | |
| 183 | // quietRun is like run but prints nothing on failure unless -v is used. |
| 184 | func quietRun(dir string, stdin []byte, cmd ...string) os.Error { |
| 185 | return genRun(dir, stdin, cmd, true) |
| 186 | } |
| 187 | |
Gustavo Niemeyer | ae33032 | 2010-06-30 23:33:49 -0700 | [diff] [blame] | 188 | // genRun implements run and quietRun. |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 189 | func genRun(dir string, stdin []byte, cmd []string, quiet bool) os.Error { |
| 190 | bin, err := exec.LookPath(cmd[0]) |
| 191 | if err != nil { |
Andrey Mirtchovski | 456642a | 2010-03-23 18:13:16 -0700 | [diff] [blame] | 192 | // report binary as well as the error |
| 193 | return os.NewError(cmd[0] + ": " + err.String()) |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 194 | } |
| 195 | p, err := exec.Run(bin, cmd, os.Environ(), dir, exec.Pipe, exec.Pipe, exec.MergeWithStdout) |
| 196 | if *verbose { |
| 197 | fmt.Fprintf(os.Stderr, "%s: %s; %s %s\n", argv0, dir, bin, strings.Join(cmd[1:], " ")) |
| 198 | } |
| 199 | if err != nil { |
| 200 | return err |
| 201 | } |
| 202 | go func() { |
| 203 | p.Stdin.Write(stdin) |
| 204 | p.Stdin.Close() |
| 205 | }() |
| 206 | var buf bytes.Buffer |
| 207 | io.Copy(&buf, p.Stdout) |
| 208 | io.Copy(&buf, p.Stdout) |
| 209 | w, err := p.Wait(0) |
| 210 | p.Close() |
Alex Brainman | 12576f9 | 2010-08-04 17:18:57 -0700 | [diff] [blame] | 211 | if err != nil { |
| 212 | return err |
| 213 | } |
Russ Cox | 3e4e4ec | 2010-03-04 17:04:50 -0800 | [diff] [blame] | 214 | if !w.Exited() || w.ExitStatus() != 0 { |
| 215 | if !quiet || *verbose { |
| 216 | if dir != "" { |
| 217 | dir = "cd " + dir + "; " |
| 218 | } |
| 219 | fmt.Fprintf(os.Stderr, "%s: === %s%s\n", argv0, dir, strings.Join(cmd, " ")) |
| 220 | os.Stderr.Write(buf.Bytes()) |
| 221 | fmt.Fprintf(os.Stderr, "--- %s\n", w) |
| 222 | } |
| 223 | return os.ErrorString("running " + cmd[0] + ": " + w.String()) |
| 224 | } |
| 225 | return nil |
| 226 | } |