/src/cmd/goinstall/main.go
Go | 421 lines | 331 code | 44 blank | 46 comment | 101 complexity | e8581060f8a2ef60f37d8fc892e651b6 MD5 | raw file
Possible License(s): BSD-3-Clause
- // 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"
- "errors"
- "flag"
- "fmt"
- "go/build"
- "go/token"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath" // use for file system paths
- "regexp"
- "runtime"
- "strings"
- )
- func usage() {
- fmt.Fprintln(os.Stderr, "usage: goinstall [flags] importpath...")
- fmt.Fprintln(os.Stderr, " goinstall [flags] -a")
- flag.PrintDefaults()
- os.Exit(2)
- }
- const logfile = "goinstall.log"
- var (
- fset = token.NewFileSet()
- argv0 = os.Args[0]
- 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")
- doGofix = flag.Bool("fix", false, "gofix each package before building it")
- 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
- )
- type PackageError struct {
- pkg string
- err error
- }
- func (e *PackageError) Error() string {
- return fmt.Sprintf("%s: %v", e.pkg, e.err)
- }
- type DownloadError struct {
- pkg string
- goroot bool
- err error
- }
- func (e *DownloadError) Error() string {
- s := fmt.Sprintf("%s: download failed: %v", e.pkg, e.err)
- if e.goroot && os.Getenv("GOPATH") == "" {
- s += " ($GOPATH is not set)"
- }
- return s
- }
- type DependencyError PackageError
- func (e *DependencyError) Error() string {
- return fmt.Sprintf("%s: depends on failing packages:\n\t%v", e.pkg, e.err)
- }
- type BuildError PackageError
- func (e *BuildError) Error() string {
- return fmt.Sprintf("%s: build failed: %v", e.pkg, e.err)
- }
- type RunError struct {
- cmd, dir string
- out []byte
- err error
- }
- func (e *RunError) Error() string {
- return fmt.Sprintf("%v\ncd %q && %q\n%s", e.err, e.dir, e.cmd, e.out)
- }
- 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 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()
- }
- errs := false
- for _, path := range args {
- if err := install(path, ""); err != nil {
- errs = true
- fmt.Fprintln(os.Stderr, err)
- }
- }
- if errs {
- 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 initializes
- // 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 {
- printf("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) error {
- // Basic validation of import path string.
- if s := schemeRe.FindString(pkg); s != "" {
- return fmt.Errorf("%q used in import path, try %q\n", s, pkg[len(s):])
- }
- if strings.HasSuffix(pkg, "/") {
- return fmt.Errorf("%q should not have trailing '/'\n", pkg)
- }
- // Make sure we're not already trying to install pkg.
- switch visit[pkg] {
- case done:
- return nil
- 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
- }()
- // 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 == "" {
- return &PackageError{pkg, errors.New("cannot goinstall the standard library")}
- }
- return nil
- }
- // 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())
- if err != nil {
- return &DownloadError{pkg, tree.Goroot, err}
- }
- } else {
- // Test if this is a public repository
- // (for reporting to dashboard).
- repo, e := findPublicRepo(pkg)
- public = repo != nil
- err = e
- }
- }
- if err != nil {
- return &PackageError{pkg, err}
- }
- // Install the package and its dependencies.
- if err := installPackage(pkg, parent, tree, false); err != nil {
- return err
- }
- 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)
- }
- }
- return nil
- }
- // installPackage installs the specified package and its dependencies.
- func installPackage(pkg, parent string, tree *build.Tree, retry bool) (installErr error) {
- printf("%s: install\n", pkg)
- // Read package information.
- dir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg))
- dirInfo, err := build.ScanDir(dir)
- if err != nil {
- return &PackageError{pkg, err}
- }
- // We reserve package main to identify commands.
- if parent != "" && dirInfo.Package == "main" {
- return &PackageError{pkg, fmt.Errorf("found only package main in %s; cannot import", dir)}
- }
- // Run gofix if we fail to build and -fix is set.
- defer func() {
- if retry || installErr == nil || !*doGofix {
- return
- }
- if e, ok := (installErr).(*DependencyError); ok {
- // If this package failed to build due to a
- // DependencyError, only attempt to gofix it if its
- // dependency failed for some reason other than a
- // DependencyError or BuildError.
- // (If a dep or one of its deps doesn't build there's
- // no way that gofixing this package can help.)
- switch e.err.(type) {
- case *DependencyError:
- return
- case *BuildError:
- return
- }
- }
- gofix(pkg, dir, dirInfo)
- installErr = installPackage(pkg, parent, tree, true) // retry
- }()
- // Install prerequisites.
- for _, p := range dirInfo.Imports {
- if p == "C" {
- continue
- }
- if err := install(p, pkg); err != nil {
- return &DependencyError{pkg, err}
- }
- }
- // Install this package.
- if *useMake {
- err := domake(dir, pkg, tree, dirInfo.IsCommand())
- if err != nil {
- return &BuildError{pkg, err}
- }
- return nil
- }
- script, err := build.Build(tree, pkg, dirInfo)
- if err != nil {
- return &BuildError{pkg, err}
- }
- 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 {
- return &BuildError{pkg, err}
- }
- } else {
- printf("%s: up-to-date\n", pkg)
- }
- }
- return nil
- }
- // gofix runs gofix against the GoFiles and CgoFiles of dirInfo in dir.
- func gofix(pkg, dir string, dirInfo *build.DirInfo) {
- printf("%s: gofix\n", pkg)
- files := append([]string{}, dirInfo.GoFiles...)
- files = append(files, dirInfo.CgoFiles...)
- for i, file := range files {
- files[i] = filepath.Join(dir, file)
- }
- cmd := exec.Command("gofix", files...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if err := cmd.Run(); err != nil {
- logf("%s: gofix: %v", pkg, err)
- }
- }
- // Is this a standard package path? strings container/list 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 verbose is set and the command fails it prints the output to stderr.
- func run(dir string, stdin []byte, arg ...string) error {
- cmd := exec.Command(arg[0], arg[1:]...)
- cmd.Stdin = bytes.NewBuffer(stdin)
- cmd.Dir = dir
- printf("cd %s && %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " "))
- if out, err := cmd.CombinedOutput(); err != nil {
- if *verbose {
- fmt.Fprintf(os.Stderr, "%v\n%s\n", err, out)
- }
- return &RunError{strings.Join(arg, " "), dir, out, err}
- }
- return nil
- }
- // isRemote returns true if the first part of the package name looks like a
- // hostname - i.e. contains at least one '.' and the last part is at least 2
- // characters.
- func isRemote(pkg string) bool {
- parts := strings.SplitN(pkg, "/", 2)
- if len(parts) != 2 {
- return false
- }
- parts = strings.Split(parts[0], ".")
- if len(parts) < 2 || len(parts[len(parts)-1]) < 2 {
- return false
- }
- return true
- }