github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/tools/depsgen/gocmd.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "bytes" 19 "os/exec" 20 "path" 21 "path/filepath" 22 "strings" 23 24 "github.com/coreos/rkt/tools/common" 25 ) 26 27 const ( 28 // goSeparator is used to separate tuple elements in go list 29 // format string. 30 goSeparator = "!_##_!" 31 // goMakeFunction is a template for generating all files for a 32 // given module in a given repo. 33 goMakeFunction = "$(shell $(GO_ENV) \"$(DEPSGENTOOL)\" go --repo \"!!!REPO!!!\" --module \"!!!MODULE!!!\" --mode files)" 34 goCmd = "go" 35 ) 36 37 type goDepsMode int 38 39 const ( 40 goMakeMode goDepsMode = iota 41 goFilesMode 42 ) 43 44 func init() { 45 cmds[goCmd] = goDeps 46 } 47 48 func goDeps(args []string) string { 49 target, repo, module, mode := goGetArgs(args) 50 deps := goGetPackageDeps(repo, module) 51 switch mode { 52 case goMakeMode: 53 return GenerateFileDeps(target, goGetMakeFunction(repo, module), deps) 54 case goFilesMode: 55 return strings.Join(deps, " ") 56 } 57 panic("Should not happen") 58 } 59 60 // goGetMakeFunction returns a make snippet which will call depsgen go 61 // with "files" mode. 62 func goGetMakeFunction(repo, module string) string { 63 return replacePlaceholders(goMakeFunction, "REPO", repo, "MODULE", module) 64 } 65 66 // getArgs parses given parameters and returns target, repo, module and 67 // mode. If mode is "files", then target is optional. 68 func goGetArgs(args []string) (string, string, string, goDepsMode) { 69 f, target := standardFlags(goCmd) 70 repo := f.String("repo", "", "Go repo (example: github.com/coreos/rkt)") 71 module := f.String("module", "", "Module inside Go repo (example: stage1)") 72 mode := f.String("mode", "make", "Mode to use (make - print deps as makefile [default], files - print a list of files)") 73 74 f.Parse(args) 75 if *repo == "" { 76 common.Die("--repo parameter must be specified and cannot be empty") 77 } 78 if *module == "" { 79 common.Die("--module parameter must be specified and cannot be empty") 80 } 81 82 var dMode goDepsMode 83 84 switch *mode { 85 case "make": 86 dMode = goMakeMode 87 if *target == "" { 88 common.Die("--target parameter must be specified and cannot be empty when using 'make' mode") 89 } 90 case "files": 91 dMode = goFilesMode 92 default: 93 common.Die("unknown --mode parameter %q - expected either 'make' or 'files'", *mode) 94 } 95 return *target, *repo, *module, dMode 96 } 97 98 // goGetPackageDeps returns a list of files that are used to build a 99 // module in a given repo. 100 func goGetPackageDeps(repo, module string) []string { 101 pkg := path.Join(repo, module) 102 deps := []string{pkg} 103 for _, d := range goGetDeps(pkg) { 104 if strings.HasPrefix(d, repo) { 105 deps = append(deps, d) 106 } 107 } 108 return goGetFiles(repo, deps) 109 } 110 111 // goGetDeps gets all dependencies, direct or indirect, of a given 112 // package. 113 func goGetDeps(pkg string) []string { 114 rawDeps := goRun(goList([]string{"Deps"}, []string{pkg})) 115 // we expect only one line 116 if len(rawDeps) != 1 { 117 return []string{} 118 } 119 return goSliceRawSlice(rawDeps[0]) 120 } 121 122 // goGetFiles returns a list of files that are in given packages. File 123 // paths are "relative" to passed repo. 124 func goGetFiles(repo string, pkgs []string) []string { 125 params := []string{ 126 "ImportPath", 127 "GoFiles", 128 "CgoFiles", 129 } 130 var allFiles []string 131 rawTuples := goRun(goList(params, pkgs)) 132 for _, raw := range rawTuples { 133 tuple := goSliceRawTuple(raw) 134 module := strings.TrimPrefix(tuple[0], repo+"/") 135 files := append(goSliceRawSlice(tuple[1]), goSliceRawSlice(tuple[2])...) 136 for i := 0; i < len(files); i++ { 137 files[i] = filepath.Join(module, files[i]) 138 } 139 allFiles = append(allFiles, files...) 140 } 141 return allFiles 142 } 143 144 // goList returns an array of strings describing go list invocation 145 // with format string consisting all given params separated with 146 // !_##_! for all given packages. 147 func goList(params, pkgs []string) []string { 148 templateParams := make([]string, 0, len(params)) 149 for _, p := range params { 150 templateParams = append(templateParams, "{{."+p+"}}") 151 } 152 return append([]string{ 153 "go", 154 "list", 155 "-f", strings.Join(templateParams, goSeparator), 156 }, pkgs...) 157 } 158 159 // goRun executes given argument list and captures its output. The 160 // output is sliced into lines with empty lines being discarded. 161 func goRun(argv []string) []string { 162 cmd := exec.Command(argv[0], argv[1:]...) 163 stdout := new(bytes.Buffer) 164 stderr := new(bytes.Buffer) 165 cmd.Stdout = stdout 166 cmd.Stderr = stderr 167 if err := cmd.Run(); err != nil { 168 common.Die("Error running %s: %v: %s", strings.Join(argv, " "), err, stderr.String()) 169 } 170 rawLines := strings.Split(stdout.String(), "\n") 171 lines := make([]string, 0, len(rawLines)) 172 for _, line := range rawLines { 173 if trimmed := strings.TrimSpace(line); trimmed != "" { 174 lines = append(lines, trimmed) 175 } 176 } 177 return lines 178 } 179 180 // goSliceRawSlice slices given string representation of a slice into 181 // slice of strings. 182 func goSliceRawSlice(s string) []string { 183 s = strings.TrimPrefix(s, "[") 184 s = strings.TrimSuffix(s, "]") 185 s = strings.TrimSpace(s) 186 if s == "" { 187 return nil 188 } 189 a := strings.Split(s, " ") 190 return a 191 } 192 193 // goSliceRawTuple slices given string along !_##_! goSeparator to slice 194 // of strings. Returned slice might need another round of slicing with 195 // goSliceRawSlice. 196 func goSliceRawTuple(t string) []string { 197 return strings.Split(t, goSeparator) 198 }