github.com/mattn/gom@v0.0.0-20190726063113-0ebf2b5d812d/install.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"os/exec"
     9  	"path"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strings"
    13  )
    14  
    15  type vcsCmd struct {
    16  	checkout     []string
    17  	update       []string
    18  	revision     []string
    19  	revisionMask string
    20  }
    21  
    22  var (
    23  	hg = &vcsCmd{
    24  		[]string{"hg", "update"},
    25  		[]string{"hg", "pull"},
    26  		[]string{"hg", "id", "-i"},
    27  		"^(.+)$",
    28  	}
    29  	git = &vcsCmd{
    30  		[]string{"git", "checkout", "-q"},
    31  		[]string{"git", "fetch"},
    32  		[]string{"git", "rev-parse", "HEAD"},
    33  		"^(.+)$",
    34  	}
    35  	bzr = &vcsCmd{
    36  		[]string{"bzr", "revert", "-r"},
    37  		[]string{"bzr", "pull"},
    38  		[]string{"bzr", "log", "-r-1", "--line"},
    39  		"^([0-9]+)",
    40  	}
    41  )
    42  
    43  func (vcs *vcsCmd) Checkout(p, destination string) error {
    44  	args := append(vcs.checkout, destination)
    45  	return vcsExec(p, args...)
    46  }
    47  
    48  func (vcs *vcsCmd) Update(p string) error {
    49  	return vcsExec(p, vcs.update...)
    50  }
    51  
    52  func (vcs *vcsCmd) Revision(dir string) (string, error) {
    53  	args := append(vcs.revision)
    54  	cmd := exec.Command(args[0], args[1:]...)
    55  	cmd.Dir = dir
    56  	cmd.Stderr = os.Stderr
    57  	b, err := cmd.Output()
    58  	if err != nil {
    59  		return "", err
    60  	}
    61  	rev := strings.TrimSpace(string(b))
    62  	if vcs.revisionMask != "" {
    63  		return regexp.MustCompile(vcs.revisionMask).FindString(rev), nil
    64  	}
    65  	return rev, nil
    66  }
    67  
    68  func (vcs *vcsCmd) Sync(p, destination string) error {
    69  	err := vcs.Checkout(p, destination)
    70  	if err != nil {
    71  		err = vcs.Update(p)
    72  		if err != nil {
    73  			return err
    74  		}
    75  		err = vcs.Checkout(p, destination)
    76  	}
    77  	return err
    78  }
    79  
    80  func vcsExec(dir string, args ...string) error {
    81  	cmd := exec.Command(args[0], args[1:]...)
    82  	cmd.Dir = dir
    83  	cmd.Stdout = os.Stdout
    84  	cmd.Stderr = os.Stderr
    85  	return cmd.Run()
    86  }
    87  
    88  func list(dir string) ([]string, error) {
    89  	cmd := exec.Command("go", "list", "./...")
    90  	cmd.Dir = dir
    91  	var stdout bytes.Buffer
    92  	cmd.Stdout = &stdout
    93  	err := cmd.Run()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	return strings.Split(stdout.String(), "\n"), nil
    99  }
   100  
   101  func has(c interface{}, key string) bool {
   102  	if m, ok := c.(map[string]interface{}); ok {
   103  		_, ok := m[key]
   104  		return ok
   105  	} else if a, ok := c.([]string); ok {
   106  		for _, s := range a {
   107  			if ok && s == key {
   108  				return true
   109  			}
   110  		}
   111  	}
   112  	return false
   113  }
   114  
   115  func (gom *Gom) Update() error {
   116  	cmdArgs := []string{"go", "get", "-u"}
   117  	if insecure, ok := gom.options["insecure"].(string); ok {
   118  		if insecure == "true" {
   119  			cmdArgs = append(cmdArgs, "-insecure")
   120  		}
   121  	}
   122  	recursive := "/..."
   123  	if recursiveFlag, ok := gom.options["recursive"].(string); ok {
   124  		if recursiveFlag == "false" {
   125  			recursive = ""
   126  		}
   127  	}
   128  	cmdArgs = append(cmdArgs, gom.name+recursive)
   129  
   130  	fmt.Printf("updating %s\n", gom.name)
   131  	return run(cmdArgs, Green)
   132  }
   133  
   134  func (gom *Gom) Clone(args []string) error {
   135  	vendor, err := filepath.Abs(vendorFolder)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	if command, ok := gom.options["command"].(string); ok {
   140  		target, ok := gom.options["target"].(string)
   141  		if !ok {
   142  			target = gom.name
   143  		}
   144  
   145  		srcdir := filepath.Join(vendor, "src", target)
   146  		if err := os.MkdirAll(srcdir, 0755); err != nil {
   147  			return err
   148  		}
   149  
   150  		customCmd := strings.Split(command, " ")
   151  		customCmd = append(customCmd, srcdir)
   152  
   153  		fmt.Printf("fetching %s (%v)\n", gom.name, customCmd)
   154  		err = run(customCmd, Blue)
   155  		if err != nil {
   156  			return err
   157  		}
   158  	} else if private, ok := gom.options["private"].(string); ok {
   159  		if private == "true" {
   160  			target, ok := gom.options["target"].(string)
   161  			if !ok {
   162  				target = gom.name
   163  			}
   164  			srcdir := filepath.Join(vendor, "src", target)
   165  			if _, err := os.Stat(srcdir); err != nil {
   166  				if err := os.MkdirAll(srcdir, 0755); err != nil {
   167  					return err
   168  				}
   169  				if err := gom.clonePrivate(srcdir); err != nil {
   170  					return err
   171  				}
   172  			} else {
   173  				if err := gom.pullPrivate(srcdir); err != nil {
   174  					return err
   175  				}
   176  			}
   177  		}
   178  	}
   179  
   180  	if skipdep, ok := gom.options["skipdep"].(string); ok {
   181  		if skipdep == "true" {
   182  			return nil
   183  		}
   184  	}
   185  	cmdArgs := []string{"go", "get", "-d"}
   186  	if insecure, ok := gom.options["insecure"].(string); ok {
   187  		if insecure == "true" {
   188  			cmdArgs = append(cmdArgs, "-insecure")
   189  		}
   190  	}
   191  	recursive := "/..."
   192  	if recursiveFlag, ok := gom.options["recursive"].(string); ok {
   193  		if recursiveFlag == "false" {
   194  			recursive = ""
   195  		}
   196  	}
   197  	cmdArgs = append(cmdArgs, args...)
   198  	cmdArgs = append(cmdArgs, gom.name+recursive)
   199  
   200  	fmt.Printf("downloading %s\n", gom.name)
   201  	return run(cmdArgs, Blue)
   202  }
   203  
   204  func (gom *Gom) pullPrivate(srcdir string) (err error) {
   205  	cwd, err := os.Getwd()
   206  	if err != nil {
   207  		return err
   208  	}
   209  	if err := os.Chdir(srcdir); err != nil {
   210  		return err
   211  	}
   212  	defer os.Chdir(cwd)
   213  
   214  	fmt.Printf("fetching private repo %s\n", gom.name)
   215  
   216  	branch := "master"
   217   	if has(gom.options, "branch") {
   218   		branch = gom.options["branch"].(string)
   219   	}
   220   
   221   	var vcs *vcsCmd
   222   	if isDir(filepath.Join(srcdir, ".git")) {
   223   		vcs = git
   224   	} else if isDir(filepath.Join(srcdir, ".hg")) {
   225   		vcs = hg
   226   	} else if isDir(filepath.Join(srcdir, ".bzr")) {
   227   		vcs = bzr
   228   	}
   229   	if vcs != nil {
   230   		err = vcs.Sync(srcdir, branch)
   231   		if err != nil {
   232   			return
   233   		}
   234   	}
   235   
   236   	pullCmd := "git pull origin " + branch
   237  	pullArgs := strings.Split(pullCmd, " ")
   238  	err = run(pullArgs, Blue)
   239  	if err != nil {
   240  		return
   241  	}
   242  
   243  	return
   244  }
   245  
   246  func (gom *Gom) clonePrivate(srcdir string) (err error) {
   247  	name := strings.Split(gom.name, "/")
   248  	if len(name) < 3 {
   249  		return errors.New("the format of private repo address is wrong")
   250  	}
   251  	nameTail := strings.Join(name[2:], "/")
   252  	privateUrl := fmt.Sprintf("git@%s:%s/%s", name[0], name[1], nameTail)
   253  
   254  	fmt.Printf("fetching private repo %s\n", gom.name)
   255  	cloneCmd := []string{"git", "clone", privateUrl, srcdir}
   256  	err = run(cloneCmd, Blue)
   257  	if err != nil {
   258  		return
   259  	}
   260  
   261  	return
   262  }
   263  
   264  func (gom *Gom) Checkout() error {
   265  	commit_or_branch_or_tag := ""
   266  	if has(gom.options, "branch") {
   267  		commit_or_branch_or_tag, _ = gom.options["branch"].(string)
   268  	}
   269  	if has(gom.options, "tag") {
   270  		commit_or_branch_or_tag, _ = gom.options["tag"].(string)
   271  	}
   272  	if has(gom.options, "commit") {
   273  		commit_or_branch_or_tag, _ = gom.options["commit"].(string)
   274  	}
   275  	if commit_or_branch_or_tag == "" {
   276  		return nil
   277  	}
   278  	vendor, err := filepath.Abs(vendorFolder)
   279  	if err != nil {
   280  		return err
   281  	}
   282  	p := filepath.Join(vendor, "src")
   283  	target, ok := gom.options["target"].(string)
   284  	if !ok {
   285  		target = gom.name
   286  	}
   287  	for _, elem := range strings.Split(target, "/") {
   288  		var vcs *vcsCmd
   289  		p = filepath.Join(p, elem)
   290  		if isDir(filepath.Join(p, ".git")) {
   291  			vcs = git
   292  		} else if isDir(filepath.Join(p, ".hg")) {
   293  			vcs = hg
   294  		} else if isDir(filepath.Join(p, ".bzr")) {
   295  			vcs = bzr
   296  		}
   297  		if vcs != nil {
   298  			p = filepath.Join(vendor, "src", target)
   299  			return vcs.Sync(p, commit_or_branch_or_tag)
   300  		}
   301  	}
   302  	fmt.Printf("Warning: don't know how to checkout for %v\n", gom.name)
   303  	return errors.New("gom currently support git/hg/bzr for specifying tag/branch/commit")
   304  }
   305  
   306  func hasGoSource(p string) bool {
   307  	dir, err := os.Open(p)
   308  	if err != nil {
   309  		return false
   310  	}
   311  	defer dir.Close()
   312  	fis, err := dir.Readdir(-1)
   313  	if err != nil {
   314  		return false
   315  	}
   316  	for _, fi := range fis {
   317  		if fi.IsDir() {
   318  			continue
   319  		}
   320  		name := fi.Name()
   321  		if strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go") {
   322  			return true
   323  		}
   324  	}
   325  	return false
   326  }
   327  
   328  func (gom *Gom) Build(args []string) (err error) {
   329  	return gom.build(args, true)
   330  }
   331  
   332  func (gom *Gom) build(args []string, move bool) (err error) {
   333  	var vendor string
   334  	vendor, err = filepath.Abs(vendorFolder)
   335  	if err != nil {
   336  		return err
   337  	}
   338  
   339  	if move && !isVendoringSupported {
   340  		err := moveSrcToVendorSrc(vendor)
   341  		if err != nil {
   342  			return err
   343  		}
   344  		defer func() {
   345  			err = moveSrcToVendor(vendor)
   346  		}()
   347  	}
   348  
   349  	installCmd := []string{"go", "get"}
   350  	hasPkg := false
   351  	for _, arg := range args {
   352  		if !strings.HasPrefix(arg, "-") {
   353  			arg = path.Join(arg, "...")
   354  			hasPkg = true
   355  		}
   356  		installCmd = append(installCmd, arg)
   357  	}
   358  
   359  	target, ok := gom.options["target"].(string)
   360  	if !ok {
   361  		target = gom.name
   362  	}
   363  	p := filepath.Join(vendor, "src", target)
   364  
   365  	if hasPkg {
   366  		return vcsExec(p, installCmd...)
   367  	}
   368  
   369  	pkgs, err := list(p)
   370  	if err != nil {
   371  		return err
   372  	}
   373  
   374  	for _, pkg := range pkgs {
   375  		if isIgnorePackage(pkg) {
   376  			continue
   377  		}
   378  		p = filepath.Join(vendor, "src", pkg)
   379  		if !hasGoSource(p) {
   380  			continue
   381  		}
   382  		err := vcsExec(p, installCmd...)
   383  		if err != nil {
   384  			return err
   385  		}
   386  	}
   387  	return nil
   388  }
   389  
   390  func isFile(p string) bool {
   391  	if fi, err := os.Stat(filepath.Join(p)); err == nil && !fi.IsDir() {
   392  		return true
   393  	}
   394  	return false
   395  }
   396  
   397  func isDir(p string) bool {
   398  	if fi, err := os.Stat(filepath.Join(p)); err == nil && fi.IsDir() {
   399  		return true
   400  	}
   401  	return false
   402  }
   403  
   404  func isIgnorePackage(pkg string) bool {
   405  	if pkg == "" {
   406  		return true
   407  	}
   408  	paths := strings.Split(pkg, "/")
   409  	for _, path := range paths {
   410  		if path == "examples" {
   411  			return true
   412  		}
   413  		if strings.HasPrefix(path, "_") {
   414  			return true
   415  		}
   416  	}
   417  	return false
   418  }
   419  
   420  func moveSrcToVendorSrc(vendor string) error {
   421  	vendorSrc := filepath.Join(vendor, "src")
   422  	dirs, err := readdirnames(vendor)
   423  	if err != nil {
   424  		return err
   425  	}
   426  	err = os.MkdirAll(vendorSrc, 0755)
   427  	if err != nil {
   428  		return err
   429  	}
   430  	for _, dir := range dirs {
   431  		if dir == "bin" || dir == "pkg" || dir == "src" {
   432  			continue
   433  		}
   434  		err = os.Rename(filepath.Join(vendor, dir), filepath.Join(vendorSrc, dir))
   435  		if err != nil {
   436  			return err
   437  		}
   438  	}
   439  	return nil
   440  }
   441  
   442  func moveSrcToVendor(vendor string) error {
   443  	vendorSrc := filepath.Join(vendor, "src")
   444  	dirs, err := readdirnames(vendorSrc)
   445  	if err != nil {
   446  		return err
   447  	}
   448  	for _, dir := range dirs {
   449  		err = os.Rename(filepath.Join(vendorSrc, dir), filepath.Join(vendor, dir))
   450  		if err != nil {
   451  			return err
   452  		}
   453  	}
   454  	err = os.Remove(vendorSrc)
   455  	if err != nil {
   456  		return err
   457  	}
   458  	return nil
   459  }
   460  
   461  func readdirnames(dirname string) ([]string, error) {
   462  	f, err := os.Open(dirname)
   463  	if err != nil {
   464  		return nil, err
   465  	}
   466  	list, err := f.Readdirnames(-1)
   467  	f.Close()
   468  	if err != nil {
   469  		return nil, err
   470  	}
   471  	return list, nil
   472  }
   473  
   474  func parseInstallFlags(args []string) (opts map[string]string, retargs []string) {
   475  	opts = make(map[string]string)
   476  	re := regexp.MustCompile(`^--([a-z][a-z_]*)(=\S*)?`)
   477  	for _, arg := range args {
   478  		ss := re.FindAllStringSubmatch(arg, -1)
   479  		if len(ss) > 0 {
   480  			opts[ss[0][1]] = opts[ss[0][2]]
   481  		} else {
   482  			retargs = append(retargs, arg)
   483  		}
   484  	}
   485  	return
   486  }
   487  
   488  func hasSaveOpts(opts map[string]string) bool {
   489  	if _, ok := opts["save"]; ok {
   490  		return true
   491  	}
   492  	if _, ok := opts["save-dev"]; ok {
   493  		return true
   494  	}
   495  	return false
   496  }
   497  
   498  func install(args []string) error {
   499  	var opts map[string]string
   500  	opts, args = parseInstallFlags(args)
   501  	allGoms, err := parseGomfile("Gomfile")
   502  	if err != nil {
   503  		return err
   504  	}
   505  	if hasSaveOpts(opts) {
   506  		found := false
   507  		for _, arg := range args {
   508  			for _, gom := range allGoms {
   509  				if gom.name == arg {
   510  					found = true
   511  					break
   512  				}
   513  			}
   514  			if !found {
   515  				options := map[string]interface{}{}
   516  				if _, ok := opts["save-dev"]; ok {
   517  					options["envs"] = []string{"development"}
   518  				}
   519  				allGoms = append(allGoms, Gom{name: arg, options: options})
   520  			}
   521  		}
   522  		err = writeGomfile("Gomfile", allGoms)
   523  		if err != nil {
   524  			return err
   525  		}
   526  	}
   527  	vendor, err := filepath.Abs(vendorFolder)
   528  	if err != nil {
   529  		return err
   530  	}
   531  	_, err = os.Stat(vendor)
   532  	if err != nil {
   533  		err = os.MkdirAll(vendor, 0755)
   534  		if err != nil {
   535  			return err
   536  		}
   537  	}
   538  	err = os.Setenv("GOPATH", vendor)
   539  	if err != nil {
   540  		return err
   541  	}
   542  	err = os.Setenv("GOBIN", filepath.Join(vendor, "bin"))
   543  	if err != nil {
   544  		return err
   545  	}
   546  
   547  	// 1. Filter goms to install
   548  	goms := make([]Gom, 0)
   549  	for _, gom := range allGoms {
   550  		if group, ok := gom.options["group"]; ok {
   551  			if !matchEnv(group) {
   552  				continue
   553  			}
   554  		}
   555  		if goos, ok := gom.options["goos"]; ok {
   556  			if !matchOS(goos) {
   557  				continue
   558  			}
   559  		}
   560  		goms = append(goms, gom)
   561  	}
   562  
   563  	err = moveSrcToVendorSrc(vendor)
   564  	if err != nil {
   565  		return err
   566  	}
   567  
   568  	// 2. Clone the repositories
   569  	for _, gom := range goms {
   570  		err = gom.Clone(args)
   571  		if err != nil {
   572  			return err
   573  		}
   574  	}
   575  
   576  	// 3. Checkout the commit/branch/tag if needed
   577  	for _, gom := range goms {
   578  		err = gom.Checkout()
   579  		if err != nil {
   580  			return err
   581  		}
   582  	}
   583  
   584  	// 4. Build and install
   585  	for _, gom := range goms {
   586  		if skipdep, ok := gom.options["skipdep"].(string); ok {
   587  			if skipdep == "true" {
   588  				continue
   589  			}
   590  		}
   591  		err = gom.build(args, false)
   592  		if err != nil {
   593  			return err
   594  		}
   595  	}
   596  
   597  	err = moveSrcToVendor(vendor)
   598  	if err != nil {
   599  		return err
   600  	}
   601  
   602  	return nil
   603  }
   604  
   605  func update() error {
   606  	goms, err := parseGomfile("Gomfile")
   607  	if err != nil {
   608  		return err
   609  	}
   610  	vendor, err := filepath.Abs(vendorFolder)
   611  	if err != nil {
   612  		return err
   613  	}
   614  	err = os.Setenv("GOPATH", vendor)
   615  	if err != nil {
   616  		return err
   617  	}
   618  	err = os.Setenv("GOBIN", filepath.Join(vendor, "bin"))
   619  	if err != nil {
   620  		return err
   621  	}
   622  
   623  	err = moveSrcToVendorSrc(vendor)
   624  	if err != nil {
   625  		return err
   626  	}
   627  
   628  	for _, gom := range goms {
   629  		err = gom.Update()
   630  		if err != nil {
   631  			return err
   632  		}
   633  		vcs, _, p := vcsScan(vendorSrc(vendor), gom.name)
   634  		if vcs != nil {
   635  			rev, err := vcs.Revision(p)
   636  			if err == nil && rev != "" {
   637  				gom.options["commit"] = rev
   638  			}
   639  		}
   640  	}
   641  
   642  	err = moveSrcToVendor(vendor)
   643  	if err != nil {
   644  		return err
   645  	}
   646  
   647  	return writeGomfile("Gomfile", goms)
   648  }