-
Notifications
You must be signed in to change notification settings - Fork 1k
Implement new spec for init #470
Implement new spec for init #470
Conversation
I would like to discuss if this is a good solution. After debugging Instead of omitting the projects, I thought assuming the remote project's version to be Also, enabled running the solver to ensure that a proper lock is generated and memo is not left empty after Need feedback. |
Integration tests are failing due to change in the expected behavior/results. Will fix them once we decide to go ahead with this solution. |
There'd be a lot of pitfalls from this. Much better is to run a solve without constraints applied, then pick the versions to put back into the manifest. Please have a look at the WIP spec doc - it describes how such an algorithm should work for picking a constraint. Because it's out of scope for this issue to change
Excellent 😄 |
oops! yeah, right. Not every project has the default branch named "master" 😅 |
if k == x.Ident().ProjectRoot { | ||
m.Dependencies[k] = getProjectPropertiesFromVersion(x.Version()) | ||
break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better way to pick the constraints and add to manifests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not one that's wrapped up in a nice function, but there is something sorta analogous - I'll make more detailed comments down on the impl. But really, the algorithm you'll want to follow for this is bulleted in the spec doc. 😄
Another attempt and a little bit closer, I guess :) Removed false version assumption for not on disk projects. Instead, running solver to get the proper version and then picking the missing constraints of not on disk projects from the solution. Since the lock from the previous solver run had missing constraints, running solver again with appropriate project version(manifest) to obtain a final lock. Sounds good? Also, with the above changes, manifest, lock and vendor are generated properly, but the lock My project has 2 direct dependencies:
When
This manifest map is then passed to the 2nd solver run to obtain the final lock, which is then written to manifest and lock files. Now, if I run
Any idea how to handle this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great progress!
if k == x.Ident().ProjectRoot { | ||
m.Dependencies[k] = getProjectPropertiesFromVersion(x.Version()) | ||
break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not one that's wrapped up in a nice function, but there is something sorta analogous - I'll make more detailed comments down on the impl. But really, the algorithm you'll want to follow for this is bulleted in the spec doc. 😄
cmd/dep/init.go
Outdated
return err | ||
} | ||
l = dep.LockFromInterface(soln) | ||
// Run solver again with appropriate constraint solutions from previous run |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's necessary to solve twice. It should be sufficient to gps.Prepare()
a new solver with the returned solution and the updated manifest, then call solver.HashInputs()
and use that as the Memo
value instead of what was returned from the initial solve.
More details about how this hash works, and what it means, are here.
cmd/dep/init.go
Outdated
case gps.IsBranch, gps.IsVersion, gps.IsRevision: | ||
pp.Constraint = v | ||
case gps.IsSemver: | ||
c, _ := gps.NewSemverConstraint("^" + v.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a TODO here to note that this will need to change when #225 is ready.
cmd/dep/init.go
Outdated
// gps.ProjectProperties with Constraint value based on the version type. | ||
func getProjectPropertiesFromVersion(v gps.Version) gps.ProjectProperties { | ||
pp := gps.ProjectProperties{} | ||
switch v.Type() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, we actually need to do a little more work here up front. It's not a guarantee of the interface (yet - this may change), but gps has a concept of "version pairing" - basically, combining a revision with a branch or tag. This information is not encoded in the gps.Version.Type()
method, so we need to tease it out first, because - per the spec - we never want to put revisions in Gopkg.toml, if we can avoid it.
There's a rough skeleton to follow here in status.go
.
cmd/dep/init.go
Outdated
} | ||
|
||
// getSolverSolution runs gps solver and returns a solution. | ||
func getSolverSolution(root string, pkgT pkgtree.PackageTree, m *dep.Manifest, l *dep.Lock, sm *gps.SourceMgr) (gps.Solution, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't object to having this abstraction in principle, but let's leave it for a later PR where it can be a dedicated refactor that affects the other commands as well.
cmd/dep/init.go
Outdated
func getProjectPropertiesFromVersion(v gps.Version) gps.ProjectProperties { | ||
pp := gps.ProjectProperties{} | ||
switch v.Type() { | ||
case gps.IsBranch, gps.IsVersion, gps.IsRevision: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the spec & the above note about revisions, if the actual type is gps.IsRevision
, then we just want to ignore it.
(for those things that weren't addressed inline...)
Yep! 🥅
👍
Ordering should not be an issue here. The input hasher is very, very careful about minimizing superfluous conflicts. It even removes irrelevant output (e.g. if a package is both imported and required, then the require value is not included in the hash, because it would be redundant and cause unnecessary hash conflicts). If gps' code is correct, it should be impossible for the caller to order inputs in a way that generate a superfluous conflict. To get a better picture of why this conflict is happening, rather than printing the manifest itself, run |
92740cb
to
a4fd873
Compare
Another attempt with all my understandings 😅
Also, the issue with different memo got resolved. I used
While on running status, hashing inputs were:
Revision value was causing the issue. With the modified constraint selections algo implementation, it got fixed. |
(travis should be failing too, but we introduced a bug - i just opened #486 for that) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
idk about limited understanding, this is looking pretty good to me 😄 next step is probably to move on to adapting the tests.
cmd/dep/init.go
Outdated
@@ -209,6 +227,45 @@ func hasImportPathPrefix(s, prefix string) bool { | |||
return strings.HasPrefix(s, prefix+"/") | |||
} | |||
|
|||
// getVersionConstituents extracts version constituents | |||
func getVersionConstituents(v gps.Version) (version, revision string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually really close to the VersionComponentStrings()
func in gps. If we were to go this route, I think I'd rather see that function reused. But I think another route might be better, as I'll describe below...
cmd/dep/init.go
Outdated
pp := gps.ProjectProperties{} | ||
|
||
// constituent version and revision. Ignoring revison for manifest. | ||
cv, _ := getVersionConstituents(v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than converting to and from strings, we can do it all with the types - in getProjectPropertiesFromVersion
, we can do something like this:
switch tv := v.(type) {
case gps.PairedVersion:
v = tv.Unpair()
case gps.Revision:
return pp
}
Now the v
is exactly what we'll want to assign into pp.Constraint
, unless we're in the semver case.
cmd/dep/init.go
Outdated
pp.Constraint = gps.NewVersion(cv) | ||
case gps.IsSemver: | ||
// TODO: remove "^" when https://github.com/golang/dep/issues/225 is ready. | ||
c, _ := gps.NewSemverConstraint("^" + cv) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's check the error here and panic if we get one. That shouldn't be possible - any valid plain semver version should be able to have the caret operand prepended to it - but if we somehow missed something, then program behavior is undefined; panicking is the safe thing to do.
Ahh, of course! Also, awesome, really glad we're going to be able to knock that one out. |
a4fd873
to
14c8cc6
Compare
cmd/dep/init_test.go
Outdated
if outsemver.Constraint.String() != expectedSemver.String() { | ||
t.Fatalf("Expected %q to be equal to %q", outsemver, expectedSemver) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do something about these tests? Especially the semver part. And if there's a way to improve the way they are being tested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the issue here is that semver.rangeConstraint
contains a slice, which means it's not directly comparable, even though that slice is almost always empty, including in this case.
The simple solution here is to use reflect.DeepEqual()
to compare them instead.
Apart from that, I think the tests are actually fine as-is; they focus in on the key issue, which is stripping out the underlying revision, and making sure that we default to the caret. The only ones I might add, just for completeness, is a duplicate set where there isn't a revision attached (the Version
is actually a gps.UnpairedVersion
). This case shouldn't occur in the wild, but it's to test for and we should know if we ever regress in handling it, for some reason.
And we are almost there ☘️ 😁
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good progress!
cmd/dep/init_test.go
Outdated
func TestGetProjectPropertiesFromVersion(t *testing.T) { | ||
cases := []struct { | ||
version gps.Version | ||
expected gps.Version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
supernit: prefer want
to expected
cmd/dep/init_test.go
Outdated
if outsemver.Constraint.String() != expectedSemver.String() { | ||
t.Fatalf("Expected %q to be equal to %q", outsemver, expectedSemver) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the issue here is that semver.rangeConstraint
contains a slice, which means it's not directly comparable, even though that slice is almost always empty, including in this case.
The simple solution here is to use reflect.DeepEqual()
to compare them instead.
Apart from that, I think the tests are actually fine as-is; they focus in on the key issue, which is stripping out the underlying revision, and making sure that we default to the caret. The only ones I might add, just for completeness, is a duplicate set where there isn't a revision attached (the Version
is actually a gps.UnpairedVersion
). This case shouldn't occur in the wild, but it's to test for and we should know if we ever regress in handling it, for some reason.
- Adds assuming project revision to be master when VersionInWorkspace fails for not on disk projects. Failed projects were being skipped from manifest before this change. - Adds running solver once at init. This ensures creating an initial lock with memo hash in lock file.
- Adds running gps solver with project versions found on disk. - Picks constraints from the solver's solution and adds them to manifest projects with missing constraints. - Runs solver again to obtain final lock with appropriate project constraints.
- Avoid running solver twice and use gps.Prepare to get the final lock. - Improve manifest constraints population based on the algo in the new spec doc.
- Removes getVersionConstituents(), inline its simplified implementation. - Adds test for getProjectPropertiesFromVersion() function.
- Added some more test cases: version without any revision attached. - Fixed breaking ensure/pkg-errors/case1 integration test.
14c8cc6
to
0a0f777
Compare
Ended up comparing their strings. |
|
||
// Test to have caret in semver version | ||
outsemver := getProjectPropertiesFromVersion(gps.NewVersion("v1.0.0")) | ||
wantSemver, _ := gps.NewSemverConstraint("^1.0.0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The actual problem here is an annoying implementation detail - it should work without needing to fall back on string comparison if you use ^v1.0.0
instead of ^1.0.0
. I believe this kind of issue is improved in newer versions of the semver lib, but we're stuck with what we've got for now.
Now all we need is to update the CLI helptext 😄 |
LGTM! I'll tweak the helptext a bit on my own, directly - no sense holding up the functionality for that 😄 |
…versioninworkspace Implement new spec for init
fails for not on disk projects. Failed projects were being skipped from
manifest before this change.
lock with memo hash in lock file.
Fixes #356 and #149