github.com/jenkins-x/jx-api@v0.0.24/cmd/codegen/util/go.go (about) 1 package util 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/jenkins-x/jx-logging/pkg/log" 10 11 uuid "github.com/satori/go.uuid" 12 13 "github.com/pkg/errors" 14 ) 15 16 const ( 17 gopath = "GOPATH" 18 defaultWritePermissions = 0760 19 ) 20 21 // GoPath returns the first element of the GOPATH. 22 // The empty string is returned if GOPATH is not set. 23 func GoPath() string { 24 goPath := os.Getenv(gopath) 25 26 // GOPATH can have multiple elements, we take the first which is consistent with what 'go get' does 27 pathElements := strings.Split(goPath, string(os.PathListSeparator)) 28 path := pathElements[0] 29 return path 30 } 31 32 // GoPathSrc returns the src directory of the first GOPATH element. 33 func GoPathSrc(gopath string) string { 34 return filepath.Join(gopath, "src") 35 } 36 37 // GoPathBin returns the bin directory of the first GOPATH element. 38 func GoPathBin(gopath string) string { 39 return filepath.Join(gopath, "bin") 40 } 41 42 // GoPathMod returns the modules directory of the first GOPATH element. 43 func GoPathMod(gopath string) string { 44 return filepath.Join(gopath, "pkg", "mod") 45 } 46 47 // EnsureGoPath ensures the GOPATH environment variable is set and points to a valid directory. 48 func EnsureGoPath() error { 49 goPath := os.Getenv(gopath) 50 if goPath == "" { 51 return errors.New("GOPATH needs to be set") 52 } 53 54 // GOPATH can have multiple elements, if so take the first 55 pathElements := strings.Split(goPath, string(os.PathListSeparator)) 56 path := pathElements[0] 57 if len(pathElements) > 1 { 58 log.Logger().Debugf("GOPATH contains more than one element using %s", path) 59 } 60 61 if _, err := os.Stat(path); err == nil { 62 return nil 63 } else if os.IsNotExist(err) { 64 return errors.New(fmt.Sprintf("the GOPATH directory %s does not exist", path)) 65 } else { 66 return err 67 } 68 } 69 70 // GoGet runs go get to install the specified binary. 71 func GoGet(path string, version string, gopath string, goModules bool, sourceOnly bool, update bool) error { 72 modulesMode := "off" 73 if goModules { 74 modulesMode = "on" 75 } 76 77 fullPath := path 78 if version != "" { 79 if goModules { 80 fullPath = fmt.Sprintf("%s@%s", path, version) 81 } else { 82 fullPath = fmt.Sprintf("%s/...", path) 83 } 84 85 } 86 args := []string{ 87 "get", 88 } 89 if update { 90 args = append(args, "-u") 91 } 92 if sourceOnly || !goModules { 93 args = append(args, "-d") 94 } 95 args = append(args, fullPath) 96 goGetCmd := Command{ 97 Name: "go", 98 Args: args, 99 Env: map[string]string{ 100 "GO111MODULE": modulesMode, 101 "GOPATH": gopath, 102 }, 103 } 104 out, err := goGetCmd.RunWithoutRetry() 105 if err != nil { 106 return errors.Wrapf(err, "error running %s, output %s", goGetCmd.String(), out) 107 } 108 parts := []string{ 109 GoPathSrc(gopath), 110 } 111 parts = append(parts, strings.Split(path, "/")...) 112 dir := filepath.Join(parts...) 113 if !goModules && version != "" { 114 115 branchNameUUID, err := uuid.NewV4() 116 if err != nil { 117 return errors.WithStack(err) 118 } 119 branchName := branchNameUUID.String() 120 oldBranchName, err := branch(dir) 121 if err != nil { 122 return errors.Wrapf(err, "getting current branch name") 123 } 124 err = createBranchFrom(dir, branchName, version) 125 if err != nil { 126 return errors.Wrapf(err, "creating branch from %s", version) 127 } 128 err = checkout(dir, branchName) 129 defer func() { 130 err := checkout(dir, oldBranchName) 131 if err != nil { 132 log.Logger().Errorf("Error checking out original branch %s: %v", oldBranchName, err) 133 } 134 }() 135 if err != nil { 136 return errors.Wrapf(err, "checking out branch from %s", branchName) 137 } 138 139 } 140 if !sourceOnly && !goModules { 141 cmd := Command{ 142 Dir: dir, 143 Name: "go", 144 Args: []string{ 145 "install", 146 }, 147 Env: map[string]string{ 148 "GO111MODULE": modulesMode, 149 "GOPATH": gopath, 150 }, 151 } 152 out, err = cmd.RunWithoutRetry() 153 if err != nil { 154 return errors.Wrapf(err, "error running %s, output %s", goGetCmd.String(), out) 155 } 156 } 157 return nil 158 } 159 160 func checkout(dir string, branch string) error { 161 return gitCmd(dir, "checkout", branch) 162 } 163 164 // branch returns the current branch of the repository located at the given directory 165 func branch(dir string) (string, error) { 166 return gitCmdWithOutput(dir, "rev-parse", "--abbrev-ref", "HEAD") 167 } 168 169 // createBranchFrom creates a new branch called branchName from startPoint 170 func createBranchFrom(dir string, branchName string, startPoint string) error { 171 return gitCmd(dir, "branch", branchName, startPoint) 172 } 173 174 func gitCmd(dir string, args ...string) error { 175 cmd := Command{ 176 Dir: dir, 177 Name: "git", 178 Args: args, 179 } 180 181 output, err := cmd.RunWithoutRetry() 182 return errors.Wrapf(err, "git output: %s", output) 183 } 184 185 func gitCmdWithOutput(dir string, args ...string) (string, error) { 186 cmd := Command{ 187 Dir: dir, 188 Name: "git", 189 Args: args, 190 } 191 return cmd.RunWithoutRetry() 192 } 193 194 // GetModuleDir determines the directory on disk of the specified module dependency. 195 // Returns the empty string if the target requirement is not part of the module graph. 196 func GetModuleDir(moduleDir string, targetRequirement string, gopath string) (string, error) { 197 out, err := getModGraph(moduleDir, gopath) 198 if err != nil { 199 return "", err 200 } 201 202 for _, line := range strings.Split(out, "\n") { 203 parts := strings.Split(line, " ") 204 if len(parts) != 2 { 205 return "", errors.Errorf("line of go mod graph should be like '<module> <requirement>' but was %s", 206 line) 207 } 208 requirement := parts[1] 209 if strings.HasPrefix(requirement, targetRequirement) { 210 return filepath.Join(GoPathMod(gopath), requirement), nil 211 } 212 } 213 return "", nil 214 } 215 216 // GetModuleRequirements returns the requirements for the GO module rooted in dir 217 // It returns a map[<module name>]map[<requirement name>]<requirement version> 218 func GetModuleRequirements(dir string, gopath string) (map[string]map[string]string, error) { 219 out, err := getModGraph(dir, gopath) 220 if err != nil { 221 return nil, err 222 } 223 224 answer := make(map[string]map[string]string) 225 for _, line := range strings.Split(out, "\n") { 226 if strings.HasPrefix(line, "go: ") { 227 // lines that start with go: are things like module download messages 228 continue 229 } 230 parts := strings.Split(line, " ") 231 if len(parts) != 2 { 232 return nil, errors.Errorf("line of go mod graph should be like '<module> <requirement>' but was %s", 233 line) 234 } 235 moduleName := parts[0] 236 requirement := parts[1] 237 parts1 := strings.Split(requirement, "@") 238 if len(parts1) != 2 { 239 return nil, errors.Errorf("go mod graph line should be like '<module> <requirementName>@<requirementVersion>' but was %s", line) 240 } 241 requirementName := parts1[0] 242 requirementVersion := parts1[1] 243 if _, ok := answer[moduleName]; !ok { 244 answer[moduleName] = make(map[string]string) 245 } 246 answer[moduleName][requirementName] = requirementVersion 247 } 248 return answer, nil 249 } 250 251 func getModGraph(dir string, gopath string) (string, error) { 252 cmd := Command{ 253 Dir: dir, 254 Name: "go", 255 Args: []string{ 256 "mod", 257 "graph", 258 }, 259 Env: map[string]string{ 260 "GO111MODULE": "on", 261 "GOPATH": gopath, 262 }, 263 } 264 out, err := cmd.RunWithoutRetry() 265 if err != nil { 266 return "", errors.Wrapf(err, "unable to retrieve module graph: %s", out) 267 } 268 269 // deal with windows 270 out = strings.Replace(out, "\r\n", "\n", -1) 271 272 return out, nil 273 } 274 275 // IsolatedGoPath returns the isolated go path for codegen 276 func IsolatedGoPath() (string, error) { 277 configDir, err := ConfigDir() 278 if err != nil { 279 return "", errors.Wrapf(err, "getting JX_HOME") 280 } 281 path := filepath.Join(configDir, "codegen", "go") 282 err = os.MkdirAll(path, defaultWritePermissions) 283 if err != nil { 284 return "", errors.Wrapf(err, "making %s", path) 285 } 286 return path, nil 287 } 288 289 // HomeDir returns the users home directory 290 func HomeDir() string { 291 if h := os.Getenv("HOME"); h != "" { 292 return h 293 } 294 h := os.Getenv("USERPROFILE") // windows 295 if h == "" { 296 h = "." 297 } 298 return h 299 } 300 301 // ConfigDir returns the JX_HOME directory, creating it if missing 302 func ConfigDir() (string, error) { 303 path := os.Getenv("JX_HOME") 304 if path != "" { 305 return path, nil 306 } 307 h := HomeDir() 308 path = filepath.Join(h, ".jx") 309 err := os.MkdirAll(path, defaultWritePermissions) 310 if err != nil { 311 return "", err 312 } 313 return path, nil 314 }