├── .gitignore ├── .travis.yml ├── Gomfile ├── README.mkd ├── exec.go ├── exec_test.go ├── gen.go ├── gen_test.go ├── gomfile.go ├── gomfile_test.go ├── install.go ├── main.go └── misc ├── vim ├── ftdetect │ └── gomfile.vim └── plugin │ └── gom.vim └── zsh └── _gom /.gitignore: -------------------------------------------------------------------------------- 1 | gom 2 | _vendor/** 3 | .idea 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - tip 4 | before_install: 5 | - go get github.com/mattn/gom 6 | script: 7 | - $HOME/gopath/bin/gom install -x 8 | - $HOME/gopath/bin/gom test -x 9 | -------------------------------------------------------------------------------- /Gomfile: -------------------------------------------------------------------------------- 1 | group :production, :development do 2 | gom 'github.com/mattn/gover' 3 | gom 'github.com/hashicorp/go-version' 4 | gom 'github.com/daviddengcn/go-colortext', :goos => [:windows, :linux, :darwin] 5 | end 6 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | gom - Go Manager 2 | ================ 3 | 4 | [![Build Status](https://travis-ci.org/mattn/gom.png?branch=master)](https://travis-ci.org/mattn/gom) 5 | 6 | Why 7 | --- 8 | 9 | The `go get` command is useful. But we want to fix the problem where package versions are different from the latest update. 10 | Are you going to do `go get -tags=1.1 ...`, `go get -tag=0.3` for each of them? We want to freeze package version. 11 | Ruby's bundle is awesome. 12 | 13 | Installation 14 | ------------ 15 | 16 | go get github.com/mattn/gom 17 | 18 | Gomfile 19 | ------- 20 | 21 | gom 'github.com/mattn/go-runewidth', :tag => 'go1' 22 | gom 'github.com/mattn/go-scan', :commit => 'ecb144fb1f2848a24ebfdadf8e64380406d87206' 23 | gom 'github.com/daviddengcn/go-colortext' 24 | gom 'github.com/mattn/go-ole', :goos => 'windows' 25 | 26 | # Execute only in the "test" environment. 27 | group :test do 28 | gom 'github.com/mattn/go-sqlite3' 29 | end 30 | 31 | # Execute only for the "custom_group" group. 32 | group :custom_group do 33 | gom 'github.com/golang/lint/golint' 34 | end 35 | 36 | By default `gom install` install all packages, except those in the listed groups. 37 | You can install packages from groups based on the environment using flags (`development`, `test` & `production`) : `gom -test install` 38 | 39 | Custom groups my be specified using the -groups flag : `gom -test -groups=custom_group,special install` 40 | 41 | Usage 42 | ----- 43 | 44 | Create \_vendor directory and bundle packages into it 45 | 46 | gom install 47 | 48 | Build on current directory with \_vendor packages 49 | 50 | gom build 51 | 52 | Run tests on current directory with \_vendor packages 53 | 54 | gom test 55 | 56 | Generate .travis.yml that uses `gom test` 57 | 58 | gom gen travis-yml 59 | 60 | You can always change the name relative to the current `$GOPATH` directory using an environment variable: `GOM_VENDOR_NAME` 61 | 62 | ```bash 63 | $ # to use a regular $GOPATH/src folder you should specify GOM_VENDOR_NAME equal '.' 64 | $ GOM_VENDOR_NAME=. gom 65 | ``` 66 | 67 | Tutorial 68 | -------- 69 | 70 | Writing Gomfile and bundle 71 | 72 | $ ls 73 | main.go 74 | 75 | $ gom gen gomfile 76 | 77 | $ cat Gomfile 78 | gom 'github.com/daviddengcn/go-colortext' 79 | gom 'github.com/mattn/go-runewidth' 80 | 81 | $ gom install 82 | installing github.com/daviddengcn/go-colortext 83 | installing github.com/mattn/go-runewidth 84 | 85 | $ find \_vendor/src -maxdepth 2 86 | \_vendor/src 87 | \_vendor/src/github.com 88 | \_vendor/src/github.com/daviddengcn 89 | \_vendor/src/github.com/mattn 90 | 91 | $ gom build 92 | 93 | If you want to bundle specified tag, branch or commit 94 | 95 | gom 'github.com/mattn/go-runewidth', :tag => 'tag_name' 96 | gom 'github.com/mattn/go-runewidth', :branch => 'branch_name' 97 | gom 'github.com/mattn/go-runewidth', :commit => 'commit_name' 98 | 99 | If you want to bundle a repository that `go get` can't access 100 | 101 | gom 'github.com/username/repository', :command => 'git clone http://example.com/repository.git' 102 | 103 | If you want to change local repository directory with command 'git clone', also skipdep and insecure, which is useful in internal network environment. 104 | 105 | gom 'github.com/username/repository', :private => 'true', :target => 'repository', :insecure=>'true', :skipdep=>'true' 106 | 107 | Todo 108 | ---- 109 | 110 | * Documentation 111 | 112 | Author 113 | ------ 114 | 115 | Yasuhiro Matsumoto mattn.jp@gmail.com 116 | 117 | License 118 | ------- 119 | 120 | MIT: http://mattn.mit-license.org/2013 121 | -------------------------------------------------------------------------------- /exec.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/daviddengcn/go-colortext" 5 | "os" 6 | "os/exec" 7 | "os/signal" 8 | "path/filepath" 9 | "strings" 10 | "syscall" 11 | ) 12 | 13 | type Color int 14 | 15 | const ( 16 | None Color = Color(ct.None) 17 | Red Color = Color(ct.Red) 18 | Blue Color = Color(ct.Blue) 19 | Green Color = Color(ct.Green) 20 | ) 21 | 22 | func handleSignal() { 23 | sc := make(chan os.Signal, 10) 24 | signal.Notify(sc, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP) 25 | go func() { 26 | <-sc 27 | ct.ResetColor() 28 | os.Exit(0) 29 | }() 30 | } 31 | 32 | func ready() error { 33 | dir, err := os.Getwd() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | vendor, err := filepath.Abs(vendorFolder) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | for { 44 | file := filepath.Join(dir, "Gomfile") 45 | if isFile(file) { 46 | vendor = filepath.Join(dir, vendorFolder) 47 | break 48 | } 49 | next := filepath.Clean(filepath.Join(dir, "..")) 50 | if next == dir { 51 | dir = "" 52 | break 53 | } 54 | dir = next 55 | } 56 | 57 | binPath := os.Getenv("PATH") + 58 | string(filepath.ListSeparator) + 59 | filepath.Join(vendor, "bin") 60 | err = os.Setenv("PATH", binPath) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | var paths []string 66 | if dir == "" { 67 | paths = []string{vendor, os.Getenv("GOPATH")} 68 | } else { 69 | paths = []string{vendor, dir, os.Getenv("GOPATH")} 70 | } 71 | vendor = strings.Join(paths, string(filepath.ListSeparator)) 72 | err = os.Setenv("GOPATH", vendor) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | return nil 78 | } 79 | 80 | var stdout = os.Stdout 81 | var stderr = os.Stderr 82 | var stdin = os.Stdin 83 | 84 | func run(args []string, c Color) error { 85 | if err := ready(); err != nil { 86 | return err 87 | } 88 | if len(args) == 0 { 89 | usage() 90 | } 91 | cmd := exec.Command(args[0], args[1:]...) 92 | cmd.Stdout = stdout 93 | cmd.Stderr = stderr 94 | cmd.Stdin = stdin 95 | ct.ChangeColor(ct.Color(c), true, ct.None, false) 96 | err := cmd.Run() 97 | ct.ResetColor() 98 | return err 99 | } 100 | -------------------------------------------------------------------------------- /exec_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "strconv" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestExec(t *testing.T) { 14 | dir, err := ioutil.TempDir("", "gom") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | defer os.RemoveAll(dir) 19 | cwd, err := os.Getwd() 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | err = os.Chdir(dir) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | defer os.Chdir(cwd) 28 | f, err := ioutil.TempFile(dir, "gom") 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | vendor := filepath.Join(dir, vendorFolder) 33 | err = os.MkdirAll(vendorSrc(vendor), 0755) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | oldstdout := stdout 38 | defer func() { 39 | stdout = oldstdout 40 | }() 41 | stdout = f 42 | err = run([]string{"go", "env"}, None) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | f.Close() 47 | stdout = oldstdout 48 | b, err := ioutil.ReadFile(f.Name()) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | gopath := "" 53 | for _, line := range strings.Split(string(b), "\n") { 54 | if runtime.GOOS == "windows" { 55 | item := strings.SplitN(line, " ", 2) 56 | if len(item) < 2 { 57 | continue 58 | } 59 | if strings.HasPrefix(item[1], "GOPATH=") { 60 | gopath = item[1][7:] 61 | } 62 | } else if strings.HasPrefix(line, "GOPATH=") { 63 | gopath, _ = strconv.Unquote(line[7:]) 64 | } 65 | } 66 | found := false 67 | vendorInfo, _ := os.Stat(vendor) 68 | for _, s := range strings.Split(gopath, string(filepath.ListSeparator)) { 69 | currentInfo, _ := os.Stat(s) 70 | if os.SameFile(vendorInfo, currentInfo) { 71 | found = true 72 | break 73 | } 74 | } 75 | if !found { 76 | t.Fatalf("Expected %v, but %v:", vendor, gopath) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /gen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "go/build" 7 | "os" 8 | "path/filepath" 9 | "sort" 10 | "strings" 11 | ) 12 | 13 | type importPackages []importPackage 14 | type importPackage struct { 15 | path string 16 | isTestFile bool 17 | } 18 | 19 | func (slice importPackages) Len() int { 20 | return len(slice) 21 | } 22 | 23 | func (slice importPackages) Less(i, j int) bool { 24 | return slice[i].path < slice[j].path 25 | } 26 | 27 | func (slice importPackages) Swap(i, j int) { 28 | slice[i], slice[j] = slice[j], slice[i] 29 | } 30 | 31 | const travis_yml = ".travis.yml" 32 | 33 | func genTravisYml() error { 34 | _, err := os.Stat(travis_yml) 35 | if err == nil { 36 | return errors.New(".travis.yml already exists") 37 | } 38 | f, err := os.Create(travis_yml) 39 | if err != nil { 40 | return err 41 | } 42 | defer f.Close() 43 | f.WriteString(`language: go 44 | go: 45 | - tip 46 | before_install: 47 | - go get github.com/mattn/gom 48 | script: 49 | - $HOME/gopath/bin/gom install 50 | - $HOME/gopath/bin/gom test 51 | `) 52 | return nil 53 | } 54 | 55 | func appendPkg(pkgs []importPackage, pkg string) ([]importPackage, bool) { 56 | for _, ele := range pkgs { 57 | if ele.path == pkg { 58 | return pkgs, false 59 | } 60 | } 61 | return append(pkgs, importPackage{path: pkg}), true 62 | } 63 | 64 | func appendPkgs(pkgs, more []importPackage) []importPackage { 65 | for _, pkg := range more { 66 | pkgs, _ = appendPkg(pkgs, pkg.path) 67 | } 68 | return pkgs 69 | } 70 | 71 | func scanDirectory(path, srcDir string) (ret []importPackage, err error) { 72 | pkg, err := build.Import(path, srcDir, build.AllowBinary) 73 | if err != nil { 74 | return ret, err 75 | } 76 | for _, imp := range pkg.Imports { 77 | if imp == "C" { 78 | continue 79 | } 80 | switch { 81 | case pkg.Goroot: 82 | // Ignore standard packages 83 | case !build.IsLocalImport(imp): 84 | // Add the external package 85 | ret, _ = appendPkg(ret, imp) 86 | fallthrough 87 | default: 88 | // Does the recursive walk 89 | pkgs, err := scanDirectory(imp, pkg.Dir) 90 | if err != nil { 91 | return ret, err 92 | } 93 | ret = appendPkgs(ret, pkgs) 94 | } 95 | } 96 | retTests := []importPackage{} 97 | isAdd := false 98 | for _, imp := range pkg.TestImports { 99 | switch { 100 | case pkg.Goroot: 101 | // Ignore standard packages 102 | break 103 | case !build.IsLocalImport(imp): 104 | // Add the external package 105 | retTests, isAdd = appendPkg(retTests, imp) 106 | if isAdd { 107 | retTests[len(retTests)-1].isTestFile = true 108 | } 109 | } 110 | } 111 | ret = append(ret, retTests...) 112 | return ret, err 113 | } 114 | 115 | func vcsScan(p, target string) (*vcsCmd, string, string) { 116 | name := "" 117 | for _, elem := range strings.Split(target, "/") { 118 | var vcs *vcsCmd 119 | p = filepath.Join(p, elem) 120 | if name == "" { 121 | name = elem 122 | } else { 123 | name += `/` + elem 124 | } 125 | if isDir(filepath.Join(p, ".git")) { 126 | vcs = git 127 | } else if isDir(filepath.Join(p, ".hg")) { 128 | vcs = hg 129 | } else if isDir(filepath.Join(p, ".bzr")) { 130 | vcs = bzr 131 | } 132 | if vcs != nil { 133 | return vcs, name, p 134 | } 135 | } 136 | return nil, "", "" 137 | } 138 | 139 | func genGomfile() error { 140 | _, err := os.Stat("Gomfile") 141 | if err == nil { 142 | return errors.New("Gomfile already exists") 143 | } 144 | dir, err := os.Getwd() 145 | if err != nil { 146 | return err 147 | } 148 | all, err := scanDirectory(".", dir) 149 | if err != nil { 150 | return err 151 | } 152 | sort.Sort(importPackages(all)) 153 | goms := make([]Gom, 0) 154 | for _, pkg := range all { 155 | for _, p := range filepath.SplitList(os.Getenv("GOPATH")) { 156 | var vcs *vcsCmd 157 | var n string 158 | vcs, n, p = vcsScan(filepath.Join(p, "src"), pkg.path) 159 | if vcs != nil { 160 | found := false 161 | for _, gom := range goms { 162 | if gom.name == n { 163 | found = true 164 | break 165 | } 166 | } 167 | if !found { 168 | gom := Gom{name: n, options: make(map[string]interface{})} 169 | rev, err := vcs.Revision(p) 170 | if err == nil && rev != "" { 171 | gom.options["commit"] = rev 172 | } 173 | if pkg.isTestFile { 174 | gom.options["group"] = "test" 175 | } 176 | goms = append(goms, gom) 177 | } 178 | } 179 | } 180 | } 181 | 182 | return writeGomfile("Gomfile", goms) 183 | } 184 | 185 | func genGomfileLock() error { 186 | allGoms, err := parseGomfile("Gomfile") 187 | if err != nil { 188 | return err 189 | } 190 | vendor, err := filepath.Abs(vendorFolder) 191 | if err != nil { 192 | return err 193 | } 194 | goms := make([]Gom, 0) 195 | for _, gom := range allGoms { 196 | if group, ok := gom.options["group"]; ok { 197 | if !matchEnv(group) { 198 | continue 199 | } 200 | } 201 | if goos, ok := gom.options["goos"]; ok { 202 | if !matchOS(goos) { 203 | continue 204 | } 205 | } 206 | goms = append(goms, gom) 207 | } 208 | 209 | for _, gom := range goms { 210 | var vcs *vcsCmd 211 | var p string 212 | vcs, _, p = vcsScan(vendorSrc(vendor), gom.name) 213 | if vcs != nil { 214 | rev, err := vcs.Revision(p) 215 | if err == nil && rev != "" { 216 | gom.options["commit"] = rev 217 | } 218 | } 219 | } 220 | err = writeGomfile("Gomfile.lock", goms) 221 | if err == nil { 222 | fmt.Println("Gomfile.lock is generated") 223 | } 224 | return err 225 | } 226 | -------------------------------------------------------------------------------- /gen_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestGomfile(t *testing.T) { 12 | dir, err := ioutil.TempDir("", "gom") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | defer os.RemoveAll(dir) 17 | 18 | // 'go env GOPATH' returns followed links while ioutil.TempDir(os.TempDir) may returns symbolic link 19 | dir, err = filepath.EvalSymlinks(dir) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | cwd, err := os.Getwd() 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | err = os.Chdir(dir) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | defer os.Chdir(cwd) 33 | f, err := ioutil.TempFile(dir, "gom") 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | vendor := filepath.Join(dir, vendorFolder) 38 | err = os.MkdirAll(filepath.Join(vendor, "src"), 0755) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | oldstdout := stdout 43 | defer func() { 44 | stdout = oldstdout 45 | }() 46 | stdout = f 47 | err = run([]string{"go", "env", "GOPATH"}, None) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | f.Close() 52 | stdout = oldstdout 53 | b, err := ioutil.ReadFile(f.Name()) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | gopath := string(b) 58 | found := false 59 | for _, s := range strings.Split(gopath, string(filepath.ListSeparator)) { 60 | if filepath.Clean(s) == filepath.Clean(vendor) { 61 | found = true 62 | break 63 | } 64 | } 65 | if !found { 66 | t.Fatalf("Expected %v, but %v:", vendor, found) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /gomfile.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "regexp" 9 | "runtime" 10 | "sort" 11 | "strings" 12 | ) 13 | 14 | var qx = `'[^']*'|"[^"]*"` 15 | var kx = `:[a-z][a-z0-9_]*` 16 | var ax = `(?:\s*` + kx + `\s*|,\s*` + kx + `\s*)` 17 | var re_group = regexp.MustCompile(`\s*group\s+((?:` + kx + `\s*|,\s*` + kx + `\s*)*)\s*do\s*$`) 18 | var re_end = regexp.MustCompile(`\s*end\s*$`) 19 | var re_gom = regexp.MustCompile(`^\s*gom\s+(` + qx + `)\s*((?:,\s*` + kx + `\s*=>\s*(?:` + qx + `|\s*\[\s*` + ax + `*\s*\]\s*))*)$`) 20 | var re_options = regexp.MustCompile(`(,\s*` + kx + `\s*=>\s*(?:` + qx + `|\s*\[\s*` + ax + `*\s*\]\s*)\s*)`) 21 | 22 | func unquote(name string) string { 23 | name = strings.TrimSpace(name) 24 | if len(name) > 2 { 25 | if (name[0] == '\'' && name[len(name)-1] == '\'') || (name[0] == '"' && name[len(name)-1] == '"') { 26 | return name[1 : len(name)-1] 27 | } 28 | } 29 | return name 30 | } 31 | 32 | func matchOS(any interface{}) bool { 33 | var envs []string 34 | if as, ok := any.([]string); ok { 35 | envs = as 36 | } else if s, ok := any.(string); ok { 37 | envs = []string{s} 38 | } else { 39 | return false 40 | } 41 | 42 | if has(envs, runtime.GOOS) { 43 | return true 44 | } 45 | return false 46 | } 47 | func matchEnv(any interface{}) bool { 48 | var envs []string 49 | if as, ok := any.([]string); ok { 50 | envs = as 51 | } else if s, ok := any.(string); ok { 52 | envs = []string{s} 53 | } else { 54 | return false 55 | } 56 | 57 | switch { 58 | case has(envs, "production") && *productionEnv: 59 | return true 60 | case has(envs, "development") && *developmentEnv: 61 | return true 62 | case has(envs, "test") && *testEnv: 63 | return true 64 | } 65 | 66 | for _, g := range customGroupList { 67 | if has(envs, g) { 68 | return true 69 | } 70 | } 71 | 72 | return false 73 | } 74 | 75 | func parseOptions(line string, options map[string]interface{}) { 76 | ss := re_options.FindAllStringSubmatch(line, -1) 77 | re_a := regexp.MustCompile(ax) 78 | for _, s := range ss { 79 | kvs := strings.SplitN(strings.TrimSpace(s[0])[1:], "=>", 2) 80 | kvs[0], kvs[1] = strings.TrimSpace(kvs[0]), strings.TrimSpace(kvs[1]) 81 | if kvs[1][0] == '[' { 82 | as := re_a.FindAllStringSubmatch(kvs[1][1:len(kvs[1])-1], -1) 83 | a := []string{} 84 | for i := range as { 85 | it := strings.TrimSpace(as[i][0]) 86 | if strings.HasPrefix(it, ",") { 87 | it = strings.TrimSpace(it[1:]) 88 | } 89 | if strings.HasPrefix(it, ":") { 90 | it = strings.TrimSpace(it[1:]) 91 | } 92 | a = append(a, it) 93 | } 94 | options[kvs[0][1:]] = a 95 | } else { 96 | options[kvs[0][1:]] = unquote(kvs[1]) 97 | } 98 | } 99 | } 100 | 101 | type Gom struct { 102 | name string 103 | options map[string]interface{} 104 | } 105 | 106 | func parseGomfile(filename string) ([]Gom, error) { 107 | f, err := os.Open(filename + ".lock") 108 | if err != nil { 109 | f, err = os.Open(filename) 110 | if err != nil { 111 | return nil, err 112 | } 113 | } 114 | br := bufio.NewReader(f) 115 | 116 | goms := make([]Gom, 0) 117 | 118 | n := 0 119 | skip := 0 120 | valid := true 121 | var envs []string 122 | for { 123 | n++ 124 | lb, _, err := br.ReadLine() 125 | if err != nil { 126 | if err == io.EOF { 127 | return goms, nil 128 | } 129 | return nil, err 130 | } 131 | line := strings.TrimSpace(string(lb)) 132 | if line == "" || strings.HasPrefix(line, "#") { 133 | continue 134 | } 135 | 136 | name := "" 137 | options := make(map[string]interface{}) 138 | var items []string 139 | if re_group.MatchString(line) { 140 | envs = strings.Split(re_group.FindStringSubmatch(line)[1], ",") 141 | for i := range envs { 142 | envs[i] = strings.TrimSpace(envs[i])[1:] 143 | } 144 | if matchEnv(envs) { 145 | valid = true 146 | continue 147 | } 148 | valid = false 149 | skip++ 150 | continue 151 | } else if re_end.MatchString(line) { 152 | if !valid { 153 | skip-- 154 | if skip < 0 { 155 | return nil, fmt.Errorf("Syntax Error at line %d", n) 156 | } 157 | } 158 | valid = false 159 | envs = nil 160 | continue 161 | } else if skip > 0 { 162 | continue 163 | } else if re_gom.MatchString(line) { 164 | items = re_gom.FindStringSubmatch(line)[1:] 165 | name = unquote(items[0]) 166 | parseOptions(items[1], options) 167 | } else { 168 | return nil, fmt.Errorf("Syntax Error at line %d", n) 169 | } 170 | if envs != nil { 171 | options["group"] = envs 172 | } 173 | goms = append(goms, Gom{name, options}) 174 | } 175 | return goms, nil 176 | } 177 | 178 | func keys(m map[string]interface{}) []string { 179 | ks := []string{} 180 | for k := range m { 181 | ks = append(ks, k) 182 | } 183 | sort.Strings(ks) 184 | return ks 185 | } 186 | 187 | func writeGomfile(filename string, goms []Gom) error { 188 | f, err := os.Create(filename) 189 | if err != nil { 190 | return err 191 | } 192 | defer f.Close() 193 | envn := map[string]interface{}{"": true} 194 | for _, gom := range goms { 195 | if e, ok := gom.options["group"]; ok { 196 | switch kv := e.(type) { 197 | case string: 198 | envn[kv] = true 199 | case []string: 200 | for _, kk := range kv { 201 | envn[kk] = true 202 | } 203 | } 204 | } 205 | } 206 | for _, env := range keys(envn) { 207 | indent := "" 208 | if env != "" { 209 | fmt.Fprintf(f, "\n") 210 | fmt.Fprintf(f, "group :%s do\n", env) 211 | indent = " " 212 | } 213 | for _, gom := range goms { 214 | if e, ok := gom.options["group"]; ok { 215 | found := false 216 | switch kv := e.(type) { 217 | case string: 218 | if kv == env { 219 | found = true 220 | } 221 | case []string: 222 | for _, kk := range kv { 223 | if kk == env { 224 | found = true 225 | break 226 | } 227 | } 228 | } 229 | if !found { 230 | continue 231 | } 232 | } else if env != "" { 233 | continue 234 | } 235 | fmt.Fprintf(f, indent+"gom '%s'", gom.name) 236 | for _, key := range keys(gom.options) { 237 | if key == "group" { 238 | continue 239 | } 240 | v := gom.options[key] 241 | switch kv := v.(type) { 242 | case string: 243 | fmt.Fprintf(f, ", :%s => '%s'", key, kv) 244 | case []string: 245 | ks := "" 246 | for _, vv := range kv { 247 | if ks == "" { 248 | ks = "[" 249 | } else { 250 | ks += ", " 251 | } 252 | ks += ":" + vv 253 | } 254 | ks += "]" 255 | fmt.Fprintf(f, ", :%s => %s", key, ks) 256 | } 257 | } 258 | fmt.Fprintf(f, "\n") 259 | } 260 | if env != "" { 261 | fmt.Fprintf(f, "end\n") 262 | } 263 | } 264 | return nil 265 | } 266 | -------------------------------------------------------------------------------- /gomfile_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func tempGomfile(content string) (string, error) { 10 | f, err := ioutil.TempFile("", "gom") 11 | if err != nil { 12 | return "", err 13 | } 14 | defer f.Close() 15 | _, err = f.WriteString(content) 16 | if err != nil { 17 | return "", err 18 | } 19 | name := f.Name() 20 | return name, nil 21 | } 22 | 23 | func TestGomfile1(t *testing.T) { 24 | filename, err := tempGomfile(` 25 | gom 'github.com/mattn/go-sqlite3', :tag => '>3.33' 26 | `) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | goms, err := parseGomfile(filename) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | expected := []Gom{ 35 | {name: "github.com/mattn/go-sqlite3", options: map[string]interface{}{"tag": ">3.33"}}, 36 | } 37 | if !reflect.DeepEqual(goms, expected) { 38 | t.Fatalf("Expected %v, but %v:", expected, goms) 39 | } 40 | } 41 | 42 | func TestGomfile2(t *testing.T) { 43 | filename, err := tempGomfile(` 44 | gom 'github.com/mattn/go-sqlite3', :tag => '>3.33' 45 | gom 'github.com/mattn/go-gtk' 46 | `) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | goms, err := parseGomfile(filename) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | expected := []Gom{ 55 | {name: "github.com/mattn/go-sqlite3", options: map[string]interface{}{"tag": ">3.33"}}, 56 | {name: "github.com/mattn/go-gtk", options: map[string]interface{}{}}, 57 | } 58 | if !reflect.DeepEqual(goms, expected) { 59 | t.Fatalf("Expected %v, but %v:", expected, goms) 60 | } 61 | } 62 | 63 | func TestGomfile3(t *testing.T) { 64 | filename, err := tempGomfile(` 65 | gom 'github.com/mattn/go-sqlite3', :tag => '3.14', :commit => 'asdfasdf' 66 | gom 'github.com/mattn/go-gtk', :foobar => 'barbaz' 67 | `) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | goms, err := parseGomfile(filename) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | expected := []Gom{ 76 | {name: "github.com/mattn/go-sqlite3", options: map[string]interface{}{"tag": "3.14", "commit": "asdfasdf"}}, 77 | {name: "github.com/mattn/go-gtk", options: map[string]interface{}{"foobar": "barbaz"}}, 78 | } 79 | if !reflect.DeepEqual(goms, expected) { 80 | t.Fatalf("Expected %v, but %v:", expected, goms) 81 | } 82 | } 83 | 84 | func TestGomfile4(t *testing.T) { 85 | filename, err := tempGomfile(` 86 | group :development do 87 | gom 'github.com/mattn/go-sqlite3', :tag => '3.14', :commit => 'asdfasdf' 88 | end 89 | 90 | group :test do 91 | gom 'github.com/mattn/go-gtk', :foobar => 'barbaz' 92 | end 93 | `) 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | 98 | *developmentEnv = true 99 | goms, err := parseGomfile(filename) 100 | *developmentEnv = false 101 | 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | expected := []Gom{ 106 | {name: "github.com/mattn/go-sqlite3", options: map[string]interface{}{"tag": "3.14", "commit": "asdfasdf", "group": []string{"development"}}}, 107 | } 108 | if !reflect.DeepEqual(goms, expected) { 109 | t.Fatalf("Expected %v, but %v:", expected, goms) 110 | } 111 | } 112 | 113 | func TestGomfile5(t *testing.T) { 114 | filename, err := tempGomfile(` 115 | group :custom_one do 116 | gom 'github.com/mattn/go-sqlite3', :tag => '3.14', :commit => 'asdfasdf' 117 | end 118 | 119 | group :custom_two do 120 | gom 'github.com/mattn/go-gtk', :foobar => 'barbaz' 121 | end 122 | `) 123 | if err != nil { 124 | t.Fatal(err) 125 | } 126 | 127 | customGroupList = []string{"custom_one", "custom_two"} 128 | goms, err := parseGomfile(filename) 129 | 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | expected := []Gom{ 134 | {name: "github.com/mattn/go-sqlite3", options: map[string]interface{}{"tag": "3.14", "commit": "asdfasdf", "group": []string{"custom_one"}}}, 135 | {name: "github.com/mattn/go-gtk", options: map[string]interface{}{"foobar": "barbaz", "group": []string{"custom_two"}}}, 136 | } 137 | if !reflect.DeepEqual(goms, expected) { 138 | t.Fatalf("Expected %v, but %v:", expected, goms) 139 | } 140 | 141 | customGroupList = []string{"custom_one"} 142 | goms, err = parseGomfile(filename) 143 | 144 | if err != nil { 145 | t.Fatal(err) 146 | } 147 | expected = []Gom{ 148 | {name: "github.com/mattn/go-sqlite3", options: map[string]interface{}{"tag": "3.14", "commit": "asdfasdf", "group": []string{"custom_one"}}}, 149 | } 150 | if !reflect.DeepEqual(goms, expected) { 151 | t.Fatalf("Expected %v, but %v:", expected, goms) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /install.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "path/filepath" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | type vcsCmd struct { 16 | checkout []string 17 | update []string 18 | revision []string 19 | revisionMask string 20 | } 21 | 22 | var ( 23 | hg = &vcsCmd{ 24 | []string{"hg", "update"}, 25 | []string{"hg", "pull"}, 26 | []string{"hg", "id", "-i"}, 27 | "^(.+)$", 28 | } 29 | git = &vcsCmd{ 30 | []string{"git", "checkout", "-q"}, 31 | []string{"git", "fetch"}, 32 | []string{"git", "rev-parse", "HEAD"}, 33 | "^(.+)$", 34 | } 35 | bzr = &vcsCmd{ 36 | []string{"bzr", "revert", "-r"}, 37 | []string{"bzr", "pull"}, 38 | []string{"bzr", "log", "-r-1", "--line"}, 39 | "^([0-9]+)", 40 | } 41 | ) 42 | 43 | func (vcs *vcsCmd) Checkout(p, destination string) error { 44 | args := append(vcs.checkout, destination) 45 | return vcsExec(p, args...) 46 | } 47 | 48 | func (vcs *vcsCmd) Update(p string) error { 49 | return vcsExec(p, vcs.update...) 50 | } 51 | 52 | func (vcs *vcsCmd) Revision(dir string) (string, error) { 53 | args := append(vcs.revision) 54 | cmd := exec.Command(args[0], args[1:]...) 55 | cmd.Dir = dir 56 | cmd.Stderr = os.Stderr 57 | b, err := cmd.Output() 58 | if err != nil { 59 | return "", err 60 | } 61 | rev := strings.TrimSpace(string(b)) 62 | if vcs.revisionMask != "" { 63 | return regexp.MustCompile(vcs.revisionMask).FindString(rev), nil 64 | } 65 | return rev, nil 66 | } 67 | 68 | func (vcs *vcsCmd) Sync(p, destination string) error { 69 | err := vcs.Checkout(p, destination) 70 | if err != nil { 71 | err = vcs.Update(p) 72 | if err != nil { 73 | return err 74 | } 75 | err = vcs.Checkout(p, destination) 76 | } 77 | return err 78 | } 79 | 80 | func vcsExec(dir string, args ...string) error { 81 | cmd := exec.Command(args[0], args[1:]...) 82 | cmd.Dir = dir 83 | cmd.Stdout = os.Stdout 84 | cmd.Stderr = os.Stderr 85 | return cmd.Run() 86 | } 87 | 88 | func list(dir string) ([]string, error) { 89 | cmd := exec.Command("go", "list", "./...") 90 | cmd.Dir = dir 91 | var stdout bytes.Buffer 92 | cmd.Stdout = &stdout 93 | err := cmd.Run() 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | return strings.Split(stdout.String(), "\n"), nil 99 | } 100 | 101 | func has(c interface{}, key string) bool { 102 | if m, ok := c.(map[string]interface{}); ok { 103 | _, ok := m[key] 104 | return ok 105 | } else if a, ok := c.([]string); ok { 106 | for _, s := range a { 107 | if ok && s == key { 108 | return true 109 | } 110 | } 111 | } 112 | return false 113 | } 114 | 115 | func (gom *Gom) Update() error { 116 | cmdArgs := []string{"go", "get", "-u"} 117 | if insecure, ok := gom.options["insecure"].(string); ok { 118 | if insecure == "true" { 119 | cmdArgs = append(cmdArgs, "-insecure") 120 | } 121 | } 122 | recursive := "/..." 123 | if recursiveFlag, ok := gom.options["recursive"].(string); ok { 124 | if recursiveFlag == "false" { 125 | recursive = "" 126 | } 127 | } 128 | cmdArgs = append(cmdArgs, gom.name+recursive) 129 | 130 | fmt.Printf("updating %s\n", gom.name) 131 | return run(cmdArgs, Green) 132 | } 133 | 134 | func (gom *Gom) Clone(args []string) error { 135 | vendor, err := filepath.Abs(vendorFolder) 136 | if err != nil { 137 | return err 138 | } 139 | if command, ok := gom.options["command"].(string); ok { 140 | target, ok := gom.options["target"].(string) 141 | if !ok { 142 | target = gom.name 143 | } 144 | 145 | srcdir := filepath.Join(vendor, "src", target) 146 | if err := os.MkdirAll(srcdir, 0755); err != nil { 147 | return err 148 | } 149 | 150 | customCmd := strings.Split(command, " ") 151 | customCmd = append(customCmd, srcdir) 152 | 153 | fmt.Printf("fetching %s (%v)\n", gom.name, customCmd) 154 | err = run(customCmd, Blue) 155 | if err != nil { 156 | return err 157 | } 158 | } else if private, ok := gom.options["private"].(string); ok { 159 | if private == "true" { 160 | target, ok := gom.options["target"].(string) 161 | if !ok { 162 | target = gom.name 163 | } 164 | srcdir := filepath.Join(vendor, "src", target) 165 | if _, err := os.Stat(srcdir); err != nil { 166 | if err := os.MkdirAll(srcdir, 0755); err != nil { 167 | return err 168 | } 169 | if err := gom.clonePrivate(srcdir); err != nil { 170 | return err 171 | } 172 | } else { 173 | if err := gom.pullPrivate(srcdir); err != nil { 174 | return err 175 | } 176 | } 177 | } 178 | } 179 | 180 | if skipdep, ok := gom.options["skipdep"].(string); ok { 181 | if skipdep == "true" { 182 | return nil 183 | } 184 | } 185 | cmdArgs := []string{"go", "get", "-d"} 186 | if insecure, ok := gom.options["insecure"].(string); ok { 187 | if insecure == "true" { 188 | cmdArgs = append(cmdArgs, "-insecure") 189 | } 190 | } 191 | recursive := "/..." 192 | if recursiveFlag, ok := gom.options["recursive"].(string); ok { 193 | if recursiveFlag == "false" { 194 | recursive = "" 195 | } 196 | } 197 | cmdArgs = append(cmdArgs, args...) 198 | cmdArgs = append(cmdArgs, gom.name+recursive) 199 | 200 | fmt.Printf("downloading %s\n", gom.name) 201 | return run(cmdArgs, Blue) 202 | } 203 | 204 | func (gom *Gom) pullPrivate(srcdir string) (err error) { 205 | cwd, err := os.Getwd() 206 | if err != nil { 207 | return err 208 | } 209 | if err := os.Chdir(srcdir); err != nil { 210 | return err 211 | } 212 | defer os.Chdir(cwd) 213 | 214 | fmt.Printf("fetching private repo %s\n", gom.name) 215 | 216 | branch := "master" 217 | if has(gom.options, "branch") { 218 | branch = gom.options["branch"].(string) 219 | } 220 | 221 | var vcs *vcsCmd 222 | if isDir(filepath.Join(srcdir, ".git")) { 223 | vcs = git 224 | } else if isDir(filepath.Join(srcdir, ".hg")) { 225 | vcs = hg 226 | } else if isDir(filepath.Join(srcdir, ".bzr")) { 227 | vcs = bzr 228 | } 229 | if vcs != nil { 230 | err = vcs.Sync(srcdir, branch) 231 | if err != nil { 232 | return 233 | } 234 | } 235 | 236 | pullCmd := "git pull origin " + branch 237 | pullArgs := strings.Split(pullCmd, " ") 238 | err = run(pullArgs, Blue) 239 | if err != nil { 240 | return 241 | } 242 | 243 | return 244 | } 245 | 246 | func (gom *Gom) clonePrivate(srcdir string) (err error) { 247 | name := strings.Split(gom.name, "/") 248 | if len(name) < 3 { 249 | return errors.New("the format of private repo address is wrong") 250 | } 251 | nameTail := strings.Join(name[2:], "/") 252 | privateUrl := fmt.Sprintf("git@%s:%s/%s", name[0], name[1], nameTail) 253 | 254 | fmt.Printf("fetching private repo %s\n", gom.name) 255 | cloneCmd := []string{"git", "clone", privateUrl, srcdir} 256 | err = run(cloneCmd, Blue) 257 | if err != nil { 258 | return 259 | } 260 | 261 | return 262 | } 263 | 264 | func (gom *Gom) Checkout() error { 265 | commit_or_branch_or_tag := "" 266 | if has(gom.options, "branch") { 267 | commit_or_branch_or_tag, _ = gom.options["branch"].(string) 268 | } 269 | if has(gom.options, "tag") { 270 | commit_or_branch_or_tag, _ = gom.options["tag"].(string) 271 | } 272 | if has(gom.options, "commit") { 273 | commit_or_branch_or_tag, _ = gom.options["commit"].(string) 274 | } 275 | if commit_or_branch_or_tag == "" { 276 | return nil 277 | } 278 | vendor, err := filepath.Abs(vendorFolder) 279 | if err != nil { 280 | return err 281 | } 282 | p := filepath.Join(vendor, "src") 283 | target, ok := gom.options["target"].(string) 284 | if !ok { 285 | target = gom.name 286 | } 287 | for _, elem := range strings.Split(target, "/") { 288 | var vcs *vcsCmd 289 | p = filepath.Join(p, elem) 290 | if isDir(filepath.Join(p, ".git")) { 291 | vcs = git 292 | } else if isDir(filepath.Join(p, ".hg")) { 293 | vcs = hg 294 | } else if isDir(filepath.Join(p, ".bzr")) { 295 | vcs = bzr 296 | } 297 | if vcs != nil { 298 | p = filepath.Join(vendor, "src", target) 299 | return vcs.Sync(p, commit_or_branch_or_tag) 300 | } 301 | } 302 | fmt.Printf("Warning: don't know how to checkout for %v\n", gom.name) 303 | return errors.New("gom currently support git/hg/bzr for specifying tag/branch/commit") 304 | } 305 | 306 | func hasGoSource(p string) bool { 307 | dir, err := os.Open(p) 308 | if err != nil { 309 | return false 310 | } 311 | defer dir.Close() 312 | fis, err := dir.Readdir(-1) 313 | if err != nil { 314 | return false 315 | } 316 | for _, fi := range fis { 317 | if fi.IsDir() { 318 | continue 319 | } 320 | name := fi.Name() 321 | if strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go") { 322 | return true 323 | } 324 | } 325 | return false 326 | } 327 | 328 | func (gom *Gom) Build(args []string) (err error) { 329 | return gom.build(args, true) 330 | } 331 | 332 | func (gom *Gom) build(args []string, move bool) (err error) { 333 | var vendor string 334 | vendor, err = filepath.Abs(vendorFolder) 335 | if err != nil { 336 | return err 337 | } 338 | 339 | if move && !isVendoringSupported { 340 | err := moveSrcToVendorSrc(vendor) 341 | if err != nil { 342 | return err 343 | } 344 | defer func() { 345 | err = moveSrcToVendor(vendor) 346 | }() 347 | } 348 | 349 | installCmd := []string{"go", "get"} 350 | hasPkg := false 351 | for _, arg := range args { 352 | if !strings.HasPrefix(arg, "-") { 353 | arg = path.Join(arg, "...") 354 | hasPkg = true 355 | } 356 | installCmd = append(installCmd, arg) 357 | } 358 | 359 | target, ok := gom.options["target"].(string) 360 | if !ok { 361 | target = gom.name 362 | } 363 | p := filepath.Join(vendor, "src", target) 364 | 365 | if hasPkg { 366 | return vcsExec(p, installCmd...) 367 | } 368 | 369 | pkgs, err := list(p) 370 | if err != nil { 371 | return err 372 | } 373 | 374 | for _, pkg := range pkgs { 375 | if isIgnorePackage(pkg) { 376 | continue 377 | } 378 | p = filepath.Join(vendor, "src", pkg) 379 | if !hasGoSource(p) { 380 | continue 381 | } 382 | err := vcsExec(p, installCmd...) 383 | if err != nil { 384 | return err 385 | } 386 | } 387 | return nil 388 | } 389 | 390 | func isFile(p string) bool { 391 | if fi, err := os.Stat(filepath.Join(p)); err == nil && !fi.IsDir() { 392 | return true 393 | } 394 | return false 395 | } 396 | 397 | func isDir(p string) bool { 398 | if fi, err := os.Stat(filepath.Join(p)); err == nil && fi.IsDir() { 399 | return true 400 | } 401 | return false 402 | } 403 | 404 | func isIgnorePackage(pkg string) bool { 405 | if pkg == "" { 406 | return true 407 | } 408 | paths := strings.Split(pkg, "/") 409 | for _, path := range paths { 410 | if path == "examples" { 411 | return true 412 | } 413 | if strings.HasPrefix(path, "_") { 414 | return true 415 | } 416 | } 417 | return false 418 | } 419 | 420 | func moveSrcToVendorSrc(vendor string) error { 421 | vendorSrc := filepath.Join(vendor, "src") 422 | dirs, err := readdirnames(vendor) 423 | if err != nil { 424 | return err 425 | } 426 | err = os.MkdirAll(vendorSrc, 0755) 427 | if err != nil { 428 | return err 429 | } 430 | for _, dir := range dirs { 431 | if dir == "bin" || dir == "pkg" || dir == "src" { 432 | continue 433 | } 434 | err = os.Rename(filepath.Join(vendor, dir), filepath.Join(vendorSrc, dir)) 435 | if err != nil { 436 | return err 437 | } 438 | } 439 | return nil 440 | } 441 | 442 | func moveSrcToVendor(vendor string) error { 443 | vendorSrc := filepath.Join(vendor, "src") 444 | dirs, err := readdirnames(vendorSrc) 445 | if err != nil { 446 | return err 447 | } 448 | for _, dir := range dirs { 449 | err = os.Rename(filepath.Join(vendorSrc, dir), filepath.Join(vendor, dir)) 450 | if err != nil { 451 | return err 452 | } 453 | } 454 | err = os.Remove(vendorSrc) 455 | if err != nil { 456 | return err 457 | } 458 | return nil 459 | } 460 | 461 | func readdirnames(dirname string) ([]string, error) { 462 | f, err := os.Open(dirname) 463 | if err != nil { 464 | return nil, err 465 | } 466 | list, err := f.Readdirnames(-1) 467 | f.Close() 468 | if err != nil { 469 | return nil, err 470 | } 471 | return list, nil 472 | } 473 | 474 | func parseInstallFlags(args []string) (opts map[string]string, retargs []string) { 475 | opts = make(map[string]string) 476 | re := regexp.MustCompile(`^--([a-z][a-z_]*)(=\S*)?`) 477 | for _, arg := range args { 478 | ss := re.FindAllStringSubmatch(arg, -1) 479 | if len(ss) > 0 { 480 | opts[ss[0][1]] = opts[ss[0][2]] 481 | } else { 482 | retargs = append(retargs, arg) 483 | } 484 | } 485 | return 486 | } 487 | 488 | func hasSaveOpts(opts map[string]string) bool { 489 | if _, ok := opts["save"]; ok { 490 | return true 491 | } 492 | if _, ok := opts["save-dev"]; ok { 493 | return true 494 | } 495 | return false 496 | } 497 | 498 | func install(args []string) error { 499 | var opts map[string]string 500 | opts, args = parseInstallFlags(args) 501 | allGoms, err := parseGomfile("Gomfile") 502 | if err != nil { 503 | return err 504 | } 505 | if hasSaveOpts(opts) { 506 | found := false 507 | for _, arg := range args { 508 | for _, gom := range allGoms { 509 | if gom.name == arg { 510 | found = true 511 | break 512 | } 513 | } 514 | if !found { 515 | options := map[string]interface{}{} 516 | if _, ok := opts["save-dev"]; ok { 517 | options["envs"] = []string{"development"} 518 | } 519 | allGoms = append(allGoms, Gom{name: arg, options: options}) 520 | } 521 | } 522 | err = writeGomfile("Gomfile", allGoms) 523 | if err != nil { 524 | return err 525 | } 526 | } 527 | vendor, err := filepath.Abs(vendorFolder) 528 | if err != nil { 529 | return err 530 | } 531 | _, err = os.Stat(vendor) 532 | if err != nil { 533 | err = os.MkdirAll(vendor, 0755) 534 | if err != nil { 535 | return err 536 | } 537 | } 538 | err = os.Setenv("GOPATH", vendor) 539 | if err != nil { 540 | return err 541 | } 542 | err = os.Setenv("GOBIN", filepath.Join(vendor, "bin")) 543 | if err != nil { 544 | return err 545 | } 546 | 547 | // 1. Filter goms to install 548 | goms := make([]Gom, 0) 549 | for _, gom := range allGoms { 550 | if group, ok := gom.options["group"]; ok { 551 | if !matchEnv(group) { 552 | continue 553 | } 554 | } 555 | if goos, ok := gom.options["goos"]; ok { 556 | if !matchOS(goos) { 557 | continue 558 | } 559 | } 560 | goms = append(goms, gom) 561 | } 562 | 563 | err = moveSrcToVendorSrc(vendor) 564 | if err != nil { 565 | return err 566 | } 567 | 568 | // 2. Clone the repositories 569 | for _, gom := range goms { 570 | err = gom.Clone(args) 571 | if err != nil { 572 | return err 573 | } 574 | } 575 | 576 | // 3. Checkout the commit/branch/tag if needed 577 | for _, gom := range goms { 578 | err = gom.Checkout() 579 | if err != nil { 580 | return err 581 | } 582 | } 583 | 584 | // 4. Build and install 585 | for _, gom := range goms { 586 | if skipdep, ok := gom.options["skipdep"].(string); ok { 587 | if skipdep == "true" { 588 | continue 589 | } 590 | } 591 | err = gom.build(args, false) 592 | if err != nil { 593 | return err 594 | } 595 | } 596 | 597 | err = moveSrcToVendor(vendor) 598 | if err != nil { 599 | return err 600 | } 601 | 602 | return nil 603 | } 604 | 605 | func update() error { 606 | goms, err := parseGomfile("Gomfile") 607 | if err != nil { 608 | return err 609 | } 610 | vendor, err := filepath.Abs(vendorFolder) 611 | if err != nil { 612 | return err 613 | } 614 | err = os.Setenv("GOPATH", vendor) 615 | if err != nil { 616 | return err 617 | } 618 | err = os.Setenv("GOBIN", filepath.Join(vendor, "bin")) 619 | if err != nil { 620 | return err 621 | } 622 | 623 | err = moveSrcToVendorSrc(vendor) 624 | if err != nil { 625 | return err 626 | } 627 | 628 | for _, gom := range goms { 629 | err = gom.Update() 630 | if err != nil { 631 | return err 632 | } 633 | vcs, _, p := vcsScan(vendorSrc(vendor), gom.name) 634 | if vcs != nil { 635 | rev, err := vcs.Revision(p) 636 | if err == nil && rev != "" { 637 | gom.options["commit"] = rev 638 | } 639 | } 640 | } 641 | 642 | err = moveSrcToVendor(vendor) 643 | if err != nil { 644 | return err 645 | } 646 | 647 | return writeGomfile("Gomfile", goms) 648 | } 649 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/hashicorp/go-version" 11 | "github.com/mattn/gover" 12 | ) 13 | 14 | func usage() { 15 | fmt.Printf(`Usage of %s: 16 | Tasks: 17 | gom build [options] : Build with _vendor packages 18 | gom install [options] : Install bundled packages into _vendor directory, by default. 19 | GOM_VENDOR_NAME=. gom install [options], for regular src folder. 20 | gom test [options] : Run tests with bundles 21 | gom run [options] : Run go file with bundles 22 | gom doc [options] : Run godoc for bundles 23 | gom exec [arguments] : Execute command with bundle environment 24 | gom tool [options] : Run go tool with bundles 25 | gom env [arguments] : Run go env 26 | gom fmt [arguments] : Run go fmt 27 | gom list [arguments] : Run go list 28 | gom vet [arguments] : Run go vet 29 | gom update : Update all dependencies (Experiment) 30 | gom gen travis-yml : Generate .travis.yml which uses "gom test" 31 | gom gen gomfile : Scan packages from current directory as root 32 | recursively, and generate Gomfile 33 | gom lock : Generate Gomfile.lock 34 | `, os.Args[0]) 35 | os.Exit(1) 36 | } 37 | 38 | var productionEnv = flag.Bool("production", false, "production environment") 39 | var developmentEnv = flag.Bool("development", false, "development environment") 40 | var testEnv = flag.Bool("test", false, "test environment") 41 | var customGroups = flag.String("groups", "", "comma-separated list of Gomfile groups") 42 | var customGroupList []string 43 | var vendorFolder string 44 | var isVendoringSupported bool 45 | 46 | func init() { 47 | isVendoringSupported = checkVendoringSupport() 48 | if isVendoringSupported { 49 | vendorFolder = "vendor" 50 | } else { 51 | if len(os.Getenv("GOM_VENDOR_NAME")) > 0 { 52 | vendorFolder = os.Getenv("GOM_VENDOR_NAME") 53 | } else { 54 | vendorFolder = "_vendor" 55 | } 56 | } 57 | } 58 | 59 | func goversion() string { 60 | defer recover() 61 | return gover.Version() 62 | } 63 | 64 | // checkVendoringSupport return whether go have native vendor support. 65 | // If return false, gom behave vendor directory as GOPATH. 66 | // If return true, gom doesn't move anything. 67 | func checkVendoringSupport() bool { 68 | go150, _ := version.NewVersion("1.5.0") 69 | go160, _ := version.NewVersion("1.6.0") 70 | go173, _ := version.NewVersion("1.7.3") 71 | ver := goversion() 72 | 73 | // TODO: maybe gccgo? 74 | if ver == "" { 75 | return true 76 | } 77 | 78 | goVer, err := version.NewVersion(strings.TrimPrefix(ver, "go")) 79 | if err != nil { 80 | panic(fmt.Sprintf("gover.Version() returned invalid semantic version: %s", ver)) 81 | } 82 | 83 | // See: https://golang.org/doc/go1.6#go_command 84 | if goVer.LessThan(go150) { 85 | return false 86 | } else if (goVer.Equal(go150) || goVer.GreaterThan(go150)) && goVer.LessThan(go160) { 87 | return os.Getenv("GO15VENDOREXPERIMENT") == "1" 88 | } else if (goVer.Equal(go160) || goVer.GreaterThan(go160)) && goVer.LessThan(go173) { 89 | return os.Getenv("GO15VENDOREXPERIMENT") != "0" 90 | } else { 91 | return true 92 | } 93 | } 94 | 95 | func vendorSrc(vendor string) string { 96 | if isVendoringSupported { 97 | return vendor 98 | } else { 99 | return filepath.Join(vendor, "src") 100 | } 101 | } 102 | 103 | func main() { 104 | flag.Usage = usage 105 | flag.Parse() 106 | if flag.NArg() == 0 { 107 | usage() 108 | } 109 | handleSignal() 110 | 111 | if !*productionEnv && !*developmentEnv && !*testEnv { 112 | *developmentEnv = true 113 | } 114 | 115 | customGroupList = strings.Split(*customGroups, ",") 116 | 117 | var err error 118 | subArgs := flag.Args()[1:] 119 | switch flag.Arg(0) { 120 | case "install", "i": 121 | err = install(subArgs) 122 | case "build", "b": 123 | err = run(append([]string{"go", "build"}, subArgs...), None) 124 | case "test", "t": 125 | err = run(append([]string{"go", "test"}, subArgs...), None) 126 | case "run", "r": 127 | err = run(append([]string{"go", "run"}, subArgs...), None) 128 | case "doc", "d": 129 | err = run(append([]string{"godoc"}, subArgs...), None) 130 | case "exec", "e": 131 | err = run(subArgs, None) 132 | case "env", "tool", "fmt", "list", "vet": 133 | err = run(append([]string{"go", flag.Arg(0)}, subArgs...), None) 134 | case "update", "u": 135 | err = update() 136 | case "gen", "g": 137 | switch flag.Arg(1) { 138 | case "travis-yml": 139 | err = genTravisYml() 140 | case "gomfile": 141 | err = genGomfile() 142 | default: 143 | usage() 144 | } 145 | case "lock", "l": 146 | err = genGomfileLock() 147 | default: 148 | usage() 149 | } 150 | if err != nil { 151 | fmt.Fprintln(os.Stderr, "gom: ", err) 152 | os.Exit(1) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /misc/vim/ftdetect/gomfile.vim: -------------------------------------------------------------------------------- 1 | au BufRead,BufNewFile Gomfile setlocal ft=gomfile syntax=ruby 2 | -------------------------------------------------------------------------------- /misc/vim/plugin/gom.vim: -------------------------------------------------------------------------------- 1 | let s:save_cpo = &cpo 2 | set cpo&vim 3 | 4 | 5 | function! s:setGomEnv() 6 | let $GOPATH = filter(split(system("gom exec env"), "\n"), "v:val =~ '^GOPATH='")[0][7:] 7 | endfunction 8 | 9 | 10 | command! SetGomEnv call s:setGomEnv() 11 | 12 | 13 | let &cpo = s:save_cpo 14 | unlet s:save_cpo 15 | -------------------------------------------------------------------------------- /misc/zsh/_gom: -------------------------------------------------------------------------------- 1 | #compdef gom 2 | 3 | _gom() { 4 | local context curcontext="$curcontext" state line cmds ret=1 5 | 6 | typeset -a build_flags 7 | build_flags=( 8 | '-a[force reinstallation of packages that are already up-to-date]' 9 | '-n[print the commands but do not run them]' 10 | '-p[number of parallel builds]:number' 11 | '-race[enable data race detection]' 12 | '-x[print the commands]' 13 | '-work[print temporary directory name and keep it]' 14 | '-ccflags[flags for 5c/6c/8c]:flags' 15 | '-gcflags[flags for 5g/6g/8g]:flags' 16 | '-ldflags[flags for 5l/6l/8l]:flags' 17 | '-gccgoflags[flags for gccgo]:flags' 18 | '-compiler[name of compiler to use]:name' 19 | '-installsuffix[suffix to add to package directory]:suffix' 20 | '-tags[list of build tags to consider satisfied]:tags' 21 | ) 22 | 23 | _arguments \ 24 | '1: :->cmds' \ 25 | '*:: :->args' \ 26 | && ret=0 27 | 28 | case $state in 29 | cmds) 30 | _values 'gom commands' \ 31 | 'build[Build with _vendor packages]' \ 32 | 'install[Install bundled packages into _vendor directory]' \ 33 | 'test[Run tests with bundles]' \ 34 | 'run[Run go file with bundles]' \ 35 | 'doc[Run godoc for bundles]' \ 36 | 'exec[Execute command with bundle environment]' \ 37 | 'gen[Generate .travis.yml or Gomfile]' \ 38 | && ret=0 39 | ;; 40 | args) 41 | case $line[1] in 42 | install) 43 | _arguments -s -w : \ 44 | ${build_flags[@]} \ 45 | '-v[show package names]' \ 46 | && ret=0 47 | ;; 48 | build) 49 | _arguments -s -w : \ 50 | ${build_flags[@]} \ 51 | '-v[show package names]' \ 52 | '-o[output file]:file:_files' \ 53 | '*:file:_path_files -g "*.go"' \ 54 | && ret=0 55 | ;; 56 | test) 57 | _arguments -s -w : \ 58 | ${build_flags[@]} \ 59 | '-c[do not run, compile the test binary]' \ 60 | '-i[do not run, install dependencies]' \ 61 | '-v[print test output]' \ 62 | '-x[print the commands]' \ 63 | '-short[use short mode]' \ 64 | '-parallel[number of parallel tests]:number' \ 65 | '-cpu[values of GOMAXPROCS to use]:number list' \ 66 | '-run[run tests and examples matching regexp]:regexp' \ 67 | '-bench[run benchmarks matching regexp]:regexp' \ 68 | '-benchmem[print memory allocation stats]' \ 69 | '-benchtime[run each benchmark until taking this long]:duration' \ 70 | '-blockprofile[write goroutine blocking profile to file]:file' \ 71 | '-blockprofilerate[set sampling rate of goroutine blocking profile]:number' \ 72 | '-timeout[kill test after that duration]:duration' \ 73 | '-cpuprofile[write CPU profile to file]:file:_files' \ 74 | '-memprofile[write heap profile to file]:file:_files' \ 75 | '-memprofilerate[set heap profiling rate]:number' \ 76 | '*:file:_path_files -g "*.go"' \ 77 | && ret=0 78 | ;; 79 | run) 80 | _arguments -s -w : \ 81 | ${build_flags[@]} \ 82 | '*:file:_path_files -g "*.go"' \ 83 | && ret=0 84 | ;; 85 | exec) 86 | _normal && ret=0 87 | ;; 88 | gen) 89 | _values 'gen commands' \ 90 | 'travis-yml[Generate .travis.yml which uses "gom test"]' \ 91 | 'gomfile[Scan packages and generate Gomfile]' \ 92 | && ret=0 93 | ;; 94 | esac 95 | ;; 96 | esac 97 | 98 | return ret 99 | } 100 | 101 | _gom "$@" 102 | --------------------------------------------------------------------------------