1 | package repo |
||
2 | |||
3 | import ( |
||
4 | "fmt" |
||
5 | "io/ioutil" |
||
6 | "os" |
||
7 | "path/filepath" |
||
8 | "runtime" |
||
9 | "strings" |
||
10 | "sync" |
||
11 | "syscall" |
||
12 | "time" |
||
13 | |||
14 | "github.com/Masterminds/glide/cache" |
||
15 | "github.com/Masterminds/glide/cfg" |
||
16 | "github.com/Masterminds/glide/dependency" |
||
17 | "github.com/Masterminds/glide/importer" |
||
18 | "github.com/Masterminds/glide/msg" |
||
19 | gpath "github.com/Masterminds/glide/path" |
||
20 | "github.com/Masterminds/glide/util" |
||
21 | "github.com/Masterminds/semver" |
||
22 | "github.com/Masterminds/vcs" |
||
23 | "github.com/codegangsta/cli" |
||
24 | ) |
||
25 | |||
26 | // Installer provides facilities for installing the repos in a config file. |
||
27 | type Installer struct { |
||
28 | |||
29 | // Force the install when certain normally stopping conditions occur. |
||
30 | Force bool |
||
31 | |||
32 | // Home is the location of cache |
||
33 | Home string |
||
34 | |||
35 | // Vendor contains the path to put the vendor packages |
||
36 | Vendor string |
||
37 | |||
38 | // ResolveAllFiles enables a resolver that will examine the dependencies |
||
39 | // of every file of every package, rather than only following imported |
||
40 | // packages. |
||
41 | ResolveAllFiles bool |
||
42 | |||
43 | // ResolveTest sets if test dependencies should be resolved. |
||
44 | ResolveTest bool |
||
45 | |||
46 | // Updated tracks the packages that have been remotely fetched. |
||
47 | Updated *UpdateTracker |
||
48 | } |
||
49 | |||
50 | // NewInstaller returns an Installer instance ready to use. This is the constructor. |
||
51 | func NewInstaller() *Installer { |
||
52 | i := &Installer{} |
||
53 | i.Updated = NewUpdateTracker() |
||
54 | return i |
||
55 | } |
||
56 | |||
57 | // VendorPath returns the path to the location to put vendor packages |
||
58 | func (i *Installer) VendorPath() string { |
||
59 | if i.Vendor != "" { |
||
60 | return i.Vendor |
||
61 | } |
||
62 | |||
63 | vp, err := gpath.Vendor() |
||
64 | if err != nil { |
||
65 | return filepath.FromSlash("./vendor") |
||
66 | } |
||
67 | |||
68 | return vp |
||
69 | } |
||
70 | |||
71 | // Install installs the dependencies from a Lockfile. |
||
72 | func (i *Installer) Install(lock *cfg.Lockfile, conf *cfg.Config) (*cfg.Config, error) { |
||
73 | |||
74 | // Create a config setup based on the Lockfile data to process with |
||
75 | // existing commands. |
||
76 | newConf := &cfg.Config{} |
||
77 | newConf.Name = conf.Name |
||
78 | |||
79 | newConf.Imports = make(cfg.Dependencies, len(lock.Imports)) |
||
80 | for k, v := range lock.Imports { |
||
81 | newConf.Imports[k] = cfg.DependencyFromLock(v) |
||
82 | } |
||
83 | |||
84 | newConf.DevImports = make(cfg.Dependencies, len(lock.DevImports)) |
||
85 | for k, v := range lock.DevImports { |
||
86 | newConf.DevImports[k] = cfg.DependencyFromLock(v) |
||
87 | } |
||
88 | |||
89 | newConf.DeDupe() |
||
90 | |||
91 | if len(newConf.Imports) == 0 && len(newConf.DevImports) == 0 { |
||
92 | msg.Info("No dependencies found. Nothing installed.") |
||
93 | return newConf, nil |
||
94 | } |
||
95 | |||
96 | msg.Info("Downloading dependencies. Please wait...") |
||
97 | |||
98 | err := LazyConcurrentUpdate(newConf.Imports, i, newConf) |
||
99 | if err != nil { |
||
100 | return newConf, err |
||
101 | } |
||
102 | err = LazyConcurrentUpdate(newConf.DevImports, i, newConf) |
||
103 | |||
104 | return newConf, err |
||
105 | } |
||
106 | |||
107 | // Checkout reads the config file and checks out all dependencies mentioned there. |
||
108 | // |
||
109 | // This is used when initializing an empty vendor directory, or when updating a |
||
110 | // vendor directory based on changed config. |
||
111 | func (i *Installer) Checkout(conf *cfg.Config) error { |
||
112 | |||
113 | msg.Info("Downloading dependencies. Please wait...") |
||
114 | |||
115 | if err := ConcurrentUpdate(conf.Imports, i, conf); err != nil { |
||
116 | return err |
||
117 | } |
||
118 | |||
119 | if i.ResolveTest { |
||
120 | return ConcurrentUpdate(conf.DevImports, i, conf) |
||
121 | } |
||
122 | |||
123 | return nil |
||
124 | } |
||
125 | |||
126 | // Update updates all dependencies. |
||
127 | // |
||
128 | // It begins with the dependencies in the config file, but also resolves |
||
129 | // transitive dependencies. The returned lockfile has all of the dependencies |
||
130 | // listed, but the version reconciliation has not been done. |
||
131 | // |
||
132 | // In other words, all versions in the Lockfile will be empty. |
||
133 | func (i *Installer) Update(conf *cfg.Config) error { |
||
134 | base := "." |
||
135 | |||
136 | ic := newImportCache() |
||
137 | |||
138 | m := &MissingPackageHandler{ |
||
139 | home: i.Home, |
||
140 | force: i.Force, |
||
141 | Config: conf, |
||
142 | Use: ic, |
||
143 | updated: i.Updated, |
||
144 | } |
||
145 | |||
146 | v := &VersionHandler{ |
||
147 | Use: ic, |
||
148 | Imported: make(map[string]bool), |
||
149 | Conflicts: make(map[string]bool), |
||
150 | Config: conf, |
||
151 | } |
||
152 | |||
153 | // Update imports |
||
154 | res, err := dependency.NewResolver(base) |
||
155 | res.ResolveTest = i.ResolveTest |
||
156 | if err != nil { |
||
157 | msg.Die("Failed to create a resolver: %s", err) |
||
158 | } |
||
159 | res.Config = conf |
||
160 | res.Handler = m |
||
161 | res.VersionHandler = v |
||
162 | res.ResolveAllFiles = i.ResolveAllFiles |
||
163 | msg.Info("Resolving imports") |
||
164 | |||
165 | imps, timps, err := res.ResolveLocal(false) |
||
166 | if err != nil { |
||
167 | msg.Die("Failed to resolve local packages: %s", err) |
||
168 | } |
||
169 | var deps cfg.Dependencies |
||
170 | var tdeps cfg.Dependencies |
||
171 | for _, v := range imps { |
||
172 | n := res.Stripv(v) |
||
173 | if conf.HasIgnore(n) { |
||
174 | continue |
||
175 | } |
||
176 | rt, sub := util.NormalizeName(n) |
||
177 | if sub == "" { |
||
178 | sub = "." |
||
179 | } |
||
180 | d := deps.Get(rt) |
||
181 | if d == nil { |
||
182 | nd := &cfg.Dependency{ |
||
183 | Name: rt, |
||
184 | Subpackages: []string{sub}, |
||
185 | } |
||
186 | deps = append(deps, nd) |
||
187 | } else if !d.HasSubpackage(sub) { |
||
188 | d.Subpackages = append(d.Subpackages, sub) |
||
189 | } |
||
190 | } |
||
191 | if i.ResolveTest { |
||
192 | for _, v := range timps { |
||
193 | n := res.Stripv(v) |
||
194 | if conf.HasIgnore(n) { |
||
195 | continue |
||
196 | } |
||
197 | rt, sub := util.NormalizeName(n) |
||
198 | if sub == "" { |
||
199 | sub = "." |
||
200 | } |
||
201 | d := deps.Get(rt) |
||
202 | if d == nil { |
||
203 | d = tdeps.Get(rt) |
||
204 | } |
||
205 | if d == nil { |
||
206 | nd := &cfg.Dependency{ |
||
207 | Name: rt, |
||
208 | Subpackages: []string{sub}, |
||
209 | } |
||
210 | tdeps = append(tdeps, nd) |
||
211 | } else if !d.HasSubpackage(sub) { |
||
212 | d.Subpackages = append(d.Subpackages, sub) |
||
213 | } |
||
214 | } |
||
215 | } |
||
216 | |||
217 | _, err = allPackages(deps, res, false) |
||
218 | if err != nil { |
||
219 | msg.Die("Failed to retrieve a list of dependencies: %s", err) |
||
220 | } |
||
221 | |||
222 | if i.ResolveTest { |
||
223 | msg.Debug("Resolving test dependencies") |
||
224 | _, err = allPackages(tdeps, res, true) |
||
225 | if err != nil { |
||
226 | msg.Die("Failed to retrieve a list of test dependencies: %s", err) |
||
227 | } |
||
228 | } |
||
229 | |||
230 | msg.Info("Downloading dependencies. Please wait...") |
||
231 | |||
232 | err = ConcurrentUpdate(conf.Imports, i, conf) |
||
233 | if err != nil { |
||
234 | return err |
||
235 | } |
||
236 | |||
237 | if i.ResolveTest { |
||
238 | err = ConcurrentUpdate(conf.DevImports, i, conf) |
||
239 | if err != nil { |
||
240 | return err |
||
241 | } |
||
242 | } |
||
243 | |||
244 | return nil |
||
245 | } |
||
246 | |||
247 | // Export from the cache to the vendor directory |
||
248 | func (i *Installer) Export(conf *cfg.Config) error { |
||
249 | tempDir, err := ioutil.TempDir(gpath.Tmp, "glide-vendor") |
||
250 | if err != nil { |
||
251 | return err |
||
252 | } |
||
253 | defer func() { |
||
254 | err = os.RemoveAll(tempDir) |
||
255 | if err != nil { |
||
256 | msg.Err(err.Error()) |
||
257 | } |
||
258 | }() |
||
259 | |||
260 | vp := filepath.Join(tempDir, "vendor") |
||
261 | err = os.MkdirAll(vp, 0755) |
||
262 | |||
263 | msg.Info("Exporting resolved dependencies...") |
||
264 | done := make(chan struct{}, concurrentWorkers) |
||
265 | in := make(chan *cfg.Dependency, concurrentWorkers) |
||
266 | var wg sync.WaitGroup |
||
267 | var lock sync.Mutex |
||
268 | var returnErr error |
||
269 | |||
270 | for ii := 0; ii < concurrentWorkers; ii++ { |
||
271 | go func(ch <-chan *cfg.Dependency) { |
||
272 | for { |
||
273 | select { |
||
274 | case dep := <-ch: |
||
275 | loc := dep.Remote() |
||
276 | key, err := cache.Key(loc) |
||
277 | if err != nil { |
||
278 | msg.Die(err.Error()) |
||
279 | } |
||
280 | cache.Lock(key) |
||
281 | |||
282 | cdir := filepath.Join(cache.Location(), "src", key) |
||
283 | repo, err := dep.GetRepo(cdir) |
||
284 | if err != nil { |
||
285 | msg.Die(err.Error()) |
||
286 | } |
||
287 | msg.Info("--> Exporting %s", dep.Name) |
||
288 | if err := repo.ExportDir(filepath.Join(vp, filepath.ToSlash(dep.Name))); err != nil { |
||
289 | msg.Err("Export failed for %s: %s\n", dep.Name, err) |
||
290 | // Capture the error while making sure the concurrent |
||
291 | // operations don't step on each other. |
||
292 | lock.Lock() |
||
293 | if returnErr == nil { |
||
294 | returnErr = err |
||
295 | } else { |
||
296 | returnErr = cli.NewMultiError(returnErr, err) |
||
297 | } |
||
298 | lock.Unlock() |
||
299 | } |
||
300 | cache.Unlock(key) |
||
301 | wg.Done() |
||
302 | case <-done: |
||
303 | return |
||
304 | } |
||
305 | } |
||
306 | }(in) |
||
307 | } |
||
308 | |||
309 | for _, dep := range conf.Imports { |
||
310 | if !conf.HasIgnore(dep.Name) { |
||
311 | err = os.MkdirAll(filepath.Join(vp, filepath.ToSlash(dep.Name)), 0755) |
||
312 | if err != nil { |
||
313 | lock.Lock() |
||
314 | if returnErr == nil { |
||
315 | returnErr = err |
||
316 | } else { |
||
317 | returnErr = cli.NewMultiError(returnErr, err) |
||
318 | } |
||
319 | lock.Unlock() |
||
320 | } |
||
321 | wg.Add(1) |
||
322 | in <- dep |
||
323 | } |
||
324 | } |
||
325 | |||
326 | if i.ResolveTest { |
||
327 | for _, dep := range conf.DevImports { |
||
328 | if !conf.HasIgnore(dep.Name) { |
||
329 | err = os.MkdirAll(filepath.Join(vp, filepath.ToSlash(dep.Name)), 0755) |
||
330 | if err != nil { |
||
331 | lock.Lock() |
||
332 | if returnErr == nil { |
||
333 | returnErr = err |
||
334 | } else { |
||
335 | returnErr = cli.NewMultiError(returnErr, err) |
||
336 | } |
||
337 | lock.Unlock() |
||
338 | } |
||
339 | wg.Add(1) |
||
340 | in <- dep |
||
341 | } |
||
342 | } |
||
343 | } |
||
344 | |||
345 | wg.Wait() |
||
346 | |||
347 | // Close goroutines setting the version |
||
348 | for ii := 0; ii < concurrentWorkers; ii++ { |
||
349 | done <- struct{}{} |
||
350 | } |
||
351 | |||
352 | if returnErr != nil { |
||
353 | return returnErr |
||
354 | } |
||
355 | |||
356 | msg.Info("Replacing existing vendor dependencies") |
||
357 | |||
358 | // Check if a .git directory exists under the old vendor dir. If it does, |
||
359 | // move it over to the newly-generated vendor dir - the user is probably |
||
360 | // submoduling, and it's easy enough not to break their setup. |
||
361 | ivg := filepath.Join(i.VendorPath(), ".git") |
||
362 | gitInfo, err := os.Stat(ivg) |
||
363 | if err == nil { |
||
364 | msg.Info("Preserving existing vendor/.git") |
||
365 | vpg := filepath.Join(vp, ".git") |
||
366 | err = os.Rename(ivg, vpg) |
||
367 | |||
368 | if terr, ok := err.(*os.LinkError); ok { |
||
369 | if gitInfo.IsDir() { |
||
370 | err = fixcle(ivg, vpg, terr) |
||
371 | } else { |
||
372 | // When this is a submodule, .git is just a file. Don't try to copy |
||
373 | // it as a directory in this case (see #828). |
||
374 | err = gpath.CopyFile(ivg, vpg) |
||
375 | } |
||
376 | |||
377 | if err != nil { |
||
378 | msg.Warn("Failed to preserve existing vendor/.git") |
||
379 | } |
||
380 | } |
||
381 | } |
||
382 | |||
383 | err = gpath.CustomRemoveAll(i.VendorPath()) |
||
384 | if err != nil { |
||
385 | return err |
||
386 | } |
||
387 | |||
388 | err = gpath.CustomRename(vp, i.VendorPath()) |
||
389 | if terr, ok := err.(*os.LinkError); ok { |
||
390 | return fixcle(vp, i.VendorPath(), terr) |
||
391 | } |
||
392 | |||
393 | return err |
||
394 | |||
395 | } |
||
396 | |||
397 | // fixcle is a helper function that tries to recover from cross-device rename |
||
398 | // errors by falling back to copying. |
||
399 | func fixcle(from, to string, terr *os.LinkError) error { |
||
400 | // When there are different physical devices we cannot rename cross device. |
||
401 | // Instead we copy. |
||
402 | |||
403 | // syscall.EXDEV is the common name for the cross device link error |
||
404 | // which has varying output text across different operating systems. |
||
405 | if terr.Err == syscall.EXDEV { |
||
406 | msg.Debug("Cross link err, trying manual copy: %s", terr) |
||
407 | return gpath.CopyDir(from, to) |
||
408 | } else if runtime.GOOS == "windows" { |
||
409 | // In windows it can drop down to an operating system call that |
||
410 | // returns an operating system error with a different number and |
||
411 | // message. Checking for that as a fall back. |
||
412 | noerr, ok := terr.Err.(syscall.Errno) |
||
413 | // 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error. |
||
414 | // See https://msdn.microsoft.com/en-us/library/cc231199.aspx |
||
415 | if ok && noerr == 0x11 { |
||
416 | msg.Debug("Cross link err on Windows, trying manual copy: %s", terr) |
||
417 | return gpath.CopyDir(from, to) |
||
418 | } |
||
419 | } |
||
420 | |||
421 | return terr |
||
422 | } |
||
423 | |||
424 | // List resolves the complete dependency tree and returns a list of dependencies. |
||
425 | func (i *Installer) List(conf *cfg.Config) []*cfg.Dependency { |
||
426 | base := "." |
||
427 | |||
428 | ic := newImportCache() |
||
429 | |||
430 | v := &VersionHandler{ |
||
431 | Use: ic, |
||
432 | Imported: make(map[string]bool), |
||
433 | Conflicts: make(map[string]bool), |
||
434 | Config: conf, |
||
435 | } |
||
436 | |||
437 | // Update imports |
||
438 | res, err := dependency.NewResolver(base) |
||
439 | if err != nil { |
||
440 | msg.Die("Failed to create a resolver: %s", err) |
||
441 | } |
||
442 | res.Config = conf |
||
443 | res.VersionHandler = v |
||
444 | res.ResolveAllFiles = i.ResolveAllFiles |
||
445 | |||
446 | msg.Info("Resolving imports") |
||
447 | _, _, err = res.ResolveLocal(false) |
||
448 | if err != nil { |
||
449 | msg.Die("Failed to resolve local packages: %s", err) |
||
450 | } |
||
451 | |||
452 | _, err = allPackages(conf.Imports, res, false) |
||
453 | if err != nil { |
||
454 | msg.Die("Failed to retrieve a list of dependencies: %s", err) |
||
455 | } |
||
456 | |||
457 | if len(conf.DevImports) > 0 { |
||
458 | msg.Warn("dev imports not resolved.") |
||
459 | } |
||
460 | |||
461 | return conf.Imports |
||
462 | } |
||
463 | |||
464 | // LazyConcurrentUpdate updates only deps that are not already checkout out at the right version. |
||
465 | // |
||
466 | // This is only safe when updating from a lock file. |
||
467 | func LazyConcurrentUpdate(deps []*cfg.Dependency, i *Installer, c *cfg.Config) error { |
||
468 | |||
469 | newDeps := []*cfg.Dependency{} |
||
470 | for _, dep := range deps { |
||
471 | |||
472 | key, err := cache.Key(dep.Remote()) |
||
473 | if err != nil { |
||
474 | newDeps = append(newDeps, dep) |
||
475 | continue |
||
476 | } |
||
477 | destPath := filepath.Join(cache.Location(), "src", key) |
||
478 | |||
479 | // Get a VCS object for this directory |
||
480 | repo, err := dep.GetRepo(destPath) |
||
481 | if err != nil { |
||
482 | newDeps = append(newDeps, dep) |
||
483 | continue |
||
484 | } |
||
485 | |||
486 | ver, err := repo.Version() |
||
487 | if err != nil { |
||
488 | newDeps = append(newDeps, dep) |
||
489 | continue |
||
490 | } |
||
491 | if dep.Reference != "" { |
||
492 | ci, err := repo.CommitInfo(dep.Reference) |
||
493 | if err == nil && ci.Commit == dep.Reference { |
||
494 | msg.Info("--> Found desired version locally %s %s!", dep.Name, dep.Reference) |
||
495 | continue |
||
496 | } |
||
497 | } |
||
498 | |||
499 | msg.Debug("--> Queue %s for update (%s != %s).", dep.Name, ver, dep.Reference) |
||
500 | newDeps = append(newDeps, dep) |
||
501 | } |
||
502 | if len(newDeps) > 0 { |
||
503 | return ConcurrentUpdate(newDeps, i, c) |
||
504 | } |
||
505 | |||
506 | return nil |
||
507 | } |
||
508 | |||
509 | // ConcurrentUpdate takes a list of dependencies and updates in parallel. |
||
510 | func ConcurrentUpdate(deps []*cfg.Dependency, i *Installer, c *cfg.Config) error { |
||
511 | done := make(chan struct{}, concurrentWorkers) |
||
512 | in := make(chan *cfg.Dependency, concurrentWorkers) |
||
513 | var wg sync.WaitGroup |
||
514 | var lock sync.Mutex |
||
515 | var returnErr error |
||
516 | |||
517 | for ii := 0; ii < concurrentWorkers; ii++ { |
||
518 | go func(ch <-chan *cfg.Dependency) { |
||
519 | for { |
||
520 | select { |
||
521 | case dep := <-ch: |
||
522 | loc := dep.Remote() |
||
523 | key, err := cache.Key(loc) |
||
524 | if err != nil { |
||
525 | msg.Die(err.Error()) |
||
526 | } |
||
527 | cache.Lock(key) |
||
528 | if err := VcsUpdate(dep, i.Force, i.Updated); err != nil { |
||
529 | msg.Err("Update failed for %s: %s\n", dep.Name, err) |
||
530 | // Capture the error while making sure the concurrent |
||
531 | // operations don't step on each other. |
||
532 | lock.Lock() |
||
533 | if returnErr == nil { |
||
534 | returnErr = err |
||
535 | } else { |
||
536 | returnErr = cli.NewMultiError(returnErr, err) |
||
537 | } |
||
538 | lock.Unlock() |
||
539 | } |
||
540 | cache.Unlock(key) |
||
541 | wg.Done() |
||
542 | case <-done: |
||
543 | return |
||
544 | } |
||
545 | } |
||
546 | }(in) |
||
547 | } |
||
548 | |||
549 | for _, dep := range deps { |
||
550 | if !c.HasIgnore(dep.Name) { |
||
551 | wg.Add(1) |
||
552 | in <- dep |
||
553 | } |
||
554 | } |
||
555 | |||
556 | wg.Wait() |
||
557 | |||
558 | // Close goroutines setting the version |
||
559 | for ii := 0; ii < concurrentWorkers; ii++ { |
||
560 | done <- struct{}{} |
||
561 | } |
||
562 | |||
563 | return returnErr |
||
564 | } |
||
565 | |||
566 | // allPackages gets a list of all packages required to satisfy the given deps. |
||
567 | func allPackages(deps []*cfg.Dependency, res *dependency.Resolver, addTest bool) ([]string, error) { |
||
568 | if len(deps) == 0 { |
||
569 | return []string{}, nil |
||
570 | } |
||
571 | |||
572 | vdir, err := gpath.Vendor() |
||
573 | if err != nil { |
||
574 | return []string{}, err |
||
575 | } |
||
576 | vdir += string(os.PathSeparator) |
||
577 | ll, err := res.ResolveAll(deps, addTest) |
||
578 | if err != nil { |
||
579 | return []string{}, err |
||
580 | } |
||
581 | |||
582 | for i := 0; i < len(ll); i++ { |
||
583 | ll[i] = strings.TrimPrefix(ll[i], vdir) |
||
584 | } |
||
585 | return ll, nil |
||
586 | } |
||
587 | |||
588 | // MissingPackageHandler is a dependency.MissingPackageHandler. |
||
589 | // |
||
590 | // When a package is not found, this attempts to resolve and fetch. |
||
591 | // |
||
592 | // When a package is found on the GOPATH, this notifies the user. |
||
593 | type MissingPackageHandler struct { |
||
594 | home string |
||
595 | force bool |
||
596 | Config *cfg.Config |
||
597 | Use *importCache |
||
598 | updated *UpdateTracker |
||
599 | } |
||
600 | |||
601 | // NotFound attempts to retrieve a package when not found in the local cache |
||
602 | // folder. It will attempt to get it from the remote location info. |
||
603 | func (m *MissingPackageHandler) NotFound(pkg string, addTest bool) (bool, error) { |
||
604 | err := m.fetchToCache(pkg, addTest) |
||
605 | if err != nil { |
||
606 | return false, err |
||
607 | } |
||
608 | |||
609 | return true, err |
||
610 | } |
||
611 | |||
612 | // OnGopath will either copy a package, already found in the GOPATH, to the |
||
613 | // vendor/ directory or download it from the internet. This is dependent if |
||
614 | // useGopath on the installer is set to true to copy from the GOPATH. |
||
615 | func (m *MissingPackageHandler) OnGopath(pkg string, addTest bool) (bool, error) { |
||
616 | |||
617 | err := m.fetchToCache(pkg, addTest) |
||
618 | if err != nil { |
||
619 | return false, err |
||
620 | } |
||
621 | |||
622 | return true, err |
||
623 | } |
||
624 | |||
625 | // InVendor updates a package in the vendor/ directory to make sure the latest |
||
626 | // is available. |
||
627 | func (m *MissingPackageHandler) InVendor(pkg string, addTest bool) error { |
||
628 | return m.fetchToCache(pkg, addTest) |
||
629 | } |
||
630 | |||
631 | // PkgPath resolves the location on the filesystem where the package should be. |
||
632 | // This handles making sure to use the cache location. |
||
633 | func (m *MissingPackageHandler) PkgPath(pkg string) string { |
||
634 | root, sub := util.NormalizeName(pkg) |
||
635 | |||
636 | // For the parent applications source skip the cache. |
||
637 | if root == m.Config.Name { |
||
638 | pth := gpath.Basepath() |
||
639 | return filepath.Join(pth, filepath.FromSlash(sub)) |
||
640 | } |
||
641 | |||
642 | d := m.Config.Imports.Get(root) |
||
643 | if d == nil { |
||
644 | d = m.Config.DevImports.Get(root) |
||
645 | } |
||
646 | |||
647 | if d == nil { |
||
648 | d, _ = m.Use.Get(root) |
||
649 | |||
650 | if d == nil { |
||
651 | d = &cfg.Dependency{Name: root} |
||
652 | } |
||
653 | } |
||
654 | |||
655 | key, err := cache.Key(d.Remote()) |
||
656 | if err != nil { |
||
657 | msg.Die("Error generating cache key for %s", d.Name) |
||
658 | } |
||
659 | |||
660 | return filepath.Join(cache.Location(), "src", key, filepath.FromSlash(sub)) |
||
661 | } |
||
662 | |||
663 | func (m *MissingPackageHandler) fetchToCache(pkg string, addTest bool) error { |
||
664 | root := util.GetRootFromPackage(pkg) |
||
665 | // Skip any references to the root package. |
||
666 | if root == m.Config.Name { |
||
667 | return nil |
||
668 | } |
||
669 | |||
670 | d := m.Config.Imports.Get(root) |
||
671 | if d == nil && addTest { |
||
672 | d = m.Config.DevImports.Get(root) |
||
673 | } |
||
674 | |||
675 | // If the dependency is nil it means the Config doesn't yet know about it. |
||
676 | if d == nil { |
||
677 | d, _ = m.Use.Get(root) |
||
678 | // We don't know about this dependency so we create a basic instance. |
||
679 | if d == nil { |
||
680 | d = &cfg.Dependency{Name: root} |
||
681 | } |
||
682 | |||
683 | if addTest { |
||
684 | m.Config.DevImports = append(m.Config.DevImports, d) |
||
685 | } else { |
||
686 | m.Config.Imports = append(m.Config.Imports, d) |
||
687 | } |
||
688 | } |
||
689 | |||
690 | return VcsUpdate(d, m.force, m.updated) |
||
691 | } |
||
692 | |||
693 | // VersionHandler handles setting the proper version in the VCS. |
||
694 | type VersionHandler struct { |
||
695 | |||
696 | // If Try to use the version here if we have one. This is a cache and will |
||
697 | // change over the course of setting versions. |
||
698 | Use *importCache |
||
699 | |||
700 | // Cache if importing scan has already occurred here. |
||
701 | Imported map[string]bool |
||
702 | |||
703 | Config *cfg.Config |
||
704 | |||
705 | // There's a problem where many sub-packages have been asked to set a version |
||
706 | // and you can end up with numerous conflict messages that are exactly the |
||
707 | // same. We are keeping track to only display them once. |
||
708 | // the parent pac |
||
709 | Conflicts map[string]bool |
||
710 | } |
||
711 | |||
712 | // Process imports dependencies for a package |
||
713 | func (d *VersionHandler) Process(pkg string) (e error) { |
||
714 | root := util.GetRootFromPackage(pkg) |
||
715 | |||
716 | // Skip any references to the root package. |
||
717 | if root == d.Config.Name { |
||
718 | return nil |
||
719 | } |
||
720 | |||
721 | // We have not tried to import, yet. |
||
722 | // Should we look in places other than the root of the project? |
||
723 | if d.Imported[root] == false { |
||
724 | d.Imported[root] = true |
||
725 | p := d.pkgPath(root) |
||
726 | f, deps, err := importer.Import(p) |
||
727 | if f && err == nil { |
||
728 | for _, dep := range deps { |
||
729 | |||
730 | // The fist one wins. Would something smater than this be better? |
||
731 | exists, _ := d.Use.Get(dep.Name) |
||
732 | if exists == nil && (dep.Reference != "" || dep.Repository != "") { |
||
733 | d.Use.Add(dep.Name, dep, root) |
||
734 | } |
||
735 | } |
||
736 | } else if err != nil { |
||
737 | msg.Err("Unable to import from %s. Err: %s", root, err) |
||
738 | e = err |
||
739 | } |
||
740 | } |
||
741 | |||
742 | return |
||
743 | } |
||
744 | |||
745 | // SetVersion sets the version for a package. If that package version is already |
||
746 | // set it handles the case by: |
||
747 | // - keeping the already set version |
||
748 | // - proviting messaging about the version conflict |
||
749 | // TODO(mattfarina): The way version setting happens can be improved. Currently not optimal. |
||
750 | func (d *VersionHandler) SetVersion(pkg string, addTest bool) (e error) { |
||
751 | root := util.GetRootFromPackage(pkg) |
||
752 | |||
753 | // Skip any references to the root package. |
||
754 | if root == d.Config.Name { |
||
755 | return nil |
||
756 | } |
||
757 | |||
758 | v := d.Config.Imports.Get(root) |
||
759 | if addTest { |
||
760 | if v == nil { |
||
761 | v = d.Config.DevImports.Get(root) |
||
762 | } else if d.Config.DevImports.Has(root) { |
||
763 | // Both imports and test imports lists the same dependency. |
||
764 | // There are import chains (because the import tree is resolved |
||
765 | // before the test tree) that can cause this. |
||
766 | tempD := d.Config.DevImports.Get(root) |
||
767 | if tempD.Reference != v.Reference { |
||
768 | msg.Warn("Using import %s (version %s) for test instead of testImport (version %s).", v.Name, v.Reference, tempD.Reference) |
||
769 | } |
||
770 | // TODO(mattfarina): Note repo difference in a warning. |
||
771 | } |
||
772 | } |
||
773 | |||
774 | dep, req := d.Use.Get(root) |
||
775 | if dep != nil && v != nil { |
||
776 | if v.Reference == "" && dep.Reference != "" { |
||
777 | v.Reference = dep.Reference |
||
778 | // Clear the pin, if set, so the new version can be used. |
||
779 | v.Pin = "" |
||
780 | dep = v |
||
781 | } else if v.Reference != "" && dep.Reference != "" && v.Reference != dep.Reference { |
||
782 | dest := d.pkgPath(pkg) |
||
783 | dep = determineDependency(v, dep, dest, req) |
||
784 | } else { |
||
785 | dep = v |
||
786 | } |
||
787 | |||
788 | } else if v != nil { |
||
789 | dep = v |
||
790 | } else if dep != nil { |
||
791 | // We've got an imported dependency to use and don't already have a |
||
792 | // record of it. Append it to the Imports. |
||
793 | if addTest { |
||
794 | d.Config.DevImports = append(d.Config.DevImports, dep) |
||
795 | } else { |
||
796 | d.Config.Imports = append(d.Config.Imports, dep) |
||
797 | } |
||
798 | } else { |
||
799 | // If we've gotten here we don't have any depenency objects. |
||
800 | r, sp := util.NormalizeName(pkg) |
||
801 | dep = &cfg.Dependency{ |
||
802 | Name: r, |
||
803 | } |
||
804 | if sp != "" { |
||
805 | dep.Subpackages = []string{sp} |
||
806 | } |
||
807 | if addTest { |
||
808 | d.Config.DevImports = append(d.Config.DevImports, dep) |
||
809 | } else { |
||
810 | d.Config.Imports = append(d.Config.Imports, dep) |
||
811 | } |
||
812 | } |
||
813 | |||
814 | err := VcsVersion(dep) |
||
815 | if err != nil { |
||
816 | msg.Warn("Unable to set version on %s to %s. Err: %s", root, dep.Reference, err) |
||
817 | e = err |
||
818 | } |
||
819 | |||
820 | return |
||
821 | } |
||
822 | |||
823 | func (d *VersionHandler) pkgPath(pkg string) string { |
||
824 | root, sub := util.NormalizeName(pkg) |
||
825 | |||
826 | // For the parent applications source skip the cache. |
||
827 | if root == d.Config.Name { |
||
828 | pth := gpath.Basepath() |
||
829 | return filepath.Join(pth, filepath.FromSlash(sub)) |
||
830 | } |
||
831 | |||
832 | dep := d.Config.Imports.Get(root) |
||
833 | if dep == nil { |
||
834 | dep = d.Config.DevImports.Get(root) |
||
835 | } |
||
836 | |||
837 | if dep == nil { |
||
838 | dep, _ = d.Use.Get(root) |
||
839 | |||
840 | if dep == nil { |
||
841 | dep = &cfg.Dependency{Name: root} |
||
842 | } |
||
843 | } |
||
844 | |||
845 | key, err := cache.Key(dep.Remote()) |
||
846 | if err != nil { |
||
847 | msg.Die("Error generating cache key for %s", dep.Name) |
||
848 | } |
||
849 | |||
850 | return filepath.Join(cache.Location(), "src", key, filepath.FromSlash(sub)) |
||
851 | } |
||
852 | |||
853 | func determineDependency(v, dep *cfg.Dependency, dest, req string) *cfg.Dependency { |
||
854 | repo, err := v.GetRepo(dest) |
||
855 | if err != nil { |
||
856 | singleWarn("Unable to access repo for %s\n", v.Name) |
||
857 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
858 | return v |
||
859 | } |
||
860 | |||
861 | vIsRef := repo.IsReference(v.Reference) |
||
862 | depIsRef := repo.IsReference(dep.Reference) |
||
863 | |||
864 | // Both are references and they are different ones. |
||
865 | if vIsRef && depIsRef { |
||
866 | singleWarn("Conflict: %s rev is currently %s, but %s wants %s\n", v.Name, v.Reference, req, dep.Reference) |
||
867 | |||
868 | displayCommitInfo(repo, v) |
||
869 | displayCommitInfo(repo, dep) |
||
870 | |||
871 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
872 | return v |
||
873 | } else if vIsRef { |
||
874 | // The current one is a reference and the suggestion is a SemVer constraint. |
||
875 | con, err := semver.NewConstraint(dep.Reference) |
||
876 | if err != nil { |
||
877 | singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", dep.Name, dep.Reference) |
||
878 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
879 | return v |
||
880 | } |
||
881 | |||
882 | ver, err := semver.NewVersion(v.Reference) |
||
883 | if err != nil { |
||
884 | // The existing version is not a semantic version. |
||
885 | singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference) |
||
886 | displayCommitInfo(repo, v) |
||
887 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
888 | return v |
||
889 | } |
||
890 | |||
891 | if con.Check(ver) { |
||
892 | singleInfo("Keeping %s %s because it fits constraint '%s'", v.Name, v.Reference, dep.Reference) |
||
893 | return v |
||
894 | } |
||
895 | singleWarn("Conflict: %s version is %s but does not meet constraint '%s'\n", v.Name, v.Reference, dep.Reference) |
||
896 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
897 | return v |
||
898 | } else if depIsRef { |
||
899 | |||
900 | con, err := semver.NewConstraint(v.Reference) |
||
901 | if err != nil { |
||
902 | singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", v.Name, v.Reference) |
||
903 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
904 | return v |
||
905 | } |
||
906 | |||
907 | ver, err := semver.NewVersion(dep.Reference) |
||
908 | if err != nil { |
||
909 | singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference) |
||
910 | displayCommitInfo(repo, dep) |
||
911 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
912 | return v |
||
913 | } |
||
914 | |||
915 | if con.Check(ver) { |
||
916 | v.Reference = dep.Reference |
||
917 | singleInfo("Using %s %s because it fits constraint '%s'", v.Name, v.Reference, v.Reference) |
||
918 | return v |
||
919 | } |
||
920 | singleWarn("Conflict: %s semantic version constraint is %s but '%s' does not meet the constraint\n", v.Name, v.Reference, v.Reference) |
||
921 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
922 | return v |
||
923 | } |
||
924 | // Neither is a vcs reference and both could be semantic version |
||
925 | // constraints that are different. |
||
926 | |||
927 | _, err = semver.NewConstraint(dep.Reference) |
||
928 | if err != nil { |
||
929 | // dd.Reference is not a reference or a valid constraint. |
||
930 | singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", dep.Name, dep.Reference) |
||
931 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
932 | return v |
||
933 | } |
||
934 | |||
935 | _, err = semver.NewConstraint(v.Reference) |
||
936 | if err != nil { |
||
937 | // existing.Reference is not a reference or a valid constraint. |
||
938 | // We really should never end up here. |
||
939 | singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", v.Name, v.Reference) |
||
940 | |||
941 | v.Reference = dep.Reference |
||
942 | v.Pin = "" |
||
943 | singleInfo("Using %s %s because it is a valid version", v.Name, v.Reference) |
||
944 | return v |
||
945 | } |
||
946 | |||
947 | // Both versions are constraints. Try to merge them. |
||
948 | // If either comparison has an || skip merging. That's complicated. |
||
949 | ddor := strings.Index(dep.Reference, "||") |
||
950 | eor := strings.Index(v.Reference, "||") |
||
951 | if ddor == -1 && eor == -1 { |
||
952 | // Add the comparisons together. |
||
953 | newRef := v.Reference + ", " + dep.Reference |
||
954 | v.Reference = newRef |
||
955 | v.Pin = "" |
||
956 | singleInfo("Combining %s semantic version constraints %s and %s", v.Name, v.Reference, dep.Reference) |
||
957 | return v |
||
958 | } |
||
959 | singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference) |
||
960 | singleInfo("Keeping %s %s", v.Name, v.Reference) |
||
961 | return v |
||
962 | } |
||
963 | |||
964 | var warningMessage = make(map[string]bool) |
||
965 | var infoMessage = make(map[string]bool) |
||
966 | |||
967 | func singleWarn(ft string, v ...interface{}) { |
||
968 | m := fmt.Sprintf(ft, v...) |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
969 | _, f := warningMessage[m] |
||
970 | if !f { |
||
971 | msg.Warn(m) |
||
972 | warningMessage[m] = true |
||
973 | } |
||
974 | } |
||
975 | |||
976 | func singleInfo(ft string, v ...interface{}) { |
||
977 | m := fmt.Sprintf(ft, v...) |
||
0 ignored issues
–
show
|
|||
978 | _, f := infoMessage[m] |
||
979 | if !f { |
||
980 | msg.Info(m) |
||
981 | infoMessage[m] = true |
||
982 | } |
||
983 | } |
||
984 | |||
985 | type importCache struct { |
||
986 | cache map[string]*cfg.Dependency |
||
987 | from map[string]string |
||
988 | } |
||
989 | |||
990 | func newImportCache() *importCache { |
||
991 | return &importCache{ |
||
992 | cache: make(map[string]*cfg.Dependency), |
||
993 | from: make(map[string]string), |
||
994 | } |
||
995 | } |
||
996 | |||
997 | func (i *importCache) Get(name string) (*cfg.Dependency, string) { |
||
998 | d, f := i.cache[name] |
||
999 | if f { |
||
1000 | return d, i.from[name] |
||
1001 | } |
||
1002 | |||
1003 | return nil, "" |
||
1004 | } |
||
1005 | |||
1006 | func (i *importCache) Add(name string, dep *cfg.Dependency, root string) { |
||
1007 | i.cache[name] = dep |
||
1008 | i.from[name] = root |
||
1009 | } |
||
1010 | |||
1011 | var displayCommitInfoPrefix = msg.Default.Color(msg.Green, "[INFO] ") |
||
1012 | var displayCommitInfoTemplate = "%s reference %s:\n" + |
||
1013 | displayCommitInfoPrefix + "- author: %s\n" + |
||
1014 | displayCommitInfoPrefix + "- commit date: %s\n" + |
||
1015 | displayCommitInfoPrefix + "- subject (first line): %s\n" |
||
1016 | |||
1017 | func displayCommitInfo(repo vcs.Repo, dep *cfg.Dependency) { |
||
1018 | c, err := repo.CommitInfo(dep.Reference) |
||
1019 | ref := dep.Reference |
||
1020 | |||
1021 | if err == nil { |
||
1022 | tgs, err2 := repo.TagsFromCommit(c.Commit) |
||
1023 | if err2 == nil && len(tgs) > 0 { |
||
1024 | if tgs[0] != dep.Reference { |
||
1025 | ref = ref + " (" + tgs[0] + ")" |
||
1026 | } |
||
1027 | } |
||
1028 | singleInfo(displayCommitInfoTemplate, dep.Name, ref, c.Author, c.Date.Format(time.RFC1123Z), commitSubjectFirstLine(c.Message)) |
||
1029 | } |
||
1030 | } |
||
1031 | |||
1032 | func commitSubjectFirstLine(sub string) string { |
||
1033 | lines := strings.Split(sub, "\n") |
||
1034 | if len(lines) <= 1 { |
||
1035 | return sub |
||
1036 | } |
||
1037 | |||
1038 | return lines[0] |
||
1039 | } |
||
1040 |