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

Commit

Permalink
Merge pull request #247 from brianstarke/resolve-symlinks
Browse files Browse the repository at this point in the history
Resolve symlinks if project root has them.
  • Loading branch information
sdboyer authored Apr 11, 2017
2 parents 7f85e04 + 753c8dd commit 3ef7bf8
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 3 deletions.
61 changes: 58 additions & 3 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import (

// Ctx defines the supporting context of the tool.
type Ctx struct {
GOPATH string // Go path
GOPATH string // Selected Go path
GOPATHS []string // Other Go paths
}

// NewContext creates a struct with the project's GOPATH. It assumes
Expand All @@ -26,18 +27,28 @@ func NewContext() (*Ctx, error) {
// this way we get the default GOPATH that was added in 1.8
buildContext := build.Default
wd, err := os.Getwd()

if err != nil {
return nil, errors.Wrap(err, "getting work directory")
}
wd = filepath.FromSlash(wd)
ctx := &Ctx{}

for _, gp := range filepath.SplitList(buildContext.GOPATH) {
gp = filepath.FromSlash(gp)

if filepath.HasPrefix(wd, gp) {
return &Ctx{GOPATH: gp}, nil
ctx.GOPATH = gp
}

ctx.GOPATHS = append(ctx.GOPATHS, gp)
}

return nil, errors.New("project not in a GOPATH")
if ctx.GOPATH == "" {
return nil, errors.New("project not in a GOPATH")
}

return ctx, nil
}

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

// The path may lie within a symlinked directory, resolve the path
// before moving forward
p.AbsRoot, err = c.resolveProjectRoot(p.AbsRoot)
if err != nil {
return nil, errors.Wrapf(err, "resolve project root")
}

ip, err := c.SplitAbsoluteProjectRoot(p.AbsRoot)
if err != nil {
return nil, errors.Wrap(err, "split absolute project root")
Expand Down Expand Up @@ -117,6 +135,43 @@ func (c *Ctx) LoadProject(path string) (*Project, error) {
return p, nil
}

// resolveProjectRoot evaluates the root directory and does the following:
//
// If the passed path is a symlink outside GOPATH to a directory within a
// GOPATH, the resolved full real path is returned.
//
// If the passed path is a symlink within a GOPATH, we return an error.
//
// If the passed path isn't a symlink at all, we just pass through.
func (c *Ctx) resolveProjectRoot(path string) (string, error) {
// Determine if this path is a Symlink
l, err := os.Lstat(path)
if err != nil {
return "", errors.Wrap(err, "resolveProjectRoot")
}

// Pass through if not
if l.Mode()&os.ModeSymlink == 0 {
return path, nil
}

// Resolve path
resolved, err := filepath.EvalSymlinks(path)
if err != nil {
return "", errors.Wrap(err, "resolveProjectRoot")
}

// Determine if the symlink is within any of the GOPATHs, in which case we're not
// sure how to resolve it.
for _, gp := range c.GOPATHS {
if filepath.HasPrefix(path, gp) {
return "", errors.Errorf("'%s' is linked to another path within a GOPATH (%s)", path, gp)
}
}

return resolved, nil
}

// SplitAbsoluteProjectRoot takes an absolute path and compares it against declared
// GOPATH(s) to determine what portion of the input path should be treated as an
// import path - as a project root.
Expand Down
72 changes: 72 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func TestSplitAbsoluteProjectRoot(t *testing.T) {
defer h.Cleanup()

h.TempDir("src")

h.Setenv("GOPATH", h.Path("."))
depCtx := &Ctx{GOPATH: h.Path(".")}

Expand Down Expand Up @@ -346,3 +347,74 @@ func TestCaseInsentitiveGOPATH(t *testing.T) {
t.Fatalf("expected %s, got %s", ip, pr)
}
}

func TestResolveProjectRoot(t *testing.T) {
tg := test.NewHelper(t)
defer tg.Cleanup()

tg.TempDir("go")
tg.TempDir("go/src")
tg.TempDir("go/src/real")
tg.TempDir("go/src/real/path")
tg.TempDir("go/src/sym")

tg.TempDir("gotwo") // Another directory used as a GOPATH
tg.TempDir("gotwo/src")
tg.TempDir("gotwo/src/real")
tg.TempDir("gotwo/src/real/path")
tg.TempDir("gotwo/src/sym")

tg.TempDir("sym") // Directory for symlinks

tg.Setenv("GOPATH", tg.Path(filepath.Join(".", "go")))

ctx := &Ctx{
GOPATH: tg.Path(filepath.Join(".", "go")),
GOPATHS: []string{
tg.Path(filepath.Join(".", "go")),
tg.Path(filepath.Join(".", "gotwo")),
},
}

realPath := filepath.Join(ctx.GOPATH, "src", "real", "path")
realPathTwo := filepath.Join(ctx.GOPATHS[1], "src", "real", "path")
symlinkedPath := filepath.Join(tg.Path("."), "sym", "symlink")
symlinkedInGoPath := filepath.Join(ctx.GOPATH, "src/sym/path")
symlinkedInOtherGoPath := filepath.Join(tg.Path("."), "sym", "symtwo")
os.Symlink(realPath, symlinkedPath)
os.Symlink(realPath, symlinkedInGoPath)
os.Symlink(realPathTwo, symlinkedInOtherGoPath)

// Real path should be returned, no symlinks to deal with
p, err := ctx.resolveProjectRoot(realPath)
if err != nil {
t.Fatalf("Error resolving project root: %s", err)
}
if p != realPath {
t.Fatalf("Want path to be %s, got %s", realPath, p)
}

// Real path should be returned, symlink is outside GOPATH
p, err = ctx.resolveProjectRoot(symlinkedPath)
if err != nil {
t.Fatalf("Error resolving project root: %s", err)
}
if p != realPath {
t.Fatalf("Want path to be %s, got %s", realPath, p)
}

// Real path should be returned, symlink is in another GOPATH
p, err = ctx.resolveProjectRoot(symlinkedInOtherGoPath)
if err != nil {
t.Fatalf("Error resolving project root: %s", err)
}
if p != realPathTwo {
t.Fatalf("Want path to be %s, got %s", realPathTwo, p)
}

// Symlinked path is inside GOPATH, should return error
_, err = ctx.resolveProjectRoot(symlinkedInGoPath)
if err == nil {
t.Fatalf("Wanted an error")
}
}

0 comments on commit 3ef7bf8

Please sign in to comment.