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