зеркало из https://github.com/golang/dep.git
287 строки
8.6 KiB
Go
287 строки
8.6 KiB
Go
// Copyright 2017 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package dep
|
|
|
|
import (
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"github.com/golang/dep/internal/fs"
|
|
"github.com/golang/dep/internal/gps"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Ctx defines the supporting context of dep.
|
|
//
|
|
// A properly initialized Ctx has a GOPATH containing the project root and non-nil Loggers.
|
|
//
|
|
// ctx := &dep.Ctx{
|
|
// WorkingDir: GOPATH + "/src/project/root",
|
|
// GOPATH: GOPATH,
|
|
// Out: log.New(os.Stdout, "", 0),
|
|
// Err: log.New(os.Stderr, "", 0),
|
|
// }
|
|
//
|
|
// Ctx.DetectProjectGOPATH() helps with setting the containing GOPATH.
|
|
//
|
|
// ctx.GOPATH, err := Ctx.DetectProjectGOPATH(project)
|
|
// if err != nil {
|
|
// // Could not determine which GOPATH to use for the project.
|
|
// }
|
|
//
|
|
type Ctx struct {
|
|
WorkingDir string // Where to execute.
|
|
GOPATH string // Selected Go path, containing WorkingDir.
|
|
GOPATHs []string // Other Go paths.
|
|
Out, Err *log.Logger // Required loggers.
|
|
Verbose bool // Enables more verbose logging.
|
|
DisableLocking bool // When set, no lock file will be created to protect against simultaneous dep processes.
|
|
}
|
|
|
|
// SetPaths sets the WorkingDir and GOPATHs fields. If GOPATHs is empty, then
|
|
// the GOPATH environment variable (or the default GOPATH) is used instead.
|
|
func (c *Ctx) SetPaths(wd string, GOPATHs ...string) error {
|
|
if wd == "" {
|
|
return errors.New("cannot set Ctx.WorkingDir to an empty path")
|
|
}
|
|
c.WorkingDir = wd
|
|
|
|
if len(GOPATHs) == 0 {
|
|
GOPATH := os.Getenv("GOPATH")
|
|
if GOPATH == "" {
|
|
GOPATH = defaultGOPATH()
|
|
}
|
|
GOPATHs = filepath.SplitList(GOPATH)
|
|
}
|
|
|
|
c.GOPATHs = append(c.GOPATHs, GOPATHs...)
|
|
|
|
return nil
|
|
}
|
|
|
|
// defaultGOPATH gets the default GOPATH that was added in 1.8
|
|
// copied from go/build/build.go
|
|
func defaultGOPATH() string {
|
|
env := "HOME"
|
|
if runtime.GOOS == "windows" {
|
|
env = "USERPROFILE"
|
|
} else if runtime.GOOS == "plan9" {
|
|
env = "home"
|
|
}
|
|
if home := os.Getenv(env); home != "" {
|
|
def := filepath.Join(home, "go")
|
|
if def == runtime.GOROOT() {
|
|
// Don't set the default GOPATH to GOROOT,
|
|
// as that will trigger warnings from the go tool.
|
|
return ""
|
|
}
|
|
return def
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// SourceManager produces an instance of gps's built-in SourceManager
|
|
// initialized to log to the receiver's logger.
|
|
func (c *Ctx) SourceManager() (*gps.SourceMgr, error) {
|
|
return gps.NewSourceManager(gps.SourceManagerConfig{
|
|
Cachedir: filepath.Join(c.GOPATH, "pkg", "dep"),
|
|
Logger: c.Out,
|
|
DisableLocking: c.DisableLocking,
|
|
})
|
|
}
|
|
|
|
// LoadProject starts from the current working directory and searches up the
|
|
// directory tree for a project root. The search stops when a file with the name
|
|
// ManifestName (Gopkg.toml, by default) is located.
|
|
//
|
|
// The Project contains the parsed manifest as well as a parsed lock file, if
|
|
// present. The import path is calculated as the remaining path segment
|
|
// below Ctx.GOPATH/src.
|
|
func (c *Ctx) LoadProject() (*Project, error) {
|
|
root, err := findProjectRoot(c.WorkingDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = checkGopkgFilenames(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := new(Project)
|
|
|
|
if err = p.SetRoot(root); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.GOPATH, err = c.DetectProjectGOPATH(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ip, err := c.ImportForAbs(p.AbsRoot)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "root project import")
|
|
}
|
|
p.ImportRoot = gps.ProjectRoot(ip)
|
|
|
|
mp := filepath.Join(p.AbsRoot, ManifestName)
|
|
mf, err := os.Open(mp)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
// TODO: list possible solutions? (dep init, cd $project)
|
|
return nil, errors.Errorf("no %v found in project root %v", ManifestName, p.AbsRoot)
|
|
}
|
|
// Unable to read the manifest file
|
|
return nil, err
|
|
}
|
|
defer mf.Close()
|
|
|
|
var warns []error
|
|
p.Manifest, warns, err = readManifest(mf)
|
|
for _, warn := range warns {
|
|
c.Err.Printf("dep: WARNING: %v\n", warn)
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error while parsing %s", mp)
|
|
}
|
|
|
|
lp := filepath.Join(p.AbsRoot, LockName)
|
|
lf, err := os.Open(lp)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
// It's fine for the lock not to exist
|
|
return p, nil
|
|
}
|
|
// But if a lock does exist and we can't open it, that's a problem
|
|
return nil, errors.Wrapf(err, "could not open %s", lp)
|
|
}
|
|
defer lf.Close()
|
|
|
|
p.Lock, err = readLock(lf)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error while parsing %s", lp)
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// DetectProjectGOPATH attempt to find the GOPATH containing the project.
|
|
//
|
|
// If p.AbsRoot is not a symlink and is within a GOPATH, the GOPATH containing p.AbsRoot is returned.
|
|
// If p.AbsRoot is a symlink and is not within any known GOPATH, the GOPATH containing p.ResolvedAbsRoot is returned.
|
|
//
|
|
// p.AbsRoot is assumed to be a symlink if it is not the same as p.ResolvedAbsRoot.
|
|
//
|
|
// DetectProjectGOPATH will return an error in the following cases:
|
|
//
|
|
// If p.AbsRoot is not a symlink and is not within any known GOPATH.
|
|
// If neither p.AbsRoot nor p.ResolvedAbsRoot are within a known GOPATH.
|
|
// If both p.AbsRoot and p.ResolvedAbsRoot are within the same GOPATH.
|
|
// If p.AbsRoot and p.ResolvedAbsRoot are each within a different GOPATH.
|
|
func (c *Ctx) DetectProjectGOPATH(p *Project) (string, error) {
|
|
if p.AbsRoot == "" || p.ResolvedAbsRoot == "" {
|
|
return "", errors.New("project AbsRoot and ResolvedAbsRoot must be set to detect GOPATH")
|
|
}
|
|
|
|
pGOPATH, perr := c.detectGOPATH(p.AbsRoot)
|
|
|
|
// If p.AbsRoot is a not symlink, attempt to detect GOPATH for p.AbsRoot only.
|
|
if equal, _ := fs.EquivalentPaths(p.AbsRoot, p.ResolvedAbsRoot); equal {
|
|
return pGOPATH, perr
|
|
}
|
|
|
|
rGOPATH, rerr := c.detectGOPATH(p.ResolvedAbsRoot)
|
|
|
|
// If detectGOPATH() failed for both p.AbsRoot and p.ResolvedAbsRoot, then both are not within any known GOPATHs.
|
|
if perr != nil && rerr != nil {
|
|
return "", errors.Errorf("both %s and %s are not within any known GOPATH", p.AbsRoot, p.ResolvedAbsRoot)
|
|
}
|
|
|
|
// If pGOPATH equals rGOPATH, then both are within the same GOPATH.
|
|
if equal, _ := fs.EquivalentPaths(pGOPATH, rGOPATH); equal {
|
|
return "", errors.Errorf("both %s and %s are in the same GOPATH %s", p.AbsRoot, p.ResolvedAbsRoot, pGOPATH)
|
|
}
|
|
|
|
if pGOPATH != "" && rGOPATH != "" {
|
|
return "", errors.Errorf("%s and %s are both in different GOPATHs", p.AbsRoot, p.ResolvedAbsRoot)
|
|
}
|
|
|
|
// Otherwise, either the p.AbsRoot or p.ResolvedAbsRoot is within a GOPATH.
|
|
if pGOPATH == "" {
|
|
return rGOPATH, nil
|
|
}
|
|
|
|
return pGOPATH, nil
|
|
}
|
|
|
|
// detectGOPATH detects the GOPATH for a given path from ctx.GOPATHs.
|
|
func (c *Ctx) detectGOPATH(path string) (string, error) {
|
|
for _, gp := range c.GOPATHs {
|
|
isPrefix, err := fs.HasFilepathPrefix(path, gp)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to detect GOPATH")
|
|
}
|
|
if isPrefix {
|
|
return gp, nil
|
|
}
|
|
}
|
|
return "", errors.Errorf("%s is not within a known GOPATH/src", path)
|
|
}
|
|
|
|
// ImportForAbs returns the import path for an absolute project path by trimming the
|
|
// `$GOPATH/src/` prefix. Returns an error for paths equal to, or without this prefix.
|
|
func (c *Ctx) ImportForAbs(path string) (string, error) {
|
|
srcprefix := filepath.Join(c.GOPATH, "src") + string(filepath.Separator)
|
|
isPrefix, err := fs.HasFilepathPrefix(path, srcprefix)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to find import path")
|
|
}
|
|
if isPrefix {
|
|
if len(path) <= len(srcprefix) {
|
|
return "", errors.New("dep does not currently support using GOPATH/src as the project root")
|
|
}
|
|
|
|
// filepath.ToSlash because we're dealing with an import path now,
|
|
// not an fs path
|
|
return filepath.ToSlash(path[len(srcprefix):]), nil
|
|
}
|
|
|
|
return "", errors.Errorf("%s is not within any GOPATH/src", path)
|
|
}
|
|
|
|
// AbsForImport returns the absolute path for the project root
|
|
// including the $GOPATH. This will not work with stdlib packages and the
|
|
// package directory needs to exist.
|
|
func (c *Ctx) AbsForImport(path string) (string, error) {
|
|
posspath := filepath.Join(c.GOPATH, "src", path)
|
|
dirOK, err := fs.IsDir(posspath)
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "checking if %s is a directory", posspath)
|
|
}
|
|
if !dirOK {
|
|
return "", errors.Errorf("%s does not exist", posspath)
|
|
}
|
|
return posspath, nil
|
|
}
|
|
|
|
// ValidateParams ensure that solving can be completed with the specified params.
|
|
func (c *Ctx) ValidateParams(sm gps.SourceManager, params gps.SolveParameters) error {
|
|
err := gps.ValidateParams(params, sm)
|
|
if err != nil {
|
|
if deduceErrs, ok := err.(gps.DeductionErrs); ok {
|
|
c.Err.Println("The following errors occurred while deducing packages:")
|
|
for ip, dErr := range deduceErrs {
|
|
c.Err.Printf(" * \"%s\": %s", ip, dErr)
|
|
}
|
|
c.Err.Println()
|
|
}
|
|
}
|
|
|
|
return errors.Wrap(err, "validateParams")
|
|
}
|