├── .go.yaml ├── env ├── deps │ ├── goget.go │ ├── hg.go │ └── git.go ├── search.go ├── dep.go └── env.go ├── .gitignore ├── docs ├── special.md ├── introduction.md ├── projfile.md └── tut.md ├── common └── dep.go ├── Makefile ├── exec └── exec.go ├── goat.go ├── README.md └── LICENSE /.go.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | path: github.com/mediocregopher/goat 3 | deps: 4 | - loc: gopkg.in/yaml.v1 5 | -------------------------------------------------------------------------------- /env/deps/goget.go: -------------------------------------------------------------------------------- 1 | package deps 2 | 3 | import ( 4 | "fmt" 5 | 6 | . "github.com/mediocregopher/goat/common" 7 | "github.com/mediocregopher/goat/exec" 8 | ) 9 | 10 | func GoGet(depdir string, dep *Dependency) error { 11 | fmt.Println("go", "get", dep.Location) 12 | return exec.PipedCmd("go", "get", dep.Location) 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Vim swap 2 | .*.sw[opn] 3 | tags 4 | 5 | # Compiled program 6 | goat 7 | 8 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 9 | *.o 10 | *.a 11 | *.so 12 | 13 | # Folders 14 | _obj 15 | _test 16 | 17 | # Architecture specific extensions/prefixes 18 | *.[568vq] 19 | [568vq].out 20 | 21 | *.cgo1.go 22 | *.cgo2.c 23 | _cgo_defun.c 24 | _cgo_gotypes.go 25 | _cgo_export.* 26 | 27 | _testmain.go 28 | 29 | *.exe 30 | 31 | .goat/ 32 | bin/ 33 | rel/ 34 | -------------------------------------------------------------------------------- /docs/special.md: -------------------------------------------------------------------------------- 1 | # Goat special features 2 | 3 | Here's some special features that goat has that don't really fit in with the 4 | rest of the documentation. 5 | 6 | ## GOAT_ACTUALGO 7 | 8 | When passing commands through to the actual go binary goat will, by default, use 9 | the `env` command to find the binary and call the return from that. However, if 10 | you've renamed the goat binary to `go` and put it on your path (useful for 11 | easy-integrations with plugins like [syntastic][syntastic]) then that's going to 12 | cause an infinite loop. You can set an environment variable called 13 | `GOAT_ACTUALGO` with the absolute path to your installed go binary. Goat will 14 | use this instead of `env` when it's present. 15 | 16 | [syntastic]: https://github.com/scrooloose/syntastic 17 | -------------------------------------------------------------------------------- /env/search.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | // FindProjRoot returns the directory name of the parent that contains the 10 | // .go.yaml file 11 | func FindProjRoot(dir string) (string, error) { 12 | 13 | if IsProjRoot(dir) { 14 | return dir, nil 15 | } 16 | 17 | parent := filepath.Dir(dir) 18 | if dir == parent { 19 | return "", errors.New(".go.yaml not found") 20 | } 21 | 22 | return FindProjRoot(parent) 23 | } 24 | 25 | // IsProjRoot returns whether or not a particular directory is the project 26 | // root for a goat project (aka, whether or not it has a goat file) 27 | func IsProjRoot(dir string) bool { 28 | goatfile := filepath.Join(dir, PROJFILE) 29 | if _, err := os.Stat(goatfile); os.IsNotExist(err) { 30 | return false 31 | } 32 | return true 33 | } 34 | -------------------------------------------------------------------------------- /common/dep.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type Dependency struct { 4 | // Location is the url/uri of the remote location where the dependency can 5 | // be found at. Required. 6 | Location string `yaml:"loc"` 7 | 8 | // Path is the path the dependency should be installed to. This should 9 | // correspond to whatever the dependency expects to be imported as. For 10 | // example: "code.google.com/p/protobuf". Default: Value of Location field 11 | Path string `yaml:"path"` 12 | 13 | // Type is what kind of project the dependency should be fetched as. 14 | // Available options are: goget, git, hg. Default: goget. 15 | Type string `yaml:"type"` 16 | 17 | // Reference is only valueable for version-control Types (e.g. git). It can 18 | // be any valid reference in that version control system (branch name, tag, 19 | // commit hash). Default depends on the Type, git is "master", hg is "tip". 20 | Reference string `yaml:"ref"` 21 | } 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DEPSDIR= .goat/deps 2 | GOPATH= GOPATH=$(DEPSDIR) 3 | GOATLOOPBACK= $(DEPSDIR)/src/github.com/mediocregopher 4 | 5 | local: bin deps 6 | $(GOPATH) go build -o bin/goat goat.go 7 | 8 | release: bin rel deps 9 | @echo -n "What's the name/version number of this release?: "; \ 10 | read version; \ 11 | mkdir bin/goat-$$version; \ 12 | for platform in darwin freebsd linux windows; do \ 13 | for arch in 386 amd64; do \ 14 | echo $$platform $$arch; \ 15 | $(GOPATH) GOOS=$$platform GOARCH=$$arch go build -ldflags "-X main.releaseVersion '$$version'" -o bin/goat-$$version/goat_"$$platform"_"$$arch" goat.go; \ 16 | done; \ 17 | done; \ 18 | cd bin; \ 19 | echo "Tar-ing into rel/goat-$$version.tar.gz"; \ 20 | tar cvzf ../rel/goat-$$version.tar.gz goat-$$version 21 | 22 | rel: 23 | mkdir rel 24 | 25 | bin: 26 | mkdir bin 27 | 28 | deps: $(GOATLOOPBACK) 29 | $(GOPATH) go get gopkg.in/yaml.v1 30 | 31 | $(GOATLOOPBACK): 32 | mkdir -p $(GOATLOOPBACK) 33 | (cd $(GOATLOOPBACK) && ln -s ../../../../.. goat) 34 | -------------------------------------------------------------------------------- /env/deps/hg.go: -------------------------------------------------------------------------------- 1 | package deps 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | . "github.com/mediocregopher/goat/common" 9 | "github.com/mediocregopher/goat/exec" 10 | ) 11 | 12 | func Hg(depdir string, dep *Dependency) error { 13 | localloc := filepath.Join(depdir, "src", dep.Path) 14 | 15 | if _, err := os.Stat(localloc); os.IsNotExist(err) { 16 | fmt.Println("hg", "clone", dep.Location, localloc) 17 | err := exec.PipedCmd("hg", "clone", dep.Location, localloc) 18 | if err != nil { 19 | return err 20 | } 21 | } else { 22 | fmt.Println(localloc, "exists") 23 | } 24 | 25 | origcwd, err := os.Getwd() 26 | if err != nil { 27 | return err 28 | } 29 | 30 | err = os.Chdir(localloc) 31 | if err != nil { 32 | return err 33 | } 34 | defer os.Chdir(origcwd) 35 | 36 | fmt.Println("hg", "pull") 37 | err = exec.PipedCmd("hg", "pull") 38 | if err != nil { 39 | return err 40 | } 41 | 42 | if dep.Reference == "" { 43 | dep.Reference = "tip" 44 | } 45 | fmt.Println("hg", "update", "-C", dep.Reference) 46 | err = exec.PipedCmd("hg", "update", "-C", dep.Reference) 47 | 48 | return err 49 | 50 | } 51 | -------------------------------------------------------------------------------- /exec/exec.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | ) 11 | 12 | // TrimmedCmd returns a command's output on stdout and stderr as 13 | // a string and error object. Before returning both stdout and stderr 14 | // have whitespace trimmed off both ends. Stderr will be nil if it was 15 | // empty 16 | func TrimmedCmd(cmdstr string, args ...string) (string, string, error) { 17 | cmd := exec.Command(cmdstr, args...) 18 | 19 | stdout, err := cmd.StdoutPipe() 20 | if err != nil { 21 | return "", "", err 22 | } 23 | 24 | stderr, err := cmd.StderrPipe() 25 | if err != nil { 26 | return "", "", err 27 | } 28 | 29 | if err = cmd.Start(); err != nil { 30 | return "", "", err 31 | } 32 | 33 | bout, err := ioutil.ReadAll(stdout) 34 | strout := strings.TrimSpace(string(bout)) 35 | if err != nil { 36 | return strout, "", err 37 | } 38 | 39 | berr, err := ioutil.ReadAll(stderr) 40 | strerr := strings.TrimSpace(string(berr)) 41 | if err != nil { 42 | return strout, strerr, err 43 | } 44 | 45 | return strout, strerr, cmd.Wait() 46 | } 47 | 48 | // PipedCmd pipes a command's out/err to this process', and returns 49 | // a channel which gives an err if anything went wrong, or returns 50 | // nil when the command completes 51 | func PipedCmd(cmdstr string, args ...string) error { 52 | cmd := exec.Command(cmdstr, args...) 53 | 54 | stdout, err := cmd.StdoutPipe() 55 | if err != nil { 56 | return err 57 | } 58 | stderr, err := cmd.StderrPipe() 59 | if err != nil { 60 | return err 61 | } 62 | 63 | go io.Copy(os.Stdout, stdout) 64 | go io.Copy(os.Stderr, stderr) 65 | 66 | err = cmd.Start() 67 | if err != nil { 68 | return err 69 | } 70 | 71 | cmd.Wait() 72 | 73 | // return an error if the command returned a non-success error code 74 | if !cmd.ProcessState.Success() { 75 | return errors.New("Command returned a non-success status code") 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This doc gives a brief introduction to what problems goat solves, and how it 4 | solves them. 5 | 6 | # The problem 7 | 8 | There are two problems that goat aims to solve: 9 | 10 | * `go get` does not allow for specifying versions of a library. 11 | 12 | * `go get` does not have an easy of way of sandboxing your project's development 13 | environment. You can either muck up your global environment with dependencies 14 | or mess with your `GOPATH` everytime you want to develop for that project. 15 | Others that want to work on your project will have to do the same. 16 | 17 | * Other dependency managers are strange and have weird command line arguments 18 | that I don't feel like learning. 19 | 20 | # The solution 21 | 22 | * The root of a goat project has a `.go.yaml` file which specifies a 23 | dependency's location, name, and version control reference if applicable. It 24 | is formatted using super-simple yaml objects, each having at most four fields. 25 | 26 | * All dependencies are placed in a `.goat/deps` directory at the root of your 27 | project. goat will automatically look for a `.go.yaml` in your current 28 | working directory or one of its parents, and call that the project root. For 29 | the rest of the command's duration your GOPATH will have 30 | `/.goat/deps` prepended to it. This has many useful properties, most 31 | notably that your dependencies are sandboxed inside your code, but are still 32 | usable exactly as they would have been if they were global. 33 | 34 | * Goat is a wrapper around the go command line utility. It adds one new command, 35 | all other commands are passed straight through to the normal go binary. This 36 | command is `goat deps`, and it retrieves all dependencies listed in your 37 | `.go.yaml` and puts them into a folder called `.goat/deps` in your project. If 38 | any of those dependencies have `.go.yaml` files then those are processed and 39 | put in your project's `.goat/deps` folder as well (this is done recursively). 40 | 41 | -------------------------------------------------------------------------------- /env/deps/git.go: -------------------------------------------------------------------------------- 1 | package deps 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | . "github.com/mediocregopher/goat/common" 10 | "github.com/mediocregopher/goat/exec" 11 | ) 12 | 13 | func Git(depdir string, dep *Dependency) error { 14 | localloc := filepath.Join(depdir, "src", dep.Path) 15 | 16 | if _, err := os.Stat(localloc); os.IsNotExist(err) { 17 | fmt.Println("git", "clone", dep.Location, localloc) 18 | err := exec.PipedCmd("git", "clone", dep.Location, localloc) 19 | if err != nil { 20 | return err 21 | } 22 | } else { 23 | fmt.Println(localloc, "exists") 24 | } 25 | 26 | origcwd, err := os.Getwd() 27 | if err != nil { 28 | return err 29 | } 30 | 31 | err = os.Chdir(localloc) 32 | if err != nil { 33 | return err 34 | } 35 | defer os.Chdir(origcwd) 36 | 37 | fmt.Println("git", "fetch", "-pv", "--all") 38 | err = exec.PipedCmd("git", "fetch", "-pv", "--all") 39 | if err != nil { 40 | return err 41 | } 42 | 43 | if dep.Reference == "" { 44 | dep.Reference = "master" 45 | } 46 | if exists, err := originBranchExists(dep.Reference); err != nil { 47 | return err 48 | } else if exists { 49 | dep.Reference = "origin/" + dep.Reference 50 | } 51 | fmt.Println("git", "checkout", dep.Reference) 52 | stdout, stderr, err := exec.TrimmedCmd("git", "checkout", dep.Reference) 53 | if err != nil { 54 | return err 55 | } 56 | if stdout != "" { 57 | fmt.Println(stdout) 58 | } 59 | // Moving to a 'detached HEAD' state causes git to output a huge explanation 60 | // to stderr. We only want the last line. 61 | lines := strings.Split(stderr, "\n") 62 | fmt.Println(lines[len(lines)-1]) 63 | 64 | fmt.Println("git", "clean", "-f", "-d") 65 | err = exec.PipedCmd("git", "clean", "-f", "-d") 66 | 67 | return err 68 | 69 | } 70 | 71 | // If 'origin/branch' exists, return true. 72 | func originBranchExists(branch string) (bool, error) { 73 | fmt.Println("git", "branch", "-r") 74 | branches, _, err := exec.TrimmedCmd("git", "branch", "-r") 75 | if err != nil { 76 | return false, err 77 | } 78 | for _, b := range strings.Split(branches, "\n") { 79 | b := strings.TrimSpace(b) 80 | if strings.HasSuffix(b, "origin/"+branch) { 81 | return true, nil 82 | } 83 | } 84 | return false, nil 85 | } 86 | -------------------------------------------------------------------------------- /env/dep.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "path/filepath" 7 | 8 | . "github.com/mediocregopher/goat/common" 9 | "github.com/mediocregopher/goat/env/deps" 10 | ) 11 | 12 | type typefunc func(string, *Dependency) error 13 | 14 | var typemap = map[string]typefunc{ 15 | "": deps.GoGet, 16 | "goget": deps.GoGet, 17 | "git": deps.Git, 18 | "hg": deps.Hg, 19 | } 20 | 21 | func header(c string, strs ...interface{}) { 22 | fmt.Printf("\n") 23 | for i := 0; i < 80; i++ { 24 | fmt.Printf(c) 25 | } 26 | fmt.Printf("\n") 27 | 28 | fmt.Println(strs...) 29 | 30 | for i := 0; i < 80; i++ { 31 | fmt.Printf(c) 32 | } 33 | fmt.Printf("\n") 34 | fmt.Printf("\n") 35 | } 36 | 37 | // FetchDependencies goes and retrieves the dependencies for the GoatEnv. If the 38 | // dependencies have any goat project files in them, this will fetch the 39 | // dependencies listed therein as well. All dependencies are placed in the root 40 | // project's lib directory, INCLUDING any sub-dependencies. This is done on 41 | // purpose! 42 | func (genv *GoatEnv) FetchDependencies(depdir string) error { 43 | var err error 44 | 45 | if len(genv.Dependencies) > 0 { 46 | header("#", "Downloading dependencies listed in", genv.AbsProjFile()) 47 | 48 | for i := range genv.Dependencies { 49 | dep := &genv.Dependencies[i] 50 | 51 | header("=", "Retrieving dependency at:", dep.Location) 52 | 53 | if dep.Path == "" { 54 | dep.Path = dep.Location 55 | } 56 | 57 | fun, ok := typemap[dep.Type] 58 | if !ok { 59 | return errors.New("Unknown dependency type: " + dep.Type) 60 | } 61 | err = fun(depdir, dep) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | depprojroot := filepath.Join(depdir, "src", dep.Path) 67 | 68 | if IsProjRoot(depprojroot) { 69 | header("-", "Reading", depprojroot, "'s dependencies") 70 | depgenv, err := NewGoatEnv(depprojroot) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | err = depgenv.FetchDependencies(depdir) 76 | if err != nil { 77 | return err 78 | } 79 | } else { 80 | header("-", "No "+PROJFILE+" found in", depprojroot) 81 | } 82 | } 83 | 84 | header("#", "Done downloading dependencies for", genv.AbsProjFile()) 85 | } else { 86 | header("-", "No dependencies listed in", genv.AbsProjFile()) 87 | } 88 | 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /goat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "syscall" 8 | 9 | "github.com/mediocregopher/goat/env" 10 | "github.com/mediocregopher/goat/exec" 11 | ) 12 | 13 | // Optionally set during go build to be output in the help block 14 | var releaseVersion string 15 | 16 | func fatal(err error) { 17 | fmt.Println(err) 18 | os.Exit(1) 19 | } 20 | 21 | func printGhelp() { 22 | fmt.Printf( 23 | `Goat is a command-line wrapper for go which handles dependency 24 | management in a sane way. Check the goat docs at github.com/mediocregopher/goat 25 | for a more in-depth overview. 26 | 27 | Usage: 28 | 29 | %s command [arguments] 30 | 31 | The commands are: 32 | 33 | deps Read the .go.yaml file for this project and set up dependencies in 34 | the dependencies folder specified (default ".deps"). Recursively 35 | download dependencies wherever a .go.yaml file is encountered 36 | 37 | ghelp Show this dialog 38 | 39 | All other commands are passed through to the go binary on your system. Try '%s 40 | help' for its available commands 41 | 42 | Goat release %s 43 | 44 | `, os.Args[0], os.Args[0], releaseVersion) 45 | os.Exit(0) 46 | } 47 | 48 | func main() { 49 | 50 | cwd, err := os.Getwd() 51 | if err != nil { 52 | fatal(err) 53 | } 54 | 55 | projroot, err := env.FindProjRoot(cwd) 56 | var genv *env.GoatEnv 57 | if err == nil { 58 | genv, err = env.NewGoatEnv(projroot) 59 | if err != nil { 60 | fatal(err) 61 | } 62 | 63 | if err = genv.PrependToGoPath(); err != nil { 64 | fatal(err) 65 | } 66 | 67 | if err = genv.Setup(); err != nil { 68 | fatal(err) 69 | } 70 | } 71 | 72 | args := os.Args[1:] 73 | if len(args) < 1 { 74 | printGhelp() 75 | } 76 | 77 | switch args[0] { 78 | case "deps": 79 | if genv != nil { 80 | err := genv.FetchDependencies(genv.AbsDepDir()) 81 | if err != nil { 82 | fatal(err) 83 | } 84 | } else { 85 | fatal(errors.New(".go.yaml file not found on current path")) 86 | } 87 | case "ghelp": 88 | printGhelp() 89 | default: 90 | if actualgo, ok := ActualGo(); ok { 91 | err := exec.PipedCmd(actualgo, args...) 92 | if err != nil { 93 | os.Exit(1) 94 | } 95 | } else { 96 | newargs := make([]string, len(args)+1) 97 | copy(newargs[1:], args) 98 | newargs[0] = "go" 99 | err := exec.PipedCmd("/usr/bin/env", newargs...) 100 | if err != nil { 101 | os.Exit(1) 102 | } 103 | } 104 | } 105 | } 106 | 107 | // ActualGo returns the GOAT_ACTUALGO environment variable contents, and whether 108 | // or not the variable was actually set 109 | func ActualGo() (string, bool) { 110 | return syscall.Getenv("GOAT_ACTUALGO") 111 | } 112 | -------------------------------------------------------------------------------- /docs/projfile.md: -------------------------------------------------------------------------------- 1 | # .go.yaml file 2 | 3 | The `.go.yaml` is used by goat both to find the root directory of the project 4 | and to get information about all of the project's dependencies. Using the 5 | `.go.yaml` to set the root of the project is covered in the 6 | [tutorial](/docs/tut.md), in this doc we'll be looking at the format of the 7 | `.go.yaml` and how it can be used. 8 | 9 | If you've never used yaml before, [wikipedia][yaml] has a fairly good outline of 10 | it including some examples and explanations of the various data-types. 11 | 12 | # Basic Format 13 | 14 | Here's a basic `.go.yaml`: 15 | 16 | ```yaml 17 | --- 18 | path: github.com/username/myproject 19 | deps: 20 | - loc: code.google.com/p/go.example/newmath 21 | type: goget 22 | 23 | - loc: https://github.com:mediocregopher/goat.git 24 | type: git 25 | ref: master 26 | path: github.com/mediocregopher/goat 27 | ``` 28 | 29 | The outer object describes attributes about the project. It has the keys `path` 30 | and `deps`. 31 | 32 | ## path 33 | 34 | The `path` field is required. It can either be a simple name for the project if 35 | you don't plan on hosting it anywhere, or a full uri based on where it is hosted 36 | (like in the example above). This uri can then be used to import your submodules 37 | within eachother. For example if there was a folder in your project called 38 | `bettermath` and you had `path: github.com/username/myproject`, you could import 39 | it in another submodule in your project with: 40 | 41 | ``` 42 | import "github.com/username/myproject/bettermath" 43 | ``` 44 | 45 | Note that your project wouldn't have to actually exist in the 46 | `github.com/username` directory tree, it could be anywhere. Goat takes care of 47 | the `GOPATH` for you. 48 | 49 | ## deps 50 | 51 | The `deps` field is a list of dependency objects that this project requires. 52 | There are four possible dependency fields. 53 | 54 | ### loc 55 | 56 | This is the only required field. It is the actual url that goat wil use fetch 57 | your project. This will be passed into whatever dependency fetcher you are using 58 | (go get, git, mercurial, etc...). 59 | 60 | ### type 61 | 62 | By default this field is `goget`, but currently `git` and `hg` are also 63 | supported. This tells goat how to fetch the dependency. For example if `type` is 64 | set to `goget` then `go get ` is used, while if it's `git` then `git clone 65 | ` will be used. 66 | 67 | ### ref 68 | 69 | The option is only valid for `type`s that have some kind of version reference 70 | system (for example `git` does, `goget` doesn't). Here are the defaults for the 71 | supported types: 72 | 73 | * git: `master` 74 | * hg: `tip` 75 | 76 | ### path 77 | 78 | The actual directory structure the dependency will be put into inside the `depdir` 79 | folder. By default this is set to whatever `loc` is set to, which works for 80 | `goget` dependencies but not so well for others where the `loc` is an actual url 81 | (like `git`). In effect, `path` is the string you want to use for importing this 82 | dependency inside your project. 83 | 84 | [yaml]: http://en.wikipedia.org/wiki/YAML 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PROJECT HAS MOVED 2 | 3 | See [https://github.com/openbohemians/goat](https://github.com/openbohemians/goat) 4 | for the new location where it will be actively maintained. Thanks to @trans for 5 | picking up my slack! 6 | 7 | # goat 8 | 9 | _)) 10 | /* \ _~ 11 | `;'\\__-' \_ A simple go dependency manager 12 | | ) _ \ \ 13 | / / `` w w 14 | w w 15 | 16 | * Unobtrusive dependency management 17 | * Allows projects to be located anywhere, regardless of GOPATH 18 | * Existing projects can be switched to goat without changing any code 19 | 20 | See the [introduction][intro] for more details. 21 | 22 | # Usage 23 | 24 | **Pulling dependencies for an existing project:** 25 | 26 | ```bash 27 | cd project/ 28 | goat deps 29 | ``` 30 | 31 | That's it. You just learned all the command-line stuff for goat. 32 | 33 | **Creating a new project:** 34 | 35 | ```bash 36 | mkdir newproject # Can be anywhere on the filesystem, regardless of GOPATH 37 | cd newproject 38 | vim .go.yaml 39 | ``` 40 | 41 | in .go.yaml put: 42 | 43 | ```yaml 44 | --- 45 | path: github.com/user/newproject 46 | ``` 47 | 48 | **Adding a dependency to a project:** 49 | 50 | Change .go.yaml to read: 51 | 52 | ```yaml 53 | --- 54 | path: github.com/user/newproject 55 | deps: 56 | - loc: gopkg.in/yaml.v1 # the same path you would use for go get 57 | ``` 58 | 59 | **Adding a dependency with a specific version:** 60 | 61 | Change .go.yaml to read: 62 | 63 | ```yaml 64 | --- 65 | path: github.com/user/newproject 66 | deps: 67 | - loc: gopkg.in/yaml.v1 # the same path you would use for go get 68 | 69 | - loc: https://github.com/mediocregopher/flagconfig.git 70 | type: git 71 | ref: v0.4.2 # A tag in this case, could be commit hash or branch name 72 | path: github.com/mediocregopher/flagconfig 73 | ``` 74 | 75 | Run `goat deps` to automatically fetch both of your dependencies into your 76 | project. Running `goat build` or `goat run` within your project will 77 | automatically use any dependencies goat has fetched. 78 | 79 | # More Usage 80 | 81 | See the [tutorial][tutorial] for a basic use case for goat with more explanation 82 | and a real project. After that check out the [.go.yaml file][projfile] for more 83 | details on what kind of features goat has for dependency management. There are 84 | also some [special features][special] that don't really fit in anywhere else 85 | that might be useful to know about. 86 | 87 | # Installation 88 | 89 | To use goat you can either get a pre-compiled binary or build it yourself. Once 90 | you get the binary I recommend renaming it as `go` (`alias go=goat`), 91 | so that `goat` gets used whenever you use the `go` utility. Don't worry, all 92 | `go` commands inside and outside of goat projects will behave the same way. 93 | 94 | ## Pre-built 95 | 96 | Check the releases tab on github, the latest release will have pre-compiled 97 | binaries for various systems, choose the one that applies to you. 98 | 99 | ## Build it yourself 100 | 101 | To build goat yourself make sure you have `go` installed (go figure). 102 | 103 | ```bash 104 | git clone https://github.com/mediocregopher/goat.git 105 | cd goat 106 | make 107 | ``` 108 | 109 | The binaries will be found in the `bin` directory. 110 | 111 | # Copyrights 112 | 113 | Goat ASCII Art (c) 1997 ejm, Creative Commons 114 | 115 | [intro]: /docs/introduction.md 116 | [tutorial]: /docs/tut.md 117 | [projfile]: /docs/projfile.md 118 | [special]: /docs/special.md 119 | -------------------------------------------------------------------------------- /env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "syscall" 8 | 9 | . "github.com/mediocregopher/goat/common" 10 | "gopkg.in/yaml.v1" 11 | ) 12 | 13 | var PROJFILE = ".go.yaml" 14 | 15 | // unmarshal takes in some bytes and tries to decode them into a GoatEnv 16 | // structure 17 | func unmarshal(genvraw []byte) (*GoatEnv, error) { 18 | genv := GoatEnv{} 19 | if err := yaml.Unmarshal(genvraw, &genv); err != nil { 20 | return nil, err 21 | } 22 | return &genv, nil 23 | } 24 | 25 | type GoatEnv struct { 26 | // ProjRoot is the absolute path to the project root in the current 27 | // environment 28 | ProjRoot string 29 | 30 | // Path is the path that the project will be using for its own internal 31 | // import statements, and consequently what other projects depending on this 32 | // one will use as well. 33 | Path string `yaml:"path"` 34 | 35 | // Dependencies are the dependencies listed in the project's project file 36 | Dependencies []Dependency `yaml:"deps"` 37 | } 38 | 39 | // NewGoatEnv takes in the directory where a goat project file can be found, 40 | // creates the GoatEnv struct based on that file, and returns it 41 | func NewGoatEnv(projroot string) (*GoatEnv, error) { 42 | projfile := filepath.Join(projroot, PROJFILE) 43 | b, err := ioutil.ReadFile(projfile) 44 | if err != nil { 45 | return nil, err 46 | } 47 | genv, err := unmarshal(b) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | genv.ProjRoot = projroot 53 | return genv, nil 54 | } 55 | 56 | // AbsDepDir is the absolute path to the dependency directory inside the goat 57 | // data directory 58 | func (genv *GoatEnv) AbsDepDir() string { 59 | return filepath.Join(genv.AbsGoatDir(), "deps") 60 | } 61 | 62 | // AbsGoatDir is the absolute path to the goat data directory 63 | func (genv *GoatEnv) AbsGoatDir() string { 64 | return filepath.Join(genv.ProjRoot, ".goat") 65 | } 66 | 67 | // AbsProjFile is the absolute path to the goat project file for this 68 | // environment 69 | func (genv *GoatEnv) AbsProjFile() string { 70 | return filepath.Join(genv.ProjRoot, PROJFILE) 71 | } 72 | 73 | func pathExists(path string) bool { 74 | if _, err := os.Stat(path); os.IsNotExist(err) { 75 | return false 76 | } 77 | return true 78 | } 79 | 80 | // Setup makes sure the goat env has all the proper directories created inside 81 | // of it. This includes the lib/ directory, and if it's specified the Path 82 | // loopback in the lib/ directory 83 | func (genv *GoatEnv) Setup() error { 84 | var err error 85 | 86 | // Make the dep directory if it doesn't exist 87 | depdir := genv.AbsDepDir() 88 | if !pathExists(depdir) { 89 | err = os.MkdirAll(depdir, 0755) 90 | if err != nil { 91 | return err 92 | } 93 | } 94 | 95 | if genv.Path != "" { 96 | loopbackPath := filepath.Join(depdir, "src", genv.Path) 97 | if !pathExists(loopbackPath) { 98 | loopbackDir := filepath.Dir(loopbackPath) 99 | // Rel returns a relative path from a -> b, we want to loop from the 100 | // loopbackPath in the dep sub-directory to the project root using a 101 | // symlink 102 | linkTo, err := filepath.Rel(loopbackDir, genv.ProjRoot) 103 | if err != nil { 104 | return err 105 | } 106 | if err = os.MkdirAll(loopbackDir, 0755); err != nil { 107 | return err 108 | } else if err = os.Symlink(linkTo, loopbackPath); err != nil { 109 | return err 110 | } 111 | } 112 | } 113 | 114 | return nil 115 | } 116 | 117 | func (genv *GoatEnv) PrependToGoPath() error { 118 | gopath, _ := syscall.Getenv("GOPATH") 119 | return syscall.Setenv("GOPATH", genv.AbsDepDir()+":"+gopath) 120 | } 121 | -------------------------------------------------------------------------------- /docs/tut.md: -------------------------------------------------------------------------------- 1 | # Hello world with goat 2 | 3 | This tutorial will walk you through the steps of setting up a simple goat 4 | project and importing some dependencies using the `.go.yaml` file. The first 5 | step is setting up the goat binary, which you can find instructions on in the 6 | main [README.md](/). I will assume you have a binary called `goat` available on 7 | your `PATH`. 8 | 9 | ## Reference 10 | 11 | We will be building a project structure in this tutorial. I'll try to be as 12 | explicit as possible when writing this, but in case you get lost here is what 13 | the project will look like at the end of this tutorial: 14 | 15 | ``` 16 | /goatproject 17 | .go.yaml 18 | main.go 19 | /foo 20 | foo.go 21 | ``` 22 | 23 | Not too complicated (I hope!) 24 | 25 | ## Initialization and .go.yaml 26 | 27 | The first step is to create a new directory. This directory will be the root of 28 | the rest of your project and can be located anywhere you want. I'm going to put 29 | mine in `/tmp`: 30 | 31 | ```bash 32 | > cd /tmp 33 | > mkdir goatproject 34 | > cd goatproject 35 | ``` 36 | 37 | Now we need to actually make this directory a goat project. To do this all that 38 | goat needs is for a `.go.yaml` to exist in the root, and for it to define the 39 | project's import-able path. 40 | 41 | ```bash 42 | echo '--- 43 | path: github.com/yourusername/goatproject' > .go.yaml 44 | ``` 45 | 46 | You'll see the meaning of `path` a bit later. 47 | 48 | Now whenever `goat` is used (in place of `go`) on the command-line and your 49 | current working directory is in `goatproject` or one of `goatproject`'s children 50 | your `GOPATH` will be set to (assuming you put your project in `/tmp` like me): 51 | 52 | ``` 53 | /tmp/goatproject/.goat/deps:$OLDGOPATH 54 | ``` 55 | 56 | This isn't too important, but it may be useful in the future so there ya go. 57 | 58 | Our project has some dependencies. Normally we would fetch these with `go get`, 59 | but we're too cool for that. Make your `.go.yaml`'s contents be the following: 60 | 61 | ```yaml 62 | --- 63 | path: github.com/yourusername/goatproject 64 | deps: 65 | - loc: code.google.com/p/go.example/newmath 66 | ``` 67 | 68 | This is the equivalent of having goat do a 69 | `go get code.google.com/p/go.example/newmath` inside our project. To see a full 70 | write-up on `.go.yaml`'s dependency syntax and how to use it see the 71 | [.go.yaml](/docs/projfile.md) documentation. 72 | 73 | To actually download the dependency do (you'll need mercurial installed): 74 | 75 | ```bash 76 | > cd /tmp/goatproject #if you hadn't already 77 | > goat deps 78 | ``` 79 | 80 | This should fetch the dependency and put it in the `.goat/deps` directory in 81 | your project. You shouldn't check this directory into your version control (if 82 | you're using any), it's just a utility for goat. 83 | 84 | If in the future you change the .go.yaml you can call `goat deps` again and it 85 | will re-setup your `.goat/deps` directory with the changes. 86 | 87 | ## Using the dependency 88 | 89 | We will now import the dependency we downloaded and use it in some of our own 90 | code. Create the file `foo/foo.go` and in it put: 91 | 92 | ```go 93 | package foo 94 | 95 | import ( 96 | "code.google.com/p/go.example/newmath" 97 | ) 98 | 99 | func SqrtTwo() float64 { 100 | return newmath.Sqrt(2) 101 | } 102 | 103 | func SqrtThree() float64 { 104 | return newmath.Sqrt(3) 105 | } 106 | ``` 107 | 108 | You can see the syntax for using this library is exactly the same as if we had 109 | installed the dependency globally. This is because the dependency is in our 110 | `.goat/deps` directory, which is at the front of our `GOPATH` (when using 111 | `goat`). 112 | 113 | ## Using our own package 114 | 115 | We have a package in our project now, `foo`, that we'd like to use. Create the 116 | file `main.go` and put in it: 117 | 118 | ```go 119 | package main 120 | 121 | import ( 122 | "github.com/yourusername/goatproject/foo" 123 | "fmt" 124 | ) 125 | 126 | func main() { 127 | fmt.Printf("The square root of two is: %v\n", foo.SqrtTwo()) 128 | } 129 | ``` 130 | 131 | The `github.com/yourusername/goatproject` part corresponds to the `path` field 132 | in the `.go.yaml`, and when you use it goat does a bit of magic so that when go 133 | searches for that path it finds the project's root directory, even though the 134 | `goatproject` directory isn't in a folder called `github.com/yourusername`. 135 | 136 | ## Action! 137 | 138 | To actually run our code: 139 | 140 | ```bash 141 | > cd /tmp/goatproject #if you haven't already 142 | > goat run main.go 143 | ``` 144 | 145 | That's it! `goat` passes through to `go` all commands that it doesn't recognize 146 | after setting up the environment variables, so it's super easy to get existing 147 | and new projects up and running. 148 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright 2014 Brian Picciano 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | --------------------------------------------------------------------------------