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  }