Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore symlinks into Bazel output rather than relying on name #1384

Merged
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
2 changes: 0 additions & 2 deletions repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ func FindExternalRepo(repoRoot, name string) (string, error) {
// <output-base>/execroot/<workspace-name>/bazel-out
// We expect the external repository to be checked out at
// <output-base>/external/<name>
// Note that users can change the prefix for most of the Bazel symlinks with
// --symlink_prefix, but this does not include bazel-out.
externalPath := strings.Join([]string{repoRoot, "bazel-out", "..", "..", "..", "external", name}, string(os.PathSeparator))
cleanPath, err := filepath.EvalSymlinks(externalPath)
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion walk/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ go_library(
deps = [
"//config",
"//flag",
"//pathtools",
"//rule",
"@com_github_bmatcuk_doublestar_v4//:doublestar",
],
Expand Down
55 changes: 3 additions & 52 deletions walk/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"strings"

"github.com/bazelbuild/bazel-gazelle/config"
"github.com/bazelbuild/bazel-gazelle/pathtools"
"github.com/bazelbuild/bazel-gazelle/rule"
)

Expand Down Expand Up @@ -75,7 +74,7 @@ const (
// including excluded files.
//
// regularFiles is a list of base names of regular files within dir, not
// including excluded files.
// including excluded files or symlinks.
//
// genFiles is a list of names of generated files, found by reading
// "out" and "outs" attributes of rules in f.
Expand Down Expand Up @@ -116,8 +115,6 @@ func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode,
}
}

symlinks := symlinkResolver{visited: []string{c.RepoRoot}}

updateRels := buildUpdateRelMap(c.RepoRoot, dirs)

var visit func(*config.Config, string, string, bool)
Expand Down Expand Up @@ -155,10 +152,10 @@ func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode,
for _, fi := range files {
base := fi.Name()
switch {
case base == "" || wc.isExcluded(rel, base):
case base == "" || wc.isExcluded(rel, base) || fi.Mode()&os.ModeSymlink != 0:
continue

case fi.IsDir() || fi.Mode()&os.ModeSymlink != 0 && symlinks.follow(c, dir, rel, base):
case fi.IsDir():
subdirs = append(subdirs, base)

default:
Expand Down Expand Up @@ -317,49 +314,3 @@ func findGenFiles(wc *walkConfig, f *rule.File) []string {
}
return genFiles
}

type symlinkResolver struct {
visited []string
}

// Decide if symlink dir/base should be followed.
func (r *symlinkResolver) follow(c *config.Config, dir, rel, base string) bool {
if dir == c.RepoRoot && strings.HasPrefix(base, "bazel-") {
// Links such as bazel-<workspace>, bazel-out, bazel-genfiles are created by
// Bazel to point to internal build directories.
return false
}

// See if the user has explicitly directed us to follow the link.
wc := getWalkConfig(c)
linkRel := path.Join(rel, base)
for _, follow := range wc.follow {
if linkRel == follow {
return true
}
}

// See if the symlink points to a tree that has been already visited.
fullpath := filepath.Join(dir, base)
dest, err := filepath.EvalSymlinks(fullpath)
if err != nil {
return false
}
if !filepath.IsAbs(dest) {
dest, err = filepath.Abs(filepath.Join(dir, dest))
if err != nil {
return false
}
}
for _, p := range r.visited {
if pathtools.HasPrefix(dest, p) || pathtools.HasPrefix(p, dest) {
return false
}
}
r.visited = append(r.visited, dest)
stat, err := os.Stat(fullpath)
if err != nil {
return false
}
return stat.IsDir()
}
166 changes: 0 additions & 166 deletions walk/walk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"flag"
"path"
"path/filepath"
"runtime"
"testing"

"github.com/bazelbuild/bazel-gazelle/config"
Expand Down Expand Up @@ -317,171 +316,6 @@ unknown_rule(
}
}

func TestSymlinksBasic(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("symlinks not supported on windows")
}
files := []testtools.FileSpec{
{Path: "root/a.go", Content: "package a"},
{Path: "root/b", Symlink: "../b"}, // symlink outside repo is followed
{Path: "root/c", Symlink: "c"}, // symlink inside repo is not followed.
{Path: "root/d", Symlink: "../b/d"}, // symlink under root/b not followed
{Path: "root/e", Symlink: "../e"},
{Path: "c/c.go", Symlink: "package c"},
{Path: "b/b.go", Content: "package b"},
{Path: "b/d/d.go", Content: "package d"},
{Path: "e/loop", Symlink: "loop2"}, // symlink loop
{Path: "e/loop2", Symlink: "loop"},
}
dir, cleanup := testtools.CreateFiles(t, files)
defer cleanup()

root := filepath.Join(dir, "root")
c, cexts := testConfig(t, root)
var rels []string
Walk(c, cexts, []string{root}, VisitAllUpdateSubdirsMode, func(_ string, rel string, _ *config.Config, _ bool, _ *rule.File, _, _, _ []string) {
rels = append(rels, rel)
})
want := []string{"b/d", "b", "e", ""}
if diff := cmp.Diff(want, rels); diff != "" {
t.Errorf("Walk relative paths (-want +got):\n%s", diff)
}
}

func TestSymlinksIgnore(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("symlinks not supported on windows")
}
files := []testtools.FileSpec{
{
Path: "root/BUILD",
Content: "# gazelle:exclude b",
},
{Path: "root/b", Symlink: "../b"},
{Path: "b/b.go", Content: "package b"},
}
dir, cleanup := testtools.CreateFiles(t, files)
defer cleanup()

root := filepath.Join(dir, "root")
c, cexts := testConfig(t, root)
var rels []string
Walk(c, cexts, []string{root}, VisitAllUpdateSubdirsMode, func(_ string, rel string, _ *config.Config, _ bool, _ *rule.File, _, _, _ []string) {
rels = append(rels, rel)
})
want := []string{""}
if diff := cmp.Diff(want, rels); diff != "" {
t.Errorf("Walk relative paths (-want +got):\n%s", diff)
}
}

func TestSymlinksMixIgnoredAndNonIgnored(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("symlinks not supported on windows")
}
files := []testtools.FileSpec{
{
Path: "root/BUILD",
Content: "# gazelle:exclude b",
},
{Path: "root/b", Symlink: "../b"}, // ignored
{Path: "root/b2", Symlink: "../b"}, // not ignored
{Path: "b/b.go", Content: "package b"},
}
dir, cleanup := testtools.CreateFiles(t, files)
defer cleanup()

root := filepath.Join(dir, "root")
c, cexts := testConfig(t, root)
var rels []string
Walk(c, cexts, []string{root}, VisitAllUpdateSubdirsMode, func(_ string, rel string, _ *config.Config, _ bool, _ *rule.File, _, _, _ []string) {
rels = append(rels, rel)
})
want := []string{"b2", ""}
if diff := cmp.Diff(want, rels); diff != "" {
t.Errorf("Walk relative paths (-want +got):\n%s", diff)
}
}

func TestSymlinksChained(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("symlinks not supported on windows")
}
files := []testtools.FileSpec{
{Path: "root/b", Symlink: "../link0"},
{Path: "link0", Symlink: "b"},
{Path: "root/b2", Symlink: "../b"},
{Path: "b/b.go", Content: "package b"},
}
dir, cleanup := testtools.CreateFiles(t, files)
defer cleanup()

root := filepath.Join(dir, "root")
c, cexts := testConfig(t, root)
var rels []string
Walk(c, cexts, []string{root}, VisitAllUpdateSubdirsMode, func(_ string, rel string, _ *config.Config, _ bool, _ *rule.File, _, _, _ []string) {
rels = append(rels, rel)
})
want := []string{"b", ""}
if diff := cmp.Diff(want, rels); diff != "" {
t.Errorf("Walk relative paths (-want +got):\n%s", diff)
}
}

func TestSymlinksDangling(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("symlinks not supported on windows")
}
files := []testtools.FileSpec{
{Path: "root/b", Symlink: "../b"},
}
dir, cleanup := testtools.CreateFiles(t, files)
defer cleanup()

root := filepath.Join(dir, "root")
c, cexts := testConfig(t, root)
var rels []string
Walk(c, cexts, []string{root}, VisitAllUpdateSubdirsMode, func(_ string, rel string, _ *config.Config, _ bool, _ *rule.File, _, _, _ []string) {
rels = append(rels, rel)
})
want := []string{""}
if diff := cmp.Diff(want, rels); diff != "" {
t.Errorf("Walk relative paths (-want +got):\n%s", diff)
}
}

func TestSymlinksFollow(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("symlinks not supported on windows")
}
files := []testtools.FileSpec{
{Path: "staging/src/k8s.io/api/"},
{Path: "staging/src/k8s.io/BUILD.bazel", Content: "# gazelle:exclude api"},
{Path: "vendor/k8s.io/api", Symlink: "../../staging/src/k8s.io/api"},
{Path: "vendor/BUILD.bazel", Content: "# gazelle:follow k8s.io/api"},
}
dir, cleanup := testtools.CreateFiles(t, files)
defer cleanup()

c, cexts := testConfig(t, dir)
var rels []string
Walk(c, cexts, []string{dir}, VisitAllUpdateSubdirsMode, func(_ string, rel string, _ *config.Config, _ bool, _ *rule.File, _, _, _ []string) {
rels = append(rels, rel)
})
want := []string{
"staging/src/k8s.io",
"staging/src",
"staging",
"vendor/k8s.io/api",
"vendor/k8s.io",
"vendor",
"",
}
if diff := cmp.Diff(want, rels); diff != "" {
t.Errorf("Walk relative paths (-want +got):\n%s", diff)
}
}

func testConfig(t *testing.T, dir string) (*config.Config, []config.Configurer) {
args := []string{"-repo_root", dir}
cexts := []config.Configurer{&config.CommonConfigurer{}, &Configurer{}}
Expand Down