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 }