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

Resolve symlinks if project root has them. #247

Merged
merged 15 commits into from
Apr 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
}
}