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

Commit 0b71dd2

Browse files
carolynvscarolynvs-msft
authored andcommitted
NEEDS REVIEW: DO NOT MERGE
This is a prototype for keeping track of the path through the selection process to a project. It is used to help dep ignore "stale" transitive constraints: constraints that when created applied to a descendent but should no longer apply now that that the project has moved to another location in the dependency graph. Questions: * I put bmi on atomWithPackages because it already had a bmi method to recreate the original bmi. Not sure if it's safe to live there considering the comments on the original bmi function about avoiding copys of the package list. So it may need to shift elsewhere or path should be be split out from the bmi struct so that it can be attached directly to an atom. * Is a non-bimodal solve ever a possibility in dep (outside of the unit tests?). I think we could drop "dep.isTransitive" in favor of using bmi.path exclusively but the unit tests have a non-bimodal set of tests that cause this to fail.
1 parent 5b8fdcb commit 0b71dd2

9 files changed

+121
-100
lines changed

gps/identifier.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ type bimodalIdentifier struct {
177177
prefv Version
178178
// Indicates that the bmi came from the root project originally
179179
fromRoot bool
180+
// The path to the atom in the graph, e.g. root -> foo -> bar
181+
path []atom
180182
}
181183

182184
type atom struct {
@@ -190,20 +192,8 @@ var nilpa = atom{
190192
}
191193

192194
type atomWithPackages struct {
193-
a atom
194-
pl []string
195-
}
196-
197-
// bmi converts an atomWithPackages into a bimodalIdentifier.
198-
//
199-
// This is mostly intended for (read-only) trace use, so the package list slice
200-
// is not copied. It is the callers responsibility to not modify the pl slice,
201-
// lest that backpropagate and cause inconsistencies.
202-
func (awp atomWithPackages) bmi() bimodalIdentifier {
203-
return bimodalIdentifier{
204-
id: awp.a.id,
205-
pl: awp.pl,
206-
}
195+
a atom
196+
bmi bimodalIdentifier
207197
}
208198

209199
// completeDep (name hopefully to change) provides the whole picture of a
@@ -223,6 +213,6 @@ type completeDep struct {
223213
// fully-realized atom as the depender (the tail/source of the edge), and a set
224214
// of requirements that any atom to be attached at the head/target must satisfy.
225215
type dependency struct {
226-
depender atom
216+
depender atomWithPackages
227217
dep completeDep
228218
}

gps/rootdata.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func (rd rootdata) rootAtom() atomWithPackages {
198198
sort.Strings(list)
199199

200200
return atomWithPackages{
201-
a: a,
202-
pl: list,
201+
a: a,
202+
bmi: bimodalIdentifier{id: a.id, pl: list},
203203
}
204204
}

gps/satisfy.go

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ package gps
1111
// The goal is to determine whether selecting the atom would result in a state
1212
// where all the solver requirements are still satisfied.
1313
func (s *solver) check(a atomWithPackages, pkgonly bool) error {
14-
pa := a.a
15-
if nilpa == pa {
14+
if nilpa == a.a {
1615
// This shouldn't be able to happen, but if it does, it unequivocally
1716
// indicates a logical bug somewhere, so blowing up is preferable
1817
panic("canary - checking version of empty ProjectAtom")
@@ -30,7 +29,7 @@ func (s *solver) check(a atomWithPackages, pkgonly bool) error {
3029
// If we're pkgonly, then base atom was already determined to be allowable,
3130
// so we can skip the checkAtomAllowable step.
3231
if !pkgonly {
33-
if err = s.checkAtomAllowable(pa); err != nil {
32+
if err = s.checkAtomAllowable(a); err != nil {
3433
return err
3534
}
3635
}
@@ -78,24 +77,24 @@ func (s *solver) check(a atomWithPackages, pkgonly bool) error {
7877

7978
// checkAtomAllowable ensures that an atom itself is acceptable with respect to
8079
// the constraints established by the current solution.
81-
func (s *solver) checkAtomAllowable(pa atom) error {
82-
constraint := s.sel.getConstraint(pa.id)
83-
if s.vUnify.matches(pa.id, constraint, pa.v) {
80+
func (s *solver) checkAtomAllowable(awp atomWithPackages) error {
81+
constraint := s.sel.getConstraint(awp.a.id, awp.bmi)
82+
if s.vUnify.matches(awp.a.id, constraint, awp.a.v) {
8483
return nil
8584
}
8685
// TODO(sdboyer) collect constraint failure reason (wait...aren't we, below?)
8786

88-
deps := s.sel.getDependenciesOn(pa.id)
87+
deps := s.sel.getDependenciesOn(awp.a.id)
8988
var failparent []dependency
9089
for _, dep := range deps {
91-
if !s.vUnify.matches(pa.id, dep.dep.Constraint, pa.v) {
92-
s.fail(dep.depender.id)
90+
if !s.vUnify.matches(awp.a.id, dep.dep.Constraint, awp.a.v) {
91+
s.fail(dep.depender.a.id)
9392
failparent = append(failparent, dep)
9493
}
9594
}
9695

9796
err := &versionNotAllowedFailure{
98-
goal: pa,
97+
goal: awp.a,
9998
failparent: failparent,
10099
c: constraint,
101100
}
@@ -120,14 +119,14 @@ func (s *solver) checkRequiredPackagesExist(a atomWithPackages) error {
120119
for _, dep := range deps {
121120
for _, pkg := range dep.dep.pl {
122121
if errdep, seen := fp[pkg]; seen {
123-
errdep.deppers = append(errdep.deppers, dep.depender)
122+
errdep.deppers = append(errdep.deppers, dep.depender.a)
124123
fp[pkg] = errdep
125124
} else {
126125
perr, has := ptree.Packages[pkg]
127126
if !has || perr.Err != nil {
128127
fp[pkg] = errDeppers{
129128
err: perr.Err,
130-
deppers: []atom{dep.depender},
129+
deppers: []atom{dep.depender.a},
131130
}
132131
}
133132
}
@@ -147,7 +146,7 @@ func (s *solver) checkRequiredPackagesExist(a atomWithPackages) error {
147146
// given dep are valid with respect to existing constraints.
148147
func (s *solver) checkDepsConstraintsAllowable(a atomWithPackages, cdep completeDep) error {
149148
dep := cdep.workingConstraint
150-
constraint := s.sel.getConstraint(dep.Ident)
149+
constraint := s.sel.getConstraint(dep.Ident, a.bmi)
151150
// Ensure the constraint expressed by the dep has at least some possible
152151
// intersection with the intersection of existing constraints.
153152
if s.vUnify.matchesAny(dep.Ident, constraint, dep.Constraint) {
@@ -160,15 +159,15 @@ func (s *solver) checkDepsConstraintsAllowable(a atomWithPackages, cdep complete
160159
var nofailsib []dependency
161160
for _, sibling := range siblings {
162161
if !s.vUnify.matchesAny(dep.Ident, sibling.dep.Constraint, dep.Constraint) {
163-
s.fail(sibling.depender.id)
162+
s.fail(sibling.depender.a.id)
164163
failsib = append(failsib, sibling)
165164
} else {
166165
nofailsib = append(nofailsib, sibling)
167166
}
168167
}
169168

170169
return &disjointConstraintFailure{
171-
goal: dependency{depender: a.a, dep: cdep},
170+
goal: dependency{depender: a, dep: cdep},
172171
failsib: failsib,
173172
nofailsib: nofailsib,
174173
c: constraint,
@@ -185,7 +184,7 @@ func (s *solver) checkDepsDisallowsSelected(a atomWithPackages, cdep completeDep
185184
s.fail(dep.Ident)
186185

187186
return &constraintNotAllowedFailure{
188-
goal: dependency{depender: a.a, dep: cdep},
187+
goal: dependency{depender: a, dep: cdep},
189188
v: selected.a.v,
190189
}
191190
}
@@ -206,7 +205,7 @@ func (s *solver) checkIdentMatches(a atomWithPackages, cdep completeDep) error {
206205
// Fail all the other deps, as there's no way atom can ever be
207206
// compatible with them
208207
for _, d := range deps {
209-
s.fail(d.depender.id)
208+
s.fail(d.depender.a.id)
210209
}
211210

212211
return &sourceMismatchFailure{
@@ -236,7 +235,7 @@ func (s *solver) checkRootCaseConflicts(a atomWithPackages, cdep completeDep) er
236235
curid, _ := s.sel.getIdentFor(current)
237236
deps := s.sel.getDependenciesOn(curid)
238237
for _, d := range deps {
239-
s.fail(d.depender.id)
238+
s.fail(d.depender.a.id)
240239
}
241240

242241
// If a project has multiple packages that import each other, we treat that
@@ -260,13 +259,13 @@ func (s *solver) checkRootCaseConflicts(a atomWithPackages, cdep completeDep) er
260259
if current == a.a.id.ProjectRoot {
261260
return &wrongCaseFailure{
262261
correct: pr,
263-
goal: dependency{depender: a.a, dep: cdep},
262+
goal: dependency{depender: a, dep: cdep},
264263
badcase: deps,
265264
}
266265
}
267266

268267
return &caseMismatchFailure{
269-
goal: dependency{depender: a.a, dep: cdep},
268+
goal: dependency{depender: a, dep: cdep},
270269
current: current,
271270
failsib: deps,
272271
}
@@ -289,7 +288,7 @@ func (s *solver) checkPackageImportsFromDepExist(a atomWithPackages, cdep comple
289288

290289
e := &depHasProblemPackagesFailure{
291290
goal: dependency{
292-
depender: a.a,
291+
depender: a,
293292
dep: cdep,
294293
},
295294
v: sel.a.v,
@@ -329,7 +328,7 @@ func (s *solver) checkRevisionExists(a atomWithPackages, cdep completeDep) error
329328

330329
return &nonexistentRevisionFailure{
331330
goal: dependency{
332-
depender: a.a,
331+
depender: a,
333332
dep: cdep,
334333
},
335334
r: r,

gps/selection.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
package gps
66

7+
import (
8+
"fmt"
9+
)
10+
711
type selection struct {
812
// projects is a stack of the atoms that have currently been selected by the
913
// solver. It can also be thought of as the vertex set of the current
@@ -134,7 +138,7 @@ func (s *selection) getSelectedPackagesIn(id ProjectIdentifier) map[string]int {
134138
uniq := make(map[string]int)
135139
for _, p := range s.projects {
136140
if p.a.a.id.eq(id) {
137-
for _, pkg := range p.a.pl {
141+
for _, pkg := range p.a.bmi.pl {
138142
uniq[pkg] = uniq[pkg] + 1
139143
}
140144
}
@@ -143,12 +147,18 @@ func (s *selection) getSelectedPackagesIn(id ProjectIdentifier) map[string]int {
143147
return uniq
144148
}
145149

146-
func (s *selection) getConstraint(id ProjectIdentifier) Constraint {
150+
func (s *selection) getConstraint(id ProjectIdentifier, bmi bimodalIdentifier) Constraint {
147151
deps, exists := s.deps[id.ProjectRoot]
148152
if !exists || len(deps) == 0 {
149153
return any
150154
}
151155

156+
// Enable quick lookup of where in the depgraph a constraint was defined
157+
ancestors := map[ProjectRoot]bool{}
158+
for _, ancestor := range bmi.path {
159+
ancestors[ancestor.id.ProjectRoot] = true
160+
}
161+
152162
// TODO(sdboyer) recomputing this sucks and is quite wasteful. Precompute/cache it
153163
// on changes to the constraint set, instead.
154164

@@ -159,6 +169,19 @@ func (s *selection) getConstraint(id ProjectIdentifier) Constraint {
159169
// Start with the open set
160170
var ret Constraint = any
161171
for _, dep := range deps {
172+
if dep.dep.isTransitive {
173+
// TODO(carolynvs): Remove print statements. It's just to help debug the transitive constraint prototype.
174+
path := ""
175+
for ancestor := range ancestors {
176+
path += "/" + string(ancestor)
177+
}
178+
if _, isAncestor := ancestors[dep.depender.a.id.ProjectRoot]; !isAncestor {
179+
fmt.Printf("*** Ignoring unreachable constraint %s on %s (path: %s) from %s ***\n", dep.dep.Constraint.String(), dep.dep.workingConstraint.Ident.ProjectRoot, path, dep.depender.a.id.ProjectRoot)
180+
continue
181+
}
182+
fmt.Printf("*** Applying transitive constraint %s on %s (path: %s) from %s ***\n", dep.dep.Constraint.String(), dep.dep.workingConstraint.Ident.ProjectRoot, path, dep.depender.a.id.ProjectRoot)
183+
}
184+
162185
ret = s.vu.intersect(id, ret, dep.dep.Constraint)
163186
}
164187

gps/solve_basic_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,15 @@ func mkDepspec(pi string, deps ...string) depspec {
226226

227227
func mkDep(atom, pdep string, pl ...string) dependency {
228228
return dependency{
229-
depender: mkAtom(atom),
229+
depender: atomWithPackages{a: mkAtom(atom)},
230230
dep: mkCDep(pdep, pl...),
231231
}
232232
}
233233

234234
func mkADep(atom, pdep string, c Constraint, pl ...string) dependency {
235235
return dependency{
236-
depender: mkAtom(atom),
236+
// TODO(carolynvs): I don't think we need to set the bmi (specifically the path to the atom) in the fixture data.
237+
depender: atomWithPackages{a: mkAtom(atom)},
237238
dep: completeDep{
238239
workingConstraint: workingConstraint{
239240
Ident: ProjectIdentifier{

gps/solve_bimodal_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ var bimodalFixtures = map[string]bimodalFixture{
132132
"b 1.0.0",
133133
),
134134
},
135-
"transitive constraint": {
135+
"used transitive constraint": {
136136
ds: []depspec{
137137
dsp(mkDepspec("root 1.0.0", "foo 1.0.0"),
138138
pkg("root", "foo"),

0 commit comments

Comments
 (0)