github.com/meatballhat/deppy@v0.0.0-20151116212532-116c2a9aa48d/go.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  )
    11  
    12  var spool = filepath.Join(os.TempDir(), "deppy")
    13  
    14  var cmdGo = &Command{
    15  	Usage: "go command [arguments]",
    16  	Short: "run the go tool in a sandbox",
    17  	Long: `
    18  Go runs the go tool in a temporary GOPATH sandbox
    19  with the dependencies listed in file Deps.
    20  
    21  Any go tool command can run this way, but "deppy go get"
    22  is unnecessary and has been disabled. Instead, use
    23  "deppy go install".
    24  `,
    25  	Run: runGo,
    26  }
    27  
    28  // Set up a sandbox and run the go tool. The sandbox is built
    29  // out of specific checked-out revisions of repos. We keep repos
    30  // and revs materialized on disk under the assumption that disk
    31  // space is cheap and plentiful, and writing files is slow.
    32  // Everything is kept in the spool directory.
    33  func runGo(cmd *Command, args []string) {
    34  	gopath := prepareGopath()
    35  	if s := os.Getenv("GOPATH"); s != "" {
    36  		gopath += string(os.PathListSeparator) + os.Getenv("GOPATH")
    37  	}
    38  	if len(args) > 0 && args[0] == "get" {
    39  		log.Printf("invalid subcommand: %q", "go get")
    40  		fmt.Fprintln(os.Stderr, "Use 'deppy go install' instead.")
    41  		fmt.Fprintln(os.Stderr, "Run 'deppy help go' for usage.")
    42  		os.Exit(2)
    43  	}
    44  	c := exec.Command("go", args...)
    45  	c.Env = append(envNoGopath(), "GOPATH="+gopath)
    46  	c.Stdin = os.Stdin
    47  	c.Stdout = os.Stdout
    48  	c.Stderr = os.Stderr
    49  	err := c.Run()
    50  	if err != nil {
    51  		log.Fatalln("go", err)
    52  	}
    53  }
    54  
    55  // prepareGopath reads dependency information from the filesystem
    56  // entry name, fetches any necessary code, and returns a gopath
    57  // causing the specified dependencies to be used.
    58  func prepareGopath() (gopath string) {
    59  	dir := findDeps()
    60  	if dir == "" {
    61  		log.Fatalln("No Deps found (or in any parent directory)")
    62  	}
    63  	g, err := ReadAndLoadDeps(filepath.Join(dir, "Deps"))
    64  	if err != nil {
    65  		log.Fatalln(err)
    66  	}
    67  	gopath, err = sandboxAll(g.Deps)
    68  	if err != nil {
    69  		log.Fatalln(err)
    70  	}
    71  	return gopath
    72  }
    73  
    74  // findDeps looks for a directory entry "Deps" in the
    75  // current directory or any parent, and returns the containing
    76  // directory and whether the entry itself is a directory.
    77  // If Deps can't be found, findDeps returns "".
    78  // For any other error, it exits the program.
    79  func findDeps() (dir string) {
    80  	wd, err := os.Getwd()
    81  	if err != nil {
    82  		log.Fatalln(err)
    83  	}
    84  	return findInParents(wd, "Deps")
    85  }
    86  
    87  // isRoot returns true iff a path is a root.
    88  // On Unix: "/".
    89  // On Windows: "C:\", "D:\", ...
    90  func isRoot(p string) bool {
    91  	p = filepath.Clean(p)
    92  	volume := filepath.VolumeName(p)
    93  
    94  	p = strings.TrimPrefix(p, volume)
    95  	p = filepath.ToSlash(p)
    96  
    97  	return p == "/"
    98  }
    99  
   100  // findInParents returns the path to the directory containing name
   101  // in dir or any ancestor, and whether name itself is a directory.
   102  // If name cannot be found, findInParents returns the empty string.
   103  func findInParents(dir, name string) (container string) {
   104  	for {
   105  		_, err := os.Stat(filepath.Join(dir, name))
   106  		if os.IsNotExist(err) && isRoot(dir) {
   107  			return ""
   108  		}
   109  		if os.IsNotExist(err) {
   110  			dir = filepath.Dir(dir)
   111  			continue
   112  		}
   113  		if err != nil {
   114  			log.Fatalln(err)
   115  		}
   116  		return dir
   117  	}
   118  }
   119  
   120  func envNoGopath() (a []string) {
   121  	for _, s := range os.Environ() {
   122  		if !strings.HasPrefix(s, "GOPATH=") {
   123  			a = append(a, s)
   124  		}
   125  	}
   126  	return a
   127  }
   128  
   129  // sandboxAll ensures that the commits in deps are available
   130  // on disk, and returns a GOPATH string that will cause them
   131  // to be used.
   132  func sandboxAll(a []Dependency) (gopath string, err error) {
   133  	var path []string
   134  	for _, dep := range a {
   135  		dir, err := sandbox(dep)
   136  		if err != nil {
   137  			return "", err
   138  		}
   139  		path = append(path, dir)
   140  	}
   141  	return strings.Join(path, ":"), nil
   142  }
   143  
   144  // sandbox ensures that commit d is available on disk,
   145  // and returns a GOPATH string that will cause it to be used.
   146  func sandbox(d Dependency) (gopath string, err error) {
   147  	if !exists(d.RepoPath()) {
   148  		if err = d.CreateRepo("fast", "main"); err != nil {
   149  			return "", fmt.Errorf("create repo: %s", err)
   150  		}
   151  	}
   152  	err = d.checkout()
   153  	if err != nil && d.FastRemotePath() != "" {
   154  		err = d.fetchAndCheckout("fast")
   155  	}
   156  	if err != nil {
   157  		err = d.fetchAndCheckout("main")
   158  	}
   159  	if err != nil {
   160  		return "", err
   161  	}
   162  	return d.Gopath(), nil
   163  }