tool/listpkgs: add --affected-by-tag

For paring back build tag variant CI runs' set of packages to test.

Updates tailscale/corp#28679

Change-Id: Iba46fd1f58c1eaee1f7888ef573bc8b14fa73208
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
main
Brad Fitzpatrick 2 months ago committed by Brad Fitzpatrick
parent fd2ebcd5bd
commit 7370c24eb4
  1. 81
      tool/listpkgs/listpkgs.go

@ -26,6 +26,7 @@ var (
withTagsAllStr = flag.String("with-tags-all", "", "if non-empty, a comma-separated list of builds tags to require (a package will only be listed if it contains all of these build tags)")
withoutTagsAnyStr = flag.String("without-tags-any", "", "if non-empty, a comma-separated list of build constraints to exclude (a package will be omitted if it contains any of these build tags)")
shard = flag.String("shard", "", "if non-empty, a string of the form 'N/M' to only print packages in shard N of M (e.g. '1/3', '2/3', '3/3/' for different thirds of the list)")
affectedByTag = flag.String("affected-by-tag", "", "if non-empty, only list packages whose test binary would be affected by the presence or absence of this build tag")
)
func main() {
@ -41,6 +42,10 @@ func main() {
Mode: packages.LoadFiles,
Env: os.Environ(),
}
if *affectedByTag != "" {
cfg.Mode |= packages.NeedImports
cfg.Tests = true
}
if *goos != "" {
cfg.Env = append(cfg.Env, "GOOS="+*goos)
}
@ -62,6 +67,11 @@ func main() {
withAll = strings.Split(*withTagsAllStr, ",")
}
var affected map[string]bool // PkgPath → true
if *affectedByTag != "" {
affected = computeAffected(pkgs, *affectedByTag)
}
seen := map[string]bool{}
matches := 0
Pkg:
@ -69,6 +79,17 @@ Pkg:
if pkg.PkgPath == "" { // malformed (shouldn’t happen)
continue
}
if affected != nil {
// Skip synthetic packages created by Tests: true:
// - for-test variants like "foo [foo.test]" (ID != PkgPath)
// - test binary packages like "foo.test" (PkgPath ends in ".test")
if pkg.ID != pkg.PkgPath || strings.HasSuffix(pkg.PkgPath, ".test") {
continue
}
if !affected[pkg.PkgPath] {
continue
}
}
if seen[pkg.PkgPath] {
continue // suppress duplicates when patterns overlap
}
@ -96,7 +117,7 @@ Pkg:
if *shard != "" {
var n, m int
if _, err := fmt.Sscanf(*shard, "%d/%d", &n, &m); err != nil || n < 1 || m < 1 {
log.Fatalf("invalid shard format %q; expected 'N/M'", *shard)
log.Fatalf("invalid shard format %q; expected ‘N/M’", *shard)
}
if m > 0 && (matches-1)%m != n-1 {
continue // not in this shard
@ -112,6 +133,62 @@ Pkg:
}
}
// computeAffected returns the set of package paths whose test binaries would
// differ with vs without the given build tag. It finds packages that directly
// mention the tag, then propagates transitively via reverse dependencies.
func computeAffected(pkgs []*packages.Package, tag string) map[string]bool {
// Build a map from package ID to package for quick lookup.
byID := make(map[string]*packages.Package, len(pkgs))
for _, pkg := range pkgs {
byID[pkg.ID] = pkg
}
// First pass: find directly affected package IDs.
directlyAffected := make(map[string]bool)
for _, pkg := range pkgs {
if hasBuildTag(pkg, tag) {
directlyAffected[pkg.ID] = true
}
}
// Build reverse dependency graph: importedID → []importingID.
reverseDeps := make(map[string][]string)
for _, pkg := range pkgs {
for _, imp := range pkg.Imports {
reverseDeps[imp.ID] = append(reverseDeps[imp.ID], pkg.ID)
}
}
// BFS from directly affected packages through reverse deps.
affectedIDs := make(map[string]bool)
queue := make([]string, 0, len(directlyAffected))
for id := range directlyAffected {
affectedIDs[id] = true
queue = append(queue, id)
}
for len(queue) > 0 {
id := queue[0]
queue = queue[1:]
for _, rdep := range reverseDeps[id] {
if !affectedIDs[rdep] {
affectedIDs[rdep] = true
queue = append(queue, rdep)
}
}
}
// Map affected IDs back to PkgPaths. For-test variants like
// "foo [foo.test]" share the same PkgPath as "foo", so the
// result naturally deduplicates.
affected := make(map[string]bool)
for id := range affectedIDs {
if pkg, ok := byID[id]; ok {
affected[pkg.PkgPath] = true
}
}
return affected
}
func isThirdParty(pkg string) bool {
return strings.HasPrefix(pkg, "tailscale.com/tempfork/")
}
@ -194,7 +271,7 @@ func getFileTags(filename string) (tagSet, error) {
mu.Lock()
defer mu.Unlock()
fileTags[filename] = ts
return tags, nil
return ts, nil
}
func fileMentionsTag(filename, tag string) (bool, error) {

Loading…
Cancel
Save