diff --git a/_testdata/src/gosimple b/_testdata/src/gosimple new file mode 120000 index 0000000000..8fd32466da --- /dev/null +++ b/_testdata/src/gosimple @@ -0,0 +1 @@ +simple \ No newline at end of file diff --git a/_testdata/src/symlinks/broken b/_testdata/src/symlinks/broken new file mode 120000 index 0000000000..d5bcc42007 --- /dev/null +++ b/_testdata/src/symlinks/broken @@ -0,0 +1 @@ +nodest \ No newline at end of file diff --git a/_testdata/src/symlinks/foo/bar b/_testdata/src/symlinks/foo/bar new file mode 120000 index 0000000000..b49d704a85 --- /dev/null +++ b/_testdata/src/symlinks/foo/bar @@ -0,0 +1 @@ +../../pkg \ No newline at end of file diff --git a/_testdata/src/symlinks/foo/foo.go b/_testdata/src/symlinks/foo/foo.go new file mode 100644 index 0000000000..bebff84371 --- /dev/null +++ b/_testdata/src/symlinks/foo/foo.go @@ -0,0 +1,7 @@ +package foo + +import "github.com/sdboyer/gps" + +var ( + _ = gps.Solve +) diff --git a/_testdata/src/symlinks/foobar b/_testdata/src/symlinks/foobar new file mode 120000 index 0000000000..337ca42526 --- /dev/null +++ b/_testdata/src/symlinks/foobar @@ -0,0 +1 @@ +foo/bar \ No newline at end of file diff --git a/_testdata/src/symlinks/gopkg b/_testdata/src/symlinks/gopkg new file mode 120000 index 0000000000..0c6117d9fb --- /dev/null +++ b/_testdata/src/symlinks/gopkg @@ -0,0 +1 @@ +pkg \ No newline at end of file diff --git a/_testdata/src/symlinks/pkg/bar b/_testdata/src/symlinks/pkg/bar new file mode 120000 index 0000000000..ba0e162e1c --- /dev/null +++ b/_testdata/src/symlinks/pkg/bar @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/_testdata/src/symlinks/pkg/gopkg.go b/_testdata/src/symlinks/pkg/gopkg.go new file mode 100644 index 0000000000..f275b838af --- /dev/null +++ b/_testdata/src/symlinks/pkg/gopkg.go @@ -0,0 +1,5 @@ +package gopkg + +const ( + foo = "foo" +) diff --git a/_testdata/src/symlinks/symlinks.go b/_testdata/src/symlinks/symlinks.go new file mode 100644 index 0000000000..02ffc6115f --- /dev/null +++ b/_testdata/src/symlinks/symlinks.go @@ -0,0 +1,12 @@ +package symlinks + +import ( + gopkg "symlinks/gopkg" + + "github.com/sdboyer/gps" +) + +var ( + _ = gps.Solve + _ = gopkg.foo +) diff --git a/analysis.go b/analysis.go index e5f0fe3cec..9b007ea2fa 100644 --- a/analysis.go +++ b/analysis.go @@ -83,7 +83,11 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) { if err != nil && err != filepath.SkipDir { return err } - if !fi.IsDir() { + + // Read the destination of named symbolic link + if fi, err := readSymlink(wp, fileRoot, fi); err != nil { + return nil + } else if !fi.IsDir() { return nil } @@ -195,6 +199,32 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) { return ptree, nil } +func readSymlink(wp, fileRoot string, fi os.FileInfo) (os.FileInfo, error) { + // read only symlink dir + if fi.IsDir() || fi.Mode()&os.ModeSymlink == 0 { + return fi, nil + } + + dst, err := os.Readlink(wp) + if err != nil { + return fi, err + } + + // All absolute symlinks are disqualified; if one is encountered, it should be skipped. + if filepath.IsAbs(dst) { + return fi, nil + } + + // Relative symlinks pointing to somewhere outside of the root (via ..) should also be skipped. + dst, err = filepath.EvalSymlinks(wp) + if err != nil { + return fi, nil + } else if !strings.HasPrefix(dst, fileRoot) { + return fi, nil + } + return os.Lstat(dst) +} + // fillPackage full of info. Assumes p.Dir is set at a minimum func fillPackage(p *build.Package) error { var buildPrefix = "// +build " diff --git a/analysis_test.go b/analysis_test.go index 47adb6a745..706ff7aa37 100644 --- a/analysis_test.go +++ b/analysis_test.go @@ -466,12 +466,13 @@ func TestListPackages(t *testing.T) { return filepath.Join(srcdir, filepath.Join(s...)) } - table := map[string]struct { + type tc struct { fileRoot string importRoot string out PackageTree err error - }{ + } + table := map[string]tc{ "empty": { fileRoot: j("empty"), importRoot: "empty", @@ -1234,6 +1235,62 @@ func TestListPackages(t *testing.T) { }, }, } + if runtime.GOOS != "windows" { + table["follow_symlink"] = tc{ + fileRoot: j("gosimple"), + importRoot: "gosimple", + out: PackageTree{ + ImportRoot: "gosimple", + Packages: map[string]PackageOrErr{}, + }, + } + table["follow symlinks inside of package"] = tc{ + fileRoot: j("symlinks"), + importRoot: "symlinks", + out: PackageTree{ + ImportRoot: "symlinks", + Packages: map[string]PackageOrErr{ + "symlinks/gopkg": { + P: Package{ + ImportPath: "symlinks/gopkg", + CommentPath: "", + Name: "gopkg", + Imports: []string{}, + }, + }, + "symlinks/pkg": { + P: Package{ + ImportPath: "symlinks/pkg", + CommentPath: "", + Name: "gopkg", + Imports: []string{}, + }, + }, + "symlinks": { + P: Package{ + ImportPath: "symlinks", + CommentPath: "", + Name: "symlinks", + Imports: []string{ + "github.com/sdboyer/gps", + "symlinks/gopkg", + }, + }, + }, + "symlinks/foo": { + P: Package{ + ImportPath: "symlinks/foo", + CommentPath: "", + Name: "foo", + Imports: []string{ + "github.com/sdboyer/gps", + }, + }, + }, + }, + }, + } + } for name, fix := range table { if _, err := os.Stat(fix.fileRoot); err != nil { diff --git a/circle.yml b/circle.yml index 8be1609360..84e08b383b 100644 --- a/circle.yml +++ b/circle.yml @@ -8,6 +8,7 @@ dependencies: - wget https://github.com/Masterminds/glide/releases/download/0.10.1/glide-0.10.1-linux-amd64.tar.gz - tar -vxz -C $HOME/bin --strip=1 -f glide-0.10.1-linux-amd64.tar.gz override: + - mkdir -p $HOME/.go_workspace/src - glide --home $HOME/.glide -y glide.yaml install --cache - mkdir -p $RD - rsync -azC --delete ./ $RD