|  | package action | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "io/ioutil" | 
|  | "log" | 
|  | "os" | 
|  | "path/filepath" | 
|  |  | 
|  | "github.com/Masterminds/glide/cfg" | 
|  | "github.com/Masterminds/glide/dependency" | 
|  | "github.com/Masterminds/glide/msg" | 
|  | gpath "github.com/Masterminds/glide/path" | 
|  | "github.com/Masterminds/glide/repo" | 
|  | "github.com/sdboyer/gps" | 
|  | ) | 
|  |  | 
|  | // Install installs a vendor directory based on an existing Glide configuration. | 
|  | func Install(installer *repo.Installer, io, so, sv bool) { | 
|  | base := "." | 
|  | // Ensure GOPATH | 
|  | EnsureGopath() | 
|  | EnsureVendorDir() | 
|  | conf := EnsureConfig() | 
|  |  | 
|  | // TODO might need a better way for discovering the root | 
|  | vend, err := gpath.Vendor() | 
|  | if err != nil { | 
|  | msg.Die("Could not find the vendor dir: %s", err) | 
|  | } | 
|  |  | 
|  | // Create the SourceManager for this run | 
|  | sm, err := gps.NewSourceManager(dependency.Analyzer{}, filepath.Join(installer.Home, "cache")) | 
|  | defer sm.Release() | 
|  | if err != nil { | 
|  | msg.Err(err.Error()) | 
|  | return | 
|  | } | 
|  |  | 
|  | rd := filepath.Dir(vend) | 
|  | rt, err := gps.ListPackages(rd, conf.Name) | 
|  | if err != nil { | 
|  | msg.Die("Error while scanning project: %s", err) | 
|  | } | 
|  |  | 
|  | params := gps.SolveParameters{ | 
|  | RootDir:         rd, | 
|  | RootPackageTree: rt, | 
|  | Manifest:        conf, | 
|  | Trace:           true, | 
|  | TraceLogger:     log.New(os.Stdout, "", 0), | 
|  | } | 
|  |  | 
|  | var s gps.Solver | 
|  | if gpath.HasLock(base) { | 
|  | var legacy bool | 
|  | params.Lock, legacy, err = loadLockfile(base, conf) | 
|  | if legacy { | 
|  | msg.Warn("glide.lock was in a legacy format. An attempt will be made to automatically update it.") | 
|  | } | 
|  | if err != nil { | 
|  | msg.Err("Could not load lockfile.") | 
|  | return | 
|  | } | 
|  |  | 
|  | s, err = gps.Prepare(params, sm) | 
|  | if err != nil { | 
|  | msg.Err("Could not set up solver: %s", err) | 
|  | return | 
|  | } | 
|  | digest := s.HashInputs() | 
|  |  | 
|  | // Check if digests match, and warn if they don't | 
|  | if bytes.Equal(digest, params.Lock.InputHash()) { | 
|  | if so { | 
|  | msg.Err("glide.yaml is out of sync with glide.lock") | 
|  | return | 
|  | } else { | 
|  | msg.Warn("glide.yaml is out of sync with glide.lock!") | 
|  | } | 
|  | } | 
|  |  | 
|  | gw := safeGroupWriter{ | 
|  | resultLock:  params.Lock, | 
|  | vendor:      vend, | 
|  | sm:          sm, | 
|  | stripVendor: sv, | 
|  | } | 
|  |  | 
|  | err = gw.writeAllSafe() | 
|  | if err != nil { | 
|  | msg.Err(err.Error()) | 
|  | return | 
|  | } | 
|  | } else if io || so { | 
|  | msg.Err("No glide.lock file could be found.") | 
|  | return | 
|  | } else { | 
|  | // There is no lock, so we have to solve first | 
|  | s, err = gps.Prepare(params, sm) | 
|  | if err != nil { | 
|  | msg.Err("Could not set up solver: %s", err) | 
|  | return | 
|  | } | 
|  |  | 
|  | r, err := s.Solve() | 
|  | if err != nil { | 
|  | // TODO better error handling | 
|  | msg.Err(err.Error()) | 
|  | return | 
|  | } | 
|  |  | 
|  | gw := safeGroupWriter{ | 
|  | resultLock:  r, | 
|  | vendor:      vend, | 
|  | sm:          sm, | 
|  | stripVendor: sv, | 
|  | } | 
|  |  | 
|  | err = gw.writeAllSafe() | 
|  | if err != nil { | 
|  | msg.Err(err.Error()) | 
|  | return | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // locksAreEquivalent compares the fingerprints between two locks to determine | 
|  | // if they're equivalent. | 
|  | // | 
|  | // If the either of the locks are nil, the input hashes are different, the | 
|  | // fingerprints are different, or any error is returned from fingerprinting, | 
|  | // this function returns false. | 
|  | func locksAreEquivalent(l1, l2 *cfg.Lockfile) bool { | 
|  | if l1 != nil && l2 != nil { | 
|  | if l1.Hash != l2.Hash { | 
|  | return false | 
|  | } | 
|  |  | 
|  | f1, err := l1.Fingerprint() | 
|  | f2, err2 := l2.Fingerprint() | 
|  | if err == nil && err2 == nil && f1 == f2 { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | // safeGroupWriter provides a slipshod-but-better-than-nothing approach to | 
|  | // grouping together yaml, lock, and vendor dir writes. | 
|  | type safeGroupWriter struct { | 
|  | conf              *cfg.Config | 
|  | lock              *cfg.Lockfile | 
|  | resultLock        gps.Lock | 
|  | sm                gps.SourceManager | 
|  | glidefile, vendor string | 
|  | stripVendor       bool | 
|  | } | 
|  |  | 
|  | // writeAllSafe writes out some combination of config yaml, lock, and a vendor | 
|  | // tree, to a temp dir, then moves them into place if and only if all the write | 
|  | // operations succeeded. It also does its best to roll back if any moves fail. | 
|  | // | 
|  | // This helps to ensure glide doesn't exit with a partial write, resulting in an | 
|  | // undefined disk state. | 
|  | // | 
|  | // - If a gw.conf is provided, it will be written to gw.glidefile | 
|  | // - If gw.lock is provided without a gw.resultLock, it will be written to | 
|  | //   `glide.lock` in the parent dir of gw.vendor | 
|  | // - If gw.lock and gw.resultLock are both provided and are not equivalent, | 
|  | //   the resultLock will be written to the same location as above, and a vendor | 
|  | //   tree will be written to gw.vendor | 
|  | // - If gw.resultLock is provided and gw.lock is not, it will write both a lock | 
|  | //   and vendor dir in the same way | 
|  | // | 
|  | // Any of the conf, lock, or result can be omitted; the grouped write operation | 
|  | // will continue for whichever inputs are present. | 
|  | func (gw safeGroupWriter) writeAllSafe() error { | 
|  | // Decide which writes we need to do | 
|  | var writeConf, writeLock, writeVendor bool | 
|  |  | 
|  | if gw.conf != nil { | 
|  | writeConf = true | 
|  | } | 
|  |  | 
|  | if gw.resultLock != nil { | 
|  | if gw.lock == nil { | 
|  | writeLock, writeVendor = true, true | 
|  | } else { | 
|  | rlf, err := cfg.LockfileFromSolverLock(gw.resultLock) | 
|  | // This err really shouldn't occur, but could if we get an unpaired | 
|  | // version back from gps somehow | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | if !locksAreEquivalent(rlf, gw.lock) { | 
|  | writeLock, writeVendor = true, true | 
|  | } | 
|  | } | 
|  | } else if gw.lock != nil { | 
|  | writeLock = true | 
|  | } | 
|  |  | 
|  | if !writeConf && !writeLock && !writeVendor { | 
|  | // nothing to do | 
|  | return nil | 
|  | } | 
|  |  | 
|  | if writeConf && gw.glidefile == "" { | 
|  | return fmt.Errorf("Must provide a path if writing out a config yaml.") | 
|  | } | 
|  |  | 
|  | if (writeLock || writeVendor) && gw.vendor == "" { | 
|  | return fmt.Errorf("Must provide a vendor dir if writing out a lock or vendor dir.") | 
|  | } | 
|  |  | 
|  | if writeVendor && gw.sm == nil { | 
|  | return fmt.Errorf("Must provide a SourceManager if writing out a vendor dir.") | 
|  | } | 
|  |  | 
|  | td, err := ioutil.TempDir(os.TempDir(), "glide") | 
|  | if err != nil { | 
|  | return fmt.Errorf("Error while creating temp dir for vendor directory: %s", err) | 
|  | } | 
|  | defer os.RemoveAll(td) | 
|  |  | 
|  | if writeConf { | 
|  | if err := gw.conf.WriteFile(filepath.Join(td, "glide.yaml")); err != nil { | 
|  | return fmt.Errorf("Failed to write glide YAML file: %s", err) | 
|  | } | 
|  | } | 
|  |  | 
|  | if writeLock { | 
|  | if gw.resultLock == nil { | 
|  | // the result lock is nil but the flag is on, so we must be writing | 
|  | // the other one | 
|  | if err := gw.lock.WriteFile(filepath.Join(td, gpath.LockFile)); err != nil { | 
|  | return fmt.Errorf("Failed to write glide lock file: %s", err) | 
|  | } | 
|  | } else { | 
|  | rlf, err := cfg.LockfileFromSolverLock(gw.resultLock) | 
|  | // As with above, this case really shouldn't get hit unless there's | 
|  | // a bug in gps, or guarantees change | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | if err := rlf.WriteFile(filepath.Join(td, gpath.LockFile)); err != nil { | 
|  | return fmt.Errorf("Failed to write glide lock file: %s", err) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if writeVendor { | 
|  | err = gps.WriteDepTree(filepath.Join(td, "vendor"), gw.resultLock, gw.sm, gw.stripVendor) | 
|  | if err != nil { | 
|  | return fmt.Errorf("Error while generating vendor tree: %s", err) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Move the existing files and dirs to the temp dir while we put the new | 
|  | // ones in, to provide insurance against errors for as long as possible | 
|  | var fail bool | 
|  | var failerr error | 
|  | type pathpair struct { | 
|  | from, to string | 
|  | } | 
|  | var restore []pathpair | 
|  |  | 
|  | if writeConf { | 
|  | if _, err := os.Stat(gw.glidefile); err == nil { | 
|  | // move out the old one | 
|  | tmploc := filepath.Join(td, "glide.yaml-old") | 
|  | failerr = os.Rename(gw.glidefile, tmploc) | 
|  | if failerr != nil { | 
|  | fail = true | 
|  | } else { | 
|  | restore = append(restore, pathpair{from: tmploc, to: gw.glidefile}) | 
|  | } | 
|  | } | 
|  |  | 
|  | // move in the new one | 
|  | failerr = os.Rename(filepath.Join(td, "glide.yaml"), gw.glidefile) | 
|  | if failerr != nil { | 
|  | fail = true | 
|  | } | 
|  | } | 
|  |  | 
|  | if !fail && writeLock { | 
|  | tgt := filepath.Join(filepath.Dir(gw.vendor), gpath.LockFile) | 
|  | if _, err := os.Stat(tgt); err == nil { | 
|  | // move out the old one | 
|  | tmploc := filepath.Join(td, "glide.lock-old") | 
|  |  | 
|  | failerr = os.Rename(tgt, tmploc) | 
|  | if failerr != nil { | 
|  | fail = true | 
|  | } else { | 
|  | restore = append(restore, pathpair{from: tmploc, to: tgt}) | 
|  | } | 
|  | } | 
|  |  | 
|  | // move in the new one | 
|  | failerr = os.Rename(filepath.Join(td, gpath.LockFile), tgt) | 
|  | if failerr != nil { | 
|  | fail = true | 
|  | } | 
|  | } | 
|  |  | 
|  | // have to declare out here so it's present later | 
|  | var vendorbak string | 
|  | if !fail && writeVendor { | 
|  | if _, err := os.Stat(gw.vendor); err == nil { | 
|  | // move out the old vendor dir. just do it into an adjacent dir, in | 
|  | // order to mitigate the possibility of a pointless cross-filesystem move | 
|  | vendorbak = gw.vendor + "-old" | 
|  | if _, err := os.Stat(vendorbak); err == nil { | 
|  | // Just in case that happens to exist... | 
|  | vendorbak = filepath.Join(td, "vendor-old") | 
|  | } | 
|  | failerr = os.Rename(gw.vendor, vendorbak) | 
|  | if failerr != nil { | 
|  | fail = true | 
|  | } else { | 
|  | restore = append(restore, pathpair{from: vendorbak, to: gw.vendor}) | 
|  | } | 
|  | } | 
|  |  | 
|  | // move in the new one | 
|  | failerr = os.Rename(filepath.Join(td, "vendor"), gw.vendor) | 
|  | if failerr != nil { | 
|  | fail = true | 
|  | } | 
|  | } | 
|  |  | 
|  | // If we failed at any point, move all the things back into place, then bail | 
|  | if fail { | 
|  | for _, pair := range restore { | 
|  | // Nothing we can do on err here, we're already in recovery mode | 
|  | os.Rename(pair.from, pair.to) | 
|  | } | 
|  | return failerr | 
|  | } | 
|  |  | 
|  | // Renames all went smoothly. The deferred os.RemoveAll will get the temp | 
|  | // dir, but if we wrote vendor, we have to clean that up directly | 
|  |  | 
|  | if writeVendor { | 
|  | // Again, kinda nothing we can do about an error at this point | 
|  | os.RemoveAll(vendorbak) | 
|  | } | 
|  |  | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // loadLockfile loads the contents of a glide.lock file. | 
|  | func loadLockfile(base string, conf *cfg.Config) (*cfg.Lockfile, bool, error) { | 
|  | yml, err := ioutil.ReadFile(filepath.Join(base, gpath.LockFile)) | 
|  | if err != nil { | 
|  | return nil, false, err | 
|  | } | 
|  | lock, legacy, err := cfg.LockfileFromYaml(yml) | 
|  | if err != nil { | 
|  | return nil, false, err | 
|  | } | 
|  |  | 
|  | return lock, legacy, nil | 
|  | } |