github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/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 "fmt" 20 "os/exec" 21 "path" 22 "path/filepath" 23 "strings" 24 25 "github.com/rkt/rkt/tools/common" 26 ) 27 28 const ( 29 // goSeparator is used to separate tuple elements in go list 30 // format string. 31 goSeparator = "!_##_!" 32 // goMakeFunction is a template for generating all files for a 33 // given module in a given repo. 34 goMakeFunction = "$(shell $(GO_ENV) \"$(DEPSGENTOOL)\" go --repo \"!!!REPO!!!\" --module \"!!!MODULE!!!\" --mode files)" 35 goCmd = "go" 36 ) 37 38 type goDepsMode int 39 40 const ( 41 goMakeMode goDepsMode = iota 42 goFilesMode 43 ) 44 45 func init() { 46 cmds[goCmd] = goDeps 47 } 48 49 func goDeps(args []string) string { 50 target, repo, module, mode := goGetArgs(args) 51 deps := goGetPackageDeps(repo, module) 52 switch mode { 53 case goMakeMode: 54 return GenerateFileDeps(target, goGetMakeFunction(repo, module), deps) 55 case goFilesMode: 56 return strings.Join(deps, " ") 57 } 58 panic("Should not happen") 59 } 60 61 // goGetMakeFunction returns a make snippet which will call depsgen go 62 // with "files" mode. 63 func goGetMakeFunction(repo, module string) string { 64 return replacePlaceholders(goMakeFunction, "REPO", repo, "MODULE", module) 65 } 66 67 // getArgs parses given parameters and returns target, repo, module and 68 // mode. If mode is "files", then target is optional. 69 func goGetArgs(args []string) (string, string, string, goDepsMode) { 70 f, target := standardFlags(goCmd) 71 repo := f.String("repo", "", "Go repo (example: github.com/rkt/rkt)") 72 module := f.String("module", "", "Module inside Go repo (example: stage1)") 73 mode := f.String("mode", "make", "Mode to use (make - print deps as makefile [default], files - print a list of files)") 74 75 f.Parse(args) 76 if *repo == "" { 77 common.Die("--repo parameter must be specified and cannot be empty") 78 } 79 if *module == "" { 80 common.Die("--module parameter must be specified and cannot be empty") 81 } 82 83 var dMode goDepsMode 84 85 switch *mode { 86 case "make": 87 dMode = goMakeMode 88 if *target == "" { 89 common.Die("--target parameter must be specified and cannot be empty when using 'make' mode") 90 } 91 case "files": 92 dMode = goFilesMode 93 default: 94 common.Die("unknown --mode parameter %q - expected either 'make' or 'files'", *mode) 95 } 96 return *target, *repo, *module, dMode 97 } 98 99 // goGetPackageDeps returns a list of files that are used to build a 100 // module in a given repo. 101 func goGetPackageDeps(repo, module string) []string { 102 dir, deps := goGetDeps(repo, module) 103 return goGetFiles(dir, deps) 104 } 105 106 // goGetDeps gets the directory of a given repo and the all 107 // dependencies, direct or indirect, of a given module in the repo. 108 func goGetDeps(repo, module string) (string, []string) { 109 pkg := path.Join(repo, module) 110 rawTuples := goRun(goList([]string{"Dir", "Deps"}, []string{pkg})) 111 if len(rawTuples) != 1 { 112 common.Die("Expected to get only one line from go list for a single package") 113 } 114 tuple := goSliceRawTuple(rawTuples[0]) 115 dir := tuple[0] 116 if module != "." { 117 dirsToStrip := 1 + strings.Count(module, "/") 118 for i := 0; i < dirsToStrip; i++ { 119 dir = filepath.Dir(dir) 120 } 121 } 122 dir = filepath.Clean(dir) 123 deps := goSliceRawSlice(tuple[1]) 124 return dir, append([]string{pkg}, deps...) 125 } 126 127 // goGetFiles returns a list of files that are in given packages. File 128 // paths are relative to a given directory. 129 func goGetFiles(dir string, pkgs []string) []string { 130 params := []string{ 131 "Dir", 132 "GoFiles", 133 "CgoFiles", 134 } 135 var allFiles []string 136 rawTuples := goRun(goList(params, pkgs)) 137 for _, raw := range rawTuples { 138 tuple := goSliceRawTuple(raw) 139 moduleDir := filepath.Clean(tuple[0]) 140 dirSep := fmt.Sprintf("%s%c", dir, filepath.Separator) 141 moduleDirSep := fmt.Sprintf("%s%c", moduleDir, filepath.Separator) 142 if !strings.HasPrefix(moduleDirSep, dirSep) { 143 continue 144 } 145 relModuleDir, err := filepath.Rel(dir, moduleDir) 146 if err != nil { 147 common.Die("Could not make a relative path from %q to %q, even if they have the same prefix", moduleDir, dir) 148 } 149 files := append(goSliceRawSlice(tuple[1]), goSliceRawSlice(tuple[2])...) 150 for i := 0; i < len(files); i++ { 151 files[i] = filepath.Join(relModuleDir, files[i]) 152 } 153 allFiles = append(allFiles, files...) 154 } 155 return allFiles 156 } 157 158 // goList returns an array of strings describing go list invocation 159 // with format string consisting all given params separated with 160 // !_##_! for all given packages. 161 func goList(params, pkgs []string) []string { 162 templateParams := make([]string, 0, len(params)) 163 for _, p := range params { 164 templateParams = append(templateParams, "{{."+p+"}}") 165 } 166 return append([]string{ 167 "go", 168 "list", 169 "-f", strings.Join(templateParams, goSeparator), 170 }, pkgs...) 171 } 172 173 // goRun executes given argument list and captures its output. The 174 // output is sliced into lines with empty lines being discarded. 175 func goRun(argv []string) []string { 176 cmd := exec.Command(argv[0], argv[1:]...) 177 stdout := new(bytes.Buffer) 178 stderr := new(bytes.Buffer) 179 cmd.Stdout = stdout 180 cmd.Stderr = stderr 181 if err := cmd.Run(); err != nil { 182 common.Die("Error running %s: %v: %s", strings.Join(argv, " "), err, stderr.String()) 183 } 184 rawLines := strings.Split(stdout.String(), "\n") 185 lines := make([]string, 0, len(rawLines)) 186 for _, line := range rawLines { 187 if trimmed := strings.TrimSpace(line); trimmed != "" { 188 lines = append(lines, trimmed) 189 } 190 } 191 return lines 192 } 193 194 // goSliceRawSlice slices given string representation of a slice into 195 // slice of strings. 196 func goSliceRawSlice(s string) []string { 197 s = strings.TrimPrefix(s, "[") 198 s = strings.TrimSuffix(s, "]") 199 s = strings.TrimSpace(s) 200 if s == "" { 201 return nil 202 } 203 a := strings.Split(s, " ") 204 return a 205 } 206 207 // goSliceRawTuple slices given string along !_##_! goSeparator to slice 208 // of strings. Returned slice might need another round of slicing with 209 // goSliceRawSlice. 210 func goSliceRawTuple(t string) []string { 211 return strings.Split(t, goSeparator) 212 }