Skip to content

Commit 9ed9df5

Browse files
committed
Exclude root dirs from source list if bad path
Fixes golang#62. Also fixes a weird loop issue that caused erroneous poisoning in wmToReach()'s depth-first traversal.
1 parent 0924ae8 commit 9ed9df5

File tree

10 files changed

+182
-59
lines changed

10 files changed

+182
-59
lines changed

_testdata/src/disallow/.m1p/a.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package m1p
2+
3+
import (
4+
"sort"
5+
6+
"github.com/sdboyer/gps"
7+
)
8+
9+
var (
10+
_ = sort.Strings
11+
S = gps.Solve
12+
)

_testdata/src/disallow/.m1p/b.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package m1p
2+
3+
import (
4+
"os"
5+
"sort"
6+
)
7+
8+
var (
9+
_ = sort.Strings
10+
_ = os.PathSeparator
11+
)

_testdata/src/disallow/a.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package disallow
2+
3+
import (
4+
"sort"
5+
"disallow/.m1p"
6+
7+
"github.com/sdboyer/gps"
8+
)
9+
10+
var (
11+
_ = sort.Strings
12+
_ = gps.Solve
13+
_ = m1p.S
14+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package testdata
2+
3+
import "hash"
4+
5+
var (
6+
H = hash.Hash
7+
)

analysis.go

+73-44
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,18 @@ func listPackages(fileRoot, importRoot string) (PackageTree, error) {
139139

140140
// Skip dirs that are known to hold non-local/dependency code.
141141
//
142-
// We don't skip .*, _*, or testdata dirs because, while it may be poor
143-
// form, it's not a compiler error to import them.
142+
// We don't skip _*, or testdata dirs because, while it may be poor
143+
// form, importing them is not a compilation error.
144144
switch fi.Name() {
145145
case "vendor", "Godeps":
146146
return filepath.SkipDir
147147
}
148+
// We do skip dot-dirs, though, because it's such a ubiquitous standard
149+
// that they not be visited by normal commands, and because things get
150+
// really weird if we don't.
151+
//
152+
// TODO(sdboyer) does this entail that we should chuck dot-led import
153+
// paths later on?
148154
if strings.HasPrefix(fi.Name(), ".") {
149155
return filepath.SkipDir
150156
}
@@ -391,6 +397,14 @@ func wmToReach(workmap map[string]wm, basedir string) map[string][]string {
391397
// path is poisoned.
392398
var clean bool
393399
for in := range w.in {
400+
// It's possible, albeit weird, for a package to import itself.
401+
// If we try to visit self, though, then it erroneously poisons
402+
// the path, as it would be interpreted as grey. In reality,
403+
// this becomes a no-op, so just skip it.
404+
if in == pkg {
405+
continue
406+
}
407+
394408
clean = dfe(in, path)
395409
if !clean {
396410
// Path is poisoned. Our reachmap was already deleted by the
@@ -720,8 +734,8 @@ type PackageOrErr struct {
720734
// transitively imported by the internal packages in the tree.
721735
//
722736
// main indicates whether (true) or not (false) to include main packages in the
723-
// analysis. main packages should generally be excluded when analyzing the
724-
// non-root dependency, as they inherently can't be imported.
737+
// analysis. main packages are generally excluded when analyzing anything other
738+
// than the root project, as they inherently can't be imported.
725739
//
726740
// tests indicates whether (true) or not (false) to include imports from test
727741
// files in packages when computing the reach map.
@@ -826,9 +840,10 @@ func (t PackageTree) ExternalReach(main, tests bool, ignore map[string]bool) map
826840
//
827841
// If an internal path is ignored, all of the external packages that it uniquely
828842
// imports are omitted. Note, however, that no internal transitivity checks are
829-
// made here - every non-ignored package in the tree is considered
830-
// independently. That means, given a PackageTree with root A and packages at A,
831-
// A/foo, and A/bar, and the following import chain:
843+
// made here - every non-ignored package in the tree is considered independently
844+
// (with one set of exceptions, noted below). That means, given a PackageTree
845+
// with root A and packages at A, A/foo, and A/bar, and the following import
846+
// chain:
832847
//
833848
// A -> A/foo -> A/bar -> B/baz
834849
//
@@ -854,50 +869,64 @@ func (t PackageTree) ExternalReach(main, tests bool, ignore map[string]bool) map
854869
// consideration; neither B/foo nor B/baz will be in the results. If A/bar, with
855870
// its errors, is ignored, however, then A will remain, and B/foo will be in the
856871
// results.
857-
func (t PackageTree) ListExternalImports(main, tests bool, ignore map[string]bool) ([]string, error) {
858-
var someerrs bool
859-
exm := make(map[string]struct{})
860-
861-
if ignore == nil {
862-
ignore = make(map[string]bool)
863-
}
864-
865-
var imps []string
866-
for ip, perr := range t.Packages {
867-
if perr.Err != nil {
868-
someerrs = true
869-
continue
870-
}
871-
872-
p := perr.P
873-
// Skip main packages, unless param says otherwise
874-
if p.Name == "main" && !main {
875-
continue
876-
}
877-
// Skip ignored packages
878-
if ignore[ip] {
879-
continue
880-
}
872+
//
873+
// Finally, note that if a directory is named "testdata", or has a leading dot
874+
// or underscore, it will not be directly analyzed as a source. This is in
875+
// keeping with Go tooling conventions that such directories should be ignored.
876+
// So, if:
877+
//
878+
// A -> B/foo
879+
// A/.bar -> B/baz
880+
// A/_qux -> B/baz
881+
// A/testdata -> B/baz
882+
//
883+
// Then B/foo will be returned, but B/baz will not, because all three of the
884+
// packages that import it are in directories with disallowed names.
885+
//
886+
// HOWEVER, in keeping with the Go compiler, if one of those packages in a
887+
// disallowed directory is imported by a package in an allowed directory, then
888+
// it *will* be used. That is, while tools like go list will ignore a directory
889+
// named .foo, you can still import from .foo. Thus, it must be included. So,
890+
// if:
891+
//
892+
// -> B/foo
893+
// /
894+
// A
895+
// \
896+
// -> A/.bar -> B/baz
897+
//
898+
// A is legal, and it imports A/.bar, so the results will include B/baz.
899+
func (t PackageTree) ListExternalImports(main, tests bool, ignore map[string]bool) []string {
900+
// First, we need a reachmap
901+
rm := t.ExternalReach(main, tests, ignore)
881902

882-
imps = imps[:0]
883-
imps = p.Imports
884-
if tests {
885-
imps = dedupeStrings(imps, p.TestImports)
903+
exm := make(map[string]struct{})
904+
for pkg, reach := range rm {
905+
// Eliminate import paths with any elements having leading dots, leading
906+
// underscores, or testdata. If these are internally reachable (which is
907+
// a no-no, but possible), any external imports will have already been
908+
// pulled up through ExternalReach. The key here is that we don't want
909+
// to treat such packages as themselves being sources.
910+
//
911+
// TODO(sdboyer) strings.Split will always heap alloc, which isn't great to do
912+
// in a loop like this. We could also just parse it ourselves...
913+
var skip bool
914+
for _, elem := range strings.Split(pkg, "/") {
915+
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
916+
skip = true
917+
break
918+
}
886919
}
887920

888-
for _, imp := range imps {
889-
if !checkPrefixSlash(filepath.Clean(imp), t.ImportRoot) && !ignore[imp] {
890-
exm[imp] = struct{}{}
921+
if !skip {
922+
for _, ex := range reach {
923+
exm[ex] = struct{}{}
891924
}
892925
}
893926
}
894927

895928
if len(exm) == 0 {
896-
if someerrs {
897-
// TODO(sdboyer) proper errs
898-
return nil, fmt.Errorf("No packages without errors in %s", t.ImportRoot)
899-
}
900-
return nil, nil
929+
return nil
901930
}
902931

903932
ex := make([]string, len(exm))
@@ -908,7 +937,7 @@ func (t PackageTree) ListExternalImports(main, tests bool, ignore map[string]boo
908937
}
909938

910939
sort.Strings(ex)
911-
return ex, nil
940+
return ex
912941
}
913942

914943
// checkPrefixSlash checks to see if the prefix is a prefix of the string as-is,

analysis_test.go

+58-7
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,50 @@ func TestListPackages(t *testing.T) {
698698
},
699699
},
700700
},
701+
// has disallowed dir names
702+
"disallowed dirs": {
703+
fileRoot: j("disallow"),
704+
importRoot: "disallow",
705+
out: PackageTree{
706+
ImportRoot: "disallow",
707+
Packages: map[string]PackageOrErr{
708+
"disallow": {
709+
P: Package{
710+
ImportPath: "disallow",
711+
CommentPath: "",
712+
Name: "disallow",
713+
Imports: []string{
714+
"disallow/.m1p",
715+
"github.com/sdboyer/gps",
716+
"sort",
717+
},
718+
},
719+
},
720+
"disallow/.m1p": {
721+
P: Package{
722+
ImportPath: "disallow/.m1p",
723+
CommentPath: "",
724+
Name: "m1p",
725+
Imports: []string{
726+
"github.com/sdboyer/gps",
727+
"os",
728+
"sort",
729+
},
730+
},
731+
},
732+
"disallow/testdata": {
733+
P: Package{
734+
ImportPath: "disallow/testdata",
735+
CommentPath: "",
736+
Name: "testdata",
737+
Imports: []string{
738+
"hash",
739+
},
740+
},
741+
},
742+
},
743+
},
744+
},
701745
// This case mostly exists for the PackageTree methods, but it does
702746
// cover a bit of range
703747
"varied": {
@@ -854,10 +898,7 @@ func TestListExternalImports(t *testing.T) {
854898
var main, tests bool
855899

856900
validate := func() {
857-
result, err := vptree.ListExternalImports(main, tests, ignore)
858-
if err != nil {
859-
t.Errorf("%q case returned err: %s", name, err)
860-
}
901+
result := vptree.ListExternalImports(main, tests, ignore)
861902
if !reflect.DeepEqual(expect, result) {
862903
t.Errorf("Wrong imports in %q case:\n\t(GOT): %s\n\t(WNT): %s", name, result, expect)
863904
}
@@ -942,8 +983,7 @@ func TestListExternalImports(t *testing.T) {
942983
ignore = map[string]bool{
943984
"varied/simple": true,
944985
}
945-
// we get github.com/sdboyer/gps from m1p, too, so it should still be
946-
// there
986+
// we get github.com/sdboyer/gps from m1p, too, so it should still be there
947987
except("go/parser")
948988
validate()
949989

@@ -990,6 +1030,18 @@ func TestListExternalImports(t *testing.T) {
9901030
}
9911031
except("sort", "github.com/sdboyer/gps", "go/parser")
9921032
validate()
1033+
1034+
// The only thing varied *doesn't* cover is disallowed path patterns
1035+
ptree, err := listPackages(filepath.Join(getwd(t), "_testdata", "src", "disallow"), "disallow")
1036+
if err != nil {
1037+
t.Fatalf("listPackages failed on disallow test case: %s", err)
1038+
}
1039+
1040+
result := ptree.ListExternalImports(false, false, nil)
1041+
expect = []string{"github.com/sdboyer/gps", "os", "sort"}
1042+
if !reflect.DeepEqual(expect, result) {
1043+
t.Errorf("Wrong imports in %q case:\n\t(GOT): %s\n\t(WNT): %s", name, result, expect)
1044+
}
9931045
}
9941046

9951047
func TestExternalReach(t *testing.T) {
@@ -1188,7 +1240,6 @@ func TestExternalReach(t *testing.T) {
11881240
"varied/namemismatch",
11891241
)
11901242
validate()
1191-
11921243
}
11931244

11941245
var _ = map[string][]string{

bridge.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ func (b *bridge) computeRootReach() ([]string, error) {
375375
return nil, err
376376
}
377377

378-
return ptree.ListExternalImports(true, true, b.s.ig)
378+
return ptree.ListExternalImports(true, true, b.s.ig), nil
379379
}
380380

381381
func (b *bridge) listRootPackages() (PackageTree, error) {

manifest.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ package gps
1414
// not themselves import. This is by design, but its implications are complex.
1515
// See the gps docs for more information: https://github.com/sdboyer/gps/wiki
1616
type Manifest interface {
17-
// Returns a list of project constraints that will be universally to
18-
// the depgraph.
17+
// Returns a list of project-level constraints.
1918
DependencyConstraints() []ProjectConstraint
2019
// Returns a list of constraints applicable to test imports. Note that this
2120
// will only be consulted for root manifests.

solve_basic_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,7 @@ func (b *depspecBridge) computeRootReach() ([]string, error) {
10891089
return nil, err
10901090
}
10911091

1092-
return ptree.ListExternalImports(true, true, dsm.ignore())
1092+
return ptree.ListExternalImports(true, true, dsm.ignore()), nil
10931093
}
10941094

10951095
// override verifyRoot() on bridge to prevent any filesystem interaction

solver.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -433,10 +433,10 @@ func (s *solver) selectRoot() error {
433433
// If we're looking for root's deps, get it from opts and local root
434434
// analysis, rather than having the sm do it
435435
mdeps := append(s.rm.DependencyConstraints(), s.rm.TestDependencyConstraints()...)
436-
reach, err := s.b.computeRootReach()
437-
if err != nil {
438-
return err
439-
}
436+
437+
// Err is not possible at this point, as it could only come from
438+
// listPackages(), which if we're here already succeeded for root
439+
reach, _ := s.b.computeRootReach()
440440

441441
deps, err := s.intersectConstraintsWithImports(mdeps, reach)
442442
if err != nil {

0 commit comments

Comments
 (0)