github.com/gohugoio/hugo@v0.88.1/modules/client.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package modules
    15  
    16  import (
    17  	"bufio"
    18  	"bytes"
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"regexp"
    28  	"strings"
    29  	"time"
    30  
    31  	"github.com/gohugoio/hugo/common/hexec"
    32  
    33  	hglob "github.com/gohugoio/hugo/hugofs/glob"
    34  
    35  	"github.com/gobwas/glob"
    36  
    37  	"github.com/gohugoio/hugo/hugofs"
    38  
    39  	"github.com/gohugoio/hugo/hugofs/files"
    40  
    41  	"github.com/gohugoio/hugo/common/loggers"
    42  
    43  	"github.com/gohugoio/hugo/config"
    44  
    45  	"github.com/rogpeppe/go-internal/module"
    46  
    47  	"github.com/gohugoio/hugo/common/hugio"
    48  
    49  	"github.com/pkg/errors"
    50  	"github.com/spf13/afero"
    51  )
    52  
    53  var fileSeparator = string(os.PathSeparator)
    54  
    55  const (
    56  	goBinaryStatusOK goBinaryStatus = iota
    57  	goBinaryStatusNotFound
    58  	goBinaryStatusTooOld
    59  )
    60  
    61  // The "vendor" dir is reserved for Go Modules.
    62  const vendord = "_vendor"
    63  
    64  const (
    65  	goModFilename = "go.mod"
    66  	goSumFilename = "go.sum"
    67  )
    68  
    69  // NewClient creates a new Client that can be used to manage the Hugo Components
    70  // in a given workingDir.
    71  // The Client will resolve the dependencies recursively, but needs the top
    72  // level imports to start out.
    73  func NewClient(cfg ClientConfig) *Client {
    74  	fs := cfg.Fs
    75  	n := filepath.Join(cfg.WorkingDir, goModFilename)
    76  	goModEnabled, _ := afero.Exists(fs, n)
    77  	var goModFilename string
    78  	if goModEnabled {
    79  		goModFilename = n
    80  	}
    81  
    82  	env := os.Environ()
    83  	mcfg := cfg.ModuleConfig
    84  
    85  	config.SetEnvVars(&env,
    86  		"PWD", cfg.WorkingDir,
    87  		"GO111MODULE", "on",
    88  		"GOPROXY", mcfg.Proxy,
    89  		"GOPRIVATE", mcfg.Private,
    90  		"GONOPROXY", mcfg.NoProxy)
    91  
    92  	if cfg.CacheDir != "" {
    93  		// Module cache stored below $GOPATH/pkg
    94  		config.SetEnvVars(&env, "GOPATH", cfg.CacheDir)
    95  	}
    96  
    97  	logger := cfg.Logger
    98  	if logger == nil {
    99  		logger = loggers.NewWarningLogger()
   100  	}
   101  
   102  	var noVendor glob.Glob
   103  	if cfg.ModuleConfig.NoVendor != "" {
   104  		noVendor, _ = hglob.GetGlob(hglob.NormalizePath(cfg.ModuleConfig.NoVendor))
   105  	}
   106  
   107  	return &Client{
   108  		fs:                fs,
   109  		ccfg:              cfg,
   110  		logger:            logger,
   111  		noVendor:          noVendor,
   112  		moduleConfig:      mcfg,
   113  		environ:           env,
   114  		GoModulesFilename: goModFilename,
   115  	}
   116  }
   117  
   118  // Client contains most of the API provided by this package.
   119  type Client struct {
   120  	fs     afero.Fs
   121  	logger loggers.Logger
   122  
   123  	noVendor glob.Glob
   124  
   125  	ccfg ClientConfig
   126  
   127  	// The top level module config
   128  	moduleConfig Config
   129  
   130  	// Environment variables used in "go get" etc.
   131  	environ []string
   132  
   133  	// Set when Go modules are initialized in the current repo, that is:
   134  	// a go.mod file exists.
   135  	GoModulesFilename string
   136  
   137  	// Set if we get a exec.ErrNotFound when running Go, which is most likely
   138  	// due to being run on a system without Go installed. We record it here
   139  	// so we can give an instructional error at the end if module/theme
   140  	// resolution fails.
   141  	goBinaryStatus goBinaryStatus
   142  }
   143  
   144  // Graph writes a module dependenchy graph to the given writer.
   145  func (c *Client) Graph(w io.Writer) error {
   146  	mc, coll := c.collect(true)
   147  	if coll.err != nil {
   148  		return coll.err
   149  	}
   150  	for _, module := range mc.AllModules {
   151  		if module.Owner() == nil {
   152  			continue
   153  		}
   154  
   155  		prefix := ""
   156  		if module.Disabled() {
   157  			prefix = "DISABLED "
   158  		}
   159  		dep := pathVersion(module.Owner()) + " " + pathVersion(module)
   160  		if replace := module.Replace(); replace != nil {
   161  			if replace.Version() != "" {
   162  				dep += " => " + pathVersion(replace)
   163  			} else {
   164  				// Local dir.
   165  				dep += " => " + replace.Dir()
   166  			}
   167  		}
   168  		fmt.Fprintln(w, prefix+dep)
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  // Tidy can be used to remove unused dependencies from go.mod and go.sum.
   175  func (c *Client) Tidy() error {
   176  	tc, coll := c.collect(false)
   177  	if coll.err != nil {
   178  		return coll.err
   179  	}
   180  
   181  	if coll.skipTidy {
   182  		return nil
   183  	}
   184  
   185  	return c.tidy(tc.AllModules, false)
   186  }
   187  
   188  // Vendor writes all the module dependencies to a _vendor folder.
   189  //
   190  // Unlike Go, we support it for any level.
   191  //
   192  // We, by default, use the /_vendor folder first, if found. To disable,
   193  // run with
   194  //    hugo --ignoreVendor
   195  //
   196  // Given a module tree, Hugo will pick the first module for a given path,
   197  // meaning that if the top-level module is vendored, that will be the full
   198  // set of dependencies.
   199  func (c *Client) Vendor() error {
   200  	vendorDir := filepath.Join(c.ccfg.WorkingDir, vendord)
   201  	if err := c.rmVendorDir(vendorDir); err != nil {
   202  		return err
   203  	}
   204  	if err := c.fs.MkdirAll(vendorDir, 0755); err != nil {
   205  		return err
   206  	}
   207  
   208  	// Write the modules list to modules.txt.
   209  	//
   210  	// On the form:
   211  	//
   212  	// # github.com/alecthomas/chroma v0.6.3
   213  	//
   214  	// This is how "go mod vendor" does it. Go also lists
   215  	// the packages below it, but that is currently not applicable to us.
   216  	//
   217  	var modulesContent bytes.Buffer
   218  
   219  	tc, coll := c.collect(true)
   220  	if coll.err != nil {
   221  		return coll.err
   222  	}
   223  
   224  	for _, t := range tc.AllModules {
   225  		if t.Owner() == nil {
   226  			// This is the project.
   227  			continue
   228  		}
   229  
   230  		if !c.shouldVendor(t.Path()) {
   231  			continue
   232  		}
   233  
   234  		if !t.IsGoMod() && !t.Vendor() {
   235  			// We currently do not vendor components living in the
   236  			// theme directory, see https://github.com/gohugoio/hugo/issues/5993
   237  			continue
   238  		}
   239  
   240  		// See https://github.com/gohugoio/hugo/issues/8239
   241  		// This is an error situation. We need something to vendor.
   242  		if t.Mounts() == nil {
   243  			return errors.Errorf("cannot vendor module %q, need at least one mount", t.Path())
   244  		}
   245  
   246  		fmt.Fprintln(&modulesContent, "# "+t.Path()+" "+t.Version())
   247  
   248  		dir := t.Dir()
   249  
   250  		for _, mount := range t.Mounts() {
   251  			sourceFilename := filepath.Join(dir, mount.Source)
   252  			targetFilename := filepath.Join(vendorDir, t.Path(), mount.Source)
   253  			fi, err := c.fs.Stat(sourceFilename)
   254  			if err != nil {
   255  				return errors.Wrap(err, "failed to vendor module")
   256  			}
   257  
   258  			if fi.IsDir() {
   259  				if err := hugio.CopyDir(c.fs, sourceFilename, targetFilename, nil); err != nil {
   260  					return errors.Wrap(err, "failed to copy module to vendor dir")
   261  				}
   262  			} else {
   263  				targetDir := filepath.Dir(targetFilename)
   264  
   265  				if err := c.fs.MkdirAll(targetDir, 0755); err != nil {
   266  					return errors.Wrap(err, "failed to make target dir")
   267  				}
   268  
   269  				if err := hugio.CopyFile(c.fs, sourceFilename, targetFilename); err != nil {
   270  					return errors.Wrap(err, "failed to copy module file to vendor")
   271  				}
   272  			}
   273  		}
   274  
   275  		// Include the resource cache if present.
   276  		resourcesDir := filepath.Join(dir, files.FolderResources)
   277  		_, err := c.fs.Stat(resourcesDir)
   278  		if err == nil {
   279  			if err := hugio.CopyDir(c.fs, resourcesDir, filepath.Join(vendorDir, t.Path(), files.FolderResources), nil); err != nil {
   280  				return errors.Wrap(err, "failed to copy resources to vendor dir")
   281  			}
   282  		}
   283  
   284  		// Also include any theme.toml or config.* files in the root.
   285  		configFiles, _ := afero.Glob(c.fs, filepath.Join(dir, "config.*"))
   286  		configFiles = append(configFiles, filepath.Join(dir, "theme.toml"))
   287  		for _, configFile := range configFiles {
   288  			if err := hugio.CopyFile(c.fs, configFile, filepath.Join(vendorDir, t.Path(), filepath.Base(configFile))); err != nil {
   289  				if !os.IsNotExist(err) {
   290  					return err
   291  				}
   292  			}
   293  		}
   294  	}
   295  
   296  	if modulesContent.Len() > 0 {
   297  		if err := afero.WriteFile(c.fs, filepath.Join(vendorDir, vendorModulesFilename), modulesContent.Bytes(), 0666); err != nil {
   298  			return err
   299  		}
   300  	}
   301  
   302  	return nil
   303  }
   304  
   305  // Get runs "go get" with the supplied arguments.
   306  func (c *Client) Get(args ...string) error {
   307  	if len(args) == 0 || (len(args) == 1 && args[0] == "-u") {
   308  		update := len(args) != 0
   309  
   310  		// We need to be explicit about the modules to get.
   311  		for _, m := range c.moduleConfig.Imports {
   312  			if !isProbablyModule(m.Path) {
   313  				// Skip themes/components stored below /themes etc.
   314  				// There may be false positives in the above, but those
   315  				// should be rare, and they will fail below with an
   316  				// "cannot find module providing ..." message.
   317  				continue
   318  			}
   319  			var args []string
   320  			if update {
   321  				args = append(args, "-u")
   322  			}
   323  			args = append(args, m.Path)
   324  			if err := c.get(args...); err != nil {
   325  				return err
   326  			}
   327  		}
   328  
   329  		return nil
   330  	}
   331  
   332  	return c.get(args...)
   333  }
   334  
   335  func (c *Client) get(args ...string) error {
   336  	var hasD bool
   337  	for _, arg := range args {
   338  		if arg == "-d" {
   339  			hasD = true
   340  			break
   341  		}
   342  	}
   343  	if !hasD {
   344  		// go get without the -d flag does not make sense to us, as
   345  		// it will try to build and install go packages.
   346  		args = append([]string{"-d"}, args...)
   347  	}
   348  	if err := c.runGo(context.Background(), c.logger.Out(), append([]string{"get"}, args...)...); err != nil {
   349  		errors.Wrapf(err, "failed to get %q", args)
   350  	}
   351  	return nil
   352  }
   353  
   354  // Init initializes this as a Go Module with the given path.
   355  // If path is empty, Go will try to guess.
   356  // If this succeeds, this project will be marked as Go Module.
   357  func (c *Client) Init(path string) error {
   358  	err := c.runGo(context.Background(), c.logger.Out(), "mod", "init", path)
   359  	if err != nil {
   360  		return errors.Wrap(err, "failed to init modules")
   361  	}
   362  
   363  	c.GoModulesFilename = filepath.Join(c.ccfg.WorkingDir, goModFilename)
   364  
   365  	return nil
   366  }
   367  
   368  var verifyErrorDirRe = regexp.MustCompile(`dir has been modified \((.*?)\)`)
   369  
   370  // Verify checks that the dependencies of the current module,
   371  // which are stored in a local downloaded source cache, have not been
   372  // modified since being downloaded.
   373  func (c *Client) Verify(clean bool) error {
   374  	// TODO(bep) add path to mod clean
   375  	err := c.runVerify()
   376  	if err != nil {
   377  		if clean {
   378  			m := verifyErrorDirRe.FindAllStringSubmatch(err.Error(), -1)
   379  			if m != nil {
   380  				for i := 0; i < len(m); i++ {
   381  					c, err := hugofs.MakeReadableAndRemoveAllModulePkgDir(c.fs, m[i][1])
   382  					if err != nil {
   383  						return err
   384  					}
   385  					fmt.Println("Cleaned", c)
   386  				}
   387  			}
   388  			// Try to verify it again.
   389  			err = c.runVerify()
   390  		}
   391  	}
   392  	return err
   393  }
   394  
   395  func (c *Client) Clean(pattern string) error {
   396  	mods, err := c.listGoMods()
   397  	if err != nil {
   398  		return err
   399  	}
   400  
   401  	var g glob.Glob
   402  
   403  	if pattern != "" {
   404  		var err error
   405  		g, err = hglob.GetGlob(pattern)
   406  		if err != nil {
   407  			return err
   408  		}
   409  	}
   410  
   411  	for _, m := range mods {
   412  		if m.Replace != nil || m.Main {
   413  			continue
   414  		}
   415  
   416  		if g != nil && !g.Match(m.Path) {
   417  			continue
   418  		}
   419  		_, err = hugofs.MakeReadableAndRemoveAllModulePkgDir(c.fs, m.Dir)
   420  		if err == nil {
   421  			c.logger.Printf("hugo: cleaned module cache for %q", m.Path)
   422  		}
   423  	}
   424  	return err
   425  }
   426  
   427  func (c *Client) runVerify() error {
   428  	return c.runGo(context.Background(), ioutil.Discard, "mod", "verify")
   429  }
   430  
   431  func isProbablyModule(path string) bool {
   432  	return module.CheckPath(path) == nil
   433  }
   434  
   435  func (c *Client) listGoMods() (goModules, error) {
   436  	if c.GoModulesFilename == "" || !c.moduleConfig.hasModuleImport() {
   437  		return nil, nil
   438  	}
   439  
   440  	downloadModules := func(modules ...string) error {
   441  		args := []string{"mod", "download"}
   442  		args = append(args, modules...)
   443  		out := ioutil.Discard
   444  		err := c.runGo(context.Background(), out, args...)
   445  		if err != nil {
   446  			return errors.Wrap(err, "failed to download modules")
   447  		}
   448  		return nil
   449  	}
   450  
   451  	if err := downloadModules(); err != nil {
   452  		return nil, err
   453  	}
   454  
   455  	listAndDecodeModules := func(handle func(m *goModule) error, modules ...string) error {
   456  		b := &bytes.Buffer{}
   457  		args := []string{"list", "-m", "-json"}
   458  		if len(modules) > 0 {
   459  			args = append(args, modules...)
   460  		} else {
   461  			args = append(args, "all")
   462  		}
   463  		err := c.runGo(context.Background(), b, args...)
   464  		if err != nil {
   465  			return errors.Wrap(err, "failed to list modules")
   466  		}
   467  
   468  		dec := json.NewDecoder(b)
   469  		for {
   470  			m := &goModule{}
   471  			if err := dec.Decode(m); err != nil {
   472  				if err == io.EOF {
   473  					break
   474  				}
   475  				return errors.Wrap(err, "failed to decode modules list")
   476  			}
   477  
   478  			if err := handle(m); err != nil {
   479  				return err
   480  			}
   481  		}
   482  		return nil
   483  	}
   484  
   485  	var modules goModules
   486  	err := listAndDecodeModules(func(m *goModule) error {
   487  		modules = append(modules, m)
   488  		return nil
   489  	})
   490  	if err != nil {
   491  		return nil, err
   492  	}
   493  
   494  	// From Go 1.17, go lazy loads transitive dependencies.
   495  	// That does not work for us.
   496  	// So, download these modules and update the Dir in the modules list.
   497  	var modulesToDownload []string
   498  	for _, m := range modules {
   499  		if m.Dir == "" {
   500  			modulesToDownload = append(modulesToDownload, fmt.Sprintf("%s@%s", m.Path, m.Version))
   501  		}
   502  	}
   503  
   504  	if len(modulesToDownload) > 0 {
   505  		if err := downloadModules(modulesToDownload...); err != nil {
   506  			return nil, err
   507  		}
   508  		err := listAndDecodeModules(func(m *goModule) error {
   509  			if mm := modules.GetByPath(m.Path); mm != nil {
   510  				mm.Dir = m.Dir
   511  			}
   512  			return nil
   513  		}, modulesToDownload...)
   514  		if err != nil {
   515  			return nil, err
   516  		}
   517  	}
   518  
   519  	return modules, err
   520  }
   521  
   522  func (c *Client) rewriteGoMod(name string, isGoMod map[string]bool) error {
   523  	data, err := c.rewriteGoModRewrite(name, isGoMod)
   524  	if err != nil {
   525  		return err
   526  	}
   527  	if data != nil {
   528  		if err := afero.WriteFile(c.fs, filepath.Join(c.ccfg.WorkingDir, name), data, 0666); err != nil {
   529  			return err
   530  		}
   531  	}
   532  
   533  	return nil
   534  }
   535  
   536  func (c *Client) rewriteGoModRewrite(name string, isGoMod map[string]bool) ([]byte, error) {
   537  	if name == goModFilename && c.GoModulesFilename == "" {
   538  		// Already checked.
   539  		return nil, nil
   540  	}
   541  
   542  	modlineSplitter := getModlineSplitter(name == goModFilename)
   543  
   544  	b := &bytes.Buffer{}
   545  	f, err := c.fs.Open(filepath.Join(c.ccfg.WorkingDir, name))
   546  	if err != nil {
   547  		if os.IsNotExist(err) {
   548  			// It's been deleted.
   549  			return nil, nil
   550  		}
   551  		return nil, err
   552  	}
   553  	defer f.Close()
   554  
   555  	scanner := bufio.NewScanner(f)
   556  	var dirty bool
   557  
   558  	for scanner.Scan() {
   559  		line := scanner.Text()
   560  		var doWrite bool
   561  
   562  		if parts := modlineSplitter(line); parts != nil {
   563  			modname, modver := parts[0], parts[1]
   564  			modver = strings.TrimSuffix(modver, "/"+goModFilename)
   565  			modnameVer := modname + " " + modver
   566  			doWrite = isGoMod[modnameVer]
   567  		} else {
   568  			doWrite = true
   569  		}
   570  
   571  		if doWrite {
   572  			fmt.Fprintln(b, line)
   573  		} else {
   574  			dirty = true
   575  		}
   576  	}
   577  
   578  	if !dirty {
   579  		// Nothing changed
   580  		return nil, nil
   581  	}
   582  
   583  	return b.Bytes(), nil
   584  }
   585  
   586  func (c *Client) rmVendorDir(vendorDir string) error {
   587  	modulestxt := filepath.Join(vendorDir, vendorModulesFilename)
   588  
   589  	if _, err := c.fs.Stat(vendorDir); err != nil {
   590  		return nil
   591  	}
   592  
   593  	_, err := c.fs.Stat(modulestxt)
   594  	if err != nil {
   595  		// If we have a _vendor dir without modules.txt it sounds like
   596  		// a _vendor dir created by others.
   597  		return errors.New("found _vendor dir without modules.txt, skip delete")
   598  	}
   599  
   600  	return c.fs.RemoveAll(vendorDir)
   601  }
   602  
   603  func (c *Client) runGo(
   604  	ctx context.Context,
   605  	stdout io.Writer,
   606  	args ...string) error {
   607  	if c.goBinaryStatus != 0 {
   608  		return nil
   609  	}
   610  
   611  	stderr := new(bytes.Buffer)
   612  	cmd, err := hexec.SafeCommandContext(ctx, "go", args...)
   613  	if err != nil {
   614  		return err
   615  	}
   616  
   617  	cmd.Env = c.environ
   618  	cmd.Dir = c.ccfg.WorkingDir
   619  	cmd.Stdout = stdout
   620  	cmd.Stderr = io.MultiWriter(stderr, os.Stderr)
   621  
   622  	if err := cmd.Run(); err != nil {
   623  		if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
   624  			c.goBinaryStatus = goBinaryStatusNotFound
   625  			return nil
   626  		}
   627  
   628  		if strings.Contains(stderr.String(), "invalid version: unknown revision") {
   629  			// See https://github.com/gohugoio/hugo/issues/6825
   630  			c.logger.Println(`hugo: you need to manually edit go.mod to resolve the unknown revision.`)
   631  		}
   632  
   633  		_, ok := err.(*exec.ExitError)
   634  		if !ok {
   635  			return errors.Errorf("failed to execute 'go %v': %s %T", args, err, err)
   636  		}
   637  
   638  		// Too old Go version
   639  		if strings.Contains(stderr.String(), "flag provided but not defined") {
   640  			c.goBinaryStatus = goBinaryStatusTooOld
   641  			return nil
   642  		}
   643  
   644  		return errors.Errorf("go command failed: %s", stderr)
   645  
   646  	}
   647  
   648  	return nil
   649  }
   650  
   651  func (c *Client) tidy(mods Modules, goModOnly bool) error {
   652  	isGoMod := make(map[string]bool)
   653  	for _, m := range mods {
   654  		if m.Owner() == nil {
   655  			continue
   656  		}
   657  		if m.IsGoMod() {
   658  			// Matching the format in go.mod
   659  			pathVer := m.Path() + " " + m.Version()
   660  			isGoMod[pathVer] = true
   661  		}
   662  	}
   663  
   664  	if err := c.rewriteGoMod(goModFilename, isGoMod); err != nil {
   665  		return err
   666  	}
   667  
   668  	if goModOnly {
   669  		return nil
   670  	}
   671  
   672  	if err := c.rewriteGoMod(goSumFilename, isGoMod); err != nil {
   673  		return err
   674  	}
   675  
   676  	return nil
   677  }
   678  
   679  func (c *Client) shouldVendor(path string) bool {
   680  	return c.noVendor == nil || !c.noVendor.Match(path)
   681  }
   682  
   683  func (c *Client) createThemeDirname(modulePath string, isProjectMod bool) (string, error) {
   684  	invalid := errors.Errorf("invalid module path %q; must be relative to themesDir when defined outside of the project", modulePath)
   685  
   686  	modulePath = filepath.Clean(modulePath)
   687  	if filepath.IsAbs(modulePath) {
   688  		if isProjectMod {
   689  			return modulePath, nil
   690  		}
   691  		return "", invalid
   692  	}
   693  
   694  	moduleDir := filepath.Join(c.ccfg.ThemesDir, modulePath)
   695  	if !isProjectMod && !strings.HasPrefix(moduleDir, c.ccfg.ThemesDir) {
   696  		return "", invalid
   697  	}
   698  	return moduleDir, nil
   699  }
   700  
   701  // ClientConfig configures the module Client.
   702  type ClientConfig struct {
   703  	Fs     afero.Fs
   704  	Logger loggers.Logger
   705  
   706  	// If set, it will be run before we do any duplicate checks for modules
   707  	// etc.
   708  	HookBeforeFinalize func(m *ModulesConfig) error
   709  
   710  	// Ignore any _vendor directory for module paths matching the given pattern.
   711  	// This can be nil.
   712  	IgnoreVendor glob.Glob
   713  
   714  	// Absolute path to the project dir.
   715  	WorkingDir string
   716  
   717  	// Absolute path to the project's themes dir.
   718  	ThemesDir string
   719  
   720  	// Eg. "production"
   721  	Environment string
   722  
   723  	CacheDir     string // Module cache
   724  	ModuleConfig Config
   725  }
   726  
   727  func (c ClientConfig) shouldIgnoreVendor(path string) bool {
   728  	return c.IgnoreVendor != nil && c.IgnoreVendor.Match(path)
   729  }
   730  
   731  type goBinaryStatus int
   732  
   733  type goModule struct {
   734  	Path     string         // module path
   735  	Version  string         // module version
   736  	Versions []string       // available module versions (with -versions)
   737  	Replace  *goModule      // replaced by this module
   738  	Time     *time.Time     // time version was created
   739  	Update   *goModule      // available update, if any (with -u)
   740  	Main     bool           // is this the main module?
   741  	Indirect bool           // is this module only an indirect dependency of main module?
   742  	Dir      string         // directory holding files for this module, if any
   743  	GoMod    string         // path to go.mod file for this module, if any
   744  	Error    *goModuleError // error loading module
   745  }
   746  
   747  type goModuleError struct {
   748  	Err string // the error itself
   749  }
   750  
   751  type goModules []*goModule
   752  
   753  func (modules goModules) GetByPath(p string) *goModule {
   754  	if modules == nil {
   755  		return nil
   756  	}
   757  
   758  	for _, m := range modules {
   759  		if strings.EqualFold(p, m.Path) {
   760  			return m
   761  		}
   762  	}
   763  
   764  	return nil
   765  }
   766  
   767  func (modules goModules) GetMain() *goModule {
   768  	for _, m := range modules {
   769  		if m.Main {
   770  			return m
   771  		}
   772  	}
   773  
   774  	return nil
   775  }
   776  
   777  func getModlineSplitter(isGoMod bool) func(line string) []string {
   778  	if isGoMod {
   779  		return func(line string) []string {
   780  			if strings.HasPrefix(line, "require (") {
   781  				return nil
   782  			}
   783  			if !strings.HasPrefix(line, "require") && !strings.HasPrefix(line, "\t") {
   784  				return nil
   785  			}
   786  			line = strings.TrimPrefix(line, "require")
   787  			line = strings.TrimSpace(line)
   788  			line = strings.TrimSuffix(line, "// indirect")
   789  
   790  			return strings.Fields(line)
   791  		}
   792  	}
   793  
   794  	return func(line string) []string {
   795  		return strings.Fields(line)
   796  	}
   797  }
   798  
   799  func pathVersion(m Module) string {
   800  	versionStr := m.Version()
   801  	if m.Vendor() {
   802  		versionStr += "+vendor"
   803  	}
   804  	if versionStr == "" {
   805  		return m.Path()
   806  	}
   807  	return fmt.Sprintf("%s@%s", m.Path(), versionStr)
   808  }