PageRenderTime 60ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/src/cmd/goinstall/main.go

https://bitbucket.org/adkulkar/goose
Go | 421 lines | 331 code | 44 blank | 46 comment | 101 complexity | e8581060f8a2ef60f37d8fc892e651b6 MD5 | raw file
Possible License(s): BSD-3-Clause
  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. package main
  5. import (
  6. "bytes"
  7. "errors"
  8. "flag"
  9. "fmt"
  10. "go/build"
  11. "go/token"
  12. "io/ioutil"
  13. "os"
  14. "os/exec"
  15. "path/filepath" // use for file system paths
  16. "regexp"
  17. "runtime"
  18. "strings"
  19. )
  20. func usage() {
  21. fmt.Fprintln(os.Stderr, "usage: goinstall [flags] importpath...")
  22. fmt.Fprintln(os.Stderr, " goinstall [flags] -a")
  23. flag.PrintDefaults()
  24. os.Exit(2)
  25. }
  26. const logfile = "goinstall.log"
  27. var (
  28. fset = token.NewFileSet()
  29. argv0 = os.Args[0]
  30. parents = make(map[string]string)
  31. visit = make(map[string]status)
  32. installedPkgs = make(map[string]map[string]bool)
  33. schemeRe = regexp.MustCompile(`^[a-z]+://`)
  34. allpkg = flag.Bool("a", false, "install all previously installed packages")
  35. reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL)
  36. update = flag.Bool("u", false, "update already-downloaded packages")
  37. doGofix = flag.Bool("fix", false, "gofix each package before building it")
  38. doInstall = flag.Bool("install", true, "build and install")
  39. clean = flag.Bool("clean", false, "clean the package directory before installing")
  40. nuke = flag.Bool("nuke", false, "clean the package directory and target before installing")
  41. useMake = flag.Bool("make", true, "use make to build and install")
  42. verbose = flag.Bool("v", false, "verbose")
  43. )
  44. type status int // status for visited map
  45. const (
  46. unvisited status = iota
  47. visiting
  48. done
  49. )
  50. type PackageError struct {
  51. pkg string
  52. err error
  53. }
  54. func (e *PackageError) Error() string {
  55. return fmt.Sprintf("%s: %v", e.pkg, e.err)
  56. }
  57. type DownloadError struct {
  58. pkg string
  59. goroot bool
  60. err error
  61. }
  62. func (e *DownloadError) Error() string {
  63. s := fmt.Sprintf("%s: download failed: %v", e.pkg, e.err)
  64. if e.goroot && os.Getenv("GOPATH") == "" {
  65. s += " ($GOPATH is not set)"
  66. }
  67. return s
  68. }
  69. type DependencyError PackageError
  70. func (e *DependencyError) Error() string {
  71. return fmt.Sprintf("%s: depends on failing packages:\n\t%v", e.pkg, e.err)
  72. }
  73. type BuildError PackageError
  74. func (e *BuildError) Error() string {
  75. return fmt.Sprintf("%s: build failed: %v", e.pkg, e.err)
  76. }
  77. type RunError struct {
  78. cmd, dir string
  79. out []byte
  80. err error
  81. }
  82. func (e *RunError) Error() string {
  83. return fmt.Sprintf("%v\ncd %q && %q\n%s", e.err, e.dir, e.cmd, e.out)
  84. }
  85. func logf(format string, args ...interface{}) {
  86. format = "%s: " + format
  87. args = append([]interface{}{argv0}, args...)
  88. fmt.Fprintf(os.Stderr, format, args...)
  89. }
  90. func printf(format string, args ...interface{}) {
  91. if *verbose {
  92. logf(format, args...)
  93. }
  94. }
  95. func main() {
  96. flag.Usage = usage
  97. flag.Parse()
  98. if runtime.GOROOT() == "" {
  99. fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0)
  100. os.Exit(1)
  101. }
  102. readPackageList()
  103. // special case - "unsafe" is already installed
  104. visit["unsafe"] = done
  105. args := flag.Args()
  106. if *allpkg {
  107. if len(args) != 0 {
  108. usage() // -a and package list both provided
  109. }
  110. // install all packages that were ever installed
  111. n := 0
  112. for _, pkgs := range installedPkgs {
  113. for pkg := range pkgs {
  114. args = append(args, pkg)
  115. n++
  116. }
  117. }
  118. if n == 0 {
  119. logf("no installed packages\n")
  120. os.Exit(1)
  121. }
  122. }
  123. if len(args) == 0 {
  124. usage()
  125. }
  126. errs := false
  127. for _, path := range args {
  128. if err := install(path, ""); err != nil {
  129. errs = true
  130. fmt.Fprintln(os.Stderr, err)
  131. }
  132. }
  133. if errs {
  134. os.Exit(1)
  135. }
  136. }
  137. // printDeps prints the dependency path that leads to pkg.
  138. func printDeps(pkg string) {
  139. if pkg == "" {
  140. return
  141. }
  142. if visit[pkg] != done {
  143. printDeps(parents[pkg])
  144. }
  145. fmt.Fprintf(os.Stderr, "\t%s ->\n", pkg)
  146. }
  147. // readPackageList reads the list of installed packages from the
  148. // goinstall.log files in GOROOT and the GOPATHs and initializes
  149. // the installedPkgs variable.
  150. func readPackageList() {
  151. for _, t := range build.Path {
  152. installedPkgs[t.Path] = make(map[string]bool)
  153. name := filepath.Join(t.Path, logfile)
  154. pkglistdata, err := ioutil.ReadFile(name)
  155. if err != nil {
  156. printf("%s\n", err)
  157. continue
  158. }
  159. pkglist := strings.Fields(string(pkglistdata))
  160. for _, pkg := range pkglist {
  161. installedPkgs[t.Path][pkg] = true
  162. }
  163. }
  164. }
  165. // logPackage logs the named package as installed in the goinstall.log file
  166. // in the given tree if the package is not already in that file.
  167. func logPackage(pkg string, tree *build.Tree) (logged bool) {
  168. if installedPkgs[tree.Path][pkg] {
  169. return false
  170. }
  171. name := filepath.Join(tree.Path, logfile)
  172. fout, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
  173. if err != nil {
  174. printf("package log: %s\n", err)
  175. return false
  176. }
  177. fmt.Fprintf(fout, "%s\n", pkg)
  178. fout.Close()
  179. return true
  180. }
  181. // install installs the package named by path, which is needed by parent.
  182. func install(pkg, parent string) error {
  183. // Basic validation of import path string.
  184. if s := schemeRe.FindString(pkg); s != "" {
  185. return fmt.Errorf("%q used in import path, try %q\n", s, pkg[len(s):])
  186. }
  187. if strings.HasSuffix(pkg, "/") {
  188. return fmt.Errorf("%q should not have trailing '/'\n", pkg)
  189. }
  190. // Make sure we're not already trying to install pkg.
  191. switch visit[pkg] {
  192. case done:
  193. return nil
  194. case visiting:
  195. fmt.Fprintf(os.Stderr, "%s: package dependency cycle\n", argv0)
  196. printDeps(parent)
  197. fmt.Fprintf(os.Stderr, "\t%s\n", pkg)
  198. os.Exit(2)
  199. }
  200. parents[pkg] = parent
  201. visit[pkg] = visiting
  202. defer func() {
  203. visit[pkg] = done
  204. }()
  205. // Check whether package is local or remote.
  206. // If remote, download or update it.
  207. tree, pkg, err := build.FindTree(pkg)
  208. // Don't build the standard library.
  209. if err == nil && tree.Goroot && isStandardPath(pkg) {
  210. if parent == "" {
  211. return &PackageError{pkg, errors.New("cannot goinstall the standard library")}
  212. }
  213. return nil
  214. }
  215. // Download remote packages if not found or forced with -u flag.
  216. remote, public := isRemote(pkg), false
  217. if remote {
  218. if err == build.ErrNotFound || (err == nil && *update) {
  219. // Download remote package.
  220. printf("%s: download\n", pkg)
  221. public, err = download(pkg, tree.SrcDir())
  222. if err != nil {
  223. return &DownloadError{pkg, tree.Goroot, err}
  224. }
  225. } else {
  226. // Test if this is a public repository
  227. // (for reporting to dashboard).
  228. repo, e := findPublicRepo(pkg)
  229. public = repo != nil
  230. err = e
  231. }
  232. }
  233. if err != nil {
  234. return &PackageError{pkg, err}
  235. }
  236. // Install the package and its dependencies.
  237. if err := installPackage(pkg, parent, tree, false); err != nil {
  238. return err
  239. }
  240. if remote {
  241. // mark package as installed in goinstall.log
  242. logged := logPackage(pkg, tree)
  243. // report installation to the dashboard if this is the first
  244. // install from a public repository.
  245. if logged && public {
  246. maybeReportToDashboard(pkg)
  247. }
  248. }
  249. return nil
  250. }
  251. // installPackage installs the specified package and its dependencies.
  252. func installPackage(pkg, parent string, tree *build.Tree, retry bool) (installErr error) {
  253. printf("%s: install\n", pkg)
  254. // Read package information.
  255. dir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg))
  256. dirInfo, err := build.ScanDir(dir)
  257. if err != nil {
  258. return &PackageError{pkg, err}
  259. }
  260. // We reserve package main to identify commands.
  261. if parent != "" && dirInfo.Package == "main" {
  262. return &PackageError{pkg, fmt.Errorf("found only package main in %s; cannot import", dir)}
  263. }
  264. // Run gofix if we fail to build and -fix is set.
  265. defer func() {
  266. if retry || installErr == nil || !*doGofix {
  267. return
  268. }
  269. if e, ok := (installErr).(*DependencyError); ok {
  270. // If this package failed to build due to a
  271. // DependencyError, only attempt to gofix it if its
  272. // dependency failed for some reason other than a
  273. // DependencyError or BuildError.
  274. // (If a dep or one of its deps doesn't build there's
  275. // no way that gofixing this package can help.)
  276. switch e.err.(type) {
  277. case *DependencyError:
  278. return
  279. case *BuildError:
  280. return
  281. }
  282. }
  283. gofix(pkg, dir, dirInfo)
  284. installErr = installPackage(pkg, parent, tree, true) // retry
  285. }()
  286. // Install prerequisites.
  287. for _, p := range dirInfo.Imports {
  288. if p == "C" {
  289. continue
  290. }
  291. if err := install(p, pkg); err != nil {
  292. return &DependencyError{pkg, err}
  293. }
  294. }
  295. // Install this package.
  296. if *useMake {
  297. err := domake(dir, pkg, tree, dirInfo.IsCommand())
  298. if err != nil {
  299. return &BuildError{pkg, err}
  300. }
  301. return nil
  302. }
  303. script, err := build.Build(tree, pkg, dirInfo)
  304. if err != nil {
  305. return &BuildError{pkg, err}
  306. }
  307. if *nuke {
  308. printf("%s: nuke\n", pkg)
  309. script.Nuke()
  310. } else if *clean {
  311. printf("%s: clean\n", pkg)
  312. script.Clean()
  313. }
  314. if *doInstall {
  315. if script.Stale() {
  316. printf("%s: install\n", pkg)
  317. if err := script.Run(); err != nil {
  318. return &BuildError{pkg, err}
  319. }
  320. } else {
  321. printf("%s: up-to-date\n", pkg)
  322. }
  323. }
  324. return nil
  325. }
  326. // gofix runs gofix against the GoFiles and CgoFiles of dirInfo in dir.
  327. func gofix(pkg, dir string, dirInfo *build.DirInfo) {
  328. printf("%s: gofix\n", pkg)
  329. files := append([]string{}, dirInfo.GoFiles...)
  330. files = append(files, dirInfo.CgoFiles...)
  331. for i, file := range files {
  332. files[i] = filepath.Join(dir, file)
  333. }
  334. cmd := exec.Command("gofix", files...)
  335. cmd.Stdout = os.Stdout
  336. cmd.Stderr = os.Stderr
  337. if err := cmd.Run(); err != nil {
  338. logf("%s: gofix: %v", pkg, err)
  339. }
  340. }
  341. // Is this a standard package path? strings container/list etc.
  342. // Assume that if the first element has a dot, it's a domain name
  343. // and is not the standard package path.
  344. func isStandardPath(s string) bool {
  345. dot := strings.Index(s, ".")
  346. slash := strings.Index(s, "/")
  347. return dot < 0 || 0 < slash && slash < dot
  348. }
  349. // run runs the command cmd in directory dir with standard input stdin.
  350. // If verbose is set and the command fails it prints the output to stderr.
  351. func run(dir string, stdin []byte, arg ...string) error {
  352. cmd := exec.Command(arg[0], arg[1:]...)
  353. cmd.Stdin = bytes.NewBuffer(stdin)
  354. cmd.Dir = dir
  355. printf("cd %s && %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " "))
  356. if out, err := cmd.CombinedOutput(); err != nil {
  357. if *verbose {
  358. fmt.Fprintf(os.Stderr, "%v\n%s\n", err, out)
  359. }
  360. return &RunError{strings.Join(arg, " "), dir, out, err}
  361. }
  362. return nil
  363. }
  364. // isRemote returns true if the first part of the package name looks like a
  365. // hostname - i.e. contains at least one '.' and the last part is at least 2
  366. // characters.
  367. func isRemote(pkg string) bool {
  368. parts := strings.SplitN(pkg, "/", 2)
  369. if len(parts) != 2 {
  370. return false
  371. }
  372. parts = strings.Split(parts[0], ".")
  373. if len(parts) < 2 || len(parts[len(parts)-1]) < 2 {
  374. return false
  375. }
  376. return true
  377. }