github.com/golang/dep@v0.5.4/context.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package dep
     6  
     7  import (
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"runtime"
    12  	"sort"
    13  	"time"
    14  
    15  	"github.com/golang/dep/gps"
    16  	"github.com/golang/dep/gps/paths"
    17  	"github.com/golang/dep/gps/pkgtree"
    18  	"github.com/golang/dep/gps/verify"
    19  	"github.com/golang/dep/internal/fs"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  // Ctx defines the supporting context of dep.
    24  //
    25  // A properly initialized Ctx has a GOPATH containing the project root and non-nil Loggers.
    26  //
    27  //	ctx := &dep.Ctx{
    28  //		WorkingDir: GOPATH + "/src/project/root",
    29  //		GOPATH: GOPATH,
    30  //		Out: log.New(os.Stdout, "", 0),
    31  //		Err: log.New(os.Stderr, "", 0),
    32  //	}
    33  //
    34  // Ctx.DetectProjectGOPATH() helps with setting the containing GOPATH.
    35  //
    36  //	ctx.GOPATH, err := Ctx.DetectProjectGOPATH(project)
    37  //	if err != nil {
    38  //		// Could not determine which GOPATH to use for the project.
    39  //	}
    40  //
    41  type Ctx struct {
    42  	WorkingDir     string        // Where to execute.
    43  	GOPATH         string        // Selected Go path, containing WorkingDir.
    44  	GOPATHs        []string      // Other Go paths.
    45  	ExplicitRoot   string        // An explicitly-set path to use as the project root.
    46  	Out, Err       *log.Logger   // Required loggers.
    47  	Verbose        bool          // Enables more verbose logging.
    48  	DisableLocking bool          // When set, no lock file will be created to protect against simultaneous dep processes.
    49  	Cachedir       string        // Cache directory loaded from environment.
    50  	CacheAge       time.Duration // Maximum valid age of cached source data. <=0: Don't cache.
    51  }
    52  
    53  // SetPaths sets the WorkingDir and GOPATHs fields. If GOPATHs is empty, then
    54  // the GOPATH environment variable (or the default GOPATH) is used instead.
    55  func (c *Ctx) SetPaths(wd string, GOPATHs ...string) error {
    56  	if wd == "" {
    57  		return errors.New("cannot set Ctx.WorkingDir to an empty path")
    58  	}
    59  	c.WorkingDir = wd
    60  
    61  	if len(GOPATHs) == 0 {
    62  		GOPATH := os.Getenv("GOPATH")
    63  		if GOPATH == "" {
    64  			GOPATH = defaultGOPATH()
    65  		}
    66  		GOPATHs = filepath.SplitList(GOPATH)
    67  	}
    68  
    69  	c.GOPATHs = append(c.GOPATHs, GOPATHs...)
    70  
    71  	c.ExplicitRoot = os.Getenv("DEPPROJECTROOT")
    72  
    73  	return nil
    74  }
    75  
    76  // defaultGOPATH gets the default GOPATH that was added in 1.8
    77  // copied from go/build/build.go
    78  func defaultGOPATH() string {
    79  	env := "HOME"
    80  	if runtime.GOOS == "windows" {
    81  		env = "USERPROFILE"
    82  	} else if runtime.GOOS == "plan9" {
    83  		env = "home"
    84  	}
    85  	if home := os.Getenv(env); home != "" {
    86  		def := filepath.Join(home, "go")
    87  		if def == runtime.GOROOT() {
    88  			// Don't set the default GOPATH to GOROOT,
    89  			// as that will trigger warnings from the go tool.
    90  			return ""
    91  		}
    92  		return def
    93  	}
    94  	return ""
    95  }
    96  
    97  // SourceManager produces an instance of gps's built-in SourceManager
    98  // initialized to log to the receiver's logger.
    99  func (c *Ctx) SourceManager() (*gps.SourceMgr, error) {
   100  	cachedir := c.Cachedir
   101  	if cachedir == "" {
   102  		// When `DEPCACHEDIR` isn't set in the env, use the default - `$GOPATH/pkg/dep`.
   103  		cachedir = filepath.Join(c.GOPATH, "pkg", "dep")
   104  		// Create the default cachedir if it does not exist.
   105  		if err := os.MkdirAll(cachedir, 0777); err != nil {
   106  			return nil, errors.Wrap(err, "failed to create default cache directory")
   107  		}
   108  	}
   109  
   110  	return gps.NewSourceManager(gps.SourceManagerConfig{
   111  		CacheAge:       c.CacheAge,
   112  		Cachedir:       cachedir,
   113  		Logger:         c.Out,
   114  		DisableLocking: c.DisableLocking,
   115  	})
   116  }
   117  
   118  // LoadProject starts from the current working directory and searches up the
   119  // directory tree for a project root.  The search stops when a file with the name
   120  // ManifestName (Gopkg.toml, by default) is located.
   121  //
   122  // The Project contains the parsed manifest as well as a parsed lock file, if
   123  // present.  The import path is calculated as the remaining path segment
   124  // below Ctx.GOPATH/src.
   125  func (c *Ctx) LoadProject() (*Project, error) {
   126  	root, err := findProjectRoot(c.WorkingDir)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	err = checkGopkgFilenames(root)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	p := new(Project)
   137  
   138  	if err = p.SetRoot(root); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	c.GOPATH, err = c.DetectProjectGOPATH(p)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	if c.ExplicitRoot != "" {
   148  		p.ImportRoot = gps.ProjectRoot(c.ExplicitRoot)
   149  	} else {
   150  		ip, err := c.ImportForAbs(p.AbsRoot)
   151  		if err != nil {
   152  			return nil, errors.Wrap(err, "root project import")
   153  		}
   154  		p.ImportRoot = gps.ProjectRoot(ip)
   155  	}
   156  
   157  	mp := filepath.Join(p.AbsRoot, ManifestName)
   158  	mf, err := os.Open(mp)
   159  	if err != nil {
   160  		if os.IsNotExist(err) {
   161  			// TODO: list possible solutions? (dep init, cd $project)
   162  			return nil, errors.Errorf("no %v found in project root %v", ManifestName, p.AbsRoot)
   163  		}
   164  		// Unable to read the manifest file
   165  		return nil, err
   166  	}
   167  	defer mf.Close()
   168  
   169  	var warns []error
   170  	p.Manifest, warns, err = readManifest(mf)
   171  	for _, warn := range warns {
   172  		c.Err.Printf("dep: WARNING: %v\n", warn)
   173  	}
   174  	if err != nil {
   175  		return nil, errors.Wrapf(err, "error while parsing %s", mp)
   176  	}
   177  
   178  	// Parse in the root package tree.
   179  	ptree, err := p.parseRootPackageTree()
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	lp := filepath.Join(p.AbsRoot, LockName)
   185  	lf, err := os.Open(lp)
   186  	if err == nil {
   187  		defer lf.Close()
   188  
   189  		p.Lock, err = readLock(lf)
   190  		if err != nil {
   191  			return nil, errors.Wrapf(err, "error while parsing %s", lp)
   192  		}
   193  
   194  		// If there's a current Lock, apply the input and pruneopt changes that we
   195  		// can know without solving.
   196  		if p.Lock != nil {
   197  			p.ChangedLock = p.Lock.dup()
   198  			p.ChangedLock.SolveMeta.InputImports = externalImportList(ptree, p.Manifest)
   199  
   200  			for k, lp := range p.ChangedLock.Projects() {
   201  				vp := lp.(verify.VerifiableProject)
   202  				vp.PruneOpts = p.Manifest.PruneOptions.PruneOptionsFor(lp.Ident().ProjectRoot)
   203  				p.ChangedLock.P[k] = vp
   204  			}
   205  		}
   206  
   207  	} else if !os.IsNotExist(err) {
   208  		// It's fine for the lock not to exist, but if a file does exist and we
   209  		// can't open it, that's a problem.
   210  		return nil, errors.Wrapf(err, "could not open %s", lp)
   211  	}
   212  
   213  	return p, nil
   214  }
   215  
   216  func externalImportList(rpt pkgtree.PackageTree, m gps.RootManifest) []string {
   217  	rm, _ := rpt.ToReachMap(true, true, false, m.IgnoredPackages())
   218  	reach := rm.FlattenFn(paths.IsStandardImportPath)
   219  	req := m.RequiredPackages()
   220  
   221  	// If there are any requires, slide them into the reach list, as well.
   222  	if len(req) > 0 {
   223  		// Make a map of imports that are both in the import path list and the
   224  		// required list to avoid duplication.
   225  		skip := make(map[string]bool, len(req))
   226  		for _, r := range reach {
   227  			if req[r] {
   228  				skip[r] = true
   229  			}
   230  		}
   231  
   232  		for r := range req {
   233  			if !skip[r] {
   234  				reach = append(reach, r)
   235  			}
   236  		}
   237  	}
   238  
   239  	sort.Strings(reach)
   240  	return reach
   241  }
   242  
   243  // DetectProjectGOPATH attempt to find the GOPATH containing the project.
   244  //
   245  //  If p.AbsRoot is not a symlink and is within a GOPATH, the GOPATH containing p.AbsRoot is returned.
   246  //  If p.AbsRoot is a symlink and is not within any known GOPATH, the GOPATH containing p.ResolvedAbsRoot is returned.
   247  //
   248  // p.AbsRoot is assumed to be a symlink if it is not the same as p.ResolvedAbsRoot.
   249  //
   250  // DetectProjectGOPATH will return an error in the following cases:
   251  //
   252  //  If p.AbsRoot is not a symlink and is not within any known GOPATH.
   253  //  If neither p.AbsRoot nor p.ResolvedAbsRoot are within a known GOPATH.
   254  //  If both p.AbsRoot and p.ResolvedAbsRoot are within the same GOPATH.
   255  //  If p.AbsRoot and p.ResolvedAbsRoot are each within a different GOPATH.
   256  func (c *Ctx) DetectProjectGOPATH(p *Project) (string, error) {
   257  	if p.AbsRoot == "" || p.ResolvedAbsRoot == "" {
   258  		return "", errors.New("project AbsRoot and ResolvedAbsRoot must be set to detect GOPATH")
   259  	}
   260  
   261  	if c.ExplicitRoot != "" {
   262  		// If an explicit root is set, just use the first GOPATH in the list.
   263  		return c.GOPATHs[0], nil
   264  	}
   265  
   266  	pGOPATH, perr := c.detectGOPATH(p.AbsRoot)
   267  
   268  	// If p.AbsRoot is a not a symlink, attempt to detect GOPATH for p.AbsRoot only.
   269  	if equal, _ := fs.EquivalentPaths(p.AbsRoot, p.ResolvedAbsRoot); equal {
   270  		return pGOPATH, perr
   271  	}
   272  
   273  	rGOPATH, rerr := c.detectGOPATH(p.ResolvedAbsRoot)
   274  
   275  	// If detectGOPATH() failed for both p.AbsRoot and p.ResolvedAbsRoot, then both are not within any known GOPATHs.
   276  	if perr != nil && rerr != nil {
   277  		return "", errors.Errorf("both %s and %s are not within any known GOPATH", p.AbsRoot, p.ResolvedAbsRoot)
   278  	}
   279  
   280  	// If pGOPATH equals rGOPATH, then both are within the same GOPATH.
   281  	if equal, _ := fs.EquivalentPaths(pGOPATH, rGOPATH); equal {
   282  		return "", errors.Errorf("both %s and %s are in the same GOPATH %s", p.AbsRoot, p.ResolvedAbsRoot, pGOPATH)
   283  	}
   284  
   285  	if pGOPATH != "" && rGOPATH != "" {
   286  		return "", errors.Errorf("%s and %s are both in different GOPATHs", p.AbsRoot, p.ResolvedAbsRoot)
   287  	}
   288  
   289  	// Otherwise, either the p.AbsRoot or p.ResolvedAbsRoot is within a GOPATH.
   290  	if pGOPATH == "" {
   291  		return rGOPATH, nil
   292  	}
   293  
   294  	return pGOPATH, nil
   295  }
   296  
   297  // detectGOPATH detects the GOPATH for a given path from ctx.GOPATHs.
   298  func (c *Ctx) detectGOPATH(path string) (string, error) {
   299  	for _, gp := range c.GOPATHs {
   300  		isPrefix, err := fs.HasFilepathPrefix(path, gp)
   301  		if err != nil {
   302  			return "", errors.Wrap(err, "failed to detect GOPATH")
   303  		}
   304  		if isPrefix {
   305  			return filepath.Clean(gp), nil
   306  		}
   307  	}
   308  	return "", errors.Errorf("%s is not within a known GOPATH/src", path)
   309  }
   310  
   311  // ImportForAbs returns the import path for an absolute project path by trimming the
   312  // `$GOPATH/src/` prefix.  Returns an error for paths equal to, or without this prefix.
   313  func (c *Ctx) ImportForAbs(path string) (string, error) {
   314  	srcprefix := filepath.Join(c.GOPATH, "src") + string(filepath.Separator)
   315  	isPrefix, err := fs.HasFilepathPrefix(path, srcprefix)
   316  	if err != nil {
   317  		return "", errors.Wrap(err, "failed to find import path")
   318  	}
   319  	if isPrefix {
   320  		if len(path) <= len(srcprefix) {
   321  			return "", errors.New("dep does not currently support using GOPATH/src as the project root")
   322  		}
   323  
   324  		// filepath.ToSlash because we're dealing with an import path now,
   325  		// not an fs path
   326  		return filepath.ToSlash(path[len(srcprefix):]), nil
   327  	}
   328  
   329  	return "", errors.Errorf("%s is not within any GOPATH/src", path)
   330  }
   331  
   332  // AbsForImport returns the absolute path for the project root
   333  // including the $GOPATH. This will not work with stdlib packages and the
   334  // package directory needs to exist.
   335  func (c *Ctx) AbsForImport(path string) (string, error) {
   336  	posspath := filepath.Join(c.GOPATH, "src", path)
   337  	dirOK, err := fs.IsDir(posspath)
   338  	if err != nil {
   339  		return "", errors.Wrapf(err, "checking if %s is a directory", posspath)
   340  	}
   341  	if !dirOK {
   342  		return "", errors.Errorf("%s does not exist", posspath)
   343  	}
   344  	return posspath, nil
   345  }
   346  
   347  // ValidateParams ensure that solving can be completed with the specified params.
   348  func (c *Ctx) ValidateParams(sm gps.SourceManager, params gps.SolveParameters) error {
   349  	err := gps.ValidateParams(params, sm)
   350  	if err != nil {
   351  		if deduceErrs, ok := err.(gps.DeductionErrs); ok {
   352  			c.Err.Println("The following errors occurred while deducing packages:")
   353  			for ip, dErr := range deduceErrs {
   354  				c.Err.Printf("  * \"%s\": %s", ip, dErr)
   355  			}
   356  			c.Err.Println()
   357  		}
   358  	}
   359  
   360  	return errors.Wrap(err, "validateParams")
   361  }