forked from golang/dep
-
Notifications
You must be signed in to change notification settings - Fork 0
/
context.go
361 lines (313 loc) · 10.9 KB
/
context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
// 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"
"sort"
"time"
"github.com/flowrean/dep/gps"
"github.com/flowrean/dep/gps/paths"
"github.com/flowrean/dep/gps/pkgtree"
"github.com/flowrean/dep/gps/verify"
"github.com/flowrean/dep/internal/fs"
"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.
ExplicitRoot string // An explicitly-set path to use as the project root.
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.
Cachedir string // Cache directory loaded from environment.
CacheAge time.Duration // Maximum valid age of cached source data. <=0: Don't cache.
}
// 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...)
c.ExplicitRoot = os.Getenv("DEPPROJECTROOT")
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) {
cachedir := c.Cachedir
if cachedir == "" {
// When `DEPCACHEDIR` isn't set in the env, use the default - `$GOPATH/pkg/dep`.
cachedir = filepath.Join(c.GOPATH, "pkg", "dep")
// Create the default cachedir if it does not exist.
if err := os.MkdirAll(cachedir, 0777); err != nil {
return nil, errors.Wrap(err, "failed to create default cache directory")
}
}
return gps.NewSourceManager(gps.SourceManagerConfig{
CacheAge: c.CacheAge,
Cachedir: cachedir,
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
}
if c.ExplicitRoot != "" {
p.ImportRoot = gps.ProjectRoot(c.ExplicitRoot)
} else {
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)
}
// Parse in the root package tree.
ptree, err := p.parseRootPackageTree()
if err != nil {
return nil, err
}
lp := filepath.Join(p.AbsRoot, LockName)
lf, err := os.Open(lp)
if err == nil {
defer lf.Close()
p.Lock, err = readLock(lf)
if err != nil {
return nil, errors.Wrapf(err, "error while parsing %s", lp)
}
// If there's a current Lock, apply the input and pruneopt changes that we
// can know without solving.
if p.Lock != nil {
p.ChangedLock = p.Lock.dup()
p.ChangedLock.SolveMeta.InputImports = externalImportList(ptree, p.Manifest)
for k, lp := range p.ChangedLock.Projects() {
vp := lp.(verify.VerifiableProject)
vp.PruneOpts = p.Manifest.PruneOptions.PruneOptionsFor(lp.Ident().ProjectRoot)
p.ChangedLock.P[k] = vp
}
}
} else if !os.IsNotExist(err) {
// It's fine for the lock not to exist, but if a file does exist and we
// can't open it, that's a problem.
return nil, errors.Wrapf(err, "could not open %s", lp)
}
return p, nil
}
func externalImportList(rpt pkgtree.PackageTree, m gps.RootManifest) []string {
rm, _ := rpt.ToReachMap(true, true, false, m.IgnoredPackages())
reach := rm.FlattenFn(paths.IsStandardImportPath)
req := m.RequiredPackages()
// If there are any requires, slide them into the reach list, as well.
if len(req) > 0 {
// Make a map of imports that are both in the import path list and the
// required list to avoid duplication.
skip := make(map[string]bool, len(req))
for _, r := range reach {
if req[r] {
skip[r] = true
}
}
for r := range req {
if !skip[r] {
reach = append(reach, r)
}
}
}
sort.Strings(reach)
return reach
}
// 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")
}
if c.ExplicitRoot != "" {
// If an explicit root is set, just use the first GOPATH in the list.
return c.GOPATHs[0], nil
}
pGOPATH, perr := c.detectGOPATH(p.AbsRoot)
// If p.AbsRoot is a not a 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 filepath.Clean(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")
}