Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit db8b66b

Browse files
committed
dep: Introduce lock verification logic
This mostly supplants the hash comparison-based checking, though it's still in rough form.
1 parent c7e220d commit db8b66b

File tree

3 files changed

+153
-24
lines changed

3 files changed

+153
-24
lines changed

cmd/dep/ensure.go

+9-23
Original file line numberDiff line numberDiff line change
@@ -256,13 +256,9 @@ func (cmd *ensureCommand) runDefault(ctx *dep.Ctx, args []string, p *dep.Project
256256
return err
257257
}
258258

259-
solver, err := gps.Prepare(params, sm)
260-
if err != nil {
261-
return errors.Wrap(err, "prepare solver")
262-
}
263-
264-
if p.Lock != nil && bytes.Equal(p.Lock.InputsDigest(), solver.HashInputs()) {
265-
// Memo matches, so there's probably nothing to do.
259+
if lsat, err := p.LockSatisfiesInputs(sm); err != nil {
260+
return err
261+
} else if !lsat.Passes() {
266262
if ctx.Verbose {
267263
ctx.Out.Printf("%s was already in sync with imports and %s\n", dep.LockName, dep.ManifestName)
268264
}
@@ -272,13 +268,7 @@ func (cmd *ensureCommand) runDefault(ctx *dep.Ctx, args []string, p *dep.Project
272268
return nil
273269
}
274270

275-
// TODO(sdboyer) The desired behavior at this point is to determine
276-
// whether it's necessary to write out vendor, or if it's already
277-
// consistent with the lock. However, we haven't yet determined what
278-
// that "verification" is supposed to look like (#121); in the meantime,
279-
// we unconditionally write out vendor/ so that `dep ensure`'s behavior
280-
// is maximally compatible with what it will eventually become.
281-
sw, err := dep.NewSafeWriter(nil, p.Lock, p.Lock, dep.VendorAlways, p.Manifest.PruneOptions)
271+
sw, err := dep.NewSafeWriter(nil, p.Lock, p.Lock, dep.VendorOnChanged, p.Manifest.PruneOptions)
282272
if err != nil {
283273
return err
284274
}
@@ -294,6 +284,11 @@ func (cmd *ensureCommand) runDefault(ctx *dep.Ctx, args []string, p *dep.Project
294284
return errors.WithMessage(sw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor")
295285
}
296286

287+
solver, err := gps.Prepare(params, sm)
288+
if err != nil {
289+
return errors.Wrap(err, "prepare solver")
290+
}
291+
297292
if cmd.noVendor && cmd.dryRun {
298293
return errors.New("Gopkg.lock was not up to date")
299294
}
@@ -361,15 +356,6 @@ func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project,
361356
return errors.Wrap(err, "fastpath solver prepare")
362357
}
363358

364-
// Compare the hashes. If they're not equal, bail out and ask the user to
365-
// run a straight `dep ensure` before updating. This is handholding the
366-
// user a bit, but the extra effort required is minimal, and it ensures the
367-
// user is isolating variables in the event of solve problems (was it the
368-
// "pending" changes, or the -update that caused the problem?).
369-
if !bytes.Equal(p.Lock.InputsDigest(), solver.HashInputs()) {
370-
ctx.Out.Printf("Warning: %s is out of sync with %s or the project's imports.", dep.LockName, dep.ManifestName)
371-
}
372-
373359
// When -update is specified without args, allow every dependency to change
374360
// versions, regardless of the lock file.
375361
if len(args) == 0 {

lock.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type SolveMeta struct {
2929
AnalyzerVersion int
3030
SolverName string
3131
SolverVersion int
32+
InputImports []string
3233
}
3334

3435
type rawLock struct {
@@ -100,7 +101,7 @@ func fromRawLock(raw rawLock) (*Lock, error) {
100101
ProjectRoot: gps.ProjectRoot(ld.Name),
101102
Source: ld.Source,
102103
}
103-
l.P[i] = gps.NewLockedProject(id, v, ld.Packages, ld.Imports)
104+
l.P[i] = gps.NewLockedProject(id, v, ld.Packages)
104105
}
105106

106107
return l, nil

project.go

+142
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,148 @@ func (p *Project) GetDirectDependencyNames(sm gps.SourceManager) (pkgtree.Packag
219219
return ptree, directDeps, nil
220220
}
221221

222+
type lockUnsatisfy uint8
223+
224+
const (
225+
missingFromLock lockUnsatisfy = iota
226+
inAdditionToLock
227+
)
228+
229+
type constraintMismatch struct {
230+
c gps.Constraint
231+
v gps.Version
232+
}
233+
234+
type constraintMismatches map[gps.ProjectRoot]constraintMismatch
235+
236+
type LockSatisfaction struct {
237+
nolock bool
238+
missingPkgs, excessPkgs []string
239+
pkgs map[string]lockUnsatisfy
240+
badovr, badconstraint constraintMismatches
241+
}
242+
243+
// Passed is a shortcut method to check if any problems with the evaluted lock
244+
// were identified.
245+
func (ls LockSatisfaction) Passed() bool {
246+
if ls.nolock {
247+
return false
248+
}
249+
250+
if len(ls.pkgs) > 0 {
251+
return false
252+
}
253+
254+
if len(ls.badovr) > 0 {
255+
return false
256+
}
257+
258+
if len(ls.badconstraint) > 0 {
259+
return false
260+
}
261+
262+
return true
263+
}
264+
265+
func (ls LockSatisfaction) MissingPackages() []string {
266+
return ls.missingPkgs
267+
}
268+
269+
func (ls LockSatisfaction) ExcessPackages() []string {
270+
return ls.excessPkgs
271+
}
272+
273+
func (ls LockSatisfaction) UnmatchedOverrides() map[gps.ProjectRoot]constraintMismatch {
274+
return ls.badovr
275+
}
276+
277+
func (ls LockSatisfaction) UnmatchedConstraints() map[gps.ProjectRoot]constraintMismatch {
278+
return ls.badconstraint
279+
}
280+
281+
// LockSatisfiesInputs determines whether the Project's lock satisfies all the
282+
// requirements indicated by the inputs (Manifest and RootPackageTree).
283+
func (p *Project) LockSatisfiesInputs(sm gps.SourceManager) (LockSatisfaction, error) {
284+
if p.Lock == nil {
285+
return LockSatisfaction{nolock: true}, nil
286+
}
287+
288+
ptree, err := p.ParseRootPackageTree()
289+
if err != nil {
290+
return LockSatisfaction{}, err
291+
}
292+
293+
var ig *pkgtree.IgnoredRuleset
294+
var req map[string]bool
295+
if p.Manifest != nil {
296+
ig = p.Manifest.IgnoredPackages()
297+
req = p.Manifest.RequiredPackages()
298+
}
299+
300+
rm, _ := ptree.ToReachMap(true, true, false, ig)
301+
reach := rm.FlattenFn(paths.IsStandardImportPath)
302+
303+
inlock := make(map[string]bool, len(p.Lock.SolveMeta.InputImports))
304+
ininputs := make(map[string]bool, len(reach)+len(req))
305+
306+
for _, imp := range reach {
307+
ininputs[imp] = true
308+
}
309+
310+
for imp := range req {
311+
ininputs[imp] = true
312+
}
313+
314+
for _, imp := range p.Lock.SolveMeta.InputImports {
315+
inlock[imp] = true
316+
}
317+
318+
lsat := LockSatisfaction{
319+
badovr: make(constraintMismatches),
320+
badconstraint: make(constraintMismatches),
321+
}
322+
323+
for ip := range ininputs {
324+
if !inlock[ip] {
325+
lsat.pkgs[ip] = missingFromLock
326+
} else {
327+
// So we don't have to revisit it below
328+
delete(inlock, ip)
329+
}
330+
}
331+
332+
for ip := range inlock {
333+
if !ininputs[ip] {
334+
lsat.pkgs[ip] = inAdditionToLock
335+
}
336+
}
337+
338+
ineff := make(map[string]bool)
339+
for _, pr := range p.FindIneffectualConstraints(sm) {
340+
ineff[string(pr)] = true
341+
}
342+
343+
for _, lp := range p.Lock.Projects() {
344+
pr := lp.Ident().ProjectRoot
345+
346+
if pp, has := p.Manifest.Ovr[pr]; has && !pp.Constraint.Matches(lp.Version()) {
347+
lsat.badovr[pr] = constraintMismatch{
348+
c: pp.Constraint,
349+
v: lp.Version(),
350+
}
351+
}
352+
353+
if pp, has := p.Manifest.Constraints[pr]; has && !ineff[string(pr)] && !pp.Constraint.Matches(lp.Version()) {
354+
lsat.badconstraint[pr] = constraintMismatch{
355+
c: pp.Constraint,
356+
v: lp.Version(),
357+
}
358+
}
359+
}
360+
361+
return lsat, nil
362+
}
363+
222364
// FindIneffectualConstraints looks for constraint rules expressed in the
223365
// manifest that will have no effect during solving, as they are specified for
224366
// projects that are not direct dependencies of the Project.

0 commit comments

Comments
 (0)