diff --git a/CHANGELOG.md b/CHANGELOG.md index 94e05b47fe..545665f893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # (next version) +# v0.5.0 + NEW FEATURES: * Add CI tests against go1.10. Drop support for go1.8. ([#1620](https://github.com/golang/dep/pull/1620)). @@ -7,6 +9,8 @@ NEW FEATURES: * List out of date projects in dep status ([#1553](https://github.com/golang/dep/pull/1553)). * Enabled opt-in persistent caching via `DEPCACHEAGE` env var. ([#1711](https://github.com/golang/dep/pull/1711)). * Allow `DEPPROJECTROOT` [environment variable](https://golang.github.io/dep/docs/env-vars.html#depprojectroot) to supersede GOPATH deduction and explicitly set the current project's [root](https://golang.github.io/dep/docs/glossary.html#project-root) ([#1883](https://github.com/golang/dep/pull/1883)). +* `dep ensure` now explains what changes to the code or Gopkg.toml have induced solving ([#1912](https://github.com/golang/dep/pull/1912)). +* Hash digests of vendor contents are now stored in `Gopkg.lock`, and the contents of vendor are only rewritten on change or hash mismatch ([#1912](https://github.com/golang/dep/pull/1912)). BUG FIXES: @@ -17,6 +21,7 @@ IMPROVEMENTS: * Add template operations support in dep status template output ([#1549](https://github.com/golang/dep/pull/1549)). * Reduce network access by trusting local source information and only pulling from upstream when necessary ([#1250](https://github.com/golang/dep/pull/1250)). * Update our dependency on Masterminds/semver to follow upstream again now that [Masterminds/semver#67](https://github.com/Masterminds/semver/pull/67) is merged([#1792](https://github.com/golang/dep/pull/1792)). +* `inputs-digest` was removed from `Gopkg.lock` ([#1912](https://github.com/golang/dep/pull/1912)). * Don't exclude `Godeps` folder ([#1822](https://github.com/golang/dep/issues/1822)). WIP: diff --git a/Gopkg.lock b/Gopkg.lock index a65c3106ee..a1aa86afc9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,91 +3,132 @@ [[projects]] branch = "2.x" + digest = "1:ee2887fecb4d923fa90f8dd9cf33e876bf9260fed62f2ca5a5c3f41b4eb07683" name = "github.com/Masterminds/semver" packages = ["."] + pruneopts = "NUT" revision = "24642bd0573145a5ee04f9be773641695289be46" [[projects]] + digest = "1:442020d26d1f891d5014cae4353b6ff589562c2b303504627de3660adf3fb217" name = "github.com/Masterminds/vcs" packages = ["."] + pruneopts = "NUT" revision = "3084677c2c188840777bff30054f2b553729d329" version = "v1.11.1" [[projects]] branch = "master" + digest = "1:60861e762bdbe39c4c7bf292c291329b731c9925388fd41125888f5c1c595feb" name = "github.com/armon/go-radix" packages = ["."] + pruneopts = "NUT" revision = "4239b77079c7b5d1243b7b4736304ce8ddb6f0f2" [[projects]] + digest = "1:a12d94258c5298ead75e142e8001224bf029f302fed9e96cd39c0eaf90f3954d" name = "github.com/boltdb/bolt" packages = ["."] + pruneopts = "NUT" revision = "2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8" version = "v1.3.1" [[projects]] + digest = "1:9f35c1344b56e5868d511d231f215edd0650aa572664f856444affdd256e43e4" name = "github.com/golang/protobuf" packages = ["proto"] + pruneopts = "NUT" revision = "925541529c1fa6821df4e44ce2723319eb2be768" version = "v1.0.0" [[projects]] + digest = "1:f5169729244becc423886eae4d72547e28ac3f13f861bed8a9d749bc7238a1c3" name = "github.com/jmank88/nuts" packages = ["."] + pruneopts = "NUT" revision = "8b28145dffc87104e66d074f62ea8080edfad7c8" version = "v0.3.0" [[projects]] branch = "master" + digest = "1:01af3a6abe28784782680e1f75ef8767cfc5d4b230dc156ff7eb8db395cbbfd2" name = "github.com/nightlyone/lockfile" packages = ["."] + pruneopts = "NUT" revision = "e83dc5e7bba095e8d32fb2124714bf41f2a30cb5" [[projects]] + digest = "1:13b8f1a2ce177961dc9231606a52f709fab896c565f3988f60a7f6b4e543a902" name = "github.com/pelletier/go-toml" packages = ["."] + pruneopts = "NUT" revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" version = "v1.1.0" [[projects]] + digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "NUT" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] branch = "master" + digest = "1:abb4b60c28323cde32c193ce6083bb600fac462d1780cf83461b4c23ed5ce904" name = "github.com/sdboyer/constext" packages = ["."] + pruneopts = "NUT" revision = "836a144573533ea4da4e6929c235fd348aed1c80" [[projects]] branch = "master" + digest = "1:6ad2104db8f34b8656382ef0a7297b9a5cc42e7bdce95d968e02b92fc97470d1" name = "golang.org/x/net" packages = ["context"] + pruneopts = "NUT" revision = "66aacef3dd8a676686c7ae3716979581e8b03c47" [[projects]] branch = "master" + digest = "1:39ebcc2b11457b703ae9ee2e8cca0f68df21969c6102cb3b705f76cca0ea0239" name = "golang.org/x/sync" packages = ["errgroup"] + pruneopts = "NUT" revision = "f52d1811a62927559de87708c8913c1650ce4f26" [[projects]] branch = "master" + digest = "1:51912e607c5e28a89fdc7e41d3377b92086ab7f76ded236765dbf98d0a704c5d" name = "golang.org/x/sys" packages = ["unix"] + pruneopts = "NUT" revision = "bb24a47a89eac6c1227fbcb2ae37a8b9ed323366" [[projects]] branch = "v2" + digest = "1:13e704c08924325be00f96e47e7efe0bfddf0913cdfc237423c83f9b183ff590" name = "gopkg.in/yaml.v2" packages = ["."] + pruneopts = "NUT" revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "460ad7112866da4b9a0a626aa3e2fe80699c17bf871afb73b93f836418fb9298" + input-imports = [ + "github.com/Masterminds/semver", + "github.com/Masterminds/vcs", + "github.com/armon/go-radix", + "github.com/boltdb/bolt", + "github.com/golang/protobuf/proto", + "github.com/jmank88/nuts", + "github.com/nightlyone/lockfile", + "github.com/pelletier/go-toml", + "github.com/pkg/errors", + "github.com/sdboyer/constext", + "golang.org/x/sync/errgroup", + "gopkg.in/yaml.v2" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/ensure.go b/cmd/dep/ensure.go index 675a3b6791..573fb6cd26 100644 --- a/cmd/dep/ensure.go +++ b/cmd/dep/ensure.go @@ -5,7 +5,6 @@ package main import ( - "bytes" "context" "flag" "fmt" @@ -21,6 +20,7 @@ import ( "github.com/golang/dep/gps" "github.com/golang/dep/gps/paths" "github.com/golang/dep/gps/pkgtree" + "github.com/golang/dep/gps/verify" "github.com/pkg/errors" ) @@ -33,9 +33,9 @@ Project spec: Ensure gets a project into a complete, reproducible, and likely compilable state: - * All non-stdlib imports are fulfilled + * All imports are fulfilled * All rules in Gopkg.toml are respected - * Gopkg.lock records precise versions for all dependencies + * Gopkg.lock records immutable versions for all dependencies * vendor/ is populated according to Gopkg.lock Ensure has fast techniques to determine that some of these steps may be @@ -184,11 +184,6 @@ func (cmd *ensureCommand) Run(ctx *dep.Ctx, args []string) error { return cmd.runVendorOnly(ctx, args, p, sm, params) } - params.RootPackageTree, err = p.ParseRootPackageTree() - if err != nil { - return err - } - if fatal, err := checkErrors(params.RootPackageTree.Packages, p.Manifest.IgnoredPackages()); err != nil { if fatal { return err @@ -211,6 +206,10 @@ func (cmd *ensureCommand) Run(ctx *dep.Ctx, args []string) error { ctx.Err.Printf("on these projects, if they happen to be transitive dependencies.\n\n") } + // Kick off vendor verification in the background. All of the remaining + // paths from here will need it, whether or not they end up solving. + go p.VerifyVendor() + if cmd.add { return cmd.runAdd(ctx, args, p, sm, params) } else if cmd.update { @@ -256,66 +255,67 @@ func (cmd *ensureCommand) runDefault(ctx *dep.Ctx, args []string, p *dep.Project return err } - solver, err := gps.Prepare(params, sm) - if err != nil { - return errors.Wrap(err, "prepare solver") - } - - if p.Lock != nil && bytes.Equal(p.Lock.InputsDigest(), solver.HashInputs()) { - // Memo matches, so there's probably nothing to do. - if ctx.Verbose { - ctx.Out.Printf("%s was already in sync with imports and %s\n", dep.LockName, dep.ManifestName) - } - - if cmd.noVendor { + var solve bool + lock := p.ChangedLock + if lock != nil { + lsat := verify.LockSatisfiesInputs(p.Lock, p.Manifest, params.RootPackageTree) + if !lsat.Satisfied() { + if ctx.Verbose { + ctx.Out.Println("Gopkg.lock is out of sync with Gopkg.toml and project code:") + for _, missing := range lsat.MissingImports { + ctx.Out.Printf("\t%s is missing from input-imports\n", missing) + } + for _, excess := range lsat.ExcessImports { + ctx.Out.Printf("\t%s is in input-imports, but isn't imported\n", excess) + } + for pr, unmatched := range lsat.UnmetOverrides { + ctx.Out.Printf("\t%s is at %s, which is not allowed by override %s\n", pr, unmatched.V, unmatched.C) + } + for pr, unmatched := range lsat.UnmetConstraints { + ctx.Out.Printf("\t%s is at %s, which is not allowed by constraint %s\n", pr, unmatched.V, unmatched.C) + } + ctx.Out.Println() + } + solve = true + } else if cmd.noVendor { // The user said not to touch vendor/, so definitely nothing to do. return nil } + } else { + solve = true + } - // TODO(sdboyer) The desired behavior at this point is to determine - // whether it's necessary to write out vendor, or if it's already - // consistent with the lock. However, we haven't yet determined what - // that "verification" is supposed to look like (#121); in the meantime, - // we unconditionally write out vendor/ so that `dep ensure`'s behavior - // is maximally compatible with what it will eventually become. - sw, err := dep.NewSafeWriter(nil, p.Lock, p.Lock, dep.VendorAlways, p.Manifest.PruneOptions) + if solve { + solver, err := gps.Prepare(params, sm) if err != nil { - return err + return errors.Wrap(err, "prepare solver") } - if cmd.dryRun { - return sw.PrintPreparedActions(ctx.Out, ctx.Verbose) - } - - var logger *log.Logger - if ctx.Verbose { - logger = ctx.Err + solution, err := solver.Solve(context.TODO()) + if err != nil { + return handleAllTheFailuresOfTheWorld(err) } - return errors.WithMessage(sw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor") + lock = dep.LockFromSolution(solution, p.Manifest.PruneOptions) } - if cmd.noVendor && cmd.dryRun { - return errors.New("Gopkg.lock was not up to date") - } - - solution, err := solver.Solve(context.TODO()) + status, err := p.VerifyVendor() if err != nil { - return handleAllTheFailuresOfTheWorld(err) + return errors.Wrap(err, "error while verifying vendor directory") } - - sw, err := dep.NewSafeWriter(nil, p.Lock, dep.LockFromSolution(solution), cmd.vendorBehavior(), p.Manifest.PruneOptions) + dw, err := dep.NewDeltaWriter(p.Lock, lock, status, p.Manifest.PruneOptions, filepath.Join(p.AbsRoot, "vendor"), cmd.vendorBehavior()) if err != nil { return err } + if cmd.dryRun { - return sw.PrintPreparedActions(ctx.Out, ctx.Verbose) + return dw.PrintPreparedActions(ctx.Out, ctx.Verbose) } var logger *log.Logger if ctx.Verbose { logger = ctx.Err } - return errors.Wrap(sw.Write(p.AbsRoot, sm, false, logger), "grouped write of manifest, lock and vendor") + return errors.WithMessage(dw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor") } func (cmd *ensureCommand) runVendorOnly(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { @@ -326,22 +326,24 @@ func (cmd *ensureCommand) runVendorOnly(ctx *dep.Ctx, args []string, p *dep.Proj if p.Lock == nil { return errors.Errorf("no %s exists from which to populate vendor/", dep.LockName) } + // Pass the same lock as old and new so that the writer will observe no - // difference and choose not to write it out. - sw, err := dep.NewSafeWriter(nil, p.Lock, p.Lock, dep.VendorAlways, p.Manifest.PruneOptions) + // difference, and write out only ncessary vendor/ changes. + dw, err := dep.NewSafeWriter(nil, p.Lock, p.Lock, dep.VendorAlways, p.Manifest.PruneOptions) + //dw, err := dep.NewDeltaWriter(p.Lock, p.Lock, p.Manifest.PruneOptions, filepath.Join(p.AbsRoot, "vendor"), dep.VendorAlways) if err != nil { return err } if cmd.dryRun { - return sw.PrintPreparedActions(ctx.Out, ctx.Verbose) + return dw.PrintPreparedActions(ctx.Out, ctx.Verbose) } var logger *log.Logger if ctx.Verbose { logger = ctx.Err } - return errors.WithMessage(sw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor") + return errors.WithMessage(dw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor") } func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { @@ -353,23 +355,6 @@ func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project, return err } - // We'll need to discard this prepared solver as later work changes params, - // but solver preparation is cheap and worth doing up front in order to - // perform the fastpath check of hash comparison. - solver, err := gps.Prepare(params, sm) - if err != nil { - return errors.Wrap(err, "fastpath solver prepare") - } - - // Compare the hashes. If they're not equal, bail out and ask the user to - // run a straight `dep ensure` before updating. This is handholding the - // user a bit, but the extra effort required is minimal, and it ensures the - // user is isolating variables in the event of solve problems (was it the - // "pending" changes, or the -update that caused the problem?). - if !bytes.Equal(p.Lock.InputsDigest(), solver.HashInputs()) { - ctx.Out.Printf("Warning: %s is out of sync with %s or the project's imports.", dep.LockName, dep.ManifestName) - } - // When -update is specified without args, allow every dependency to change // versions, regardless of the lock file. if len(args) == 0 { @@ -381,7 +366,7 @@ func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project, } // Re-prepare a solver now that our params are complete. - solver, err = gps.Prepare(params, sm) + solver, err := gps.Prepare(params, sm) if err != nil { return errors.Wrap(err, "fastpath solver prepare") } @@ -393,19 +378,23 @@ func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project, return handleAllTheFailuresOfTheWorld(err) } - sw, err := dep.NewSafeWriter(nil, p.Lock, dep.LockFromSolution(solution), cmd.vendorBehavior(), p.Manifest.PruneOptions) + status, err := p.VerifyVendor() + if err != nil { + return errors.Wrap(err, "error while verifying vendor directory") + } + dw, err := dep.NewDeltaWriter(p.Lock, dep.LockFromSolution(solution, p.Manifest.PruneOptions), status, p.Manifest.PruneOptions, filepath.Join(p.AbsRoot, "vendor"), cmd.vendorBehavior()) if err != nil { return err } if cmd.dryRun { - return sw.PrintPreparedActions(ctx.Out, ctx.Verbose) + return dw.PrintPreparedActions(ctx.Out, ctx.Verbose) } var logger *log.Logger if ctx.Verbose { logger = ctx.Err } - return errors.Wrap(sw.Write(p.AbsRoot, sm, false, logger), "grouped write of manifest, lock and vendor") + return errors.Wrap(dw.Write(p.AbsRoot, sm, false, logger), "grouped write of manifest, lock and vendor") } func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { @@ -417,54 +406,29 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm return err } - // We'll need to discard this prepared solver as later work changes params, - // but solver preparation is cheap and worth doing up front in order to - // perform the fastpath check of hash comparison. - solver, err := gps.Prepare(params, sm) - if err != nil { - return errors.Wrap(err, "fastpath solver prepare") - } - - // Compare the hashes. If they're not equal, bail out and ask the user to - // run a straight `dep ensure` before updating. This is handholding the - // user a bit, but the extra effort required is minimal, and it ensures the - // user is isolating variables in the event of solve problems (was it the - // "pending" changes, or the -add that caused the problem?). - if p.Lock != nil && !bytes.Equal(p.Lock.InputsDigest(), solver.HashInputs()) { - ctx.Out.Printf("Warning: %s is out of sync with %s or the project's imports.", dep.LockName, dep.ManifestName) - } - - rm, _ := params.RootPackageTree.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) - - // TODO(sdboyer) re-enable this once we ToReachMap() intelligently filters out normally-excluded (_*, .*), dirs from errmap - //rm, errmap := params.RootPackageTree.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) - // Having some problematic internal packages isn't cause for termination, - // but the user needs to be warned. - //for fail, err := range errmap { - //if _, is := err.Err.(*build.NoGoError); !is { - //ctx.Err.Printf("Warning: %s, %s", fail, err) - //} - //} - // Compile unique sets of 1) all external packages imported or required, and // 2) the project roots under which they fall. exmap := make(map[string]bool) - exrmap := make(map[gps.ProjectRoot]bool) - - for _, ex := range append(rm.FlattenFn(paths.IsStandardImportPath), p.Manifest.Required...) { - exmap[ex] = true - root, err := sm.DeduceProjectRoot(ex) - if err != nil { - // This should be very uncommon to hit, as it entails that we - // couldn't deduce the root for an import, but that some previous - // solve run WAS able to deduce the root. It's most likely to occur - // if the user has e.g. not connected to their organization's VPN, - // and thus cannot access an internal go-get metadata service. - return errors.Wrapf(err, "could not deduce project root for %s", ex) + if p.ChangedLock != nil { + for _, imp := range p.ChangedLock.InputImports() { + exmap[imp] = true + } + } else { + // We'll only hit this branch if Gopkg.lock did not exist. + rm, _ := p.RootPackageTree.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) + for _, imp := range rm.FlattenFn(paths.IsStandardImportPath) { + exmap[imp] = true + } + for imp := range p.Manifest.RequiredPackages() { + exmap[imp] = true } - exrmap[root] = true } + //exrmap, err := p.GetDirectDependencyNames(sm) + //if err != nil { + //return err + //} + // Note: these flags are only partially used by the latter parts of the // algorithm; rather, it relies on inference. However, they remain in their // entirety as future needs may make further use of them, being a handy, @@ -642,7 +606,7 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm } // Re-prepare a solver now that our params are complete. - solver, err = gps.Prepare(params, sm) + solver, err := gps.Prepare(params, sm) if err != nil { return errors.Wrap(err, "fastpath solver prepare") } @@ -692,20 +656,24 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm } sort.Strings(reqlist) - sw, err := dep.NewSafeWriter(nil, p.Lock, dep.LockFromSolution(solution), dep.VendorOnChanged, p.Manifest.PruneOptions) + status, err := p.VerifyVendor() + if err != nil { + return errors.Wrap(err, "error while verifying vendor directory") + } + dw, err := dep.NewDeltaWriter(p.Lock, dep.LockFromSolution(solution, p.Manifest.PruneOptions), status, p.Manifest.PruneOptions, filepath.Join(p.AbsRoot, "vendor"), cmd.vendorBehavior()) if err != nil { return err } if cmd.dryRun { - return sw.PrintPreparedActions(ctx.Out, ctx.Verbose) + return dw.PrintPreparedActions(ctx.Out, ctx.Verbose) } var logger *log.Logger if ctx.Verbose { logger = ctx.Err } - if err := errors.Wrap(sw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor"); err != nil { + if err := errors.Wrap(dw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor"); err != nil { return err } diff --git a/cmd/dep/hash_in.go b/cmd/dep/hash_in.go deleted file mode 100644 index ed7f356934..0000000000 --- a/cmd/dep/hash_in.go +++ /dev/null @@ -1,51 +0,0 @@ -// 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 main - -import ( - "flag" - - "github.com/golang/dep" - "github.com/golang/dep/gps" - "github.com/golang/dep/gps/pkgtree" - "github.com/pkg/errors" -) - -func (cmd *hashinCommand) Name() string { return "hash-inputs" } -func (cmd *hashinCommand) Args() string { return "" } -func (cmd *hashinCommand) ShortHelp() string { return "" } -func (cmd *hashinCommand) LongHelp() string { return "" } -func (cmd *hashinCommand) Hidden() bool { return true } - -func (cmd *hashinCommand) Register(fs *flag.FlagSet) {} - -type hashinCommand struct{} - -func (hashinCommand) Run(ctx *dep.Ctx, args []string) error { - p, err := ctx.LoadProject() - if err != nil { - return err - } - - sm, err := ctx.SourceManager() - if err != nil { - return err - } - sm.UseDefaultSignalHandling() - defer sm.Release() - - params := p.MakeParams() - params.RootPackageTree, err = pkgtree.ListPackages(p.ResolvedAbsRoot, string(p.ImportRoot)) - if err != nil { - return errors.Wrap(err, "gps.ListPackages") - } - - s, err := gps.Prepare(params, sm) - if err != nil { - return errors.Wrap(err, "prepare solver") - } - ctx.Out.Println(gps.HashingInputsAsString(s)) - return nil -} diff --git a/cmd/dep/init.go b/cmd/dep/init.go index dc3f38d9ff..c50d82cbf0 100644 --- a/cmd/dep/init.go +++ b/cmd/dep/init.go @@ -102,12 +102,12 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error { ctx.Out.Println("Getting direct dependencies...") } - ptree, directDeps, err := p.GetDirectDependencyNames(sm) + directDeps, err := p.GetDirectDependencyNames(sm) if err != nil { return errors.Wrap(err, "init failed: unable to determine direct dependencies") } if ctx.Verbose { - ctx.Out.Printf("Checked %d directories for packages.\nFound %d direct dependencies.\n", len(ptree.Packages), len(directDeps)) + ctx.Out.Printf("Checked %d directories for packages.\nFound %d direct dependencies.\n", len(p.RootPackageTree.Packages), len(directDeps)) } // Initialize with imported data, then fill in the gaps using the GOPATH @@ -133,7 +133,7 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error { params := gps.SolveParameters{ RootDir: root, - RootPackageTree: ptree, + RootPackageTree: p.RootPackageTree, Manifest: p.Manifest, Lock: p.Lock, ProjectAnalyzer: rootAnalyzer, @@ -157,19 +157,10 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error { err = handleAllTheFailuresOfTheWorld(err) return errors.Wrap(err, "init failed: unable to solve the dependency graph") } - p.Lock = dep.LockFromSolution(soln) + p.Lock = dep.LockFromSolution(soln, p.Manifest.PruneOptions) rootAnalyzer.FinalizeRootManifestAndLock(p.Manifest, p.Lock, copyLock) - // Run gps.Prepare with appropriate constraint solutions from solve run - // to generate the final lock memo. - s, err = gps.Prepare(params, sm) - if err != nil { - return errors.Wrap(err, "init failed: unable to recalculate the lock digest") - } - - p.Lock.SolveMeta.InputsDigest = s.HashInputs() - // Pass timestamp (yyyyMMddHHmmss format) as suffix to backup name. vendorbak, err := dep.BackupVendor(filepath.Join(root, "vendor"), time.Now().Format("20060102150405")) if err != nil { diff --git a/cmd/dep/integration_test.go b/cmd/dep/integration_test.go index 1468300fb9..d000899168 100644 --- a/cmd/dep/integration_test.go +++ b/cmd/dep/integration_test.go @@ -74,34 +74,6 @@ func TestDepCachedir(t *testing.T) { initPath := filepath.Join("testdata", "cachedir") - t.Run("env-cachedir", func(t *testing.T) { - t.Parallel() - testProj := integration.NewTestProject(t, initPath, wd, runMain) - defer testProj.Cleanup() - - testProj.TempDir("cachedir") - cachedir := testProj.Path("cachedir") - testProj.Setenv("DEPCACHEDIR", cachedir) - - // Running `dep ensure` will pull in the dependency into cachedir. - err = testProj.DoRun([]string{"ensure"}) - if err != nil { - // Log the error output from running `dep ensure`, could be useful. - t.Logf("`dep ensure` error output: \n%s", testProj.GetStderr()) - t.Errorf("got an unexpected error: %s", err) - } - - // Check that the cache was created in the cachedir. Our fixture has the dependency - // `github.com/sdboyer/deptest` - _, err = os.Stat(testProj.Path("cachedir", "sources", "https---github.com-sdboyer-deptest")) - if err != nil { - if os.IsNotExist(err) { - t.Error("expected cachedir to have been populated but none was found") - } else { - t.Errorf("got an unexpected error: %s", err) - } - } - }) t.Run("env-invalid-cachedir", func(t *testing.T) { t.Parallel() testProj := integration.NewTestProject(t, initPath, wd, runMain) diff --git a/cmd/dep/main.go b/cmd/dep/main.go index 57f0a196dd..a4a2efcb42 100644 --- a/cmd/dep/main.go +++ b/cmd/dep/main.go @@ -91,7 +91,6 @@ func (c *Config) Run() int { &statusCommand{}, &ensureCommand{}, &pruneCommand{}, - &hashinCommand{}, &versionCommand{}, } diff --git a/cmd/dep/prune.go b/cmd/dep/prune.go index 30f3efb5b7..d02f7debf4 100644 --- a/cmd/dep/prune.go +++ b/cmd/dep/prune.go @@ -5,7 +5,6 @@ package main import ( - "bytes" "flag" "io/ioutil" "log" @@ -75,19 +74,10 @@ func (cmd *pruneCommand) Run(ctx *dep.Ctx, args []string) error { params.TraceLogger = ctx.Err } - s, err := gps.Prepare(params, sm) - if err != nil { - return errors.Wrap(err, "could not set up solver for input hashing") - } - if p.Lock == nil { return errors.Errorf("Gopkg.lock must exist for prune to know what files are safe to remove.") } - if !bytes.Equal(s.HashInputs(), p.Lock.SolveMeta.InputsDigest) { - return errors.Errorf("Gopkg.lock is out of sync; run dep ensure before pruning.") - } - pruneLogger := ctx.Err if !ctx.Verbose { pruneLogger = log.New(ioutil.Discard, "", 0) diff --git a/cmd/dep/root_analyzer.go b/cmd/dep/root_analyzer.go index 1cdee4dbf1..72ba9c22ab 100644 --- a/cmd/dep/root_analyzer.go +++ b/cmd/dep/root_analyzer.go @@ -167,7 +167,7 @@ func (a *rootAnalyzer) DeriveManifestAndLock(dir string, pr gps.ProjectRoot) (gp func (a *rootAnalyzer) FinalizeRootManifestAndLock(m *dep.Manifest, l *dep.Lock, ol dep.Lock) { // Iterate through the new projects in solved lock and add them to manifest // if they are direct deps and log feedback for all the new projects. - diff := gps.DiffLocks(&ol, l) + diff := fb.DiffLocks(&ol, l) bi := fb.NewBrokenImportFeedback(diff) bi.LogFeedback(a.ctx.Err) for _, y := range l.Projects() { diff --git a/cmd/dep/status.go b/cmd/dep/status.go index ed1311dd03..f87c78679e 100644 --- a/cmd/dep/status.go +++ b/cmd/dep/status.go @@ -7,7 +7,6 @@ package main import ( "bytes" "context" - "encoding/hex" "encoding/json" "flag" "fmt" @@ -24,6 +23,7 @@ import ( "github.com/golang/dep" "github.com/golang/dep/gps" "github.com/golang/dep/gps/paths" + "github.com/golang/dep/gps/verify" "github.com/pkg/errors" ) @@ -328,13 +328,13 @@ type dotOutput struct { func (out *dotOutput) BasicHeader() error { out.g = new(graphviz).New() - ptree, err := out.p.ParseRootPackageTree() + ptree := out.p.RootPackageTree // TODO(sdboyer) should be true, true, false, out.p.Manifest.IgnoredPackages() prm, _ := ptree.ToReachMap(true, false, false, nil) out.g.createNode(string(out.p.ImportRoot), "", prm.FlattenFn(paths.IsStandardImportPath)) - return err + return nil } func (out *dotOutput) BasicFooter() error { @@ -648,10 +648,7 @@ func (os OldStatus) marshalJSON() *rawOldStatus { func (cmd *statusCommand) runOld(ctx *dep.Ctx, out oldOutputter, p *dep.Project, sm gps.SourceManager) error { // While the network churns on ListVersions() requests, statically analyze // code from the current project. - ptree, err := p.ParseRootPackageTree() - if err != nil { - return err - } + ptree := p.RootPackageTree // Set up a solver in order to check the InputHash. params := gps.SolveParameters{ @@ -781,7 +778,6 @@ func newRawMetadata(metadata *dep.SolveMeta) rawDetailMetadata { return rawDetailMetadata{ AnalyzerName: metadata.AnalyzerName, AnalyzerVersion: metadata.AnalyzerVersion, - InputsDigest: hex.EncodeToString(metadata.InputsDigest), SolverName: metadata.SolverName, SolverVersion: metadata.SolverVersion, } @@ -887,10 +883,7 @@ type MissingStatus struct { func (cmd *statusCommand) runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceManager) (hasMissingPkgs bool, errCount int, err error) { // While the network churns on ListVersions() requests, statically analyze // code from the current project. - ptree, err := p.ParseRootPackageTree() - if err != nil { - return false, 0, err - } + ptree := p.RootPackageTree // Set up a solver in order to check the InputHash. params := gps.SolveParameters{ @@ -912,11 +905,6 @@ func (cmd *statusCommand) runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Proje return false, 0, err } - s, err := gps.Prepare(params, sm) - if err != nil { - return false, 0, errors.Wrapf(err, "could not set up solver for input hashing") - } - // Errors while collecting constraints should not fail the whole status run. // It should count the error and tell the user about incomplete results. cm, ccerrs := collectConstraints(ctx, p, sm) @@ -931,8 +919,13 @@ func (cmd *statusCommand) runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Proje sort.Slice(slp, func(i, j int) bool { return slp[i].Ident().Less(slp[j].Ident()) }) + slcp := p.ChangedLock.Projects() + sort.Slice(slcp, func(i, j int) bool { + return slcp[i].Ident().Less(slcp[j].Ident()) + }) - if bytes.Equal(s.HashInputs(), p.Lock.SolveMeta.InputsDigest) { + lsat := verify.LockSatisfiesInputs(p.Lock, p.Manifest, params.RootPackageTree) + if lsat.Satisfied() { // If these are equal, we're guaranteed that the lock is a transitively // complete picture of all deps. That eliminates the need for at least // some checks. @@ -1308,7 +1301,7 @@ func collectConstraints(ctx *dep.Ctx, p *dep.Project, sm gps.SourceManager) (con // Collect the complete set of direct project dependencies, incorporating // requireds and ignores appropriately. - _, directDeps, err := p.GetDirectDependencyNames(sm) + directDeps, err := p.GetDirectDependencyNames(sm) if err != nil { // Return empty collection, not nil, if we fail here. return constraintCollection, []error{errors.Wrap(err, "failed to get direct dependencies")} diff --git a/cmd/dep/testdata/harness_tests/ensure/add/all-new-double-spec/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/add/all-new-double-spec/final/Gopkg.lock index eddb25596a..cb57bfd519 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/all-new-double-spec/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/add/all-new-double-spec/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] branch = "master" + digest = "1:d08235d21a5df95ab12e1eb0191ffe9c4ceb4fa8005f079f6815e8ff507855d3" name = "github.com/sdboyer/deptesttres" packages = ["."] + pruneopts = "UT" revision = "54aaeb0023e1f3dcf5f98f31dd8c565457945a12" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "645b5b52e1bfb9e3db1cefde758485e009edfe5bad611b490582d94467f9c1b0" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptesttres" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/add/all-new-double/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/add/all-new-double/final/Gopkg.lock index 8481da4451..3cb5eedc0b 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/all-new-double/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/add/all-new-double/final/Gopkg.lock @@ -2,23 +2,31 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:d62f7f8be8f431ede67fae7f90d75f923dddc627b309b9134ea1db95f0e34e6d" name = "github.com/sdboyer/deptesttres" packages = [ ".", "subp" ] + pruneopts = "UT" revision = "54aaeb0023e1f3dcf5f98f31dd8c565457945a12" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "432bc141db9511df4e1b5754c6c4d8cf4dd8b4f8d5a13fd7d189c17c14e000b7" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptesttres", + "github.com/sdboyer/deptesttres/subp" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/add/all-new-spec/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/add/all-new-spec/final/Gopkg.lock index eddb25596a..cb57bfd519 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/all-new-spec/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/add/all-new-spec/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] branch = "master" + digest = "1:d08235d21a5df95ab12e1eb0191ffe9c4ceb4fa8005f079f6815e8ff507855d3" name = "github.com/sdboyer/deptesttres" packages = ["."] + pruneopts = "UT" revision = "54aaeb0023e1f3dcf5f98f31dd8c565457945a12" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "645b5b52e1bfb9e3db1cefde758485e009edfe5bad611b490582d94467f9c1b0" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptesttres" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/add/all-new/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/add/all-new/final/Gopkg.lock index 5531a3e128..977af25e08 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/all-new/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/add/all-new/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:d08235d21a5df95ab12e1eb0191ffe9c4ceb4fa8005f079f6815e8ff507855d3" name = "github.com/sdboyer/deptesttres" packages = ["."] + pruneopts = "UT" revision = "54aaeb0023e1f3dcf5f98f31dd8c565457945a12" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "8f0b74fd1169808bd0e31dd7ad6c601c7b8f7ef25eec9e8a45e72b8a384ebb5c" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptesttres" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/add/desync/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/add/desync/final/Gopkg.lock index 2987289328..2e150705d1 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/desync/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/add/desync/final/Gopkg.lock @@ -2,26 +2,36 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [[projects]] branch = "master" + digest = "1:0dba41ffdf62b10cbbd79009edceb0eaf635031e854fb456fdd5be154802f8d3" name = "github.com/sdboyer/deptesttres" packages = ["."] + pruneopts = "" revision = "54aaeb0023e1f3dcf5f98f31dd8c565457945a12" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "86240895e0ee5788e7e8bb56e0d77afd58009a491b69f6835e546db9e5dacfcd" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos", + "github.com/sdboyer/deptesttres" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/add/desync/stdout.txt b/cmd/dep/testdata/harness_tests/ensure/add/desync/stdout.txt index c0d7219848..a7dfa2d65e 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/desync/stdout.txt +++ b/cmd/dep/testdata/harness_tests/ensure/add/desync/stdout.txt @@ -1,2 +1 @@ -Warning: Gopkg.lock is out of sync with Gopkg.toml or the project's imports. Fetching sources... diff --git a/cmd/dep/testdata/harness_tests/ensure/add/errs/double-diff-spec/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/add/errs/double-diff-spec/final/Gopkg.lock index 66ef021c68..b34d4cfd9f 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/errs/double-diff-spec/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/add/errs/double-diff-spec/final/Gopkg.lock @@ -3,13 +3,15 @@ [[projects]] branch = "master" + digest = "1:d08235d21a5df95ab12e1eb0191ffe9c4ceb4fa8005f079f6815e8ff507855d3" name = "github.com/sdboyer/deptesttres" packages = ["."] + pruneopts = "UT" revision = "54aaeb0023e1f3dcf5f98f31dd8c565457945a12" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "342afd8c8a616d084eb7b67bf3a891710eca3ce5abc3cf60af0dae4ccfdcd001" + input-imports = ["github.com/sdboyer/deptesttres"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/add/errs/self-add/case2/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/add/errs/self-add/case2/final/Gopkg.lock index bef2d0092e..10ef811182 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/errs/self-add/case2/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/add/errs/self-add/case2/final/Gopkg.lock @@ -4,6 +4,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7" + input-imports = [] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/add/exists-imports/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/add/exists-imports/final/Gopkg.lock index 66ef021c68..39b9ac12c9 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/exists-imports/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/add/exists-imports/final/Gopkg.lock @@ -3,13 +3,15 @@ [[projects]] branch = "master" + digest = "1:0dba41ffdf62b10cbbd79009edceb0eaf635031e854fb456fdd5be154802f8d3" name = "github.com/sdboyer/deptesttres" packages = ["."] + pruneopts = "" revision = "54aaeb0023e1f3dcf5f98f31dd8c565457945a12" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "342afd8c8a616d084eb7b67bf3a891710eca3ce5abc3cf60af0dae4ccfdcd001" + input-imports = ["github.com/sdboyer/deptesttres"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/add/exists-manifest-constraint/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/add/exists-manifest-constraint/final/Gopkg.lock index e235bce702..b22de034ea 100644 --- a/cmd/dep/testdata/harness_tests/ensure/add/exists-manifest-constraint/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/add/exists-manifest-constraint/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:0dba41ffdf62b10cbbd79009edceb0eaf635031e854fb456fdd5be154802f8d3" name = "github.com/sdboyer/deptesttres" packages = ["."] + pruneopts = "" revision = "54aaeb0023e1f3dcf5f98f31dd8c565457945a12" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "d1fe1d4f4dd98b75908b524bd73d43a4b9e3ce0b9522ea6ce9d6c9ea15190c1d" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptesttres" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/default/hasheq-novendor/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/default/hasheq-novendor/final/Gopkg.lock index c7f497e7a1..188ece4f77 100644 --- a/cmd/dep/testdata/harness_tests/ensure/default/hasheq-novendor/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/default/hasheq-novendor/final/Gopkg.lock @@ -2,14 +2,16 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "14b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c" + input-imports = ["github.com/sdboyer/deptest"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/default/hasheq/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/default/hasheq/final/Gopkg.lock index c7f497e7a1..188ece4f77 100644 --- a/cmd/dep/testdata/harness_tests/ensure/default/hasheq/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/default/hasheq/final/Gopkg.lock @@ -2,14 +2,16 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "14b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c" + input-imports = ["github.com/sdboyer/deptest"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/final/Gopkg.lock deleted file mode 100644 index 11cb12c378..0000000000 --- a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/final/Gopkg.lock +++ /dev/null @@ -1,16 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - name = "github.com/sdboyer/deptest" - packages = ["."] - revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" - version = "v1.0.0" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - # manually modified hash digest, it will not match any known inputs - inputs-digest = "94b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/final/Gopkg.toml deleted file mode 100644 index e242e02114..0000000000 --- a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/final/Gopkg.toml +++ /dev/null @@ -1,4 +0,0 @@ - -[[constraint]] - name = "github.com/sdboyer/deptest" - version = "1.0.0" diff --git a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/initial/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/initial/Gopkg.lock deleted file mode 100644 index 11cb12c378..0000000000 --- a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/initial/Gopkg.lock +++ /dev/null @@ -1,16 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - name = "github.com/sdboyer/deptest" - packages = ["."] - revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" - version = "v1.0.0" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - # manually modified hash digest, it will not match any known inputs - inputs-digest = "94b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/initial/Gopkg.toml b/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/initial/Gopkg.toml deleted file mode 100644 index e242e02114..0000000000 --- a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/initial/Gopkg.toml +++ /dev/null @@ -1,4 +0,0 @@ - -[[constraint]] - name = "github.com/sdboyer/deptest" - version = "1.0.0" diff --git a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/initial/main.go b/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/initial/main.go deleted file mode 100644 index 1fe0d19d6a..0000000000 --- a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/initial/main.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2016 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 main - -import ( - _ "github.com/sdboyer/deptest" -) - -func main() { -} diff --git a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/testcase.json b/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/testcase.json deleted file mode 100644 index 2e54069437..0000000000 --- a/cmd/dep/testdata/harness_tests/ensure/default/hashneq-novendor-dry/testcase.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "commands": [ - ["ensure", "-no-vendor", "-dry-run"] - ], - "error-expected": "Gopkg.lock was not up to date" -} diff --git a/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.lock index c7f497e7a1..49180c231e 100644 --- a/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/empty/case1/final/Gopkg.lock @@ -2,14 +2,16 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "14b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c" + input-imports = ["github.com/sdboyer/deptest"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/empty/case2/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/empty/case2/final/Gopkg.lock index 02a1eabe8f..a09c4263a0 100644 --- a/cmd/dep/testdata/harness_tests/ensure/empty/case2/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/empty/case2/final/Gopkg.lock @@ -2,14 +2,16 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e7725ea56516a42a641aaaf5d48754258d9f3c59949cb8a0e8a21b1ab6e07179" + input-imports = ["github.com/sdboyer/deptest"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/empty/case3/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/empty/case3/final/Gopkg.lock index d2153e3747..c00429e5d4 100644 --- a/cmd/dep/testdata/harness_tests/ensure/empty/case3/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/empty/case3/final/Gopkg.lock @@ -3,13 +3,15 @@ [[projects]] branch = "master" + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e5c16e09ed6f0a1a2b3cf472c34b7fd50861dd070e81d5e623f72e8173f0c065" + input-imports = ["github.com/sdboyer/deptest"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.lock index bef2d0092e..10ef811182 100644 --- a/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/pkg-errors/case1/final/Gopkg.lock @@ -4,6 +4,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7" + input-imports = [] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/final/Gopkg.lock index 944e8436fa..c00429e5d4 100644 --- a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/final/Gopkg.lock @@ -3,13 +3,15 @@ [[projects]] branch = "master" + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "5210e61a67f6e64dabb1eb8f28df2dbeeedfca1588c102067a6ec8a35e0b15f9" + input-imports = ["github.com/sdboyer/deptest"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/stdout.txt b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/stdout.txt deleted file mode 100644 index 74542e41e5..0000000000 --- a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/stdout.txt +++ /dev/null @@ -1,11 +0,0 @@ --CONSTRAINTS- -github.com/sdboyer/deptest -b-master --IMPORTS/REQS- -github.com/sdboyer/deptest --IGNORES- --OVERRIDES- --ANALYZER- -dep -1 - diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/testcase.json b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/testcase.json index 5641e85616..729de9d0f4 100644 --- a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/testcase.json +++ b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-ignore/testcase.json @@ -1,7 +1,6 @@ { "commands": [ - ["ensure"], - ["hash-inputs"] + ["ensure"] ], "error-expected": "", "vendor-final": [ diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/final/Gopkg.lock index 53e42dcc48..10ef811182 100644 --- a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/final/Gopkg.lock @@ -4,6 +4,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "b02b7a80e20404724ba5dbffab28e772017b03800916327f58bff0da86071b6a" + input-imports = [] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/stdout.txt b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/stdout.txt deleted file mode 100644 index a273de0e56..0000000000 --- a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/stdout.txt +++ /dev/null @@ -1,9 +0,0 @@ --CONSTRAINTS- --IMPORTS/REQS- --IGNORES- -github.com/sdboyer/deptest* --OVERRIDES- --ANALYZER- -dep -1 - diff --git a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/testcase.json b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/testcase.json index 4f16d1c611..7c94832d8a 100644 --- a/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/testcase.json +++ b/cmd/dep/testdata/harness_tests/ensure/pkg-ignored/wildcard-other-root/testcase.json @@ -1,7 +1,6 @@ { "commands": [ - ["ensure"], - ["hash-inputs"] + ["ensure"] ], "error-expected": "", "vendor-final": [] diff --git a/cmd/dep/testdata/harness_tests/ensure/update/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/update/case1/final/Gopkg.lock index df995b1e94..e060be7657 100644 --- a/cmd/dep/testdata/harness_tests/ensure/update/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/update/case1/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1b381263a360eafafe3ef7f9be626672668d17250a3c9a8debd169d1b5e2eebb" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/update/desync/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/update/desync/final/Gopkg.lock index df995b1e94..e060be7657 100644 --- a/cmd/dep/testdata/harness_tests/ensure/update/desync/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/update/desync/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1b381263a360eafafe3ef7f9be626672668d17250a3c9a8debd169d1b5e2eebb" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/ensure/update/desync/stdout.txt b/cmd/dep/testdata/harness_tests/ensure/update/desync/stdout.txt index 9adb1974eb..e69de29bb2 100644 --- a/cmd/dep/testdata/harness_tests/ensure/update/desync/stdout.txt +++ b/cmd/dep/testdata/harness_tests/ensure/update/desync/stdout.txt @@ -1 +0,0 @@ -Warning: Gopkg.lock is out of sync with Gopkg.toml or the project's imports. diff --git a/cmd/dep/testdata/harness_tests/ensure/update/novendor/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/ensure/update/novendor/final/Gopkg.lock index c7f497e7a1..188ece4f77 100644 --- a/cmd/dep/testdata/harness_tests/ensure/update/novendor/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/ensure/update/novendor/final/Gopkg.lock @@ -2,14 +2,16 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "14b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c" + input-imports = ["github.com/sdboyer/deptest"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.lock index 15b4e08bac..bbaecf622f 100644 --- a/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/case1/final/Gopkg.lock @@ -2,19 +2,26 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v0.8.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "a0196baa11ea047dd65037287451d36b861b00ea" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1b381263a360eafafe3ef7f9be626672668d17250a3c9a8debd169d1b5e2eebb" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.lock index 608d5a8d97..a5fdd6b108 100644 --- a/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/case2/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v0.8.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "ced51326ad990b11098d8076d0f7d72d89eee1ba6e8dacc7bc73be05cddac438" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.lock index c4f18284da..6f9ea67f7e 100644 --- a/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/case3/final/Gopkg.lock @@ -3,18 +3,25 @@ [[projects]] branch = "master" + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "a0196baa11ea047dd65037287451d36b861b00ea" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "af9a783a5430dabcaaf44683c09e2b729e1c0d61f13bfdf6677c4fd0b41387ca" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/case4/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/case4/final/Gopkg.lock index e076e162c8..75fc1a6682 100644 --- a/cmd/dep/testdata/harness_tests/init/case4/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/case4/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a6ba2237d28d125b55fc6c86e94e33363f1dfd880d471118d36d7587398c30b4" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/glide/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/glide/case1/final/Gopkg.lock index 7455467c42..852bb14531 100644 --- a/cmd/dep/testdata/harness_tests/init/glide/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/glide/case1/final/Gopkg.lock @@ -2,25 +2,34 @@ [[projects]] + digest = "1:4f2c2c251356e56fdbe13960044263cdbde63355689e21db07267c4d0de33f3f" name = "github.com/carolynvs/deptest-subpkg" packages = ["subby"] + pruneopts = "UT" revision = "6c41d90f78bb1015696a2ad591debfa8971512d5" [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "def34af0f7cd619e1601eb68bdabf399c9b36a79c2081306adefa0ced03d182b" + input-imports = [ + "github.com/carolynvs/deptest-subpkg/subby", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/glide/case2/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/glide/case2/final/Gopkg.lock index ac445c05d2..09b9e1e14b 100644 --- a/cmd/dep/testdata/harness_tests/init/glide/case2/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/glide/case2/final/Gopkg.lock @@ -2,20 +2,24 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1ed417a0bec57ffe988fae1cba8f3d49994fb893394d61844e0b3c96d69573fe" + input-imports = ["github.com/sdboyer/deptestdos"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/glide/case3/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/glide/case3/final/Gopkg.lock index 9c2e59f243..a7bf0c2ab3 100644 --- a/cmd/dep/testdata/harness_tests/init/glide/case3/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/glide/case3/final/Gopkg.lock @@ -2,20 +2,24 @@ [[projects]] + digest = "1:41a463620bcc5eba54d225d6108f58da4be08bc6307ecc9d17c6d1a5c1f2df30" name = "github.com/carolynvs/deptestglide" packages = ["."] + pruneopts = "UT" revision = "aa7fea6e17ca281c6f210afb93fc3c98ef29a695" version = "v0.1.1" [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "87996f836c70eac9c14221085412cbc96eb98cc6a0782c0724575a56511abe8d" + input-imports = ["github.com/carolynvs/deptestglide"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/glide/case4/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/glide/case4/final/Gopkg.lock index 1aadf7f962..f52bc428f9 100644 --- a/cmd/dep/testdata/harness_tests/init/glide/case4/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/glide/case4/final/Gopkg.lock @@ -2,20 +2,24 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1ed417a0bec57ffe988fae1cba8f3d49994fb893394d61844e0b3c96d69573fe" + input-imports = ["github.com/sdboyer/deptestdos"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/glide/corrupt-glide/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/glide/corrupt-glide/final/Gopkg.lock index da4412685f..45f3b2de0f 100644 --- a/cmd/dep/testdata/harness_tests/init/glide/corrupt-glide/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/glide/corrupt-glide/final/Gopkg.lock @@ -2,26 +2,32 @@ [[projects]] + digest = "1:c0ee004f748a2e0a166f94d0aae3e4b34d0cb1aa95672075969feded052cde73" name = "github.com/ChinmayR/deptestglideA" packages = ["."] + pruneopts = "UT" revision = "cead75b1cde64ae1353ddbf73f6089303d6001b4" version = "v0.3.0" [[projects]] + digest = "1:855fce2344c810402e7e6d34a1e7e21f6b5e161689d0c3c086f920a212e3b074" name = "github.com/ChinmayR/deptestglideB" packages = ["."] + pruneopts = "UT" revision = "571b81795d767461736e6d0ca69e5f9840bdbf0e" version = "v0.5.0" [[projects]] + digest = "1:2cb412b34b26e26b270605d2c54e94a01b5f018ca060a87543bb3b72e21dca07" name = "github.com/ChinmayR/deptestglideC" packages = ["."] + pruneopts = "UT" revision = "4d3546304e8a1ceb6bb01e7e6201e852abb8ae4d" version = "v0.1.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "86bfffc8c6e5de1a4f6c613dcd88d5b76d8b5b17bf797320eb6842bf9239837d" + input-imports = ["github.com/ChinmayR/deptestglideA"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/glide/direct-trans-no-conflict/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/glide/direct-trans-no-conflict/final/Gopkg.lock index acbf835f22..aa049360e8 100644 --- a/cmd/dep/testdata/harness_tests/init/glide/direct-trans-no-conflict/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/glide/direct-trans-no-conflict/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:2bb2f3f169ad31382b7b41969518a99fe8974f4f5a737b6c30501a36f2fd40dc" name = "github.com/ChinmayR/deptestglideA" packages = ["."] + pruneopts = "UT" revision = "26ab0f16d85723be5ff44e5b4bd2a8e0f3a34989" version = "v0.2.0" [[projects]] + digest = "1:d35fc62a5ecad295b86623f47a2b3d6ce4e81cd9584c04b41d05c9cafea9137e" name = "github.com/ChinmayR/deptestglideB" packages = ["."] + pruneopts = "UT" revision = "143bb0e8f4cc3a3227a2d250f99d08ee879c7909" version = "v0.2.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "9f6691009992b85820af581ac5f81b1537fd791351a83ec852c7a553939dbae5" + input-imports = [ + "github.com/ChinmayR/deptestglideA", + "github.com/ChinmayR/deptestglideB" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/glide/trans-trans-unspecified/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/glide/trans-trans-unspecified/final/Gopkg.lock index 2cea226574..3d8decf695 100644 --- a/cmd/dep/testdata/harness_tests/init/glide/trans-trans-unspecified/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/glide/trans-trans-unspecified/final/Gopkg.lock @@ -2,26 +2,35 @@ [[projects]] + digest = "1:f3ebbb24c30241998a9b891d83113b4edd70b7d710fac33a4a20cb7b135f2677" name = "github.com/ChinmayR/deptestglideA" packages = ["."] + pruneopts = "UT" revision = "703f28fdee407d70dcc4cb774a0fbb82fa70daa9" version = "v0.4.0" [[projects]] + digest = "1:1c78f2479f39bf0b209d0ec082acfb2816ad3c79813ac49a57ce8997a6039b29" name = "github.com/ChinmayR/deptestglideB" packages = ["."] + pruneopts = "UT" revision = "55b6737d9d84461196123a51baa02b156abc4543" version = "v0.4.0" [[projects]] + digest = "1:2cb412b34b26e26b270605d2c54e94a01b5f018ca060a87543bb3b72e21dca07" name = "github.com/ChinmayR/deptestglideC" packages = ["."] + pruneopts = "UT" revision = "4d3546304e8a1ceb6bb01e7e6201e852abb8ae4d" version = "v0.1.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "178a09819c33edcef8eb9c4ed26cc9053aed45e4f04645085feaef7921c8688c" + input-imports = [ + "github.com/ChinmayR/deptestglideA", + "github.com/ChinmayR/deptestglideB" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/glide/trans-trans/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/glide/trans-trans/final/Gopkg.lock index 1184b2c41b..868fafff4c 100644 --- a/cmd/dep/testdata/harness_tests/init/glide/trans-trans/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/glide/trans-trans/final/Gopkg.lock @@ -2,26 +2,35 @@ [[projects]] + digest = "1:698cd4951cb265ae57d473cc883630bd2d5cc9a472fe513acd54886751cb0457" name = "github.com/ChinmayR/deptestglideA" packages = ["."] + pruneopts = "UT" revision = "2f77d68963bb3dff94b88330d930cb59714cd2fc" version = "v0.5.0" [[projects]] + digest = "1:0ed6d2f0ec01022dbca6d19f6a89a4200a9430c51f07309446c3751591fc3c39" name = "github.com/ChinmayR/deptestglideB" packages = ["."] + pruneopts = "UT" revision = "7f8abdec9e29a008d40cfcbb0848b82cc4000d25" version = "v0.3.0" [[projects]] + digest = "1:4f14135d41f9b3692c6ac4e9defe4ea020ddeb41a169ba26fd1abdd193e097cd" name = "github.com/ChinmayR/deptestglideC" packages = ["."] + pruneopts = "UT" revision = "73ba3c1897d21e64bec0b89a026a1acb6604e846" version = "v0.2.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e4ba06c77ad87955efd40ad92c8ddc0cd6dba1137fecdc958f02cc79b1f64202" + input-imports = [ + "github.com/ChinmayR/deptestglideA", + "github.com/ChinmayR/deptestglideB" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/glock/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/glock/case1/final/Gopkg.lock index ac445c05d2..09b9e1e14b 100644 --- a/cmd/dep/testdata/harness_tests/init/glock/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/glock/case1/final/Gopkg.lock @@ -2,20 +2,24 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1ed417a0bec57ffe988fae1cba8f3d49994fb893394d61844e0b3c96d69573fe" + input-imports = ["github.com/sdboyer/deptestdos"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/godep/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/godep/case1/final/Gopkg.lock index ac445c05d2..09b9e1e14b 100644 --- a/cmd/dep/testdata/harness_tests/init/godep/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/godep/case1/final/Gopkg.lock @@ -2,20 +2,24 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1ed417a0bec57ffe988fae1cba8f3d49994fb893394d61844e0b3c96d69573fe" + input-imports = ["github.com/sdboyer/deptestdos"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.lock index ac445c05d2..09b9e1e14b 100644 --- a/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/govend/case1/final/Gopkg.lock @@ -2,20 +2,24 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1ed417a0bec57ffe988fae1cba8f3d49994fb893394d61844e0b3c96d69573fe" + input-imports = ["github.com/sdboyer/deptestdos"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/govendor/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/govendor/case1/final/Gopkg.lock index 529e48897d..09b9e1e14b 100644 --- a/cmd/dep/testdata/harness_tests/init/govendor/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/govendor/case1/final/Gopkg.lock @@ -2,20 +2,24 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "9cc662f2e1b80c8df205d9d667fe2c47825a06961ceae378f44a8290d01dd359" + input-imports = ["github.com/sdboyer/deptestdos"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/gvt/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/gvt/case1/final/Gopkg.lock index 1eb5e5e887..ce29be8565 100644 --- a/cmd/dep/testdata/harness_tests/init/gvt/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/gvt/case1/final/Gopkg.lock @@ -2,27 +2,37 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" source = "https://github.com/carolynvs/deptest" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [[projects]] branch = "v2" + digest = "1:10978cfda94a2069ac38ed0884b606aafe89f4578ff700b7845b02201a2d6b51" name = "gopkg.in/yaml.v2" packages = ["."] + pruneopts = "UT" revision = "f7716cbe52baa25d2e9b0d0da546fcf909fc16b4" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "d1681978cbca0e845950451461e0d69b58c5e896d9fd10ec5c159a4db3175161" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos", + "gopkg.in/yaml.v2" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.lock index c7f497e7a1..49180c231e 100644 --- a/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/skip-hidden/final/Gopkg.lock @@ -2,14 +2,16 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "14b07b05e0f01051b03887ab2bf80b516bc5510ea92f75f76c894b1745d8850c" + input-imports = ["github.com/sdboyer/deptest"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/init/vndr/case1/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/init/vndr/case1/final/Gopkg.lock index ac445c05d2..09b9e1e14b 100644 --- a/cmd/dep/testdata/harness_tests/init/vndr/case1/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/init/vndr/case1/final/Gopkg.lock @@ -2,20 +2,24 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "UT" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "UT" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1ed417a0bec57ffe988fae1cba8f3d49994fb893394d61844e0b3c96d69573fe" + input-imports = ["github.com/sdboyer/deptestdos"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/case1/dot/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/case1/dot/final/Gopkg.lock index 77278d07bc..6dd996ff8c 100644 --- a/cmd/dep/testdata/harness_tests/status/case1/dot/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/status/case1/dot/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v0.8.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1b381263a360eafafe3ef7f9be626672668d17250a3c9a8debd169d1b5e2eebb" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/case1/json/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/case1/json/final/Gopkg.lock index 77278d07bc..6dd996ff8c 100644 --- a/cmd/dep/testdata/harness_tests/status/case1/json/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/status/case1/json/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v0.8.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1b381263a360eafafe3ef7f9be626672668d17250a3c9a8debd169d1b5e2eebb" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/case1/table/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/case1/table/final/Gopkg.lock index 77278d07bc..6dd996ff8c 100644 --- a/cmd/dep/testdata/harness_tests/status/case1/table/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/status/case1/table/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v0.8.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1b381263a360eafafe3ef7f9be626672668d17250a3c9a8debd169d1b5e2eebb" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/case1/template/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/case1/template/final/Gopkg.lock index 77278d07bc..6dd996ff8c 100644 --- a/cmd/dep/testdata/harness_tests/status/case1/template/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/status/case1/template/final/Gopkg.lock @@ -2,20 +2,27 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v0.8.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1b381263a360eafafe3ef7f9be626672668d17250a3c9a8debd169d1b5e2eebb" + input-imports = [ + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/final/Gopkg.lock deleted file mode 100644 index bef2d0092e..0000000000 --- a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/final/Gopkg.lock +++ /dev/null @@ -1,9 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/final/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/final/Gopkg.toml deleted file mode 100644 index 418ac251f8..0000000000 --- a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/final/Gopkg.toml +++ /dev/null @@ -1,2 +0,0 @@ -ignored = ["github.com/sdboyer/deptestdos"] - diff --git a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/initial/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/initial/Gopkg.lock deleted file mode 100644 index bef2d0092e..0000000000 --- a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/initial/Gopkg.lock +++ /dev/null @@ -1,9 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/initial/Gopkg.toml b/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/initial/Gopkg.toml deleted file mode 100644 index 418ac251f8..0000000000 --- a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/initial/Gopkg.toml +++ /dev/null @@ -1,2 +0,0 @@ -ignored = ["github.com/sdboyer/deptestdos"] - diff --git a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/initial/main.go b/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/initial/main.go deleted file mode 100644 index 6fa0454844..0000000000 --- a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/initial/main.go +++ /dev/null @@ -1,12 +0,0 @@ -// 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 main - -import ( - _ "github.com/sdboyer/deptestdos" -) - -func main() { -} diff --git a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/testcase.json b/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/testcase.json deleted file mode 100644 index dc1776d5f2..0000000000 --- a/cmd/dep/testdata/harness_tests/status/ignore_lock_mismatch/testcase.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "commands": [ - ["status"] - ], - "error-expected": "This happens when Gopkg.toml is modified", - "vendor-final": [] -} diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock index 7f844d35d6..27b9d7d0f6 100644 --- a/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/final/Gopkg.lock @@ -2,26 +2,36 @@ [[projects]] + digest = "1:9f15720b74cca39adad1ea61f19e1aee73ed1a83cc3922521101fc758fa75715" name = "github.com/carolynvs/go-dep-test" packages = ["."] + pruneopts = "" revision = "b9c5511fa463628e6251554db29a4be161d02aed" version = "0.1.0" [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "c89811fc98c9a1310c94dc63b84f364d13c46ea3a40bd2cba7d77377ab346543" + input-imports = [ + "github.com/carolynvs/go-dep-test", + "github.com/sdboyer/deptest", + "github.com/sdboyer/deptestdos" + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt b/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt index ad557f414b..ec1c1a398e 100644 --- a/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt +++ b/cmd/dep/testdata/harness_tests/status/old_constraints/stdout.txt @@ -1,2 +1,2 @@ PROJECT CONSTRAINT REVISION LATEST -github.com/carolynvs/go-dep-test ^0.1.0 b9c5511 4069198 +github.com/carolynvs/go-dep-test ^0.1.0 b9c5511 4069198 diff --git a/cmd/dep/testdata/harness_tests/status/override_constraint/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/override_constraint/final/Gopkg.lock index f987a57915..304b102928 100644 --- a/cmd/dep/testdata/harness_tests/status/override_constraint/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/status/override_constraint/final/Gopkg.lock @@ -2,20 +2,24 @@ [[projects]] + digest = "1:6a4b7ea94689d9d4f231605ecc0248fbcbf16419d8571adb59c00396e37bbfc2" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "3f4c3bea144e112a69bbe5d8d01c1b09a544253f" version = "v0.8.1" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "5c607206be5decd28e6263ffffdcee067266015e" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1c4444f47ab5d5c484634d1a0c95d99beb879a37337bc0d7aecbd97cf79b6cb1" + input-imports = ["github.com/sdboyer/deptestdos"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/dep/testdata/harness_tests/status/revision_constraint/final/Gopkg.lock b/cmd/dep/testdata/harness_tests/status/revision_constraint/final/Gopkg.lock index 204b990861..4e58519b10 100644 --- a/cmd/dep/testdata/harness_tests/status/revision_constraint/final/Gopkg.lock +++ b/cmd/dep/testdata/harness_tests/status/revision_constraint/final/Gopkg.lock @@ -2,19 +2,23 @@ [[projects]] + digest = "1:ddbbbe7f7a81c86d54e89fa388b532f4c144d666a14e8e483ba04fa58265b135" name = "github.com/sdboyer/deptest" packages = ["."] + pruneopts = "" revision = "ff2948a2ac8f538c4ecd55962e919d1e13e74baf" version = "v1.0.0" [[projects]] + digest = "1:d71dc37a7f6ffbbe0c768f28d904acade8f068cbd96c6e6f0885425d3c3b8df9" name = "github.com/sdboyer/deptestdos" packages = ["."] + pruneopts = "" revision = "a0196baa11ea047dd65037287451d36b861b00ea" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a64abd431f23d6fbc8d83aef311d33ab12b3a6c74a46c271e89c2542c98bbb9a" + input-imports = ["github.com/sdboyer/deptestdos"] solver-name = "gps-cdcl" solver-version = 1 diff --git a/context.go b/context.go index d7b403ab8c..d1af270dfe 100644 --- a/context.go +++ b/context.go @@ -9,9 +9,13 @@ import ( "os" "path/filepath" "runtime" + "sort" "time" "github.com/golang/dep/gps" + "github.com/golang/dep/gps/paths" + "github.com/golang/dep/gps/pkgtree" + "github.com/golang/dep/gps/verify" "github.com/golang/dep/internal/fs" "github.com/pkg/errors" ) @@ -171,24 +175,69 @@ func (c *Ctx) LoadProject() (*Project, error) { 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 { - if os.IsNotExist(err) { - // It's fine for the lock not to exist - return p, nil + 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 + } } - // But if a lock does exist and we can't open it, that's a problem + + } 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) } - defer lf.Close() - p.Lock, err = readLock(lf) - if err != nil { - return nil, errors.Wrapf(err, "error while parsing %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) + } + } } - return p, nil + sort.Strings(reach) + return reach } // DetectProjectGOPATH attempt to find the GOPATH containing the project. diff --git a/docs/Gopkg.lock.md b/docs/Gopkg.lock.md index 7b7876e210..1a930fcccb 100644 --- a/docs/Gopkg.lock.md +++ b/docs/Gopkg.lock.md @@ -1,5 +1,5 @@ --- -title: Gopkg.lock + title: Gopkg.lock --- The `Gopkg.lock` file is generated by `dep ensure` and `dep init`. It is the output of [the solving function](ensure-mechanics.md#functional-flow): a transitively complete snapshot of a project's dependency graph, expressed as a series of `[[project]]` stanzas. That means: @@ -8,7 +8,7 @@ The `Gopkg.lock` file is generated by `dep ensure` and `dep init`. It is the out * Plus any [`required`](Gopkg.toml.md#required) packages * Less any [`ignored`](Gopkg.toml.md#ignored) packages -`Gopkg.lock` also contains some metadata about the algorithm used to arrive at the final graph, under `[solve-meta]`. +`Gopkg.lock` also contains some metadata about the algorithm and inputs used to arrive at the final graph, under `[solve-meta]`. `Gopkg.lock` always includes a `revision` for all listed dependencies, as the semantics of `revision` guarantee them to be immutable. Thus, the `Gopkg.lock` acts as a reproducible build list - as long as the upstream remains available, all dependencies can be precisely reproduced. @@ -28,6 +28,8 @@ These are all the properties that can appear in a `[[projects]]` stanza, and whe | `revision` | Y | | `version` | N | | `branch` | N | +| `pruneopts` | Y | +| `digest` | Y | ### `name` @@ -47,6 +49,27 @@ In general, this is the set of packages that were found to be participants in th * Being imported by a package from either the current project or a different dependency * Being imported by a package from within this project that, directly or transitively, is imported by a package from a different project +### `pruneopts` + +A compactly-encoded form of the [prune options designated in `Gopkg.toml`](Gopkg.toml.md#prune) . Each character represents one of the three possible rules: + +| Character | Pruning Rule in `Gopkg.toml` | +| --------- | ---------------------------- | +| `N` | `non-go` | +| `U` | `unused-packages` | +| `T` | `go-tests` | + +If the character is present in `pruneopts`, the pruning rule is enabled for that project. Thus, `NUT` indicates that all three pruning rules are active. + +### `digest` + +The hash digest of the contents of `vendor/` for this project, _after_ pruning rules have been applied. The digest is versioned, by way of a colon-delimited prefix; the string is of the form `:` . The hashing algorithm corresponding to version 1 is SHA256, as implemented in the stdlib package `crypto/sha256`. + +There are some tweaks that differentiate the hasher apart from a naive filesystem tree hashing implementation: + +* Symlinks are ignored. +* Line endings are normalized to LF (using an algorithm similar to git's) in order to ensure digests do not vary across platforms. + ### Version information: `revision`, `version`, and `branch` In order to provide reproducible builds, it is an absolute requirement that every project stanza contain a `revision`, no matter what kinds of constraints were encountered in `Gopkg.toml` files. It is further possible that exactly one of either `version` or `branch` will _additionally_ be present. @@ -61,6 +84,10 @@ More details on "analyzer" and "solver" follow, but the versioning principle is By bumping versions only on solution set contractions, but not expansions, it allows us to avoid having to bump constantly (which could make using dep across teams awkward), while still making it likely that when the solver and version numbers match between `Gopkg.lock` and a running version of dep, what's recorded in the file is acceptable by the running version's rules. +### `input-imports` + +A sorted list of all the import inputs that were present at the time the `Gopkg.lock` was computed. This list includes both actual `import` statements from the project, as well as any `required` import paths listed in `Gopkg.toml`. + ### `analyzer-name` and `analyzer-version` The analyzer is an internal dep component responsible for interpreting the contents of `Gopkg.toml` files, as well as metadata files from any tools dep knows about: `glide.yaml`, `vendor.json`, etc. @@ -75,12 +102,4 @@ The solver is the algorithm behind [the solving function](ensure-mechanics.md#fu The solver is named because, like the analyzer, it is pluggable; an alternative algorithm could be written that applies different rules to achieve the same goal. The one dep uses, "gps-cdcl", is named after [the general class of SAT solving algorithm it most resembles](https://en.wikipedia.org/wiki/Conflict-Driven_Clause_Learning), though the algorithm is actually a specialized, domain-specific [SMT solver](https://en.wikipedia.org/wiki/Satisfiability_modulo_theories). -The same general principles of version-bumping apply to the solver version: if the solver starts enforcing [Go 1.4 import path comments](https://golang.org/cmd/go/#hdr-Import_path_checking), that entails a bump, because it can only narrow the solution set. If it were to later relax that requirement, it would not require a bump, as that can only expand the solution set. - -### `inputs-digest` - -A SHA256 hash digest of all the [inputs to the solving function](ensure-mechanics.md#functional-flow). Those inputs can be shown directly with the hidden command `dep hash-inputs`, allowing this value to be generated directly: - -``` -dep hash-inputs | tr -d “\n” | shasum -a256 -``` +The same general principles of version-bumping apply to the solver version: if the solver starts enforcing [Go 1.4 import path comments](https://golang.org/cmd/go/#hdr-Import_path_checking), that entails a bump, because it can only narrow the solution set. If it were to later relax that requirement, it would not require a bump, as that can only expand the solution set. \ No newline at end of file diff --git a/docs/daily-dep.md b/docs/daily-dep.md index 7ef4432d01..7bf0066e37 100644 --- a/docs/daily-dep.md +++ b/docs/daily-dep.md @@ -83,7 +83,7 @@ $ dep ensure -update ### Adding and removing `import` statements -As noted in [the section on adding dependencies](#adding-a-new-dependency), dep relies on the import statements in your code to figure out which dependencies your project actually needs. Thus, when you add or remove import statements, dep might need to care about it. +As noted in [the section on adding dependencies](#adding-a-new-dependency), dep relies on the `import` statements in your code to figure out which dependencies your project actually needs. Thus, when you add or remove import statements, dep might need to care about it. It's only "might," though, because most of the time, adding or removing imports doesn't matter to dep. Only if one of the following has occurred will a `dep ensure` be necessary to bring the project back in sync: diff --git a/docs/ensure-mechanics.md b/docs/ensure-mechanics.md index 7d813f82a8..78b80b6359 100644 --- a/docs/ensure-mechanics.md +++ b/docs/ensure-mechanics.md @@ -51,9 +51,7 @@ The four state system, and these functional flows through it, are the foundation One of dep's design goals is that both of its "functions" minimize both the work they do, and the change they induce in their respective results. (Note: "minimize" is not currently formally defined with respect to a cost function.) Consequently, both functions peek ahead at the pre-existing result to understand what work actually needs to be done: * The solving function checks the existing `Gopkg.lock` to determine if all of its inputs (project import statements + `Gopkg.toml` rules) are satisfied. If they are, the solving function can be bypassed entirely. If not, the solving function proceeds, but attempts to change as few of the selections in `Gopkg.lock` as possible. - * WIP: The current implementation's check relies on a coarse heuristic check that can be wrong in some cases. There is a [plan to fix this](https://github.com/golang/dep/issues/1496). * The vendoring function hashes each discrete project already in `vendor/` to see if the code present on disk is what `Gopkg.lock` indicates it should be. Only projects that deviate from expectations are written out. - * WIP: the hashing check is generally referred to as "vendor verification," and [is not yet complete](https://github.com/golang/dep/issues/121). Without this verification, dep is blind to whether code in `vendor/` is correct or not; as such, dep must defensively re-write all projects to ensure the state of `vendor/` is correct. Of course, it's possible that, in peeking ahead, either function might discover that the pre-existing result is already correct - so no work need be done at all. Either way, when each function completes, we can be sure that the output, changed or not, is correct with respect to the inputs. In other words, the inputs and outputs are "in sync." Indeed, being in sync is the "known good state" of dep; `dep ensure` (without flags) guarantees that if it exits 0, all four states in the project are in sync. diff --git a/docs/env-vars.md b/docs/env-vars.md index 83aeed9218..7f38fef194 100644 --- a/docs/env-vars.md +++ b/docs/env-vars.md @@ -25,8 +25,4 @@ This is primarily useful if you're not using the standard `go` toolchain as a co ### `DEPNOLOCK` -By default, dep creates an `sm.lock` file at `$DEPCACHEDIR/sm.lock` in order to -prevent multiple dep processes from interacting with the [local -cache](glossary.md#local-cache) simultaneously. Setting this variable will -bypass that protection; no file will be created. This can be useful on certain -filesystems; VirtualBox shares in particular are known to misbehave. +By default, dep creates an `sm.lock` file at `$DEPCACHEDIR/sm.lock` in order to prevent multiple dep processes from interacting with the [local cache](glossary.md#local-cache) simultaneously. Setting this variable will bypass that protection; no file will be created. This can be useful on certain filesystems; VirtualBox shares in particular are known to misbehave. diff --git a/docs/glossary.md b/docs/glossary.md index 9c491aa211..5b78e980d3 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -69,7 +69,7 @@ An `import` statement that points to a package in a project other than the one i ### GPS -Stands for "Go packaging solver", it is [a subtree of library-style packages within dep](https://godoc.org/github.com/golang/dep/gps), and is the engine around which dep is built. Most commonly referred to as "gps." +Acronym for "Go packaging solver", it is [a subtree of library-style packages within dep](https://godoc.org/github.com/golang/dep/gps), and is the engine around which dep is built. Most commonly referred to as "gps." ### Local cache diff --git a/gps/bridge.go b/gps/bridge.go index dc6812b87c..6f5c38a540 100644 --- a/gps/bridge.go +++ b/gps/bridge.go @@ -195,8 +195,8 @@ func (b *bridge) breakLock() { } for _, lp := range b.s.rd.rl.Projects() { - if _, is := b.s.sel.selected(lp.pi); !is { - pi, v := lp.pi, lp.Version() + if _, is := b.s.sel.selected(lp.Ident()); !is { + pi, v := lp.Ident(), lp.Version() go func() { // Sync first b.sm.SyncSourceFor(pi) diff --git a/gps/constraint.go b/gps/constraint.go index 1cb9451960..f7c600316e 100644 --- a/gps/constraint.go +++ b/gps/constraint.go @@ -362,27 +362,6 @@ func pcSliceToMap(l []ProjectConstraint, r ...[]ProjectConstraint) ProjectConstr return final } -func (m ProjectConstraints) asSortedSlice() []ProjectConstraint { - pcs := make([]ProjectConstraint, len(m)) - - k := 0 - for pr, pp := range m { - pcs[k] = ProjectConstraint{ - Ident: ProjectIdentifier{ - ProjectRoot: pr, - Source: pp.Source, - }, - Constraint: pp.Constraint, - } - k++ - } - - sort.SliceStable(pcs, func(i, j int) bool { - return pcs[i].Ident.Less(pcs[j].Ident) - }) - return pcs -} - // overrideAll treats the receiver ProjectConstraints map as a set of override // instructions, and applies overridden values to the ProjectConstraints. // diff --git a/gps/hash.go b/gps/hash.go deleted file mode 100644 index 556933bcf3..0000000000 --- a/gps/hash.go +++ /dev/null @@ -1,133 +0,0 @@ -// 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 gps - -import ( - "bytes" - "crypto/sha256" - "io" - "sort" - "strconv" - "strings" -) - -// string headers used to demarcate sections in hash input creation -const ( - hhConstraints = "-CONSTRAINTS-" - hhImportsReqs = "-IMPORTS/REQS-" - hhIgnores = "-IGNORES-" - hhOverrides = "-OVERRIDES-" - hhAnalyzer = "-ANALYZER-" -) - -// HashInputs computes a hash digest of all data in SolveParams and the -// RootManifest that act as function inputs to Solve(). -// -// The digest returned from this function is the same as the digest that would -// be included with a Solve() Result. As such, it's appropriate for comparison -// against the digest stored in a lock file, generated by a previous Solve(): if -// the digests match, then manifest and lock are in sync, and a Solve() is -// unnecessary. -// -// (Basically, this is for memoization.) -func (s *solver) HashInputs() (digest []byte) { - h := sha256.New() - s.writeHashingInputs(h) - - hd := h.Sum(nil) - digest = hd[:] - return -} - -func (s *solver) writeHashingInputs(w io.Writer) { - writeString := func(s string) { - // Skip zero-length string writes; it doesn't affect the real hash - // calculation, and keeps misleading newlines from showing up in the - // debug output. - if s != "" { - // All users of writeHashingInputs cannot error on Write(), so just - // ignore it - w.Write([]byte(s)) - } - } - - // We write "section headers" into the hash purely to ease scanning when - // debugging this input-constructing algorithm; as long as the headers are - // constant, then they're effectively a no-op. - writeString(hhConstraints) - - // getApplicableConstraints will apply overrides, incorporate requireds, - // apply local ignores, drop stdlib imports, and finally trim out - // ineffectual constraints. - for _, pd := range s.rd.getApplicableConstraints(s.stdLibFn) { - writeString(string(pd.Ident.ProjectRoot)) - writeString(pd.Ident.Source) - writeString(pd.Constraint.typedString()) - } - - // Write out each discrete import, including those derived from requires. - writeString(hhImportsReqs) - imports := s.rd.externalImportList(s.stdLibFn) - sort.Strings(imports) - for _, im := range imports { - writeString(im) - } - - // Add ignores, skipping any that point under the current project root; - // those will have already been implicitly incorporated by the import - // lister. - writeString(hhIgnores) - - ig := s.rd.ir.ToSlice() - sort.Strings(ig) - for _, igp := range ig { - // Typical prefix comparison checks will erroneously fail if the wildcard - // is present. Trim it off, if present. - tigp := strings.TrimSuffix(igp, "*") - if !strings.HasPrefix(tigp, s.rd.rpt.ImportRoot) || !isPathPrefixOrEqual(s.rd.rpt.ImportRoot, tigp) { - writeString(igp) - } - } - - // Overrides *also* need their own special entry distinct from basic - // constraints, to represent the unique effects they can have on the entire - // solving process beyond root's immediate scope. - writeString(hhOverrides) - for _, pc := range s.rd.ovr.asSortedSlice() { - writeString(string(pc.Ident.ProjectRoot)) - if pc.Ident.Source != "" { - writeString(pc.Ident.Source) - } - if pc.Constraint != nil { - writeString(pc.Constraint.typedString()) - } - } - - writeString(hhAnalyzer) - ai := s.rd.an.Info() - writeString(ai.Name) - writeString(strconv.Itoa(ai.Version)) -} - -// bytes.Buffer wrapper that injects newlines after each call to Write(). -type nlbuf bytes.Buffer - -func (buf *nlbuf) Write(p []byte) (n int, err error) { - n, _ = (*bytes.Buffer)(buf).Write(p) - (*bytes.Buffer)(buf).WriteByte('\n') - return n + 1, nil -} - -// HashingInputsAsString returns the raw input data used by Solver.HashInputs() -// as a string. -// -// This is primarily intended for debugging purposes. -func HashingInputsAsString(s Solver) string { - ts := s.(*solver) - buf := new(nlbuf) - ts.writeHashingInputs(buf) - - return (*bytes.Buffer)(buf).String() -} diff --git a/gps/hash_test.go b/gps/hash_test.go deleted file mode 100644 index 1be02f6870..0000000000 --- a/gps/hash_test.go +++ /dev/null @@ -1,686 +0,0 @@ -// 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 gps - -import ( - "bytes" - "crypto/sha256" - "fmt" - "strings" - "testing" - "text/tabwriter" - - "github.com/golang/dep/gps/pkgtree" -) - -func TestHashInputs(t *testing.T) { - fix := basicFixtures["shared dependency with overlapping constraints"] - - params := SolveParameters{ - RootDir: string(fix.ds[0].n), - RootPackageTree: fix.rootTree(), - Manifest: fix.rootmanifest(), - ProjectAnalyzer: naiveAnalyzer{}, - stdLibFn: func(string) bool { return false }, - mkBridgeFn: overrideMkBridge, - } - - s, err := Prepare(params, newdepspecSM(fix.ds, nil)) - if err != nil { - t.Fatalf("Unexpected error while prepping solver: %s", err) - } - - dig := s.HashInputs() - h := sha256.New() - - elems := []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - hhOverrides, - hhAnalyzer, - "naive-analyzer", - "1", - } - for _, v := range elems { - h.Write([]byte(v)) - } - correct := h.Sum(nil) - - if !bytes.Equal(dig, correct) { - t.Errorf("Hashes are not equal. Inputs:\n%s", diffHashingInputs(s, elems)) - } else if strings.Join(elems, "\n")+"\n" != HashingInputsAsString(s) { - t.Errorf("Hashes are equal, but hashing input strings are not:\n%s", diffHashingInputs(s, elems)) - } -} - -func TestHashInputsReqsIgs(t *testing.T) { - fix := basicFixtures["shared dependency with overlapping constraints"] - - rm := fix.rootmanifest().(simpleRootManifest).dup() - rm.ig = pkgtree.NewIgnoredRuleset([]string{"foo", "bar"}) - - params := SolveParameters{ - RootDir: string(fix.ds[0].n), - RootPackageTree: fix.rootTree(), - Manifest: rm, - ProjectAnalyzer: naiveAnalyzer{}, - stdLibFn: func(string) bool { return false }, - mkBridgeFn: overrideMkBridge, - } - - s, err := Prepare(params, newdepspecSM(fix.ds, nil)) - if err != nil { - t.Fatalf("Unexpected error while prepping solver: %s", err) - } - - dig := s.HashInputs() - h := sha256.New() - - elems := []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - "bar", - "foo", - hhOverrides, - hhAnalyzer, - "naive-analyzer", - "1", - } - for _, v := range elems { - h.Write([]byte(v)) - } - correct := h.Sum(nil) - - if !bytes.Equal(dig, correct) { - t.Errorf("Hashes are not equal. Inputs:\n%s", diffHashingInputs(s, elems)) - } - - // Add requires - rm.req = map[string]bool{ - "baz": true, - "qux": true, - } - - params.Manifest = rm - - s, err = Prepare(params, newdepspecSM(fix.ds, nil)) - if err != nil { - t.Fatalf("Unexpected error while prepping solver: %s", err) - } - - dig = s.HashInputs() - h = sha256.New() - - elems = []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - "baz", - "qux", - hhIgnores, - "bar", - "foo", - hhOverrides, - hhAnalyzer, - "naive-analyzer", - "1", - } - for _, v := range elems { - h.Write([]byte(v)) - } - correct = h.Sum(nil) - - if !bytes.Equal(dig, correct) { - t.Errorf("Hashes are not equal. Inputs:\n%s", diffHashingInputs(s, elems)) - } - - // remove ignores, just test requires alone - rm.ig = nil - params.Manifest = rm - - s, err = Prepare(params, newdepspecSM(fix.ds, nil)) - if err != nil { - t.Fatalf("Unexpected error while prepping solver: %s", err) - } - - dig = s.HashInputs() - h = sha256.New() - - elems = []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - "baz", - "qux", - hhIgnores, - hhOverrides, - hhAnalyzer, - "naive-analyzer", - "1", - } - for _, v := range elems { - h.Write([]byte(v)) - } - correct = h.Sum(nil) - - if !bytes.Equal(dig, correct) { - t.Errorf("Hashes are not equal. Inputs:\n%s", diffHashingInputs(s, elems)) - } -} - -func TestHashInputsOverrides(t *testing.T) { - basefix := basicFixtures["shared dependency with overlapping constraints"] - - // Set up base state that we'll mutate over the course of each test - rm := basefix.rootmanifest().(simpleRootManifest).dup() - params := SolveParameters{ - RootDir: string(basefix.ds[0].n), - RootPackageTree: basefix.rootTree(), - Manifest: rm, - ProjectAnalyzer: naiveAnalyzer{}, - stdLibFn: func(string) bool { return false }, - mkBridgeFn: overrideMkBridge, - } - - table := []struct { - name string - mut func() - elems []string - }{ - { - name: "override source; not imported, no deps pp", - mut: func() { - // First case - override just source, on something without - // corresponding project properties in the dependencies from - // root - rm.ovr = map[ProjectRoot]ProjectProperties{ - "c": { - Source: "car", - }, - } - }, - elems: []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - hhOverrides, - "c", - "car", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "override source; required, no deps pp", - mut: func() { - // Put c into the requires list, which should make it show up under - // constraints - rm.req = map[string]bool{ - "c": true, - } - }, - elems: []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - "c", - "car", - "any-*", // Any isn't included under the override, but IS for the constraint b/c it's equivalent - hhImportsReqs, - "a", - "b", - "c", - hhIgnores, - hhOverrides, - "c", - "car", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "override source; required & imported, no deps pp", - mut: func() { - // Put c in the root's imports - poe := params.RootPackageTree.Packages["root"] - poe.P.Imports = []string{"a", "b", "c"} - params.RootPackageTree.Packages["root"] = poe - }, - elems: []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - "c", - "car", - "any-*", // Any isn't included under the override, but IS for the constraint b/c it's equivalent - hhImportsReqs, - "a", - "b", - "c", - hhIgnores, - hhOverrides, - "c", - "car", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "override source; imported, no deps pp", - mut: func() { - // Take c out of requires list - now it's only imported - rm.req = nil - }, - elems: []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - "c", - "car", - "any-*", - hhImportsReqs, - "a", - "b", - "c", - hhIgnores, - hhOverrides, - "c", - "car", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "other override constraint; not imported, no deps pp", - mut: func() { - // Override not in root, just with constraint - rm.ovr["d"] = ProjectProperties{ - Constraint: NewBranch("foobranch"), - } - }, - elems: []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - "c", - "car", - "any-*", - hhImportsReqs, - "a", - "b", - "c", - hhIgnores, - hhOverrides, - "c", - "car", - "d", - "b-foobranch", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "override constraint; not imported, no deps pp", - mut: func() { - // Remove the "c" pkg from imports for remainder of tests - poe := params.RootPackageTree.Packages["root"] - poe.P.Imports = []string{"a", "b"} - params.RootPackageTree.Packages["root"] = poe - }, - elems: []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - hhOverrides, - "c", - "car", - "d", - "b-foobranch", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "override both; not imported, no deps pp", - mut: func() { - // Override not in root, both constraint and network name - rm.ovr["c"] = ProjectProperties{ - Source: "groucho", - Constraint: NewBranch("plexiglass"), - } - }, - elems: []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - hhOverrides, - "c", - "groucho", - "b-plexiglass", - "d", - "b-foobranch", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "override constraint; imported, with constraint", - mut: func() { - // Override dep present in root, just constraint - rm.ovr["a"] = ProjectProperties{ - Constraint: NewVersion("fluglehorn"), - } - }, - elems: []string{ - hhConstraints, - "a", - "pv-fluglehorn", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - hhOverrides, - "a", - "pv-fluglehorn", - "c", - "groucho", - "b-plexiglass", - "d", - "b-foobranch", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "override source; imported, with constraint", - mut: func() { - // Override in root, only network name - rm.ovr["a"] = ProjectProperties{ - Source: "nota", - } - }, - elems: []string{ - hhConstraints, - "a", - "nota", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - hhOverrides, - "a", - "nota", - "c", - "groucho", - "b-plexiglass", - "d", - "b-foobranch", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "override both; imported, with constraint", - mut: func() { - // Override in root, network name and constraint - rm.ovr["a"] = ProjectProperties{ - Source: "nota", - Constraint: NewVersion("fluglehorn"), - } - }, - elems: []string{ - hhConstraints, - "a", - "nota", - "pv-fluglehorn", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - hhOverrides, - "a", - "nota", - "pv-fluglehorn", - "c", - "groucho", - "b-plexiglass", - "d", - "b-foobranch", - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - } - - for _, fix := range table { - fix.mut() - params.Manifest = rm - - s, err := Prepare(params, newdepspecSM(basefix.ds, nil)) - if err != nil { - t.Errorf("(fix: %q) Unexpected error while prepping solver: %s", fix.name, err) - continue - } - - h := sha256.New() - for _, v := range fix.elems { - h.Write([]byte(v)) - } - - if !bytes.Equal(s.HashInputs(), h.Sum(nil)) { - t.Errorf("(fix: %q) Hashes are not equal. Inputs:\n%s", fix.name, diffHashingInputs(s, fix.elems)) - } - } -} - -func diffHashingInputs(s Solver, wnt []string) string { - actual := HashingInputsAsString(s) - got := strings.Split(actual, "\n") - // got has a trailing empty, add that to wnt - wnt = append(wnt, "") - - lg, lw := len(got), len(wnt) - - var buf bytes.Buffer - tw := tabwriter.NewWriter(&buf, 4, 4, 2, ' ', 0) - fmt.Fprintln(tw, " (GOT) \t (WANT) \t") - - lmiss, rmiss := ">>>>>>>>>>", "<<<<<<<<<<" - if lg == lw { - // same length makes the loop pretty straightforward - for i := 0; i < lg; i++ { - fmt.Fprintf(tw, "%s\t%s\t\n", got[i], wnt[i]) - } - } else if lg > lw { - offset := 0 - for i := 0; i < lg; i++ { - if lw <= i-offset { - fmt.Fprintf(tw, "%s\t%s\t\n", got[i], rmiss) - } else if got[i] != wnt[i-offset] && i+1 < lg && got[i+1] == wnt[i-offset] { - // if the next slot is a match, realign by skipping this one and - // bumping the offset - fmt.Fprintf(tw, "%s\t%s\t\n", got[i], rmiss) - offset++ - } else { - fmt.Fprintf(tw, "%s\t%s\t\n", got[i], wnt[i-offset]) - } - } - } else { - offset := 0 - for i := 0; i < lw; i++ { - if lg <= i-offset { - fmt.Fprintf(tw, "%s\t%s\t\n", lmiss, wnt[i]) - } else if got[i-offset] != wnt[i] && i+1 < lw && got[i-offset] == wnt[i+1] { - // if the next slot is a match, realign by skipping this one and - // bumping the offset - fmt.Fprintf(tw, "%s\t%s\t\n", lmiss, wnt[i]) - offset++ - } else { - fmt.Fprintf(tw, "%s\t%s\t\n", got[i-offset], wnt[i]) - } - } - } - - tw.Flush() - return buf.String() -} - -func TestHashInputsIneffectualWildcardIgs(t *testing.T) { - fix := basicFixtures["shared dependency with overlapping constraints"] - - rm := fix.rootmanifest().(simpleRootManifest).dup() - - params := SolveParameters{ - RootDir: string(fix.ds[0].n), - RootPackageTree: fix.rootTree(), - Manifest: rm, - ProjectAnalyzer: naiveAnalyzer{}, - stdLibFn: func(string) bool { return false }, - mkBridgeFn: overrideMkBridge, - } - - cases := []struct { - name string - ignoreMap []string - elems []string - }{ - { - name: "no wildcard ignores", - elems: []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - hhOverrides, - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - { - name: "different wildcard ignores", - ignoreMap: []string{ - "foobar*", - "foobarbaz*", - "foozapbar*", - }, - elems: []string{ - hhConstraints, - "a", - "sv-1.0.0", - "b", - "sv-1.0.0", - hhImportsReqs, - "a", - "b", - hhIgnores, - "foobar*", - "foozapbar*", - hhOverrides, - hhAnalyzer, - "naive-analyzer", - "1", - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - - rm.ig = pkgtree.NewIgnoredRuleset(c.ignoreMap) - - params.Manifest = rm - - s, err := Prepare(params, newdepspecSM(fix.ds, nil)) - if err != nil { - t.Fatalf("Unexpected error while prepping solver: %s", err) - } - - dig := s.HashInputs() - h := sha256.New() - - for _, v := range c.elems { - h.Write([]byte(v)) - } - correct := h.Sum(nil) - - if !bytes.Equal(dig, correct) { - t.Errorf("Hashes are not equal. Inputs:\n%s", diffHashingInputs(s, c.elems)) - } - }) - } -} diff --git a/gps/lock.go b/gps/lock.go index d2f9d59119..1fb909ecf8 100644 --- a/gps/lock.go +++ b/gps/lock.go @@ -5,7 +5,6 @@ package gps import ( - "bytes" "fmt" "sort" ) @@ -18,40 +17,17 @@ import ( // solution is all that would be necessary to constitute a lock file, though // tools can include whatever other information they want in their storage. type Lock interface { - // The hash digest of inputs to gps that resulted in this lock data. - InputsDigest() []byte - // Projects returns the list of LockedProjects contained in the lock data. Projects() []LockedProject -} - -// LocksAreEq checks if two locks are equivalent. This checks that -// all contained LockedProjects are equal, and optionally (if `checkHash` is -// true) whether the locks' input hashes are equal. -func LocksAreEq(l1, l2 Lock, checkHash bool) bool { - // Cheapest ops first - if checkHash && !bytes.Equal(l1.InputsDigest(), l2.InputsDigest()) { - return false - } - p1, p2 := l1.Projects(), l2.Projects() - if len(p1) != len(p2) { - return false - } - - p1 = sortedLockedProjects(p1) - p2 = sortedLockedProjects(p2) - - for k, lp := range p1 { - if !lp.Eq(p2[k]) { - return false - } - } - return true + // The set of imports (and required statements) that were the inputs that + // generated this Lock. It is acceptable to return a nil slice from this + // method if the information cannot reasonably be made available. + InputImports() []string } -// sortedLockedProjects returns a sorted copy of lps, or itself if already sorted. -func sortedLockedProjects(lps []LockedProject) []LockedProject { +// sortLockedProjects returns a sorted copy of lps, or itself if already sorted. +func sortLockedProjects(lps []LockedProject) []LockedProject { if len(lps) <= 1 || sort.SliceIsSorted(lps, func(i, j int) bool { return lps[i].Ident().Less(lps[j].Ident()) }) { @@ -69,7 +45,16 @@ func sortedLockedProjects(lps []LockedProject) []LockedProject { // project's name, one or both of version and underlying revision, the network // URI for accessing it, the path at which it should be placed within a vendor // directory, and the packages that are used in it. -type LockedProject struct { +type LockedProject interface { + Ident() ProjectIdentifier + Version() Version + Packages() []string + Eq(LockedProject) bool + String() string +} + +// lockedProject is the default implementation of LockedProject. +type lockedProject struct { pi ProjectIdentifier v UnpairedVersion r Revision @@ -77,23 +62,22 @@ type LockedProject struct { } // SimpleLock is a helper for tools to easily describe lock data when they know -// that no hash, or other complex information, is available. +// that input imports are unavailable. type SimpleLock []LockedProject var _ Lock = SimpleLock{} -// InputsDigest always returns an empty string for SimpleLock. This makes it useless -// as a stable lock to be written to disk, but still useful for some ephemeral -// purposes. -func (SimpleLock) InputsDigest() []byte { - return nil -} - // Projects returns the entire contents of the SimpleLock. func (l SimpleLock) Projects() []LockedProject { return l } +// InputImports returns a nil string slice, as SimpleLock does not provide a way +// of capturing string slices. +func (l SimpleLock) InputImports() []string { + return nil +} + // NewLockedProject creates a new LockedProject struct with a given // ProjectIdentifier (name and optional upstream source URL), version. and list // of packages required from the project. @@ -109,7 +93,7 @@ func NewLockedProject(id ProjectIdentifier, v Version, pkgs []string) LockedProj panic("must provide a non-nil version to create a LockedProject") } - lp := LockedProject{ + lp := lockedProject{ pi: id, pkgs: pkgs, } @@ -134,13 +118,13 @@ func NewLockedProject(id ProjectIdentifier, v Version, pkgs []string) LockedProj // Ident returns the identifier describing the project. This includes both the // local name (the root name by which the project is referenced in import paths) // and the network name, where the upstream source lives. -func (lp LockedProject) Ident() ProjectIdentifier { +func (lp lockedProject) Ident() ProjectIdentifier { return lp.pi } // Version assembles together whatever version and/or revision data is // available into a single Version. -func (lp LockedProject) Version() Version { +func (lp lockedProject) Version() Version { if lp.r == "" { return lp.v } @@ -152,37 +136,53 @@ func (lp LockedProject) Version() Version { return lp.v.Pair(lp.r) } -// Eq checks if two LockedProject instances are equal. -func (lp LockedProject) Eq(lp2 LockedProject) bool { - if lp.pi != lp2.pi { +// Eq checks if two LockedProject instances are equal. The implementation +// assumes both Packages lists are already sorted lexicographically. +func (lp lockedProject) Eq(lp2 LockedProject) bool { + if lp.pi != lp2.Ident() { return false } - if lp.r != lp2.r { - return false - } - - if len(lp.pkgs) != len(lp2.pkgs) { - return false - } - - for k, v := range lp.pkgs { - if lp2.pkgs[k] != v { + var uv UnpairedVersion + switch tv := lp2.Version().(type) { + case Revision: + if lp.r != tv { + return false + } + case versionPair: + if lp.r != tv.r { return false } + uv = tv.v + case branchVersion, semVersion, plainVersion: + // For now, we're going to say that revisions must be present in order + // to indicate equality. We may need to change this later, as it may be + // more appropriate to enforce elsewhere. + return false } v1n := lp.v == nil - v2n := lp2.v == nil + v2n := uv == nil if v1n != v2n { return false } - if !v1n && !lp.v.Matches(lp2.v) { + if !v1n && !lp.v.Matches(uv) { return false } + opkgs := lp2.Packages() + if len(lp.pkgs) != len(opkgs) { + return false + } + + for k, v := range lp.pkgs { + if opkgs[k] != v { + return false + } + } + return true } @@ -195,22 +195,22 @@ func (lp LockedProject) Eq(lp2 LockedProject) bool { // safe to remove - it could contain C files, or other assets, that can't be // safely removed. // * The slice is not a copy. If you need to modify it, copy it first. -func (lp LockedProject) Packages() []string { +func (lp lockedProject) Packages() []string { return lp.pkgs } -func (lp LockedProject) String() string { +func (lp lockedProject) String() string { return fmt.Sprintf("%s@%s with packages: %v", lp.Ident(), lp.Version(), lp.pkgs) } type safeLock struct { - h []byte p []LockedProject + i []string } -func (sl safeLock) InputsDigest() []byte { - return sl.h +func (sl safeLock) InputImports() []string { + return sl.i } func (sl safeLock) Projects() []LockedProject { @@ -226,10 +226,12 @@ func prepLock(l Lock) safeLock { pl := l.Projects() rl := safeLock{ - h: l.InputsDigest(), p: make([]LockedProject, len(pl)), } copy(rl.p, pl) + rl.i = make([]string, len(l.InputImports())) + copy(rl.i, l.InputImports()) + return rl } diff --git a/gps/lock_test.go b/gps/lock_test.go index 3f9ca6ff2c..2833993eec 100644 --- a/gps/lock_test.go +++ b/gps/lock_test.go @@ -34,14 +34,14 @@ func TestLockedProjectSorting(t *testing.T) { func TestLockedProjectsEq(t *testing.T) { lps := []LockedProject{ - NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), []string{"gps"}), - NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), nil), - NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), []string{"gps", "flugle"}), - NewLockedProject(mkPI("foo"), NewVersion("nada"), []string{"foo"}), - NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), []string{"flugle", "gps"}), - NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("278a227dfc3d595a33a77ff3f841fd8ca1bc8cd0"), []string{"gps"}), - NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.11.0"), []string{"gps"}), - NewLockedProject(mkPI("github.com/sdboyer/gps"), Revision("278a227dfc3d595a33a77ff3f841fd8ca1bc8cd0"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("REV"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("REV"), nil), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("REV"), []string{"gps", "flugle"}), + NewLockedProject(mkPI("foo"), NewVersion("nada").Pair("OTHERREV"), []string{"foo"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("REV"), []string{"flugle", "gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("REV2"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.11.0").Pair("REV"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), Revision("REV2"), []string{"gps"}), } fix := map[string]struct { @@ -83,55 +83,6 @@ func TestLockedProjectsEq(t *testing.T) { } } -func TestLocksAreEq(t *testing.T) { - gpl := NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("278a227dfc3d595a33a77ff3f841fd8ca1bc8cd0"), []string{"gps"}) - svpl := NewLockedProject(mkPI("github.com/Masterminds/semver"), NewVersion("v2.0.0"), []string{"semver"}) - bbbt := NewLockedProject(mkPI("github.com/beeblebrox/browntown"), NewBranch("master").Pair("63fc17eb7966a6f4cc0b742bf42731c52c4ac740"), []string{"browntown", "smoochies"}) - - l1 := solution{ - hd: []byte("foo"), - p: []LockedProject{ - gpl, - bbbt, - svpl, - }, - } - - l2 := solution{ - p: []LockedProject{ - svpl, - gpl, - }, - } - - if LocksAreEq(l1, l2, true) { - t.Fatal("should have failed on hash check") - } - - if LocksAreEq(l1, l2, false) { - t.Fatal("should have failed on length check") - } - - l2.p = append(l2.p, bbbt) - - if !LocksAreEq(l1, l2, false) { - t.Fatal("should be eq, must have failed on individual lp check") - } - - // ensure original input sort order is maintained - if !l1.p[0].Eq(gpl) { - t.Error("checking equality resorted l1") - } - if !l2.p[0].Eq(svpl) { - t.Error("checking equality resorted l2") - } - - l1.p[0] = NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.11.0"), []string{"gps"}) - if LocksAreEq(l1, l2, false) { - t.Error("should fail when individual lp were not eq") - } -} - func TestLockedProjectsString(t *testing.T) { tt := []struct { name string diff --git a/gps/manifest.go b/gps/manifest.go index 4ae302281c..3974c95997 100644 --- a/gps/manifest.go +++ b/gps/manifest.go @@ -96,28 +96,6 @@ func (m simpleRootManifest) IgnoredPackages() *pkgtree.IgnoredRuleset { func (m simpleRootManifest) RequiredPackages() map[string]bool { return m.req } -func (m simpleRootManifest) dup() simpleRootManifest { - m2 := simpleRootManifest{ - c: make(ProjectConstraints, len(m.c)), - ovr: make(ProjectConstraints, len(m.ovr)), - req: make(map[string]bool, len(m.req)), - } - - for k, v := range m.c { - m2.c[k] = v - } - for k, v := range m.ovr { - m2.ovr[k] = v - } - for k, v := range m.req { - m2.req[k] = v - } - - // IgnoredRulesets are immutable, and safe to reuse. - m2.ig = m.ig - - return m2 -} // prepManifest ensures a manifest is prepared and safe for use by the solver. // This is mostly about ensuring that no outside routine can modify the manifest diff --git a/gps/prune.go b/gps/prune.go index b705874145..a0a68fadf3 100644 --- a/gps/prune.go +++ b/gps/prune.go @@ -5,6 +5,8 @@ package gps import ( + "bytes" + "fmt" "os" "path/filepath" "sort" @@ -57,6 +59,47 @@ type CascadingPruneOptions struct { PerProjectOptions map[ProjectRoot]PruneOptionSet } +// ParsePruneOptions extracts PruneOptions from a string using the standard +// encoding. +func ParsePruneOptions(input string) (PruneOptions, error) { + var po PruneOptions + for _, char := range input { + switch char { + case 'T': + po |= PruneGoTestFiles + case 'U': + po |= PruneUnusedPackages + case 'N': + po |= PruneNonGoFiles + case 'V': + po |= PruneNestedVendorDirs + default: + return 0, errors.Errorf("unknown pruning code %q", char) + } + } + + return po, nil +} + +func (po PruneOptions) String() string { + var buf bytes.Buffer + + if po&PruneNonGoFiles != 0 { + fmt.Fprintf(&buf, "N") + } + if po&PruneUnusedPackages != 0 { + fmt.Fprintf(&buf, "U") + } + if po&PruneGoTestFiles != 0 { + fmt.Fprintf(&buf, "T") + } + if po&PruneNestedVendorDirs != 0 { + fmt.Fprintf(&buf, "V") + } + + return buf.String() +} + // PruneOptionsFor returns the PruneOptions bits for the given project, // indicating which pruning rules should be applied to the project's code. // diff --git a/gps/prune_test.go b/gps/prune_test.go index 39d3dc0b48..27f54dc3b2 100644 --- a/gps/prune_test.go +++ b/gps/prune_test.go @@ -111,7 +111,7 @@ func TestPruneProject(t *testing.T) { h.TempDir(pr) baseDir := h.Path(".") - lp := LockedProject{ + lp := lockedProject{ pi: ProjectIdentifier{ ProjectRoot: ProjectRoot(pr), }, @@ -143,7 +143,7 @@ func TestPruneUnusedPackages(t *testing.T) { }{ { "one-package", - LockedProject{ + lockedProject{ pi: pi, pkgs: []string{ ".", @@ -165,7 +165,7 @@ func TestPruneUnusedPackages(t *testing.T) { }, { "nested-package", - LockedProject{ + lockedProject{ pi: pi, pkgs: []string{ "pkg", @@ -194,7 +194,7 @@ func TestPruneUnusedPackages(t *testing.T) { }, { "complex-project", - LockedProject{ + lockedProject{ pi: pi, pkgs: []string{ "pkg", diff --git a/gps/selection.go b/gps/selection.go index 8c0b7d3a62..c8d41a9c47 100644 --- a/gps/selection.go +++ b/gps/selection.go @@ -119,8 +119,9 @@ func (s *selection) getRequiredPackagesIn(id ProjectIdentifier) map[string]int { return uniq } -// Suppress unused warning. +// Suppress unused linting warning. var _ = (*selection)(nil).getSelectedPackagesIn +var _ = (*selection)(nil).getProjectImportMap // Compute a list of the unique packages within the given ProjectIdentifier that // are currently selected, and the number of times each package has been @@ -141,6 +142,29 @@ func (s *selection) getSelectedPackagesIn(id ProjectIdentifier) map[string]int { return uniq } +// getProjectImportMap extracts the set of package imports from the used +// packages in each selected project. +func (s *selection) getProjectImportMap() map[ProjectRoot]map[string]struct{} { + importMap := make(map[ProjectRoot]map[string]struct{}) + for _, edges := range s.deps { + for _, edge := range edges { + var curmap map[string]struct{} + if imap, has := importMap[edge.depender.id.ProjectRoot]; !has { + curmap = make(map[string]struct{}) + } else { + curmap = imap + } + + for _, pl := range edge.dep.pl { + curmap[pl] = struct{}{} + } + importMap[edge.depender.id.ProjectRoot] = curmap + } + } + + return importMap +} + func (s *selection) getConstraint(id ProjectIdentifier) Constraint { deps, exists := s.deps[id.ProjectRoot] if !exists || len(deps) == 0 { diff --git a/gps/solution.go b/gps/solution.go index e76510f769..3612a1305f 100644 --- a/gps/solution.go +++ b/gps/solution.go @@ -31,15 +31,15 @@ type Solution interface { } type solution struct { - // A list of the projects selected by the solver. + // The projects selected by the solver. p []LockedProject + // The import inputs that created this solution (including requires). + i []string + // The number of solutions that were attempted att int - // The hash digest of the input opts - hd []byte - // The analyzer info analyzerInfo ProjectAnalyzerInfo @@ -153,12 +153,12 @@ func (r solution) Projects() []LockedProject { return r.p } -func (r solution) Attempts() int { - return r.att +func (r solution) InputImports() []string { + return r.i } -func (r solution) InputsDigest() []byte { - return r.hd +func (r solution) Attempts() int { + return r.att } func (r solution) AnalyzerName() string { diff --git a/gps/solution_test.go b/gps/solution_test.go index 299b9222b1..b18395e9e2 100644 --- a/gps/solution_test.go +++ b/gps/solution_test.go @@ -39,15 +39,6 @@ func init() { }, } basicResult.analyzerInfo = (naiveAnalyzer{}).Info() - - // Just in case something needs punishing, kubernetes offers a complex, - // real-world set of dependencies, and this revision is known to work. - /* - _ = atom{ - id: pi("github.com/kubernetes/kubernetes"), - v: NewVersion("1.0.0").Pair(Revision("528f879e7d3790ea4287687ef0ab3f2a01cc2718")), - } - */ } func testWriteDepTree(t *testing.T) { @@ -101,7 +92,7 @@ func testWriteDepTree(t *testing.T) { for _, p := range r.p { go func(pi ProjectIdentifier) { sm.SyncSourceFor(pi) - }(p.pi) + }(p.Ident()) } // nil lock/result should err immediately diff --git a/gps/solve_basic_test.go b/gps/solve_basic_test.go index 0ee4558328..b961911a19 100644 --- a/gps/solve_basic_test.go +++ b/gps/solve_basic_test.go @@ -308,7 +308,7 @@ func mksolution(inputs ...interface{}) map[ProjectIdentifier]LockedProject { a := mkAtom(t) m[a.id] = NewLockedProject(a.id, a.v, []string{"."}) case LockedProject: - m[t.pi] = t + m[t.Ident()] = t default: panic(fmt.Sprintf("unexpected input to mksolution: %T %s", in, in)) } @@ -1454,6 +1454,10 @@ func (sm *depspecSourceManager) ExportProject(context.Context, ProjectIdentifier return fmt.Errorf("dummy sm doesn't support exporting") } +func (sm *depspecSourceManager) ExportPrunedProject(context.Context, LockedProject, PruneOptions, string) error { + return fmt.Errorf("dummy sm doesn't support exporting") +} + func (sm *depspecSourceManager) DeduceProjectRoot(ip string) (ProjectRoot, error) { fip := toFold(ip) for _, ds := range sm.allSpecs() { @@ -1556,23 +1560,23 @@ func (ds depspec) DependencyConstraints() ProjectConstraints { type fixLock []LockedProject // impl Lock interface -func (fixLock) InputsDigest() []byte { - return []byte("fooooorooooofooorooofoo") +func (l fixLock) Projects() []LockedProject { + return l } // impl Lock interface -func (l fixLock) Projects() []LockedProject { - return l +func (fixLock) InputImports() []string { + return nil } type dummyLock struct{} // impl Lock interface -func (dummyLock) InputsDigest() []byte { - return []byte("fooooorooooofooorooofoo") +func (dummyLock) Projects() []LockedProject { + return nil } // impl Lock interface -func (dummyLock) Projects() []LockedProject { +func (dummyLock) InputImports() []string { return nil } diff --git a/gps/solve_test.go b/gps/solve_test.go index 607bcd7eb6..92eabcbb45 100644 --- a/gps/solve_test.go +++ b/gps/solve_test.go @@ -171,7 +171,7 @@ func fixtureSolveSimpleChecks(fix specfix, soln Solution, err error, t *testing. // Dump result projects into a map for easier interrogation rp := make(map[ProjectIdentifier]LockedProject) for _, lp := range r.p { - rp[lp.pi] = lp + rp[lp.Ident()] = lp } fixlen, rlen := len(fix.solution()), len(rp) @@ -192,8 +192,8 @@ func fixtureSolveSimpleChecks(fix specfix, soln Solution, err error, t *testing. t.Errorf("Expected version %q of project %q, but actual version was %q", pv(flp.Version()), ppi(id), pv(lp.Version())) } - if !reflect.DeepEqual(lp.pkgs, flp.pkgs) { - t.Errorf("Package list was not not as expected for project %s@%s:\n\t(GOT) %s\n\t(WNT) %s", ppi(id), pv(lp.Version()), lp.pkgs, flp.pkgs) + if !reflect.DeepEqual(lp.Packages(), flp.Packages()) { + t.Errorf("Package list was not not as expected for project %s@%s:\n\t(GOT) %s\n\t(WNT) %s", ppi(id), pv(lp.Version()), lp.Packages(), flp.Packages()) } } } @@ -201,7 +201,7 @@ func fixtureSolveSimpleChecks(fix specfix, soln Solution, err error, t *testing. // Now walk through remaining actual results for id, lp := range rp { if _, exists := fix.solution()[id]; !exists { - t.Errorf("Unexpected project %s@%s present in results, with pkgs:\n\t%s", ppi(id), pv(lp.Version()), lp.pkgs) + t.Errorf("Unexpected project %s@%s present in results, with pkgs:\n\t%s", ppi(id), pv(lp.Version()), lp.Packages()) } } } @@ -243,7 +243,10 @@ func TestRootLockNoVersionPairMatching(t *testing.T) { l2 := make(fixLock, 1) copy(l2, fix.l) - l2[0].v = nil + + l2lp := l2[0].(lockedProject) + l2lp.v = nil + l2[0] = l2lp params := SolveParameters{ RootDir: string(fix.ds[0].n), diff --git a/gps/solver.go b/gps/solver.go index 4061f8e9fe..c9b541d953 100644 --- a/gps/solver.go +++ b/gps/solver.go @@ -323,14 +323,6 @@ func Prepare(params SolveParameters, sm SourceManager) (Solver, error) { // a "lock file" - and/or use it to write out a directory tree of dependencies, // suitable to be a vendor directory, via CreateVendorTree. type Solver interface { - // HashInputs hashes the unique inputs to this solver, returning the hash - // digest. It is guaranteed that, if the resulting digest is equal to the - // digest returned from a previous Solution.InputHash(), that that Solution - // is valid for this Solver's inputs. - // - // In such a case, it may not be necessary to run Solve() at all. - HashInputs() []byte - // Solve initiates a solving run. It will either abort due to a canceled // Context, complete successfully with a Solution, or fail with an // informative error. @@ -455,14 +447,19 @@ func (s *solver) Solve(ctx context.Context) (Solution, error) { solv: s, } soln.analyzerInfo = s.rd.an.Info() - soln.hd = s.HashInputs() + soln.i = s.rd.externalImportList(s.stdLibFn) // Convert ProjectAtoms into LockedProjects - soln.p = make([]LockedProject, len(all)) - k := 0 + soln.p = make([]LockedProject, 0, len(all)) for pa, pl := range all { - soln.p[k] = pa2lp(pa, pl) - k++ + lp := pa2lp(pa, pl) + // Pass back the original inputlp directly if it Eqs what was + // selected. + if inputlp, has := s.rd.rlm[lp.Ident().ProjectRoot]; has && lp.Eq(inputlp) { + lp = inputlp + } + + soln.p = append(soln.p, lp) } } @@ -1347,7 +1344,7 @@ func (s *solver) unselectLast() (atomWithPackages, bool, error) { // simple (temporary?) helper just to convert atoms into locked projects func pa2lp(pa atom, pkgs map[string]struct{}) LockedProject { - lp := LockedProject{ + lp := lockedProject{ pi: pa.id, } @@ -1363,18 +1360,16 @@ func pa2lp(pa atom, pkgs map[string]struct{}) LockedProject { panic("unreachable") } - lp.pkgs = make([]string, len(pkgs)) - k := 0 + lp.pkgs = make([]string, 0, len(pkgs)) pr := string(pa.id.ProjectRoot) trim := pr + "/" for pkg := range pkgs { if pkg == string(pa.id.ProjectRoot) { - lp.pkgs[k] = "." + lp.pkgs = append(lp.pkgs, ".") } else { - lp.pkgs[k] = strings.TrimPrefix(pkg, trim) + lp.pkgs = append(lp.pkgs, strings.TrimPrefix(pkg, trim)) } - k++ } sort.Strings(lp.pkgs) diff --git a/gps/source.go b/gps/source.go index 0d9deb14d2..1a1a0456a0 100644 --- a/gps/source.go +++ b/gps/source.go @@ -357,6 +357,35 @@ func (sg *sourceGateway) exportVersionTo(ctx context.Context, v Version, to stri return err } +func (sg *sourceGateway) exportPrunedVersionTo(ctx context.Context, lp LockedProject, prune PruneOptions, to string) error { + sg.mu.Lock() + defer sg.mu.Unlock() + + err := sg.require(ctx, sourceExistsLocally) + if err != nil { + return err + } + + r, err := sg.convertToRevision(ctx, lp.Version()) + if err != nil { + return err + } + + if fastprune, ok := sg.src.(sourceFastPrune); ok { + return sg.suprvsr.do(ctx, sg.src.upstreamURL(), ctExportTree, func(ctx context.Context) error { + return fastprune.exportPrunedRevisionTo(ctx, r, lp.Packages(), prune, to) + }) + } + + if err = sg.suprvsr.do(ctx, sg.src.upstreamURL(), ctExportTree, func(ctx context.Context) error { + return sg.src.exportRevisionTo(ctx, r, to) + }); err != nil { + return err + } + + return PruneProject(to, lp, prune) +} + func (sg *sourceGateway) getManifestAndLock(ctx context.Context, pr ProjectRoot, v Version, an ProjectAnalyzer) (Manifest, Lock, error) { sg.mu.Lock() defer sg.mu.Unlock() @@ -674,3 +703,8 @@ type source interface { // requires the source to exist locally. listVersionsRequiresLocal() bool } + +type sourceFastPrune interface { + source + exportPrunedRevisionTo(context.Context, Revision, []string, PruneOptions, string) error +} diff --git a/gps/source_cache.go b/gps/source_cache.go index 1123f317d8..966008a388 100644 --- a/gps/source_cache.go +++ b/gps/source_cache.go @@ -7,6 +7,7 @@ package gps import ( "fmt" "path" + "sort" "strings" "sync" @@ -271,3 +272,36 @@ func (c *singleSourceCacheMemory) toUnpaired(v Version) (UnpairedVersion, bool) panic(fmt.Sprintf("unknown version type %T", v)) } } + +// TODO(sdboyer) remove once source caching can be moved into separate package +func locksAreEq(l1, l2 Lock) bool { + ii1, ii2 := l1.InputImports(), l2.InputImports() + if len(ii1) != len(ii2) { + return false + } + + ilen := len(ii1) + if ilen > 0 { + sort.Strings(ii1) + sort.Strings(ii2) + for i := 0; i < ilen; i++ { + if ii1[i] != ii2[i] { + return false + } + } + } + + p1, p2 := l1.Projects(), l2.Projects() + if len(p1) != len(p2) { + return false + } + + p1, p2 = sortLockedProjects(p1), sortLockedProjects(p2) + + for k, lp := range p1 { + if !lp.Eq(p2[k]) { + return false + } + } + return true +} diff --git a/gps/source_cache_bolt_encode.go b/gps/source_cache_bolt_encode.go index 61d062c5cb..5b6a903cf6 100644 --- a/gps/source_cache_bolt_encode.go +++ b/gps/source_cache_bolt_encode.go @@ -6,6 +6,7 @@ package gps import ( "encoding/binary" + "strings" "time" "github.com/boltdb/bolt" @@ -17,19 +18,19 @@ import ( ) var ( - cacheKeyComment = []byte("c") - cacheKeyConstraint = cacheKeyComment - cacheKeyError = []byte("e") - cacheKeyHash = []byte("h") - cacheKeyIgnored = []byte("i") - cacheKeyImport = cacheKeyIgnored - cacheKeyLock = []byte("l") - cacheKeyName = []byte("n") - cacheKeyOverride = []byte("o") - cacheKeyPTree = []byte("p") - cacheKeyRequired = []byte("r") - cacheKeyRevision = cacheKeyRequired - cacheKeyTestImport = []byte("t") + cacheKeyComment = []byte("c") + cacheKeyConstraint = cacheKeyComment + cacheKeyError = []byte("e") + cacheKeyInputImports = []byte("m") + cacheKeyIgnored = []byte("i") + cacheKeyImport = cacheKeyIgnored + cacheKeyLock = []byte("l") + cacheKeyName = []byte("n") + cacheKeyOverride = []byte("o") + cacheKeyPTree = []byte("p") + cacheKeyRequired = []byte("r") + cacheKeyRevision = cacheKeyRequired + cacheKeyTestImport = []byte("t") cacheRevision = byte('r') cacheVersion = byte('v') @@ -240,19 +241,49 @@ func cacheGetManifest(b *bolt.Bucket) (RootManifest, error) { } // copyTo returns a serializable representation of lp. -func (lp LockedProject) copyTo(msg *pb.LockedProject, c *pb.Constraint) { +func (lp lockedProject) copyTo(msg *pb.LockedProject, c *pb.Constraint) { if lp.v == nil { msg.UnpairedVersion = nil } else { lp.v.copyTo(c) msg.UnpairedVersion = c } + msg.Root = string(lp.pi.ProjectRoot) msg.Source = lp.pi.Source msg.Revision = string(lp.r) msg.Packages = lp.pkgs } +// copyLockedProjectTo hydrates pointers to serializable representations of a +// LockedProject with the appropriate data. +func copyLockedProjectTo(lp LockedProject, msg *pb.LockedProject, c *pb.Constraint) { + if nlp, ok := lp.(lockedProject); ok { + nlp.copyTo(msg, c) + return + } + + v := lp.Version() + if v == nil { + msg.UnpairedVersion = nil + } else { + v.copyTo(c) + msg.UnpairedVersion = c + + switch tv := v.(type) { + case Revision: + msg.Revision = string(tv) + case versionPair: + msg.Revision = string(tv.r) + } + } + + pi := lp.Ident() + msg.Root = string(pi.ProjectRoot) + msg.Source = pi.Source + msg.Packages = lp.Packages() +} + // lockedProjectFromCache returns a new LockedProject with fields from m. func lockedProjectFromCache(m *pb.LockedProject) (LockedProject, error) { var uv UnpairedVersion @@ -260,10 +291,10 @@ func lockedProjectFromCache(m *pb.LockedProject) (LockedProject, error) { if m.UnpairedVersion != nil { uv, err = unpairedVersionFromCache(m.UnpairedVersion) if err != nil { - return LockedProject{}, err + return lockedProject{}, err } } - return LockedProject{ + return lockedProject{ pi: ProjectIdentifier{ ProjectRoot: ProjectRoot(m.Root), Source: m.Source, @@ -276,11 +307,10 @@ func lockedProjectFromCache(m *pb.LockedProject) (LockedProject, error) { // cachePutLock stores the Lock as fields in the bolt.Bucket. func cachePutLock(b *bolt.Bucket, l Lock) error { - // InputHash - if v := l.InputsDigest(); len(v) > 0 { - if err := b.Put(cacheKeyHash, v); err != nil { - return errors.Wrap(err, "failed to put hash") - } + // Input imports, if present. + byt := []byte(strings.Join(l.InputImports(), "#")) + if err := b.Put(cacheKeyInputImports, byt); err != nil { + return errors.Wrap(err, "failed to put input imports") } // Projects @@ -293,7 +323,7 @@ func cachePutLock(b *bolt.Bucket, l Lock) error { var msg pb.LockedProject var cMsg pb.Constraint for i, lp := range projects { - lp.copyTo(&msg, &cMsg) + copyLockedProjectTo(lp, &msg, &cMsg) v, err := proto.Marshal(&msg) if err != nil { return err @@ -310,9 +340,11 @@ func cachePutLock(b *bolt.Bucket, l Lock) error { // cacheGetLock returns a new *safeLock with the fields retrieved from the bolt.Bucket. func cacheGetLock(b *bolt.Bucket) (*safeLock, error) { - l := &safeLock{ - h: b.Get(cacheKeyHash), + l := &safeLock{} + if ii := b.Get(cacheKeyInputImports); len(ii) > 0 { + l.i = strings.Split(string(ii), "#") } + if locked := b.Bucket(cacheKeyLock); locked != nil { var msg pb.LockedProject err := locked.ForEach(func(_, v []byte) error { diff --git a/gps/source_cache_bolt_test.go b/gps/source_cache_bolt_test.go index c8ad87e54a..7bfdf2b7a9 100644 --- a/gps/source_cache_bolt_test.go +++ b/gps/source_cache_bolt_test.go @@ -53,13 +53,12 @@ func TestBoltCacheTimeout(t *testing.T) { } lock := &safeLock{ - h: []byte("test_hash"), p: []LockedProject{ - NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), []string{"gps"}), - NewLockedProject(mkPI("github.com/sdboyer/gps2"), NewVersion("v0.10.0"), nil), - NewLockedProject(mkPI("github.com/sdboyer/gps3"), NewVersion("v0.10.0"), []string{"gps", "flugle"}), - NewLockedProject(mkPI("foo"), NewVersion("nada"), []string{"foo"}), - NewLockedProject(mkPI("github.com/sdboyer/gps4"), NewVersion("v0.10.0"), []string{"flugle", "gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("foo"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps2"), NewVersion("v0.10.0").Pair("bar"), nil), + NewLockedProject(mkPI("github.com/sdboyer/gps3"), NewVersion("v0.10.0").Pair("baz"), []string{"gps", "flugle"}), + NewLockedProject(mkPI("foo"), NewVersion("nada").Pair("zero"), []string{"foo"}), + NewLockedProject(mkPI("github.com/sdboyer/gps4"), NewVersion("v0.10.0").Pair("qux"), []string{"flugle", "gps"}), }, } @@ -120,8 +119,9 @@ func TestBoltCacheTimeout(t *testing.T) { t.Error("no manifest and lock found for revision") } compareManifests(t, manifest, gotM) - if dl := DiffLocks(lock, gotL); dl != nil { - t.Errorf("lock differences:\n\t %#v", dl) + // TODO(sdboyer) use DiffLocks after refactoring to avoid import cycles + if !locksAreEq(lock, gotL) { + t.Errorf("locks are different:\n\t(GOT): %s\n\t(WNT): %s", lock, gotL) } got, ok := c.getPackageTree(rev, root) @@ -162,8 +162,9 @@ func TestBoltCacheTimeout(t *testing.T) { t.Error("no manifest and lock found for revision") } compareManifests(t, manifest, gotM) - if dl := DiffLocks(lock, gotL); dl != nil { - t.Errorf("lock differences:\n\t %#v", dl) + // TODO(sdboyer) use DiffLocks after refactoring to avoid import cycles + if !locksAreEq(lock, gotL) { + t.Errorf("locks are different:\n\t(GOT): %s\n\t(WNT): %s", lock, gotL) } gotPtree, ok := c.getPackageTree(rev, root) @@ -195,8 +196,9 @@ func TestBoltCacheTimeout(t *testing.T) { t.Error("no manifest and lock found for revision") } compareManifests(t, manifest, gotM) - if dl := DiffLocks(lock, gotL); dl != nil { - t.Errorf("lock differences:\n\t %#v", dl) + // TODO(sdboyer) use DiffLocks after refactoring to avoid import cycles + if !locksAreEq(lock, gotL) { + t.Errorf("locks are different:\n\t(GOT): %s\n\t(WNT): %s", lock, gotL) } got, ok := c.getPackageTree(rev, root) @@ -233,10 +235,10 @@ func TestBoltCacheTimeout(t *testing.T) { } newLock := &safeLock{ - h: []byte("new_test_hash"), p: []LockedProject{ - NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v1"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v1").Pair("rev1"), []string{"gps"}), }, + i: []string{"foo", "bar"}, } newPtree := pkgtree.PackageTree{ @@ -283,8 +285,9 @@ func TestBoltCacheTimeout(t *testing.T) { t.Error("no manifest and lock found for revision") } compareManifests(t, newManifest, gotM) - if dl := DiffLocks(newLock, gotL); dl != nil { - t.Errorf("lock differences:\n\t %#v", dl) + // TODO(sdboyer) use DiffLocks after refactoring to avoid import cycles + if !locksAreEq(newLock, gotL) { + t.Errorf("locks are different:\n\t(GOT): %s\n\t(WNT): %s", newLock, gotL) } got, ok := c.getPackageTree(rev, root) diff --git a/gps/source_cache_test.go b/gps/source_cache_test.go index 9e15b65ac9..5e39063311 100644 --- a/gps/source_cache_test.go +++ b/gps/source_cache_test.go @@ -116,13 +116,12 @@ func (test singleSourceCacheTest) run(t *testing.T) { ig: pkgtree.NewIgnoredRuleset([]string{"a", "b"}), } var l Lock = &safeLock{ - h: []byte("test_hash"), p: []LockedProject{ - NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), []string{"gps"}), - NewLockedProject(mkPI("github.com/sdboyer/gps2"), NewVersion("v0.10.0"), nil), - NewLockedProject(mkPI("github.com/sdboyer/gps3"), NewVersion("v0.10.0"), []string{"gps", "flugle"}), - NewLockedProject(mkPI("foo"), NewVersion("nada"), []string{"foo"}), - NewLockedProject(mkPI("github.com/sdboyer/gps4"), NewVersion("v0.10.0"), []string{"flugle", "gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("anything"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps2"), NewVersion("v0.10.0").Pair("whatever"), nil), + NewLockedProject(mkPI("github.com/sdboyer/gps3"), NewVersion("v0.10.0").Pair("again"), []string{"gps", "flugle"}), + NewLockedProject(mkPI("foo"), NewVersion("nada").Pair("itsaliving"), []string{"foo"}), + NewLockedProject(mkPI("github.com/sdboyer/gps4"), NewVersion("v0.10.0").Pair("meow"), []string{"flugle", "gps"}), }, } c.setManifestAndLock(rev, testAnalyzerInfo, m, l) @@ -140,8 +139,9 @@ func (test singleSourceCacheTest) run(t *testing.T) { t.Error("no manifest and lock found for revision") } compareManifests(t, m, gotM) - if dl := DiffLocks(l, gotL); dl != nil { - t.Errorf("lock differences:\n\t %#v", dl) + // TODO(sdboyer) use DiffLocks after refactoring to avoid import cycles + if !locksAreEq(l, gotL) { + t.Errorf("locks are different:\n\t(GOT): %s\n\t(WNT): %s", l, gotL) } m = &simpleRootManifest{ @@ -163,10 +163,9 @@ func (test singleSourceCacheTest) run(t *testing.T) { ig: pkgtree.NewIgnoredRuleset([]string{"c", "d"}), } l = &safeLock{ - h: []byte("different_test_hash"), p: []LockedProject{ NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("278a227dfc3d595a33a77ff3f841fd8ca1bc8cd0"), []string{"gps"}), - NewLockedProject(mkPI("github.com/sdboyer/gps2"), NewVersion("v0.11.0"), []string{"gps"}), + NewLockedProject(mkPI("github.com/sdboyer/gps2"), NewVersion("v0.11.0").Pair("anything"), []string{"gps"}), NewLockedProject(mkPI("github.com/sdboyer/gps3"), Revision("278a227dfc3d595a33a77ff3f841fd8ca1bc8cd0"), []string{"gps"}), }, } @@ -185,8 +184,9 @@ func (test singleSourceCacheTest) run(t *testing.T) { t.Error("no manifest and lock found for revision") } compareManifests(t, m, gotM) - if dl := DiffLocks(l, gotL); dl != nil { - t.Errorf("lock differences:\n\t %#v", dl) + // TODO(sdboyer) use DiffLocks after refactoring to avoid import cycles + if !locksAreEq(l, gotL) { + t.Errorf("locks are different:\n\t(GOT): %s\n\t(WNT): %s", l, gotL) } }) diff --git a/gps/source_manager.go b/gps/source_manager.go index 277e66a6ee..16c3f4816d 100644 --- a/gps/source_manager.go +++ b/gps/source_manager.go @@ -104,6 +104,15 @@ type SourceManager interface { // provided version, to the provided directory. ExportProject(context.Context, ProjectIdentifier, Version, string) error + // ExportPrunedProject writes out the tree corresponding to the provided + // LockedProject, the provided version, to the provided directory, applying + // the provided pruning options. + // + // The first return value is the hex-encoded string representation of the + // hash, including colon-separated leaders indicating the version of the + // hashing function used, and the prune options that were applied. + ExportPrunedProject(context.Context, LockedProject, PruneOptions, string) error + // DeduceProjectRoot takes an import path and deduces the corresponding // project/source root. DeduceProjectRoot(ip string) (ProjectRoot, error) @@ -113,9 +122,9 @@ type SourceManager interface { // In general, these URLs differ only by protocol (e.g. https vs. ssh), not path SourceURLsForPath(ip string) ([]*url.URL, error) - // Release lets go of any locks held by the SourceManager. Once called, it is - // no longer safe to call methods against it; all method calls will - // immediately result in errors. + // Release lets go of any locks held by the SourceManager. Once called, it + // is no longer allowed to call methods of that SourceManager; all + // method calls will immediately result in errors. Release() // InferConstraint tries to puzzle out what kind of version is given in a string - @@ -396,9 +405,9 @@ func (e CouldNotCreateLockError) Error() string { return e.Err.Error() } -// Release lets go of any resources held by the SourceManager. Once called, it is no -// longer safe to call methods against it; all method calls will immediately -// result in errors. +// Release lets go of any locks held by the SourceManager. Once called, it is no +// longer allowed to call methods of that SourceManager; all method calls will +// immediately result in errors. func (sm *SourceMgr) Release() { atomic.StoreInt32(&sm.releasing, 1) @@ -550,6 +559,21 @@ func (sm *SourceMgr) ExportProject(ctx context.Context, id ProjectIdentifier, v return srcg.exportVersionTo(ctx, v, to) } +// ExportPrunedProject writes out a tree of the provided LockedProject, applying +// provided pruning rules as appropriate. +func (sm *SourceMgr) ExportPrunedProject(ctx context.Context, lp LockedProject, prune PruneOptions, to string) error { + if atomic.LoadInt32(&sm.releasing) == 1 { + return ErrSourceManagerIsReleased + } + + srcg, err := sm.srcCoord.getSourceGatewayFor(ctx, lp.Ident()) + if err != nil { + return err + } + + return srcg.exportPrunedVersionTo(ctx, lp, prune, to) +} + // DeduceProjectRoot takes an import path and deduces the corresponding // project/source root. // diff --git a/gps/trace.go b/gps/trace.go index 4c579d30aa..d4dd24a136 100644 --- a/gps/trace.go +++ b/gps/trace.go @@ -101,7 +101,7 @@ func (s *solver) traceFinish(sol solution, err error) { if err == nil { var pkgcount int for _, lp := range sol.Projects() { - pkgcount += len(lp.pkgs) + pkgcount += len(lp.Packages()) } s.tl.Printf("%s%s found solution with %v packages from %v projects", innerIndent, successChar, pkgcount, len(sol.Projects())) } else { diff --git a/gps/pkgtree/digest.go b/gps/verify/digest.go similarity index 88% rename from gps/pkgtree/digest.go rename to gps/verify/digest.go index 31ed243ab3..a424f50daf 100644 --- a/gps/pkgtree/digest.go +++ b/gps/verify/digest.go @@ -2,21 +2,30 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package pkgtree +package verify import ( "bytes" "crypto/sha256" "encoding/binary" + "encoding/hex" + "fmt" "hash" "io" "os" "path/filepath" "strconv" + "strings" "github.com/pkg/errors" ) +// HashVersion is an arbitrary number that identifies the hash algorithm used by +// the directory hasher. +// +// 1: SHA256, as implemented in crypto/sha256 +const HashVersion = 1 + const osPathSeparator = string(filepath.Separator) // lineEndingReader is a `io.Reader` that converts CRLF sequences to LF. @@ -151,7 +160,7 @@ type dirWalkClosure struct { // While filepath.Walk could have been used, that standard library function // skips symbolic links, and for now, we want the hash to include the symbolic // link referents. -func DigestFromDirectory(osDirname string) ([]byte, error) { +func DigestFromDirectory(osDirname string) (VersionedDigest, error) { osDirname = filepath.Clean(osDirname) // Create a single hash instance for the entire operation, rather than a new @@ -255,10 +264,15 @@ func DigestFromDirectory(osDirname string) ([]byte, error) { } return err }) + if err != nil { - return nil, err + return VersionedDigest{}, err } - return closure.someHash.Sum(nil), nil + + return VersionedDigest{ + HashVersion: HashVersion, + Digest: closure.someHash.Sum(nil), + }, nil } // VendorStatus represents one of a handful of possible status conditions for a @@ -280,12 +294,17 @@ const ( // EmptyDigestInLock is used when the digest for a dependency listed in the // lock file is the empty string. While this is a special case of - // DigestMismatchInLock, keeping both cases discrete is a desired feature. + // DigestMismatchInLock, separating the cases is a desired feature. EmptyDigestInLock // DigestMismatchInLock is used when the digest for a dependency listed in // the lock file does not match what is calculated from the file system. DigestMismatchInLock + + // HashVersionMismatch indicates that the hashing algorithm used to generate + // the digest being compared against is not the same as the one used by the + // current program. + HashVersionMismatch ) func (ls VendorStatus) String() string { @@ -300,6 +319,8 @@ func (ls VendorStatus) String() string { return "empty digest in lock" case DigestMismatchInLock: return "mismatch" + case HashVersionMismatch: + return "hasher changed" } return "unknown" } @@ -315,7 +336,44 @@ type fsnode struct { myIndex, parentIndex int // index of this node and its parent in the tree's slice } -// VerifyDepTree verifies a dependency tree according to expected digest sums, +// VersionedDigest comprises both a hash digest, and a simple integer indicating +// the version of the hash algorithm that produced the digest. +type VersionedDigest struct { + HashVersion int + Digest []byte +} + +func (vd VersionedDigest) String() string { + return fmt.Sprintf("%s:%s", strconv.Itoa(vd.HashVersion), hex.EncodeToString(vd.Digest)) +} + +// IsEmpty indicates if the VersionedDigest is the zero value. +func (vd VersionedDigest) IsEmpty() bool { + return vd.HashVersion == 0 && len(vd.Digest) == 0 +} + +// ParseVersionedDigest decodes the string representation of versioned digest +// information - a colon-separated string with a version number in the first +// part and the hex-encdoed hash digest in the second - as a VersionedDigest. +func ParseVersionedDigest(input string) (VersionedDigest, error) { + var vd VersionedDigest + var err error + + parts := strings.Split(input, ":") + if len(parts) != 2 { + return VersionedDigest{}, errors.Errorf("expected two colon-separated components in the versioned hash digest, got %q", input) + } + if vd.Digest, err = hex.DecodeString(parts[1]); err != nil { + return VersionedDigest{}, err + } + if vd.HashVersion, err = strconv.Atoi(parts[0]); err != nil { + return VersionedDigest{}, err + } + + return vd, nil +} + +// CheckDepTree verifies a dependency tree according to expected digest sums, // and returns an associative array of file system nodes and their respective // vendor status conditions. // @@ -325,7 +383,7 @@ type fsnode struct { // platform where the file system path separator is a character other than // solidus, one particular dependency would be represented as // "github.com/alice/alice1". -func VerifyDepTree(osDirname string, wantSums map[string][]byte) (map[string]VendorStatus, error) { +func CheckDepTree(osDirname string, wantDigests map[string]VersionedDigest) (map[string]VendorStatus, error) { osDirname = filepath.Clean(osDirname) // Ensure top level pathname is a directory @@ -387,7 +445,7 @@ func VerifyDepTree(osDirname string, wantSums map[string][]byte) (map[string]Ven // project is later found while traversing the vendor root hierarchy, its // status will be updated to reflect whether its digest is empty, or, // whether or not it matches the expected digest. - for slashPathname := range wantSums { + for slashPathname := range wantDigests { slashStatus[slashPathname] = NotInTree } @@ -400,14 +458,18 @@ func VerifyDepTree(osDirname string, wantSums map[string][]byte) (map[string]Ven slashPathname := filepath.ToSlash(currentNode.osRelative) osPathname := filepath.Join(osDirname, currentNode.osRelative) - if expectedSum, ok := wantSums[slashPathname]; ok { + if expectedSum, ok := wantDigests[slashPathname]; ok { ls := EmptyDigestInLock - if len(expectedSum) > 0 { + if expectedSum.HashVersion != HashVersion { + if !expectedSum.IsEmpty() { + ls = HashVersionMismatch + } + } else if len(expectedSum.Digest) > 0 { projectSum, err := DigestFromDirectory(osPathname) if err != nil { return nil, errors.Wrap(err, "cannot compute dependency hash") } - if bytes.Equal(projectSum, expectedSum) { + if bytes.Equal(projectSum.Digest, expectedSum.Digest) { ls = NoMismatch } else { ls = DigestMismatchInLock diff --git a/gps/pkgtree/digest_test.go b/gps/verify/digest_test.go similarity index 74% rename from gps/pkgtree/digest_test.go rename to gps/verify/digest_test.go index 0569340357..36fccb3dcb 100644 --- a/gps/pkgtree/digest_test.go +++ b/gps/verify/digest_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package pkgtree +package verify import ( "bytes" @@ -122,7 +122,7 @@ func TestDigestFromDirectory(t *testing.T) { if err != nil { t.Fatal(err) } - if !bytes.Equal(got, want) { + if !bytes.Equal(got.Digest, want) { t.Errorf("\n(GOT):\n\t%#v\n(WNT):\n\t%#v", got, want) } }) @@ -133,7 +133,7 @@ func TestDigestFromDirectory(t *testing.T) { if err != nil { t.Fatal(err) } - if !bytes.Equal(got, want) { + if !bytes.Equal(got.Digest, want) { t.Errorf("\n(GOT):\n\t%#v\n(WNT):\n\t%#v", got, want) } }) @@ -152,15 +152,6 @@ func TestVerifyDepTree(t *testing.T) { "launchpad.net/match": {0x7e, 0x10, 0x6, 0x2f, 0x8, 0x3, 0x3c, 0x76, 0xae, 0xbc, 0xa4, 0xc9, 0xec, 0x73, 0x67, 0x15, 0x70, 0x2b, 0x0, 0x89, 0x27, 0xbb, 0x61, 0x9d, 0xc7, 0xc3, 0x39, 0x46, 0x3, 0x91, 0xb7, 0x3b}, } - status, err := VerifyDepTree(vendorRoot, wantSums) - if err != nil { - t.Fatal(err) - } - - if got, want := len(status), 7; got != want { - t.Errorf("\n(GOT): %v; (WNT): %v", got, want) - } - checkStatus := func(t *testing.T, status map[string]VendorStatus, key string, want VendorStatus) { got, ok := status[key] if !ok { @@ -172,25 +163,74 @@ func TestVerifyDepTree(t *testing.T) { } } - checkStatus(t, status, "github.com/alice/match", NoMismatch) - checkStatus(t, status, "github.com/alice/mismatch", DigestMismatchInLock) - checkStatus(t, status, "github.com/alice/notInLock", NotInLock) - checkStatus(t, status, "github.com/bob/match", NoMismatch) - checkStatus(t, status, "github.com/bob/emptyDigest", EmptyDigestInLock) - checkStatus(t, status, "github.com/charlie/notInTree", NotInTree) - checkStatus(t, status, "launchpad.net/match", NoMismatch) - - if t.Failed() { - for k, want := range wantSums { - got, err := DigestFromDirectory(filepath.Join(vendorRoot, k)) - if err != nil { - t.Error(err) + t.Run("normal", func(t *testing.T) { + t.Parallel() + wantDigests := make(map[string]VersionedDigest) + for k, v := range wantSums { + wantDigests[k] = VersionedDigest{ + HashVersion: HashVersion, + Digest: v, } - if !bytes.Equal(got, want) { - t.Errorf("%q\n(GOT):\n\t%#v\n(WNT):\n\t%#v", k, got, want) + } + + status, err := CheckDepTree(vendorRoot, wantDigests) + if err != nil { + t.Fatal(err) + } + + if got, want := len(status), 7; got != want { + t.Errorf("Unexpected result count from VerifyDepTree:\n\t(GOT): %v\n\t(WNT): %v", got, want) + } + + checkStatus(t, status, "github.com/alice/match", NoMismatch) + checkStatus(t, status, "github.com/alice/mismatch", DigestMismatchInLock) + checkStatus(t, status, "github.com/alice/notInLock", NotInLock) + checkStatus(t, status, "github.com/bob/match", NoMismatch) + checkStatus(t, status, "github.com/bob/emptyDigest", EmptyDigestInLock) + checkStatus(t, status, "github.com/charlie/notInTree", NotInTree) + checkStatus(t, status, "launchpad.net/match", NoMismatch) + + if t.Failed() { + for k, want := range wantSums { + got, err := DigestFromDirectory(filepath.Join(vendorRoot, k)) + if err != nil { + t.Error(err) + } + if !bytes.Equal(got.Digest, want) { + t.Errorf("Digest mismatch for %q\n(GOT):\n\t%#v\n(WNT):\n\t%#v", k, got, want) + } } } - } + + }) + + t.Run("hashv-mismatch", func(t *testing.T) { + t.Parallel() + wantDigests := make(map[string]VersionedDigest) + for k, v := range wantSums { + wantDigests[k] = VersionedDigest{ + HashVersion: HashVersion + 1, + Digest: v, + } + } + + status, err := CheckDepTree(vendorRoot, wantDigests) + if err != nil { + t.Fatal(err) + } + + if got, want := len(status), 7; got != want { + t.Errorf("Unexpected result count from VerifyDepTree:\n\t(GOT): %v\n\t(WNT): %v", got, want) + } + + checkStatus(t, status, "github.com/alice/match", HashVersionMismatch) + checkStatus(t, status, "github.com/alice/mismatch", HashVersionMismatch) + checkStatus(t, status, "github.com/alice/notInLock", NotInLock) + checkStatus(t, status, "github.com/bob/match", HashVersionMismatch) + checkStatus(t, status, "github.com/bob/emptyDigest", HashVersionMismatch) + checkStatus(t, status, "github.com/charlie/notInTree", NotInTree) + checkStatus(t, status, "launchpad.net/match", HashVersionMismatch) + }) } func BenchmarkDigestFromDirectory(b *testing.B) { @@ -212,7 +252,7 @@ func BenchmarkVerifyDepTree(b *testing.B) { prefix := filepath.Join(os.Getenv("GOPATH"), "src") for i := 0; i < b.N; i++ { - _, err := VerifyDepTree(prefix, nil) + _, err := CheckDepTree(prefix, nil) if err != nil { b.Fatal(err) } diff --git a/gps/pkgtree/dirwalk.go b/gps/verify/dirwalk.go similarity index 99% rename from gps/pkgtree/dirwalk.go rename to gps/verify/dirwalk.go index 350c1606c3..4010c4a03f 100644 --- a/gps/pkgtree/dirwalk.go +++ b/gps/verify/dirwalk.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package pkgtree +package verify import ( "os" diff --git a/gps/verify/helper_types_test.go b/gps/verify/helper_types_test.go new file mode 100644 index 0000000000..75d4ddf6f5 --- /dev/null +++ b/gps/verify/helper_types_test.go @@ -0,0 +1,119 @@ +// Copyright 2018 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 verify + +import ( + "github.com/golang/dep/gps" + "github.com/golang/dep/gps/pkgtree" +) + +// mkPI creates a ProjectIdentifier with the ProjectRoot as the provided +// string, and the Source unset. +// +// Call normalize() on the returned value if you need the Source to be be +// equal to the ProjectRoot. +func mkPI(root string) gps.ProjectIdentifier { + return gps.ProjectIdentifier{ + ProjectRoot: gps.ProjectRoot(root), + } +} + +type safeLock struct { + p []gps.LockedProject + i []string +} + +func (sl safeLock) InputImports() []string { + return sl.i +} + +func (sl safeLock) Projects() []gps.LockedProject { + return sl.p +} + +func (sl safeLock) dup() safeLock { + sl2 := safeLock{ + i: make([]string, len(sl.i)), + p: make([]gps.LockedProject, 0, len(sl.p)), + } + copy(sl2.i, sl.i) + + for _, lp := range sl.p { + // Only for use with VerifiableProjects. + sl2.p = append(sl2.p, lp.(VerifiableProject).dup()) + } + + return sl2 +} + +func (vp VerifiableProject) dup() VerifiableProject { + pkglist := make([]string, len(vp.Packages())) + copy(pkglist, vp.Packages()) + hashbytes := make([]byte, len(vp.Digest.Digest)) + copy(hashbytes, vp.Digest.Digest) + + return VerifiableProject{ + LockedProject: gps.NewLockedProject(vp.Ident(), vp.Version(), pkglist), + PruneOpts: vp.PruneOpts, + Digest: VersionedDigest{ + HashVersion: vp.Digest.HashVersion, + Digest: hashbytes, + }, + } +} + +// simpleRootManifest exists so that we have a safe value to swap into solver +// params when a nil Manifest is provided. +type simpleRootManifest struct { + c, ovr gps.ProjectConstraints + ig *pkgtree.IgnoredRuleset + req map[string]bool +} + +func (m simpleRootManifest) DependencyConstraints() gps.ProjectConstraints { + return m.c +} +func (m simpleRootManifest) Overrides() gps.ProjectConstraints { + return m.ovr +} +func (m simpleRootManifest) IgnoredPackages() *pkgtree.IgnoredRuleset { + return m.ig +} +func (m simpleRootManifest) RequiredPackages() map[string]bool { + return m.req +} + +func (m simpleRootManifest) dup() simpleRootManifest { + m2 := simpleRootManifest{ + c: make(gps.ProjectConstraints), + ovr: make(gps.ProjectConstraints), + ig: pkgtree.NewIgnoredRuleset(m.ig.ToSlice()), + req: make(map[string]bool), + } + + for k, v := range m.c { + m2.c[k] = v + } + + for k, v := range m.ovr { + m2.ovr[k] = v + } + + for k := range m.req { + m2.req[k] = true + } + + return m2 +} + +func newVerifiableProject(id gps.ProjectIdentifier, v gps.Version, pkgs []string) VerifiableProject { + return VerifiableProject{ + LockedProject: gps.NewLockedProject(id, v, pkgs), + Digest: VersionedDigest{ + HashVersion: HashVersion, + Digest: []byte("something"), + }, + } +} diff --git a/gps/verify/lock.go b/gps/verify/lock.go new file mode 100644 index 0000000000..d6742a8655 --- /dev/null +++ b/gps/verify/lock.go @@ -0,0 +1,18 @@ +// Copyright 2018 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 verify + +import ( + "github.com/golang/dep/gps" +) + +// VerifiableProject composes a LockedProject to indicate what the hash digest +// of a file tree for that LockedProject should be, given the PruneOptions and +// the list of packages. +type VerifiableProject struct { + gps.LockedProject + PruneOpts gps.PruneOptions + Digest VersionedDigest +} diff --git a/gps/verify/lockdiff.go b/gps/verify/lockdiff.go new file mode 100644 index 0000000000..60928d0876 --- /dev/null +++ b/gps/verify/lockdiff.go @@ -0,0 +1,423 @@ +// Copyright 2018 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 verify + +import ( + "bytes" + "sort" + "strings" + + "github.com/golang/dep/gps" +) + +// DeltaDimension defines a bitset enumerating all of the different dimensions +// along which a Lock, and its constitutent components, can change. +type DeltaDimension uint32 + +// Each flag represents an ortohgonal dimension along which Locks can vary with +// respect to each other. +const ( + InputImportsChanged DeltaDimension = 1 << iota + ProjectAdded + ProjectRemoved + SourceChanged + VersionChanged + RevisionChanged + PackagesChanged + PruneOptsChanged + HashVersionChanged + HashChanged + AnyChanged = (1 << iota) - 1 +) + +// LockDelta represents all possible differences between two Locks. +type LockDelta struct { + AddedImportInputs []string + RemovedImportInputs []string + ProjectDeltas map[gps.ProjectRoot]LockedProjectDelta +} + +// LockedProjectDelta represents all possible state changes of a LockedProject +// within a Lock. It encapsulates the property-level differences represented by +// a LockedProjectPropertiesDelta, but can also represent existence deltas - a +// given name came to exist, or cease to exist, across two Locks. +type LockedProjectDelta struct { + Name gps.ProjectRoot + ProjectRemoved, ProjectAdded bool + LockedProjectPropertiesDelta +} + +// LockedProjectPropertiesDelta represents all possible differences between the +// properties of two LockedProjects. It can represent deltas for +// VerifiableProject properties, as well. +type LockedProjectPropertiesDelta struct { + PackagesAdded, PackagesRemoved []string + VersionBefore, VersionAfter gps.UnpairedVersion + RevisionBefore, RevisionAfter gps.Revision + SourceBefore, SourceAfter string + PruneOptsBefore, PruneOptsAfter gps.PruneOptions + HashChanged, HashVersionChanged bool +} + +// DiffLocks compares two locks and computes a semantically rich delta between +// them. +func DiffLocks(l1, l2 gps.Lock) LockDelta { + // Default nil locks to empty locks, so that we can still generate a diff. + if l1 == nil { + if l2 == nil { + // But both locks being nil results in an empty delta. + return LockDelta{} + } + l1 = gps.SimpleLock{} + } + if l2 == nil { + l2 = gps.SimpleLock{} + } + + p1, p2 := l1.Projects(), l2.Projects() + + p1 = sortLockedProjects(p1) + p2 = sortLockedProjects(p2) + + diff := LockDelta{ + ProjectDeltas: make(map[gps.ProjectRoot]LockedProjectDelta), + } + + var i2next int + for i1 := 0; i1 < len(p1); i1++ { + lp1 := p1[i1] + pr1 := lp1.Ident().ProjectRoot + + lpd := LockedProjectDelta{ + Name: pr1, + } + + for i2 := i2next; i2 < len(p2); i2++ { + lp2 := p2[i2] + pr2 := lp2.Ident().ProjectRoot + + switch strings.Compare(string(pr1), string(pr2)) { + case 0: // Found a matching project + lpd.LockedProjectPropertiesDelta = DiffLockedProjectProperties(lp1, lp2) + i2next = i2 + 1 // Don't visit this project again + case +1: // Found a new project + diff.ProjectDeltas[pr2] = LockedProjectDelta{ + Name: pr2, + ProjectAdded: true, + } + i2next = i2 + 1 // Don't visit this project again + continue // Keep looking for a matching project + case -1: // Project has been removed, handled below + lpd.ProjectRemoved = true + } + + break // Done evaluating this project, move onto the next + } + + diff.ProjectDeltas[pr1] = lpd + } + + // Anything that still hasn't been evaluated are adds + for i2 := i2next; i2 < len(p2); i2++ { + lp2 := p2[i2] + pr2 := lp2.Ident().ProjectRoot + diff.ProjectDeltas[pr2] = LockedProjectDelta{ + Name: pr2, + ProjectAdded: true, + } + } + + diff.AddedImportInputs, diff.RemovedImportInputs = findAddedAndRemoved(l1.InputImports(), l2.InputImports()) + + return diff +} + +func findAddedAndRemoved(l1, l2 []string) (add, remove []string) { + // Computing package add/removes might be optimizable to O(n) (?), but it's + // not critical path for any known case, so not worth the effort right now. + p1, p2 := make(map[string]bool, len(l1)), make(map[string]bool, len(l2)) + + for _, pkg := range l1 { + p1[pkg] = true + } + for _, pkg := range l2 { + p2[pkg] = true + } + + for pkg := range p1 { + if !p2[pkg] { + remove = append(remove, pkg) + } + } + for pkg := range p2 { + if !p1[pkg] { + add = append(add, pkg) + } + } + + return add, remove +} + +// DiffLockedProjectProperties takes two gps.LockedProject and computes a delta +// for each of their component properties. +// +// This function is focused exclusively on the properties of a LockedProject. As +// such, it does not compare the ProjectRoot part of the LockedProject's +// ProjectIdentifier, as those are names, and the concern here is a difference +// in properties, not intrinsic identity. +func DiffLockedProjectProperties(lp1, lp2 gps.LockedProject) LockedProjectPropertiesDelta { + ld := LockedProjectPropertiesDelta{ + SourceBefore: lp1.Ident().Source, + SourceAfter: lp2.Ident().Source, + } + + ld.PackagesAdded, ld.PackagesRemoved = findAddedAndRemoved(lp1.Packages(), lp2.Packages()) + + switch v := lp1.Version().(type) { + case gps.PairedVersion: + ld.VersionBefore, ld.RevisionBefore = v.Unpair(), v.Revision() + case gps.Revision: + ld.RevisionBefore = v + case gps.UnpairedVersion: + // This should ideally never happen + ld.VersionBefore = v + } + + switch v := lp2.Version().(type) { + case gps.PairedVersion: + ld.VersionAfter, ld.RevisionAfter = v.Unpair(), v.Revision() + case gps.Revision: + ld.RevisionAfter = v + case gps.UnpairedVersion: + // This should ideally never happen + ld.VersionAfter = v + } + + vp1, ok1 := lp1.(VerifiableProject) + vp2, ok2 := lp2.(VerifiableProject) + + if ok1 && ok2 { + ld.PruneOptsBefore, ld.PruneOptsAfter = vp1.PruneOpts, vp2.PruneOpts + + if vp1.Digest.HashVersion != vp2.Digest.HashVersion { + ld.HashVersionChanged = true + } + if !bytes.Equal(vp1.Digest.Digest, vp2.Digest.Digest) { + ld.HashChanged = true + } + } else if ok1 { + ld.PruneOptsBefore = vp1.PruneOpts + ld.HashVersionChanged = true + ld.HashChanged = true + } else if ok2 { + ld.PruneOptsAfter = vp2.PruneOpts + ld.HashVersionChanged = true + ld.HashChanged = true + } + + return ld +} + +// Changed indicates whether the delta contains a change along the dimensions +// with their corresponding bits set. +// +// This implementation checks the topmost-level Lock properties +func (ld LockDelta) Changed(dims DeltaDimension) bool { + if dims&InputImportsChanged != 0 && (len(ld.AddedImportInputs) > 0 || len(ld.RemovedImportInputs) > 0) { + return true + } + + for _, ld := range ld.ProjectDeltas { + if ld.Changed(dims & ^InputImportsChanged) { + return true + } + } + + return false +} + +// Changes returns a bitset indicating the dimensions along which deltas exist across +// all contents of the LockDelta. +// +// This recurses down into the individual LockedProjectDeltas contained within +// the LockDelta. A single delta along a particular dimension from a single +// project is sufficient to flip the bit on for that dimension. +func (ld LockDelta) Changes() DeltaDimension { + var dd DeltaDimension + if len(ld.AddedImportInputs) > 0 || len(ld.RemovedImportInputs) > 0 { + dd |= InputImportsChanged + } + + for _, ld := range ld.ProjectDeltas { + dd |= ld.Changes() + } + + return dd +} + +// Changed indicates whether the delta contains a change along the dimensions +// with their corresponding bits set. +// +// For example, if only the Revision changed, and this method is called with +// SourceChanged | VersionChanged, it will return false; if it is called with +// VersionChanged | RevisionChanged, it will return true. +func (ld LockedProjectDelta) Changed(dims DeltaDimension) bool { + if dims&ProjectAdded != 0 && ld.WasAdded() { + return true + } + + if dims&ProjectRemoved != 0 && ld.WasRemoved() { + return true + } + + return ld.LockedProjectPropertiesDelta.Changed(dims & ^ProjectAdded & ^ProjectRemoved) +} + +// Changes returns a bitset indicating the dimensions along which there were +// changes between the compared LockedProjects. This includes both +// existence-level deltas (add/remove) and property-level deltas. +func (ld LockedProjectDelta) Changes() DeltaDimension { + var dd DeltaDimension + if ld.WasAdded() { + dd |= ProjectAdded + } + + if ld.WasRemoved() { + dd |= ProjectRemoved + } + + return dd | ld.LockedProjectPropertiesDelta.Changes() +} + +// WasRemoved returns true if the named project existed in the first lock, but +// did not exist in the second lock. +func (ld LockedProjectDelta) WasRemoved() bool { + return ld.ProjectRemoved +} + +// WasAdded returns true if the named project did not exist in the first lock, +// but did exist in the second lock. +func (ld LockedProjectDelta) WasAdded() bool { + return ld.ProjectAdded +} + +// Changed indicates whether the delta contains a change along the dimensions +// with their corresponding bits set. +// +// For example, if only the Revision changed, and this method is called with +// SourceChanged | VersionChanged, it will return false; if it is called with +// VersionChanged | RevisionChanged, it will return true. +func (ld LockedProjectPropertiesDelta) Changed(dims DeltaDimension) bool { + if dims&SourceChanged != 0 && ld.SourceChanged() { + return true + } + if dims&RevisionChanged != 0 && ld.RevisionChanged() { + return true + } + if dims&PruneOptsChanged != 0 && ld.PruneOptsChanged() { + return true + } + if dims&HashChanged != 0 && ld.HashChanged { + return true + } + if dims&HashVersionChanged != 0 && ld.HashVersionChanged { + return true + } + if dims&VersionChanged != 0 && ld.VersionChanged() { + return true + } + if dims&PackagesChanged != 0 && ld.PackagesChanged() { + return true + } + + return false +} + +// Changes returns a bitset indicating the dimensions along which there were +// changes between the compared LockedProjects. +func (ld LockedProjectPropertiesDelta) Changes() DeltaDimension { + var dd DeltaDimension + if ld.SourceChanged() { + dd |= SourceChanged + } + if ld.RevisionChanged() { + dd |= RevisionChanged + } + if ld.PruneOptsChanged() { + dd |= PruneOptsChanged + } + if ld.HashChanged { + dd |= HashChanged + } + if ld.HashVersionChanged { + dd |= HashVersionChanged + } + if ld.VersionChanged() { + dd |= VersionChanged + } + if ld.PackagesChanged() { + dd |= PackagesChanged + } + + return dd +} + +// SourceChanged returns true if the source field differed between the first and +// second locks. +func (ld LockedProjectPropertiesDelta) SourceChanged() bool { + return ld.SourceBefore != ld.SourceAfter +} + +// VersionChanged returns true if the version property differed between the +// first and second locks. In addition to simple changes (e.g. 1.0.1 -> 1.0.2), +// this also includes all possible version type changes either going from a +// paired version to a plain revision, or the reverse direction, or the type of +// unpaired version changing (e.g. branch -> semver). +func (ld LockedProjectPropertiesDelta) VersionChanged() bool { + if ld.VersionBefore == nil && ld.VersionAfter == nil { + return false + } else if (ld.VersionBefore == nil || ld.VersionAfter == nil) || (ld.VersionBefore.Type() != ld.VersionAfter.Type()) { + return true + } else if !ld.VersionBefore.Matches(ld.VersionAfter) { + return true + } + + return false +} + +// RevisionChanged returns true if the revision property differed between the +// first and second locks. +func (ld LockedProjectPropertiesDelta) RevisionChanged() bool { + return ld.RevisionBefore != ld.RevisionAfter +} + +// PackagesChanged returns true if the package set gained or lost members (or +// both) between the first and second locks. +func (ld LockedProjectPropertiesDelta) PackagesChanged() bool { + return len(ld.PackagesAdded) > 0 || len(ld.PackagesRemoved) > 0 +} + +// PruneOptsChanged returns true if the pruning flags for the project changed +// between teh first and second locks. +func (ld LockedProjectPropertiesDelta) PruneOptsChanged() bool { + return ld.PruneOptsBefore != ld.PruneOptsAfter +} + +// sortLockedProjects returns a sorted copy of lps, or itself if already sorted. +func sortLockedProjects(lps []gps.LockedProject) []gps.LockedProject { + if len(lps) <= 1 || sort.SliceIsSorted(lps, func(i, j int) bool { + return lps[i].Ident().Less(lps[j].Ident()) + }) { + return lps + } + + cp := make([]gps.LockedProject, len(lps)) + copy(cp, lps) + + sort.Slice(cp, func(i, j int) bool { + return cp[i].Ident().Less(cp[j].Ident()) + }) + return cp +} diff --git a/gps/verify/lockdiff_test.go b/gps/verify/lockdiff_test.go new file mode 100644 index 0000000000..52955a392d --- /dev/null +++ b/gps/verify/lockdiff_test.go @@ -0,0 +1,483 @@ +// Copyright 2018 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 verify + +import ( + "fmt" + "math/bits" + "strings" + "testing" + + "github.com/golang/dep/gps" +) + +func contains(haystack []string, needle string) bool { + for _, str := range haystack { + if str == needle { + return true + } + } + return false +} + +func (dd DeltaDimension) String() string { + var parts []string + + for dd != 0 { + index := bits.TrailingZeros32(uint32(dd)) + dd &= ^(1 << uint(index)) + + switch DeltaDimension(1 << uint(index)) { + case InputImportsChanged: + parts = append(parts, "input imports") + case ProjectAdded: + parts = append(parts, "project added") + case ProjectRemoved: + parts = append(parts, "project removed") + case SourceChanged: + parts = append(parts, "source changed") + case VersionChanged: + parts = append(parts, "version changed") + case RevisionChanged: + parts = append(parts, "revision changed") + case PackagesChanged: + parts = append(parts, "packages changed") + case PruneOptsChanged: + parts = append(parts, "pruneopts changed") + case HashVersionChanged: + parts = append(parts, "hash version changed") + case HashChanged: + parts = append(parts, "hash digest changed") + } + } + + return strings.Join(parts, ", ") +} + +func TestLockDelta(t *testing.T) { + fooversion := gps.NewVersion("v1.0.0").Pair("foorev1") + bazversion := gps.NewVersion("v2.0.0").Pair("bazrev1") + transver := gps.NewVersion("v0.5.0").Pair("transrev1") + l := safeLock{ + i: []string{"foo.com/bar", "baz.com/qux"}, + p: []gps.LockedProject{ + newVerifiableProject(mkPI("foo.com/bar"), fooversion, []string{".", "subpkg"}), + newVerifiableProject(mkPI("baz.com/qux"), bazversion, []string{".", "other"}), + newVerifiableProject(mkPI("transitive.com/dependency"), transver, []string{"."}), + }, + } + + var dup lockTransformer = func(l safeLock) safeLock { + return l.dup() + } + + tt := map[string]struct { + lt lockTransformer + delta DeltaDimension + checkfn func(*testing.T, LockDelta) + }{ + "ident": { + lt: dup, + }, + "added import": { + lt: dup.addII("other.org"), + delta: InputImportsChanged, + }, + "added import 2x": { + lt: dup.addII("other.org").addII("andsomethingelse.com/wowie"), + delta: InputImportsChanged, + checkfn: func(t *testing.T, ld LockDelta) { + if !contains(ld.AddedImportInputs, "other.org") { + t.Error("first added input import missing") + } + if !contains(ld.AddedImportInputs, "andsomethingelse.com/wowie") { + t.Error("first added input import missing") + } + }, + }, + "removed import": { + lt: dup.rmII("baz.com/qux"), + delta: InputImportsChanged, + checkfn: func(t *testing.T, ld LockDelta) { + if !contains(ld.RemovedImportInputs, "baz.com/qux") { + t.Error("removed input import missing") + } + }, + }, + "add project": { + lt: dup.addDumbProject("madeup.org"), + delta: ProjectAdded, + }, + "remove project": { + lt: dup.rmProject("foo.com/bar"), + delta: ProjectRemoved, + }, + "all": { + lt: dup.addII("other.org").rmII("baz.com/qux").addDumbProject("zebrafun.org").rmProject("foo.com/bar"), + delta: InputImportsChanged | ProjectRemoved | ProjectAdded, + }, + } + + for name, fix := range tt { + fix := fix + t.Run(name, func(t *testing.T) { + fixl := fix.lt(l) + ld := DiffLocks(l, fixl) + + if !ld.Changed(AnyChanged) && fix.delta != 0 { + t.Errorf("Changed() reported false when expecting some dimensions to be changed: %s", fix.delta) + } else if ld.Changed(AnyChanged) && fix.delta == 0 { + t.Error("Changed() reported true when expecting no changes") + } + if ld.Changed(AnyChanged & ^fix.delta) { + t.Errorf("Changed() reported true when checking along not-expected dimensions: %s", ld.Changes() & ^fix.delta) + } + + gotdelta := ld.Changes() + if fix.delta & ^gotdelta != 0 { + t.Errorf("wanted change in some dimensions that were unchanged: %s", fix.delta & ^gotdelta) + } + if gotdelta & ^fix.delta != 0 { + t.Errorf("did not want change in some dimensions that were changed: %s", gotdelta & ^fix.delta) + } + + if fix.checkfn != nil { + fix.checkfn(t, ld) + } + }) + } +} + +func TestLockedProjectPropertiesDelta(t *testing.T) { + fooversion, foorev := gps.NewVersion("v1.0.0"), gps.Revision("foorev1") + foopair := fooversion.Pair(foorev) + foovp := VerifiableProject{ + LockedProject: gps.NewLockedProject(mkPI("foo.com/project"), foopair, []string{".", "subpkg"}), + PruneOpts: gps.PruneNestedVendorDirs, + Digest: VersionedDigest{ + HashVersion: HashVersion, + Digest: []byte("foobytes"), + }, + } + var dup lockedProjectTransformer = func(lp gps.LockedProject) gps.LockedProject { + return lp.(VerifiableProject).dup() + } + + tt := map[string]struct { + lt1, lt2 lockedProjectTransformer + delta DeltaDimension + checkfn func(*testing.T, LockedProjectPropertiesDelta) + }{ + "ident": { + lt1: dup, + }, + "add pkg": { + lt1: dup.addPkg("whatev"), + delta: PackagesChanged, + }, + "rm pkg": { + lt1: dup.rmPkg("subpkg"), + delta: PackagesChanged, + }, + "add and rm pkg": { + lt1: dup.rmPkg("subpkg").addPkg("whatev"), + delta: PackagesChanged, + checkfn: func(t *testing.T, ld LockedProjectPropertiesDelta) { + if !contains(ld.PackagesAdded, "whatev") { + t.Error("added pkg missing from list") + } + if !contains(ld.PackagesRemoved, "subpkg") { + t.Error("removed pkg missing from list") + } + }, + }, + "add source": { + lt1: dup.setSource("somethingelse"), + delta: SourceChanged, + }, + "remove source": { + lt1: dup.setSource("somethingelse"), + lt2: dup, + delta: SourceChanged, + }, + "to rev only": { + lt1: dup.setVersion(foorev), + delta: VersionChanged, + }, + "from rev only": { + lt1: dup.setVersion(foorev), + lt2: dup, + delta: VersionChanged, + }, + "to new rev only": { + lt1: dup.setVersion(gps.Revision("newrev")), + delta: VersionChanged | RevisionChanged, + }, + "from new rev only": { + lt1: dup.setVersion(gps.Revision("newrev")), + lt2: dup, + delta: VersionChanged | RevisionChanged, + }, + "version change": { + lt1: dup.setVersion(gps.NewVersion("v0.5.0").Pair(foorev)), + delta: VersionChanged, + }, + "version change to norev": { + lt1: dup.setVersion(gps.NewVersion("v0.5.0")), + delta: VersionChanged | RevisionChanged, + }, + "version change from norev": { + lt1: dup.setVersion(gps.NewVersion("v0.5.0")), + lt2: dup.setVersion(gps.NewVersion("v0.5.0").Pair(foorev)), + delta: RevisionChanged, + }, + "to branch": { + lt1: dup.setVersion(gps.NewBranch("master").Pair(foorev)), + delta: VersionChanged, + }, + "to branch new rev": { + lt1: dup.setVersion(gps.NewBranch("master").Pair(gps.Revision("newrev"))), + delta: VersionChanged | RevisionChanged, + }, + "to empty prune opts": { + lt1: dup.setPruneOpts(0), + delta: PruneOptsChanged, + }, + "from empty prune opts": { + lt1: dup.setPruneOpts(0), + lt2: dup, + delta: PruneOptsChanged, + }, + "prune opts change": { + lt1: dup.setPruneOpts(gps.PruneNestedVendorDirs | gps.PruneNonGoFiles), + delta: PruneOptsChanged, + }, + "empty digest": { + lt1: dup.setDigest(VersionedDigest{}), + delta: HashVersionChanged | HashChanged, + }, + "to empty digest": { + lt1: dup.setDigest(VersionedDigest{}), + lt2: dup, + delta: HashVersionChanged | HashChanged, + }, + "hash version changed": { + lt1: dup.setDigest(VersionedDigest{HashVersion: HashVersion + 1, Digest: []byte("foobytes")}), + delta: HashVersionChanged, + }, + "hash contents changed": { + lt1: dup.setDigest(VersionedDigest{HashVersion: HashVersion, Digest: []byte("barbytes")}), + delta: HashChanged, + }, + "to plain locked project": { + lt1: dup.toPlainLP(), + delta: PruneOptsChanged | HashChanged | HashVersionChanged, + }, + "from plain locked project": { + lt1: dup.toPlainLP(), + lt2: dup, + delta: PruneOptsChanged | HashChanged | HashVersionChanged, + }, + "all": { + lt1: dup.setDigest(VersionedDigest{}).setVersion(gps.NewBranch("master").Pair(gps.Revision("newrev"))).setPruneOpts(gps.PruneNestedVendorDirs | gps.PruneNonGoFiles).setSource("whatever"), + delta: SourceChanged | VersionChanged | RevisionChanged | PruneOptsChanged | HashChanged | HashVersionChanged, + }, + } + + for name, fix := range tt { + fix := fix + t.Run(name, func(t *testing.T) { + // Use two patterns for constructing locks to compare: if only lt1 + // is set, use foovp as the first lp and compare with the lt1 + // transforms applied. If lt2 is set, transform foovp with lt1 for + // the first lp, then transform foovp with lt2 for the second lp. + var lp1, lp2 gps.LockedProject + if fix.lt2 == nil { + lp1 = foovp + lp2 = fix.lt1(foovp) + } else { + lp1 = fix.lt1(foovp) + lp2 = fix.lt2(foovp) + } + + lppd := DiffLockedProjectProperties(lp1, lp2) + if !lppd.Changed(AnyChanged) && fix.delta != 0 { + t.Errorf("Changed() reporting false when expecting some dimensions to be changed: %s", fix.delta) + } else if lppd.Changed(AnyChanged) && fix.delta == 0 { + t.Error("Changed() reporting true when expecting no changes") + } + if lppd.Changed(AnyChanged & ^fix.delta) { + t.Errorf("Changed() reported true when checking along not-expected dimensions: %s", lppd.Changes() & ^fix.delta) + } + + gotdelta := lppd.Changes() + if fix.delta & ^gotdelta != 0 { + t.Errorf("wanted change in some dimensions that were unchanged: %s", fix.delta & ^gotdelta) + } + if gotdelta & ^fix.delta != 0 { + t.Errorf("did not want change in some dimensions that were changed: %s", gotdelta & ^fix.delta) + } + + if fix.checkfn != nil { + fix.checkfn(t, lppd) + } + }) + } +} + +type lockTransformer func(safeLock) safeLock + +func (lt lockTransformer) compose(lt2 lockTransformer) lockTransformer { + if lt == nil { + return lt2 + } + return func(l safeLock) safeLock { + return lt2(lt(l)) + } +} + +func (lt lockTransformer) addDumbProject(root string) lockTransformer { + vp := newVerifiableProject(mkPI(root), gps.NewVersion("whatever").Pair("addedrev"), []string{"."}) + return lt.compose(func(l safeLock) safeLock { + for _, lp := range l.p { + if lp.Ident().ProjectRoot == vp.Ident().ProjectRoot { + panic(fmt.Sprintf("%q already in lock", vp.Ident().ProjectRoot)) + } + } + l.p = append(l.p, vp) + return l + }) +} + +func (lt lockTransformer) rmProject(pr string) lockTransformer { + return lt.compose(func(l safeLock) safeLock { + for k, lp := range l.p { + if lp.Ident().ProjectRoot == gps.ProjectRoot(pr) { + l.p = l.p[:k+copy(l.p[k:], l.p[k+1:])] + return l + } + } + panic(fmt.Sprintf("%q not in lock", pr)) + }) +} + +func (lt lockTransformer) addII(path string) lockTransformer { + return lt.compose(func(l safeLock) safeLock { + for _, impath := range l.i { + if path == impath { + panic(fmt.Sprintf("%q already in input imports", impath)) + } + } + l.i = append(l.i, path) + return l + }) +} + +func (lt lockTransformer) rmII(path string) lockTransformer { + return lt.compose(func(l safeLock) safeLock { + for k, impath := range l.i { + if path == impath { + l.i = l.i[:k+copy(l.i[k:], l.i[k+1:])] + return l + } + } + panic(fmt.Sprintf("%q not in input imports", path)) + }) +} + +type lockedProjectTransformer func(gps.LockedProject) gps.LockedProject + +func (lpt lockedProjectTransformer) compose(lpt2 lockedProjectTransformer) lockedProjectTransformer { + if lpt == nil { + return lpt2 + } + return func(lp gps.LockedProject) gps.LockedProject { + return lpt2(lpt(lp)) + } +} + +func (lpt lockedProjectTransformer) addPkg(path string) lockedProjectTransformer { + return lpt.compose(func(lp gps.LockedProject) gps.LockedProject { + for _, pkg := range lp.Packages() { + if path == pkg { + panic(fmt.Sprintf("%q already in pkg list", path)) + } + } + + nlp := gps.NewLockedProject(lp.Ident(), lp.Version(), append(lp.Packages(), path)) + if vp, ok := lp.(VerifiableProject); ok { + vp.LockedProject = nlp + return vp + } + return nlp + }) +} + +func (lpt lockedProjectTransformer) rmPkg(path string) lockedProjectTransformer { + return lpt.compose(func(lp gps.LockedProject) gps.LockedProject { + pkglist := lp.Packages() + for k, pkg := range pkglist { + if path == pkg { + pkglist = pkglist[:k+copy(pkglist[k:], pkglist[k+1:])] + nlp := gps.NewLockedProject(lp.Ident(), lp.Version(), pkglist) + if vp, ok := lp.(VerifiableProject); ok { + vp.LockedProject = nlp + return vp + } + return nlp + } + } + panic(fmt.Sprintf("%q not in pkg list", path)) + }) +} + +func (lpt lockedProjectTransformer) setSource(source string) lockedProjectTransformer { + return lpt.compose(func(lp gps.LockedProject) gps.LockedProject { + ident := lp.Ident() + ident.Source = source + nlp := gps.NewLockedProject(ident, lp.Version(), lp.Packages()) + if vp, ok := lp.(VerifiableProject); ok { + vp.LockedProject = nlp + return vp + } + return nlp + }) +} + +func (lpt lockedProjectTransformer) setVersion(v gps.Version) lockedProjectTransformer { + return lpt.compose(func(lp gps.LockedProject) gps.LockedProject { + nlp := gps.NewLockedProject(lp.Ident(), v, lp.Packages()) + if vp, ok := lp.(VerifiableProject); ok { + vp.LockedProject = nlp + return vp + } + return nlp + }) +} + +func (lpt lockedProjectTransformer) setPruneOpts(po gps.PruneOptions) lockedProjectTransformer { + return lpt.compose(func(lp gps.LockedProject) gps.LockedProject { + vp := lp.(VerifiableProject) + vp.PruneOpts = po + return vp + }) +} + +func (lpt lockedProjectTransformer) setDigest(vd VersionedDigest) lockedProjectTransformer { + return lpt.compose(func(lp gps.LockedProject) gps.LockedProject { + vp := lp.(VerifiableProject) + vp.Digest = vd + return vp + }) +} + +func (lpt lockedProjectTransformer) toPlainLP() lockedProjectTransformer { + return lpt.compose(func(lp gps.LockedProject) gps.LockedProject { + if vp, ok := lp.(VerifiableProject); ok { + return vp.LockedProject + } + return lp + }) +} diff --git a/gps/verify/locksat.go b/gps/verify/locksat.go new file mode 100644 index 0000000000..bd0321aa21 --- /dev/null +++ b/gps/verify/locksat.go @@ -0,0 +1,199 @@ +// Copyright 2018 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 verify + +import ( + radix "github.com/armon/go-radix" + "github.com/golang/dep/gps" + "github.com/golang/dep/gps/paths" + "github.com/golang/dep/gps/pkgtree" +) + +// LockSatisfaction holds the compound result of LockSatisfiesInputs, allowing +// the caller to inspect each of several orthogonal possible types of failure. +// +// The zero value assumes that there was no input lock, which necessarily means +// the inputs were not satisfied. This zero value means we err on the side of +// failure. +type LockSatisfaction struct { + // If LockExisted is false, it indicates that a nil gps.Lock was passed to + // LockSatisfiesInputs(). + LockExisted bool + // MissingImports is the set of import paths that were present in the + // inputs but missing in the Lock. + MissingImports []string + // ExcessImports is the set of import paths that were present in the Lock + // but absent from the inputs. + ExcessImports []string + // UnmatchedConstraints reports any normal, non-override constraint rules that + // were not satisfied by the corresponding LockedProject in the Lock. + UnmetConstraints map[gps.ProjectRoot]ConstraintMismatch + // UnmatchedOverrides reports any override rules that were not satisfied by the + // corresponding LockedProject in the Lock. + UnmetOverrides map[gps.ProjectRoot]ConstraintMismatch +} + +// ConstraintMismatch is a two-tuple of a gps.Version, and a gps.Constraint that +// does not allow that version. +type ConstraintMismatch struct { + C gps.Constraint + V gps.Version +} + +// LockSatisfiesInputs determines whether the provided Lock satisfies all the +// requirements indicated by the inputs (RootManifest and PackageTree). +// +// The second parameter is expected to be the list of imports that were used to +// generate the input Lock. Without this explicit list, it is not possible to +// compute package imports that may have been removed. Figuring out that +// negative space would require exploring the entire graph to ensure there are +// no in-edges for particular imports. +func LockSatisfiesInputs(l gps.Lock, m gps.RootManifest, ptree pkgtree.PackageTree) LockSatisfaction { + if l == nil { + return LockSatisfaction{} + } + + lsat := LockSatisfaction{ + LockExisted: true, + UnmetOverrides: make(map[gps.ProjectRoot]ConstraintMismatch), + UnmetConstraints: make(map[gps.ProjectRoot]ConstraintMismatch), + } + + var ig *pkgtree.IgnoredRuleset + var req map[string]bool + if m != nil { + ig = m.IgnoredPackages() + req = m.RequiredPackages() + } + + rm, _ := ptree.ToReachMap(true, true, false, ig) + reach := rm.FlattenFn(paths.IsStandardImportPath) + + inlock := make(map[string]bool, len(l.InputImports())) + ininputs := make(map[string]bool, len(reach)+len(req)) + + type lockUnsatisfy uint8 + const ( + missingFromLock lockUnsatisfy = iota + inAdditionToLock + ) + + pkgDiff := make(map[string]lockUnsatisfy) + + for _, imp := range reach { + ininputs[imp] = true + } + + for imp := range req { + ininputs[imp] = true + } + + for _, imp := range l.InputImports() { + inlock[imp] = true + } + + for ip := range ininputs { + if !inlock[ip] { + pkgDiff[ip] = missingFromLock + } else { + // So we don't have to revisit it below + delete(inlock, ip) + } + } + + // Something in the missing list might already be in the packages list, + // because another package in the depgraph imports it. We could make a + // special case for that, but it would break the simplicity of the model and + // complicate the notion of LockSatisfaction.Passed(), so let's see if we + // can get away without it. + + for ip := range inlock { + if !ininputs[ip] { + pkgDiff[ip] = inAdditionToLock + } + } + + for ip, typ := range pkgDiff { + if typ == missingFromLock { + lsat.MissingImports = append(lsat.MissingImports, ip) + } else { + lsat.ExcessImports = append(lsat.ExcessImports, ip) + } + } + + eff := findEffectualConstraints(m, ininputs) + ovr, constraints := m.Overrides(), m.DependencyConstraints() + + for _, lp := range l.Projects() { + pr := lp.Ident().ProjectRoot + + if pp, has := ovr[pr]; has { + if !pp.Constraint.Matches(lp.Version()) { + lsat.UnmetOverrides[pr] = ConstraintMismatch{ + C: pp.Constraint, + V: lp.Version(), + } + } + // The constraint isn't considered if we have an override, + // independent of whether the override is satisfied. + continue + } + + if pp, has := constraints[pr]; has && eff[string(pr)] && !pp.Constraint.Matches(lp.Version()) { + lsat.UnmetConstraints[pr] = ConstraintMismatch{ + C: pp.Constraint, + V: lp.Version(), + } + } + } + + return lsat +} + +// Satisfied is a shortcut method that indicates whether there were any ways in +// which the Lock did not satisfy the inputs. It will return true only if the +// Lock was satisfactory in all respects vis-a-vis the inputs. +func (ls LockSatisfaction) Satisfied() bool { + if !ls.LockExisted { + return false + } + + if len(ls.MissingImports) > 0 { + return false + } + + if len(ls.ExcessImports) > 0 { + return false + } + + if len(ls.UnmetOverrides) > 0 { + return false + } + + if len(ls.UnmetConstraints) > 0 { + return false + } + + return true +} + +func findEffectualConstraints(m gps.Manifest, imports map[string]bool) map[string]bool { + eff := make(map[string]bool) + xt := radix.New() + + for pr := range m.DependencyConstraints() { + // FIXME(sdboyer) this has the trailing slash ambiguity problem; adapt + // code from the solver + xt.Insert(string(pr), nil) + } + + for imp := range imports { + if root, _, has := xt.LongestPrefix(imp); has { + eff[root] = true + } + } + + return eff +} diff --git a/gps/verify/locksat_test.go b/gps/verify/locksat_test.go new file mode 100644 index 0000000000..658e04ceb3 --- /dev/null +++ b/gps/verify/locksat_test.go @@ -0,0 +1,259 @@ +// Copyright 2018 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 verify + +import ( + "strings" + "testing" + + "github.com/golang/dep/gps" + "github.com/golang/dep/gps/pkgtree" +) + +type lockUnsatisfactionDimension uint8 + +const ( + noLock lockUnsatisfactionDimension = 1 << iota + missingImports + excessImports + unmatchedOverrides + unmatchedConstraints +) + +func (lsd lockUnsatisfactionDimension) String() string { + var parts []string + for i := uint(0); i < 5; i++ { + if lsd&(1<