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>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
fd2ebcd5bd
commit
7370c24eb4
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user