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

Commit c882f60

Browse files
committed
s/ListExternalImports/Flatten/
Much better name. Also adds the capability of filtering out stdlib from PackageTree imports, addressing sdboyer/gps#113.
1 parent 0a9f6d9 commit c882f60

File tree

5 files changed

+50
-94
lines changed

5 files changed

+50
-94
lines changed

analysis.go

Lines changed: 31 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -737,103 +737,58 @@ func wmToReach(workmap map[string]wm) ReachMap {
737737
return rm
738738
}
739739

740-
// ListExternalImports computes a sorted, deduplicated list of all the external
741-
// packages that are reachable through imports from all valid packages in a
742-
// ReachMap, as computed by PackageTree.ExternalReach().
740+
// FlattenAll flattens a reachmap into a sorted, deduplicated list of all the
741+
// external imports named by its contained packages.
743742
//
744-
// If an internal path is ignored, all of the external packages that it uniquely
745-
// imports are omitted. Note, however, that no internal transitivity checks are
746-
// made here - every non-ignored package in the tree is considered independently
747-
// (with one set of exceptions, noted below). That means, given a PackageTree
748-
// with root A and packages at A, A/foo, and A/bar, and the following import
749-
// chain:
750-
//
751-
// A -> A/foo -> A/bar -> B/baz
752-
//
753-
// If you ignore A or A/foo, A/bar will still be visited, and B/baz will be
754-
// returned, because this method visits ALL packages in the tree, not only those
755-
// reachable from the root (or any other) packages. If your use case requires
756-
// interrogating external imports with respect to only specific package entry
757-
// points, you need ExternalReach() instead.
758-
//
759-
// It is safe to pass a nil map if there are no packages to ignore.
760-
//
761-
// If an internal package has an error (that is, PackageOrErr is Err), it is excluded from
762-
// consideration. Internal packages that transitively import the error package
763-
// are also excluded. So, if:
764-
//
765-
// -> B/foo
766-
// /
767-
// A
768-
// \
769-
// -> A/bar -> B/baz
770-
//
771-
// And A/bar has some error in it, then both A and A/bar will be eliminated from
772-
// consideration; neither B/foo nor B/baz will be in the results. If A/bar, with
773-
// its errors, is ignored, however, then A will remain, and B/foo will be in the
774-
// results.
775-
//
776-
// Finally, note that if a directory is named "testdata", or has a leading dot
777-
// or underscore, it will not be directly analyzed as a source. This is in
778-
// keeping with Go tooling conventions that such directories should be ignored.
779-
// So, if:
780-
//
781-
// A -> B/foo
782-
// A/.bar -> B/baz
783-
// A/_qux -> B/baz
784-
// A/testdata -> B/baz
785-
//
786-
// Then B/foo will be returned, but B/baz will not, because all three of the
787-
// packages that import it are in directories with disallowed names.
788-
//
789-
// HOWEVER, in keeping with the Go compiler, if one of those packages in a
790-
// disallowed directory is imported by a package in an allowed directory, then
791-
// it *will* be used. That is, while tools like go list will ignore a directory
792-
// named .foo, you can still import from .foo. Thus, it must be included. So,
793-
// if:
794-
//
795-
// -> B/foo
796-
// /
797-
// A
798-
// \
799-
// -> A/.bar -> B/baz
743+
// If stdlib is true, then stdlib imports are excluded from the result.
744+
func (rm ReachMap) FlattenAll(stdlib bool) []string {
745+
return rm.flatten(func(pkg string) bool { return true }, stdlib)
746+
}
747+
748+
// Flatten flattens a reachmap into a sorted, deduplicated list of all the
749+
// external imports named by its contained packages, but excludes imports coming
750+
// from packages with disallowed patterns in their names: any path element with
751+
// a leading dot, a leading underscore, with the name "testdata".
800752
//
801-
// A is legal, and it imports A/.bar, so the results will include B/baz.
802-
func (rm ReachMap) ListExternalImports() []string {
803-
exm := make(map[string]struct{})
804-
for pkg, reach := range rm {
753+
// If stdlib is true, then stdlib imports are excluded from the result.
754+
func (rm ReachMap) Flatten(stdlib bool) []string {
755+
f := func(pkg string) bool {
805756
// Eliminate import paths with any elements having leading dots, leading
806757
// underscores, or testdata. If these are internally reachable (which is
807758
// a no-no, but possible), any external imports will have already been
808759
// pulled up through ExternalReach. The key here is that we don't want
809760
// to treat such packages as themselves being sources.
810-
//
811-
// TODO(sdboyer) strings.Split will always heap alloc, which isn't great to do
812-
// in a loop like this. We could also just parse it ourselves...
813-
var skip bool
814761
for _, elem := range strings.Split(pkg, "/") {
815762
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
816-
skip = true
817-
break
763+
return false
818764
}
819765
}
766+
return true
767+
}
820768

821-
if !skip {
822-
for _, ex := range reach {
769+
return rm.flatten(f, stdlib)
770+
}
771+
772+
func (rm ReachMap) flatten(filter func(string) bool, stdlib bool) []string {
773+
exm := make(map[string]struct{})
774+
for pkg, ie := range rm {
775+
if filter(pkg) {
776+
for _, ex := range ie.External {
777+
if !stdlib && isStdLib(ex) {
778+
continue
779+
}
823780
exm[ex] = struct{}{}
824781
}
825782
}
826783
}
827784

828785
if len(exm) == 0 {
829-
return nil
786+
return []string{}
830787
}
831788

832-
ex := make([]string, len(exm))
833-
k := 0
789+
ex := make([]string, 0, len(exm))
834790
for p := range exm {
835-
ex[k] = p
836-
k++
791+
ex = append(ex, p)
837792
}
838793

839794
sort.Strings(ex)

analysis_test.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,7 +1269,7 @@ func TestListPackagesNoPerms(t *testing.T) {
12691269
}
12701270
}
12711271

1272-
func TestListExternalImports(t *testing.T) {
1272+
func TestFlattenReachMap(t *testing.T) {
12731273
// There's enough in the 'varied' test case to test most of what matters
12741274
vptree, err := ListPackages(filepath.Join(getwd(t), "_testdata", "src", "github.com", "example", "varied"), "github.com/example/varied")
12751275
if err != nil {
@@ -1279,11 +1279,11 @@ func TestListExternalImports(t *testing.T) {
12791279
var expect []string
12801280
var name string
12811281
var ignore map[string]bool
1282-
var main, tests bool
1282+
var stdlib, main, tests bool
12831283

12841284
validate := func() {
12851285
rm := vptree.ToReachMap(main, tests, ignore)
1286-
result := rm.ListExternalImports()
1286+
result := rm.Flatten(stdlib)
12871287
if !reflect.DeepEqual(expect, result) {
12881288
t.Errorf("Wrong imports in %q case:\n\t(GOT): %s\n\t(WNT): %s", name, result, expect)
12891289
}
@@ -1323,12 +1323,22 @@ func TestListExternalImports(t *testing.T) {
13231323
// everything on
13241324
name = "simple"
13251325
except()
1326-
main, tests = true, true
1326+
stdlib, main, tests = true, true, true
13271327
validate()
13281328

1329-
// Now without tests, which should just cut one
1329+
// turning off stdlib should cut most things, but we need to override the
1330+
// function
1331+
isStdLib = doIsStdLib
1332+
name = "no stdlib"
1333+
stdlib = false
1334+
except("encoding/binary", "go/parser", "hash", "net/http", "os", "sort")
1335+
validate()
1336+
// Restore stdlib func override
1337+
overrideIsStdLib()
1338+
1339+
// stdlib back in; now exclude tests, which should just cut one
13301340
name = "no tests"
1331-
tests = false
1341+
stdlib, tests = true, false
13321342
except("encoding/binary")
13331343
validate()
13341344

@@ -1423,7 +1433,7 @@ func TestListExternalImports(t *testing.T) {
14231433
}
14241434

14251435
rm := ptree.ToReachMap(false, false, nil)
1426-
result := rm.Flatten()
1436+
result := rm.Flatten(true)
14271437
expect = []string{"github.com/sdboyer/gps", "hash", "sort"}
14281438
if !reflect.DeepEqual(expect, result) {
14291439
t.Errorf("Wrong imports in %q case:\n\t(GOT): %s\n\t(WNT): %s", name, result, expect)

rootdata.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type rootdata struct {
4646
// Ignores and requires are taken into consideration, and stdlib is excluded.
4747
func (rd rootdata) externalImportList() []string {
4848
rm := rd.rpt.ToReachMap(true, true, rd.ig)
49-
all := rm.ListExternalImports()
49+
all := rm.Flatten(false)
5050
reach := make([]string, 0, len(all))
5151
for _, r := range all {
5252
if !isStdLib(r) {

solve_basic_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,15 +1416,6 @@ func (sm *depspecSourceManager) ExternalReach(id ProjectIdentifier, v Version) (
14161416
return nil, fmt.Errorf("No reach data for %s at version %s", id.errString(), v)
14171417
}
14181418

1419-
func (sm *depspecSourceManager) ListExternal(id ProjectIdentifier, v Version) ([]string, error) {
1420-
// This should only be called for the root
1421-
pid := pident{n: ProjectRoot(id.normalizedSource()), v: v}
1422-
if r, exists := sm.rm[pid]; exists {
1423-
return r[string(id.ProjectRoot)], nil
1424-
}
1425-
return nil, fmt.Errorf("No reach data for %s at version %s", id.errString(), v)
1426-
}
1427-
14281419
func (sm *depspecSourceManager) ListPackages(id ProjectIdentifier, v Version) (PackageTree, error) {
14291420
pid := pident{n: ProjectRoot(id.normalizedSource()), v: v}
14301421

solve_bimodal_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ func (sm *bmSourceManager) GetManifestAndLock(id ProjectIdentifier, v Version) (
11211121

11221122
// computeBimodalExternalMap takes a set of depspecs and computes an
11231123
// internally-versioned external reach map that is useful for quickly answering
1124-
// ListExternal()-type calls.
1124+
// ReachMap.Flatten()-type calls.
11251125
//
11261126
// Note that it does not do things like stripping out stdlib packages - these
11271127
// maps are intended for use in SM fixtures, and that's a higher-level

0 commit comments

Comments
 (0)