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

Add -gopath flag to init #497

Merged
merged 3 commits into from
Jun 17, 2017
Merged

Conversation

darkowlzz
Copy link
Collaborator

  • Separate network mode and GOPATH mode of operations.
  • In GOPATH mode, fetch projectData by searching through the current
    GOPATH. Solve the dependency constraints based on the projects found on
    disk and use network to solve for projects not found on disk.
  • In Network mode, do not check GOPATH or any ondisk projects. Solve the
    dependency constraints completely over network.

Fixes #496

@darkowlzz
Copy link
Collaborator Author

This is implementation of the 2nd approach mentioned in #496 . Looking at code might help in better understanding :)

Integration tests are failing due to change in behavior (Network mode by default). Will fix them once we have a properly discussed fix.

@darkowlzz
Copy link
Collaborator Author

Also, should dep tell the user about the execution mode, or how it's solving the dependency constraints?
Something like:

$ dep init -gopath
......
Searching GOPATH to solve dependencies
......

@sdboyer
Copy link
Member

sdboyer commented May 2, 2017

This is implementation of the 2nd approach mentioned in #496 .

As noted over there, I think this is the right approach to start with 😄

Integration tests are failing due to change in behavior (Network mode by default). Will fix them once we have a properly discussed fix.

👍

Also, should dep tell the user about the execution mode, or how it's solving the dependency constraints?

Definitely. I'd go a step further, actually - I'll update the spec doc with some things to this effect.

Copy link
Member

@sdboyer sdboyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you're headed in a good direction 😄

cmd/dep/init.go Outdated
P: make([]gps.LockedProject, 0, len(pd.ondisk)),
}
if cmd.gopath {
pd, err = getProjectData(ctx, pkgT, cpr, sm)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation of getProjectData() is likely to need a fair bit of modification, as it should really only be filling in holes in the graph. I suspect that's going to result in nontrivial changes to that graph traversal algorithm.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I left a reply here 🤔 maybe my machine got disconnected and it wasn't saved.

So here again, would be better to create a dedicated issue to discuss how we modify getProjectData() 😬. Would be great if you could add some more details about the ideas you have in making it better. The function is huge and a lot of stuff seems to be happening in it. 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure, it's a rabbit hole. Separate issue makes sense.

cmd/dep/init.go Outdated
)
for pr, v := range pd.ondisk {
// That we have to chop off these path prefixes is a symptom of
// a problem in gps itself
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment might actually be out of date, I think I changed subpackage naming a while back. It wouldn't be a terrible thing if you checked on it while you were poking around in this 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I came across this while working on the previous init PR. I observed that the Lock obtained after a solve contains LockedProject with proper list of packages required from the project, no chopping required. But I couldn't figure out how to create LockedProject's package list using gps, to avoiding the above chopping code block.

Can you create a new issue with some more details?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I couldn't figure out how to create LockedProject's package list using gps, to avoiding the above chopping code block.

Yeah there's a family of functions I've been considering adding for this, but right now everything's waiting behind getting gps moved into dep.

Can you create a new issue with some more details?

Sure, but...which part? Just the whole thing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, for now just that chopping code block part. With little details about how to create LockedProject with proper list of packages using gps, to replace the outdated comment and code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With little details about how to create LockedProject with proper list of packages using gps

Well, this is kinda what I'm getting at - the "properness" of the package list in LockedProject's isn't separable from a full Solve(). NewLockedProject() will let you create instances as needed, but being proper/correct is only defined in the context of an overall dependency set, computed by solving.

So, is the question here about simply creating instances, or creating correct ones?

Copy link
Collaborator Author

@darkowlzz darkowlzz May 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, from what I have seen, a full solve is required to get a proper package list. I was wondering if there's a way to partially run solve (or some gps function) and get the same package list that we need to create locks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, unfortunately, the problem can't be partitioned (in fact, it's NP-complete). It's all-or-nothing; hence why gps.Solution is a superset of gps.Lock.

@darkowlzz
Copy link
Collaborator Author

  • Added a single line message when -gopath mode is executed.
  • Fixed breaking tests.
  • Added a new test for the default network mode.

Will implement itemized feedback in another PR. Would create another issue for the same.

@darkowlzz
Copy link
Collaborator Author

Also, should we explicitly print the default network mode, "Using network for projects...", kinda message?
IMO, since it's default, it's not necessary. Only when we go away from default, like passing -gopath, we should inform the user about it.

@sdboyer
Copy link
Member

sdboyer commented May 2, 2017

IMO, since it's default, it's not necessary. Only when we go away from default, like passing -gopath, we should inform the user about it.

👍 👍 👍

cmd/dep/init.go Outdated
P: make([]gps.LockedProject, 0, len(pd.ondisk)),
}
if cmd.gopath {
fmt.Fprintln(os.Stderr, "Searching GOPATH for projects...")
Copy link
Member

@sdboyer sdboyer May 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use internal.Logf() here

Copy link
Collaborator Author

@darkowlzz darkowlzz May 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh! didn't see that existed. Found fmt.Fprintln() being used in main.go. So used the same 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh...yeah, hah, i think that's older stuff there. it should be updated 😄

@darkowlzz
Copy link
Collaborator Author

@sdboyer are we waiting for some changes here? 😅 Just making sure no change requests are pending. You can take your time :)

@sdboyer
Copy link
Member

sdboyer commented May 4, 2017

@darkowlzz sorry, I meant to ask you the same 😄

We do need the general CLI helptext to be updated. We're in a transitional state with this, so it needn't be flawless, but we can't mislead users about behavior.

@darkowlzz
Copy link
Collaborator Author

@sdboyer oops! forgot about that 😅 Updated!

@sdboyer
Copy link
Member

sdboyer commented May 13, 2017

OK, needs just a little catchup after I merged #512 - then I can pull it into the feature branch that I mentioned before 😄

@darkowlzz
Copy link
Collaborator Author

@sdboyer yep, already working on it.

@darkowlzz darkowlzz force-pushed the 496-gopath-flag-init branch 2 times, most recently from 3b22acb to 396dc2a Compare May 13, 2017 20:55
@darkowlzz
Copy link
Collaborator Author

@sdboyer squashed, rebased and adjusted to work with current code.

@sdboyer
Copy link
Member

sdboyer commented May 14, 2017

awesome. it looks like i should be able to get the feature branch up this afternoon. thanks!

@sdboyer
Copy link
Member

sdboyer commented Jun 6, 2017

oh, we're totally ready for this now! this is in @carolynvs's domain, so marking her for review 😄

Copy link
Collaborator

@carolynvs carolynvs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, so much of the logic that you are building on has moved that a rebase is needed before I can review further.

cmd/dep/init.go Outdated

- Tags conforming to semver (sorted by semver rules)
- Default branch(es) (sorted lexicographically)
- Non-semver tags (sorted lexicographically)

An alternate mode can be activated by passing -gopath. In this mode, version of
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this mode, the version of...

cmd/dep/init.go Outdated
l := &dep.Lock{
P: make([]gps.LockedProject, 0, len(pd.ondisk)),
}
if cmd.gopath {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all this logic moved out of init.go into gopath_scanner.go, would you please rebase and move over your changes too? Sorry for the merge trouble!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's where the various bits are in gopath_scanner:

  • Scan to generate project data -> gopathScanner.scanGopathForDependencies
  • Populate a manifest using the project data -> gopathScanner.InitializeRootManifestAndLock
  • Combine the results of the importer with what we found in GOPATH, printing feedback for what was used (things from the importers win) -> gopathScanner.overlay
  • Tweak the lock based on the results of solving -> gopathScanner.FinalizeRootManifestAndLock

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks :)

@darkowlzz darkowlzz force-pushed the 496-gopath-flag-init branch 2 times, most recently from 537b95b to 8a82838 Compare June 7, 2017 13:30
@darkowlzz
Copy link
Collaborator Author

darkowlzz commented Jun 7, 2017

Rebased and made changes to work with new code.

I feel the need to have some kind of loading animation or as simple as incremental dots (....) because in network mode, unlike in gopath mode, there's no intermediate output. Feedback constraints are printed only after a solution is obtained.


[[constraint]]
name = "github.com/sdboyer/deptest"
version = "1.0.0"
Copy link
Collaborator Author

@darkowlzz darkowlzz Jun 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, this used to be with a ^. Now, I think because of ImpliedCaretString, versions are written as exact. Is it intended?
Also, the feedback that we print for network mode, prints with ^ right now. So there's a mismatch between what the feedback shows version and what's written in manifest.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, when init generates the manifest it should not write out the ^. When the manifest is read, the caret is automatically added. Here's some backstory on why we don't include the caret in the manifest.

My preference is for the verbose output to print what's really going on and show the caret, but will leave that call to @sdboyer. If we do end up changing that, let's do it in a separate PR.

cmd/dep/init.go Outdated
if cmd.gopath {
gs.FinalizeRootManifestAndLock(m, l)
} else {
// Pick the direct dependencies from the above solution and add to manifest
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you help me understand why you added this else clause? It looks like the old code from the gopath scanning but in this case is being applied to the results of importing config from an external tool.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, this was done to generate the final lock memo with the obtained solution.
When there's no external tool and -gopath is not passed, we use an empty manifest in the solver to get a solution. Then we use the lock obtained from the solution and pick direct dependencies from there and create a new manifest, using which we generate the final lock memo.

Now, in case of an external tool, our initial manifest may or may not be empty. So we use the solution lock to fill up the manifest with direct dependencies. In case, manifest was empty, it gets filled. And in case, manifest already had packages, there wouldn't be any change. Or if there were any missing direct dependencies in manifest, they get added too.

Hope this answers your question.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carolynvs I just figured out why you asked this question. Went through the epic #186 and came across

ignore all the existing local information - GOPATH, existing tools, everything - and just have the solver pick out the latest versions it can.

😅 which is totally different from what's being done here. Lemme change that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I'm not entirely sure what's the right approach. 😊 I was just asking because I was confused.

What I am pretty sure about is:

  • The logic should be in rootAnalyzer.FinalizeManifestAndLock instead of the init command directly.
  • When someone runs dep init -skip-tools -gopath it shouldn't result in a populated lock and an empty manifest.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic should be in rootAnalyzer.FinalizeManifestAndLock instead of the init command directly.

Yeah, I felt I made init dirty. It became elegant with all the new stuff and with all these changes, it felt dirty 😅 I'll change that.

When someone runs dep init -skip-tools -gopath it shouldn't result in a populated lock and an empty manifest.

We have Integration/init/case3 to handle this scenarios 😊

@darkowlzz darkowlzz force-pushed the 496-gopath-flag-init branch 2 times, most recently from 3fdd31c to b858322 Compare June 10, 2017 12:13
@darkowlzz
Copy link
Collaborator Author

Made rootAnalyzer and gopathScanner -gopath flag aware and skip usual operations when in network mode.

Much cleaner now :)

Copy link
Collaborator

@carolynvs carolynvs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies if my comments sent you off on the wrong path... 😊

This is super close, and should be ready to merge after shuffling things back a bit to the way they were initially.

ctx: ctx,
sm: sm,
directDeps: directDeps,
}
}

func (a *rootAnalyzer) InitializeRootManifestAndLock(dir string, pr gps.ProjectRoot) (rootM *dep.Manifest, rootL *dep.Lock, err error) {
if !a.skipTools {
// Use importer tools only in gopath mode
if a.gopath && !a.skipTools {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't match the behavior outlined in the updated command spec

If -gopath is passed, then when the legacy tool import is complete (or skipped, in the event that -skip-tools is also passed), an additional search pass should be made through the current (as inferred from the cwd) GOPATH

As I read it, -gopath and -skip-tools are completely independent.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment discussing this in the google doc: dep: updated command spec.

if pr == y.Ident().ProjectRoot {
used = true
break
if a.gopath {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both paths of this if/else are safe to run unconditionally. If a direct dep was added during solve, either because the imported config was incomplete or due to "network solving", then it's appropriate to add it to the manifest. Same for removing unused dependencies from the manifest.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we stop using the a.gopath in both functions, we can remove it entirely from the struct.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need some way to make root_analyzer aware of -gopath flag. If else part of this is kept here itself, in root_analyzer, it would execute the feedback code. So, in gopath mode, feedbacks would be logged twice.


pd projectData
origM *dep.Manifest
origL *dep.Lock
}

func newGopathScanner(ctx *dep.Ctx, directDeps map[string]bool, sm gps.SourceManager) *gopathScanner {
func newGopathScanner(ctx *dep.Ctx, directDeps map[string]bool, sm gps.SourceManager, gopath bool) *gopathScanner {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this went a bit too far in trying to keep init "clean". 😀 Let's have the init command handle the -gopath flag and call gopathScanner or not based on its value.

f = fb.NewLockedProjectFeedback(y, fb.DepTypeTransitive)
}
f.LogFeedback(a.ctx.Err)
}
Copy link
Collaborator Author

@darkowlzz darkowlzz Jun 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem with this is that, since rootAnalyzer isn't aware of network/gopath mode of execution, even in gopath mode, these feedbacks would run. Resulting in double feedbacks.

Should this be moved to init? or should we make at least rootAnalyzer aware of gopath flag? or maybe there's another way 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@darkowlzz I wrote out the workflow for init to help answer this question and decided to dump my notes, instead of just the one part that addresses the question. 😁

  1. rootAnalyzer.importManifestAndLock
    • Import constraints and locked projects from external config.
  2. gopathScanner.InitializeManifestAndLock
    • Add constraints and locked projects from GOPATH.
  3. SOLVE
    • Uses dark magic to add more goodies to the lock.
  4. rootAnalyzer.FinalizeManifestAndLock
    • Remove unused constraints. I'll remove this in a separate PR. I just realized that it's not needed anymore thanks to rootAnalyzer.removeTransitiveDependencies.
    • This PR adds printing feedback for direct deps found during solve.
    • This PR adds printing feedback for transitive deps locked during solve.
  5. gopathScanner.FinalizeManifestAndLock
    • Add constraints found during solve that weren't initially in the manifest
      because they weren't on disk.

I believe that we can generalize the FinalizeManifestAndLock activities to "Identify new constraints and locked projects found during solve". New constraints are direct deps that are in the final lock but not the manifest, and new locked projects are identified by keeping track of the initial lock. The same finalization tasks can be performed of how we got to this state:

  • -gopath wasn't specified and nothing was imported, 100% network solve.
  • The GOPATH didn't have repos for all of the direct deps.
  • The imported config was incomplete and didn't have constraints for all of the direct deps.

Then we could have init keep a copy of the lock given to the solver, and pass it in as an additional argument to rootAnalyzer.FinalizeManifestAndLock. Now all that logic is in one place and we can also remove gopathScanner.FinalizeManifestAndLock.

What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember we used to keep a copy of lock given to the solver and use the same to identify new constraints and projects, by comparing with new lock, at one point of time. I still have my dep fork at that point of time, copyLock 😊

So, yeah, a unified FinalizeManifestAndLock is what we need 🙌

@darkowlzz darkowlzz force-pushed the 496-gopath-flag-init branch 2 times, most recently from d56d41b to 0821c76 Compare June 17, 2017 14:08
cmd/dep/init.go Outdated
@@ -164,8 +175,8 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error {
}
p.Lock = dep.LockFromSolution(soln)

rootAnalyzer.FinalizeRootManifestAndLock(p.Manifest, p.Lock)
gs.FinalizeRootManifestAndLock(p.Manifest, p.Lock)
rootAnalyzer.RemoveTransitiveDependencies(p.Manifest)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carolynvs I used RemoveTransitiveDependencies() in this PR itself. Hopefully this is how you intended to use it 😅

- Removes gopathScanner's FinalizeManifestAndLock() and its usage.
- Adds using removeTransitiveDependencies() to remove unused
constraints.
- Changes rootAnalyzer's FinalizeManifestAndLock to log feedback for new
constraints and locked projects only.
used = true
break
func (a *rootAnalyzer) FinalizeRootManifestAndLock(m *dep.Manifest, l *dep.Lock, ol dep.Lock) {
a.removeTransitiveDependencies(m)
Copy link
Collaborator Author

@darkowlzz darkowlzz Jun 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carolynvs I used removeTransitiveDependencies() in this PR itself. Hopefully this is how you intended to use it 😅

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK only the importers may add a transitive or unused dep to the manifest, because they are just responsible for converting from the external config to dep's. In rootAnalyzer.importManifestAndLock we are calling removeTransitiveDependencies after the importer is run, to clean up after them.

So unless someplace in the code else may be adding things to the manifest that may not belong, I don't think we need to call it again in FinalizeManifestAndLock.

pc := gps.ProjectConstraint{Ident: y.Ident(), Constraint: pp.Constraint}
f = fb.NewConstraintFeedback(pc, fb.DepTypeDirect)
} else {
f = fb.NewLockedProjectFeedback(y, fb.DepTypeDirect)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I split up the old feedback function into the constructors, a single call no longer does "double duty". So NewConstraintFeedback will only generate a using entry and NewLockedProjectFeedback will only generate a locking entry.

What we have right here will only print either a using or a locking, but never both. Was that the intent? I expected that if we can deduce a constraint, that we could want to print the constraint and the locked project. If you agree, I think we need to add a LogFeedback call after NewConstraintFeedback and then unconditionally call NewLockedProjectFeedback and LogFeedback instead of putting it in this else clause.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh! yeah, that's right. Didn't realize that got changed 😅 no need of the conditional else.

- Removes `removeTransitiveDependencies()` from
`FinalizeRootManifestAndLock`.
- Moves locked project feedback for new direct deps out of conditional
clause.
@carolynvs
Copy link
Collaborator

I opened #763 for one of the failed tests, I've seen it randomly fail a few times. Looks good after restarting the build!

@carolynvs
Copy link
Collaborator

Thank you for staying on top of this and your patience with the uncertain expected behaviors! 💖

@carolynvs carolynvs merged commit 52acb7e into golang:master Jun 17, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants