-
Notifications
You must be signed in to change notification settings - Fork 1k
Handling symlinks as project root #641
Handling symlinks as project root #641
Conversation
Hmm - it seems we've regressed then, because this definitely did work when we brought in #247. Clearly our tests aren't properly covering this case. Instead of updating the FAQ (which reflects how it's supposed to be), let's fix the code. |
As of now, running @sdboyer The only change I made to the FAQ (besides trying to clarify it) is the following change:
to
This doesn't make sense, since determining the GOPATH for this case is not possible (unless we assume it's the Let me know if this should be reverted. |
Signed-off-by: Ibrahim AshShohail <[email protected]>
Signed-off-by: Ibrahim AshShohail <[email protected]>
…ctRootAndGoPath() and add detectGoPath() Signed-off-by: Ibrahim AshShohail <[email protected]>
f3df625
to
91ac4d2
Compare
Signed-off-by: Ibrahim AshShohail <[email protected]>
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.
just one little nit right now. unfortunately, i'm out of reviewing juice tonight 😢
context.go
Outdated
// If path is a symlink within a GOPATH, an error is returned. | ||
// If both path and the directory it resolves to are within the same GOPATH. | ||
// If path and the directory it resolves to are each within a different GOPATH. | ||
func (c *Ctx) ResolveProjectRootAndGoPath(path string) (string, string, 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.
s/GoPath/GOPATH/
…GOPATH() Edit letter case of: * detectGoPath() to detectGOPATH() * ResolveProjectRootAndGoPath() to ResolveProjectRootAndGOPATH() Signed-off-by: Ibrahim AshShohail <[email protected]>
Signed-off-by: Ibrahim AshShohail <[email protected]>
This will need more changes since #673 is merged. 😁 |
@ibrasho I tried your branch with project root as a symlink to a directory outside GOPATH and got A little digging revealed this line to be the culprit. Maybe have a way to handle symlinked directories in pkgtree as well? |
@ofpiyush unfortunately, we don't plan on supporting this. It makes clear, consistent behavior vastly more difficult. There have been PRs in the past that attempted it - often going on for weeks, and ultimately resulting in unexpected behaviors that we didn't initially anticipate. |
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.
@ibrasho i'm sorry this keeps sliding - tbh, i have just put so much time into reviewing mind-numbing symlink PRs already that my mind seems to be resisting grokking yet one more. But, I've dug in a bit.
The direction here looks fundamentally correct. The most important point to make, really, is that #247 (comment) remains the closest thing to a spec for how this behavior should work that we have. If something deviates from that, then it's a bug. And this seems to bring us closer, which is good.
but yes, as you note, given #673, this'll need some more refactoring 😞
FAQ.md
Outdated
- If the symlink is outside `GOPATH` and links to a directory within a `GOPATH`, or vice versa, then `dep` will choose whichever path is within `GOPATH`. | ||
- If the symlink is within a `GOPATH` and the resolved path is within a *different* `GOPATH`, then an error is thrown. | ||
- If both the symlink and the resolved path are in the same `GOPATH`, then an error is thrown. | ||
- If both the symlink and the resolved path are not in a `GOPATH`, then an error is thrown. |
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.
Sentence construction thing:
"If neither the symlink nor the resolved path are in a GOPATH
, then an error is thrown".
internal/fs/fs.go
Outdated
|
||
// ResolvePath resolves the path of the given symlink. If the given path is not a symlink, | ||
// then it returns the same path. | ||
func ResolvePath(path string) (string, 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.
How is this func different from just calling filepath.EvalSymlinks()
directly?
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.
It's totally useless. 😅
I just noticed I already call IsSymlink()
before ResolvePath()
.
I'll remove it when I've time to refactor the PR.
For this particular case, the toolchain has less complex requirements than dep does. The toolchain is entirely package-oriented right now; we have to be project-oriented (trees of packages).
Yep, I've seen #743. Without having dug too far into what cloud builder is doing, it does not appear to rely on arbitrary symlinking within package trees - only symlinked roots, which is what we're addressing here. I understand that broader symlinking support can be helpful for certain cases, but we can't have it unless we can contain the complexity (not to mention potential attack vectors) it introduces in the system. Feel free to look through some of the past wreckage: sdboyer/gps#157 sdboyer/gps#177 |
I think there is some miscommunication here. I am talking about this case in particular.
Case: In this case, I get the error
As an end user, I generally expect all tools to behave in a consistent way with each other, even if that way might not be something I agree with. It's pretty sad that I hit that one case where one tool has to diverge from the norm :-/
I will definitely check it out through this weekend. Though, I suspect I am not going to find the golden fix if better people have tried and given up. Worth a shot anyway. ☮️ Edit: ordering based on importance |
Yes, and it's something that either this PR, or a follow-up, should fix.
Throughout the process, we've sought to minimize the number of significant changes that dep introduces to the ecosystem. The committee came to the conclusion that the knock-on effects of not adopting a project-orientated view were much worse than doing so. Possible changes in symlink behavior are, unfortunately, one of the results. |
That's how I reached pkgtree. This line fails because it's expecting a directory and the project root is a symlink. Would a special case fix for project roots work? |
…oots-changes Signed-off-by: Ibrahim AshShohail <[email protected]>
Signed-off-by: Ibrahim AshShohail <[email protected]>
Signed-off-by: Ibrahim AshShohail <[email protected]>
Project.ResolvedAbsRoot() returns the resolved project.AbsRoot if it's a symlink. Signed-off-by: Ibrahim AshShohail <[email protected]>
e0f2a5f
to
5bf9205
Compare
96ede09
to
aa6fd8e
Compare
Signed-off-by: Ibrahim AshShohail <[email protected]>
aa6fd8e
to
0b8edb1
Compare
Tests should be passing now. |
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.
very close now
} | ||
|
||
*/ | ||
// Ctx defines the supporting context of dep. |
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'd like to see equivalent docs to what was previously there - examples, and such.
context.go
Outdated
if err != nil { | ||
return "", errors.Wrap(err, "resolveProjectRoot") | ||
// DetectProjectGOPATH will return an error in the following cases: | ||
// If p.AbsRoot is not a symlink and is not within any known GOPATH. |
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.
Put in a newline and further indent each of these by one space. That'll make godoc format them as a fixed block. As-written, this will all get collapsed without any line breaks.
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.
Done.
project.go
Outdated
p.ResolvedAbsRoot = root | ||
|
||
// if p.AbsRoot is a symlink, set p.ResolvedAbsRoot to the resolved path. | ||
if sym, err := fs.IsSymlink(p.AbsRoot); err != nil { |
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.
Probably don't need to check this; filepath.EvalSymlinks()
will just return the input if it's not a symlink, which is the desired behavior anyway.
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.
filepath.EvalSymlinks() will recursively walk over the symlinks and errors might happen along the way (lstat
or readlink
could fail). 😞
Anyhow, I changed the flow a bit.
project.go
Outdated
// SetRoot set the project AbsRoot. If the root is a symlink, it attempts to resolve it. | ||
func (p *Project) SetRoot(root string) error { | ||
p.AbsRoot = root | ||
p.ResolvedAbsRoot = root |
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.
It's probably preferable that we only change the *Project
's internal state if filepath.EvalSymlinks()
exits without an error. This makes it generally consistent with the practice that Go functions should not return values from funcs when an error is also returned - just, in this case, those values are internal properties, rather than being return values.
Does that make sense?
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.
👍
Signed-off-by: Ibrahim AshShohail <[email protected]>
0306540
to
f40426e
Compare
Signed-off-by: Ibrahim AshShohail <[email protected]>
53e69de
to
b98d713
Compare
context.go
Outdated
l, err := os.Lstat(path) | ||
if err != nil { | ||
return "", errors.Wrap(err, "resolveProjectRoot") | ||
/* |
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.
nit: i know this works, but the standard is really just to use the //
leader for godoc comments, even when they become quite large.
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'm just now realizing that some of the existing comments did this. that's my bad - i shouldn't have merged those in the first place. still, let's not repeat the pattern.
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.
Done. 👍 I've fixed the existing occurrences.
project.go
Outdated
// ImportRoot is the import path of the project's root directory. | ||
ImportRoot gps.ProjectRoot | ||
Manifest *Manifest | ||
Lock *Lock // Optional | ||
} | ||
|
||
// SetRoot set the project AbsRoot. If the root is a symlink, it attempts to resolve it. | ||
func (p *Project) SetRoot(root string) error { | ||
sym, err := fs.IsSymlink(root) |
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.
What I was suggesting before would be this:
func (p *Project) SetRoot(root string) error {
rroot, err := filepath.EvalSymlinks(root)
if err != nil {
return err
}
p.ResolvedAbsRoot, p.AbsRoot = rroot, root
return nil
}
I think this would be equivalent. If root
is not a symlink, then it's basically a no-op. If root
is a symlink, then any error that would occur from fs.IsSymlink()
are a subset of the errors from filepath.EvalSymlinks()
.
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.
Apparently, I misunderstood the original comment. Updated now. 😁
Signed-off-by: Ibrahim AshShohail <[email protected]>
Signed-off-by: Ibrahim AshShohail <[email protected]>
Signed-off-by: Ibrahim AshShohail <[email protected]>
Signed-off-by: Ibrahim AshShohail <[email protected]>
0d05390
to
4945522
Compare
ok, i think we're looking good - waiting for tests now :D |
Signed-off-by: Ibrahim AshShohail <[email protected]>
4945522
to
13f512f
Compare
Tests are passing now. 🎉 |
and in we finally go 😄 |
This started as an attempt to clarify the FAQ update done by @brianstarke in #247.
I went to the code to understand how handling symlinks as project root is implemented currently and came to the following conclusion:
context.NewContext
will return an error if thecwd
is not within aGOPATH
beforecontext.resolveProjectRoot
is reached where symlinks are handled currently. Tests pass currently since they don't go through main like a regular cmd will.I've tried to rewrite the FAQ answer to provide some clarification:
project root
instead ofcwd
, since it's more consistent (runningdep ensure
in a project subdirectory will traverse up to the project root regardless of thecwd
).Fixes #218.