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

Commit 3ef7bf8

Browse files
authored
Merge pull request #247 from brianstarke/resolve-symlinks
Resolve symlinks if project root has them.
2 parents 7f85e04 + 753c8dd commit 3ef7bf8

File tree

2 files changed

+130
-3
lines changed

2 files changed

+130
-3
lines changed

context.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import (
1717

1818
// Ctx defines the supporting context of the tool.
1919
type Ctx struct {
20-
GOPATH string // Go path
20+
GOPATH string // Selected Go path
21+
GOPATHS []string // Other Go paths
2122
}
2223

2324
// NewContext creates a struct with the project's GOPATH. It assumes
@@ -26,18 +27,28 @@ func NewContext() (*Ctx, error) {
2627
// this way we get the default GOPATH that was added in 1.8
2728
buildContext := build.Default
2829
wd, err := os.Getwd()
30+
2931
if err != nil {
3032
return nil, errors.Wrap(err, "getting work directory")
3133
}
3234
wd = filepath.FromSlash(wd)
35+
ctx := &Ctx{}
36+
3337
for _, gp := range filepath.SplitList(buildContext.GOPATH) {
3438
gp = filepath.FromSlash(gp)
39+
3540
if filepath.HasPrefix(wd, gp) {
36-
return &Ctx{GOPATH: gp}, nil
41+
ctx.GOPATH = gp
3742
}
43+
44+
ctx.GOPATHS = append(ctx.GOPATHS, gp)
3845
}
3946

40-
return nil, errors.New("project not in a GOPATH")
47+
if ctx.GOPATH == "" {
48+
return nil, errors.New("project not in a GOPATH")
49+
}
50+
51+
return ctx, nil
4152
}
4253

4354
func (c *Ctx) SourceManager() (*gps.SourceMgr, error) {
@@ -74,6 +85,13 @@ func (c *Ctx) LoadProject(path string) (*Project, error) {
7485
return nil, err
7586
}
7687

88+
// The path may lie within a symlinked directory, resolve the path
89+
// before moving forward
90+
p.AbsRoot, err = c.resolveProjectRoot(p.AbsRoot)
91+
if err != nil {
92+
return nil, errors.Wrapf(err, "resolve project root")
93+
}
94+
7795
ip, err := c.SplitAbsoluteProjectRoot(p.AbsRoot)
7896
if err != nil {
7997
return nil, errors.Wrap(err, "split absolute project root")
@@ -117,6 +135,43 @@ func (c *Ctx) LoadProject(path string) (*Project, error) {
117135
return p, nil
118136
}
119137

138+
// resolveProjectRoot evaluates the root directory and does the following:
139+
//
140+
// If the passed path is a symlink outside GOPATH to a directory within a
141+
// GOPATH, the resolved full real path is returned.
142+
//
143+
// If the passed path is a symlink within a GOPATH, we return an error.
144+
//
145+
// If the passed path isn't a symlink at all, we just pass through.
146+
func (c *Ctx) resolveProjectRoot(path string) (string, error) {
147+
// Determine if this path is a Symlink
148+
l, err := os.Lstat(path)
149+
if err != nil {
150+
return "", errors.Wrap(err, "resolveProjectRoot")
151+
}
152+
153+
// Pass through if not
154+
if l.Mode()&os.ModeSymlink == 0 {
155+
return path, nil
156+
}
157+
158+
// Resolve path
159+
resolved, err := filepath.EvalSymlinks(path)
160+
if err != nil {
161+
return "", errors.Wrap(err, "resolveProjectRoot")
162+
}
163+
164+
// Determine if the symlink is within any of the GOPATHs, in which case we're not
165+
// sure how to resolve it.
166+
for _, gp := range c.GOPATHS {
167+
if filepath.HasPrefix(path, gp) {
168+
return "", errors.Errorf("'%s' is linked to another path within a GOPATH (%s)", path, gp)
169+
}
170+
}
171+
172+
return resolved, nil
173+
}
174+
120175
// SplitAbsoluteProjectRoot takes an absolute path and compares it against declared
121176
// GOPATH(s) to determine what portion of the input path should be treated as an
122177
// import path - as a project root.

context_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func TestSplitAbsoluteProjectRoot(t *testing.T) {
3838
defer h.Cleanup()
3939

4040
h.TempDir("src")
41+
4142
h.Setenv("GOPATH", h.Path("."))
4243
depCtx := &Ctx{GOPATH: h.Path(".")}
4344

@@ -346,3 +347,74 @@ func TestCaseInsentitiveGOPATH(t *testing.T) {
346347
t.Fatalf("expected %s, got %s", ip, pr)
347348
}
348349
}
350+
351+
func TestResolveProjectRoot(t *testing.T) {
352+
tg := test.NewHelper(t)
353+
defer tg.Cleanup()
354+
355+
tg.TempDir("go")
356+
tg.TempDir("go/src")
357+
tg.TempDir("go/src/real")
358+
tg.TempDir("go/src/real/path")
359+
tg.TempDir("go/src/sym")
360+
361+
tg.TempDir("gotwo") // Another directory used as a GOPATH
362+
tg.TempDir("gotwo/src")
363+
tg.TempDir("gotwo/src/real")
364+
tg.TempDir("gotwo/src/real/path")
365+
tg.TempDir("gotwo/src/sym")
366+
367+
tg.TempDir("sym") // Directory for symlinks
368+
369+
tg.Setenv("GOPATH", tg.Path(filepath.Join(".", "go")))
370+
371+
ctx := &Ctx{
372+
GOPATH: tg.Path(filepath.Join(".", "go")),
373+
GOPATHS: []string{
374+
tg.Path(filepath.Join(".", "go")),
375+
tg.Path(filepath.Join(".", "gotwo")),
376+
},
377+
}
378+
379+
realPath := filepath.Join(ctx.GOPATH, "src", "real", "path")
380+
realPathTwo := filepath.Join(ctx.GOPATHS[1], "src", "real", "path")
381+
symlinkedPath := filepath.Join(tg.Path("."), "sym", "symlink")
382+
symlinkedInGoPath := filepath.Join(ctx.GOPATH, "src/sym/path")
383+
symlinkedInOtherGoPath := filepath.Join(tg.Path("."), "sym", "symtwo")
384+
os.Symlink(realPath, symlinkedPath)
385+
os.Symlink(realPath, symlinkedInGoPath)
386+
os.Symlink(realPathTwo, symlinkedInOtherGoPath)
387+
388+
// Real path should be returned, no symlinks to deal with
389+
p, err := ctx.resolveProjectRoot(realPath)
390+
if err != nil {
391+
t.Fatalf("Error resolving project root: %s", err)
392+
}
393+
if p != realPath {
394+
t.Fatalf("Want path to be %s, got %s", realPath, p)
395+
}
396+
397+
// Real path should be returned, symlink is outside GOPATH
398+
p, err = ctx.resolveProjectRoot(symlinkedPath)
399+
if err != nil {
400+
t.Fatalf("Error resolving project root: %s", err)
401+
}
402+
if p != realPath {
403+
t.Fatalf("Want path to be %s, got %s", realPath, p)
404+
}
405+
406+
// Real path should be returned, symlink is in another GOPATH
407+
p, err = ctx.resolveProjectRoot(symlinkedInOtherGoPath)
408+
if err != nil {
409+
t.Fatalf("Error resolving project root: %s", err)
410+
}
411+
if p != realPathTwo {
412+
t.Fatalf("Want path to be %s, got %s", realPathTwo, p)
413+
}
414+
415+
// Symlinked path is inside GOPATH, should return error
416+
_, err = ctx.resolveProjectRoot(symlinkedInGoPath)
417+
if err == nil {
418+
t.Fatalf("Wanted an error")
419+
}
420+
}

0 commit comments

Comments
 (0)