When choosing the canonical dep for an import, favor the dep that is already on the rule.
PiperOrigin-RevId: 241808406
diff --git a/ts_auto_deps/analyze/analyze.go b/ts_auto_deps/analyze/analyze.go
index a80a554..b81c4b0 100644
--- a/ts_auto_deps/analyze/analyze.go
+++ b/ts_auto_deps/analyze/analyze.go
@@ -82,7 +82,7 @@
//
// Only returns rules visible to currentPkg. If currentPkg is an empty string
// returns all targets regardless of visibility.
- LoadImportPaths(ctx context.Context, currentPkg, root string, paths []string) (map[string]*appb.Rule, error)
+ LoadImportPaths(ctx context.Context, targetToAnalyze *appb.Rule, currentPkg, root string, paths []string) (map[string]*appb.Rule, error)
}
// Analyzer uses a BuildLoader to generate dependency reports.
@@ -315,9 +315,9 @@
// resolveImports finds targets which provide the imported file or library
// for imports without known targets.
func (a *Analyzer) resolveImports(ctx context.Context, currentPkg, root string, targets map[string]*resolvedTarget) error {
- var paths []string
- needingResolution := make(map[string][]*ts_auto_depsImport)
for _, target := range targets {
+ var paths []string
+ needingResolution := make(map[string][]*ts_auto_depsImport)
for _, imports := range target.imports {
handlingImports:
for _, imp := range imports {
@@ -343,18 +343,18 @@
imp.knownTarget = d
}
}
- }
- if len(needingResolution) == 0 {
- return nil
- }
- res, err := a.loader.LoadImportPaths(ctx, currentPkg, root, paths)
- if err != nil {
- return err
- }
- for path, imports := range needingResolution {
- if target, ok := res[path]; ok {
- for _, imp := range imports {
- imp.knownTarget = redirectedLabel(target)
+ if len(needingResolution) == 0 {
+ continue
+ }
+ res, err := a.loader.LoadImportPaths(ctx, target.rule, currentPkg, root, paths)
+ if err != nil {
+ return err
+ }
+ for path, imports := range needingResolution {
+ if target, ok := res[path]; ok {
+ for _, imp := range imports {
+ imp.knownTarget = redirectedLabel(target)
+ }
}
}
}
diff --git a/ts_auto_deps/analyze/analyze_test.go b/ts_auto_deps/analyze/analyze_test.go
index 9e97220..140e025 100644
--- a/ts_auto_deps/analyze/analyze_test.go
+++ b/ts_auto_deps/analyze/analyze_test.go
@@ -67,7 +67,7 @@
bl.targetsByLabels[label] = value
}
-func (bl *fakeTargetLoader) LoadImportPaths(_ context.Context, _, _ string, paths []string) (map[string]*appb.Rule, error) {
+func (bl *fakeTargetLoader) LoadImportPaths(_ context.Context, _ *appb.Rule, _, _ string, paths []string) (map[string]*appb.Rule, error) {
return bl.loadRules(bl.targetsByImportPaths, paths)
}
diff --git a/ts_auto_deps/analyze/loader.go b/ts_auto_deps/analyze/loader.go
index c42ddb8..90b61fa 100644
--- a/ts_auto_deps/analyze/loader.go
+++ b/ts_auto_deps/analyze/loader.go
@@ -153,7 +153,7 @@
// LoadImportPaths uses Bazel Query to load targets associated with import
// paths from BUILD files.
-func (q *QueryBasedTargetLoader) LoadImportPaths(ctx context.Context, currentPkg, workspaceRoot string, paths []string) (map[string]*appb.Rule, error) {
+func (q *QueryBasedTargetLoader) LoadImportPaths(ctx context.Context, targetToAnalyze *appb.Rule, currentPkg, workspaceRoot string, paths []string) (map[string]*appb.Rule, error) {
debugf("loading imports visible to %q relative to %q: %q", currentPkg, workspaceRoot, paths)
results := make(map[string]*appb.Rule)
@@ -270,7 +270,7 @@
matchingDeps = append(matchingDeps, candidate)
}
if len(matchingDeps) > 0 {
- canonicalRule, err := q.chooseCanonicalRule(currentPkg, matchingDeps)
+ canonicalRule, err := q.chooseCanonicalDep(currentPkg, targetToAnalyze, matchingDeps)
if err != nil {
return nil, err
}
@@ -282,11 +282,11 @@
return results, nil
}
-// chooseCanonicalRule chooses between rules which include the imported file as
+// chooseCanonicalDep chooses between rules which include the imported file as
// a source (ie the rule that includes the file as a src, and any reexporting
// libraries).
//
-// It filters the rules in a 2 stage process:
+// It filters the rules in a 3 stage process:
//
// 1. If only one of the rules is visible, choose that one, since the rule
// creator intended it to be imported.
@@ -294,41 +294,81 @@
// 2. If all or none of the rules are visible, choose the rule that directly
// includes the file as a src, since that reduces the chance of introducing
// circular dependencies.
-func (q *QueryBasedTargetLoader) chooseCanonicalRule(currentPkg string, rules []*appb.Rule) (*appb.Rule, error) {
+//
+// 3. Choose the rule that is already included as a dep.
+func (q *QueryBasedTargetLoader) chooseCanonicalDep(currentPkg string, targetToAnalyze *appb.Rule, deps []*appb.Rule) (*appb.Rule, error) {
// check for visibility
- var labels []string
- for _, r := range rules {
- labels = append(labels, r.GetName())
- }
- visibleRulesMap, err := q.LoadRules(currentPkg, labels)
- if err != nil {
- return nil, err
- }
-
- var visibleRules []*appb.Rule
- for _, r := range visibleRulesMap {
- if r != nil {
- visibleRules = append(visibleRules, r)
+ filterForVisibility := func(deps []*appb.Rule) ([]*appb.Rule, error) {
+ var labels []string
+ for _, d := range deps {
+ labels = append(labels, d.GetName())
}
- }
+ visibleDepsMap, err := q.LoadRules(currentPkg, labels)
+ if err != nil {
+ return nil, err
+ }
- if len(visibleRules) == 1 {
- return visibleRules[0], nil
- } else if len(visibleRules) > 0 {
- rules = visibleRules
+ var visibleDeps []*appb.Rule
+ for _, d := range visibleDepsMap {
+ if d != nil {
+ visibleDeps = append(visibleDeps, d)
+ }
+ }
+
+ return visibleDeps, nil
}
// if there's a visible reexporting lib and a visible lib with the src, favor
// the lib with the src, to reduce the chance of introducing a circular
// dependency
- for _, r := range rules {
- if !isReexportingLib(r) {
- return r, nil
+ filterForBaseLibs := func(deps []*appb.Rule) ([]*appb.Rule, error) {
+ var baseDeps []*appb.Rule
+ for _, d := range deps {
+ if !isReexportingLib(d) {
+ baseDeps = append(baseDeps, d)
+ }
+ }
+
+ return baseDeps, nil
+ }
+
+ // favor the dep that's already on the rule
+ filterForExistingDeps := func(deps []*appb.Rule) ([]*appb.Rule, error) {
+ var existingDeps []*appb.Rule
+ for _, d := range deps {
+ for _, existing := range listAttribute(targetToAnalyze, "deps") {
+ if d.GetName() == existing {
+ existingDeps = append(existingDeps, d)
+ }
+ }
+ }
+
+ return existingDeps, nil
+ }
+
+ filters := []func(deps []*appb.Rule) ([]*appb.Rule, error){
+ filterForVisibility,
+ filterForBaseLibs,
+ filterForExistingDeps,
+ }
+
+ // for each filter, return if it returned a single rule, narrow the set of deps if
+ // it discarded some, but not all, and try the full set with the next filter if it
+ // discarded them all
+ for _, filter := range filters {
+ filteredDeps, err := filter(deps)
+ if err != nil {
+ return nil, err
+ }
+ if len(filteredDeps) == 1 {
+ return filteredDeps[0], nil
+ } else if len(filteredDeps) > 0 {
+ deps = filteredDeps
}
}
- // if no rules matched the filter, just return the first rule
- return rules[0], nil
+ // no filter got down to a single rule, just return the first
+ return deps[0], nil
}
// ruleLabel returns the label for a target which is a rule. Returns an error if