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  }