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)")
|
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)")
|
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)")
|
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() {
|
func main() {
|
||||||
@@ -41,6 +42,10 @@ func main() {
|
|||||||
Mode: packages.LoadFiles,
|
Mode: packages.LoadFiles,
|
||||||
Env: os.Environ(),
|
Env: os.Environ(),
|
||||||
}
|
}
|
||||||
|
if *affectedByTag != "" {
|
||||||
|
cfg.Mode |= packages.NeedImports
|
||||||
|
cfg.Tests = true
|
||||||
|
}
|
||||||
if *goos != "" {
|
if *goos != "" {
|
||||||
cfg.Env = append(cfg.Env, "GOOS="+*goos)
|
cfg.Env = append(cfg.Env, "GOOS="+*goos)
|
||||||
}
|
}
|
||||||
@@ -62,6 +67,11 @@ func main() {
|
|||||||
withAll = strings.Split(*withTagsAllStr, ",")
|
withAll = strings.Split(*withTagsAllStr, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var affected map[string]bool // PkgPath → true
|
||||||
|
if *affectedByTag != "" {
|
||||||
|
affected = computeAffected(pkgs, *affectedByTag)
|
||||||
|
}
|
||||||
|
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
matches := 0
|
matches := 0
|
||||||
Pkg:
|
Pkg:
|
||||||
@@ -69,6 +79,17 @@ Pkg:
|
|||||||
if pkg.PkgPath == "" { // malformed (shouldn’t happen)
|
if pkg.PkgPath == "" { // malformed (shouldn’t happen)
|
||||||
continue
|
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] {
|
if seen[pkg.PkgPath] {
|
||||||
continue // suppress duplicates when patterns overlap
|
continue // suppress duplicates when patterns overlap
|
||||||
}
|
}
|
||||||
@@ -96,7 +117,7 @@ Pkg:
|
|||||||
if *shard != "" {
|
if *shard != "" {
|
||||||
var n, m int
|
var n, m int
|
||||||
if _, err := fmt.Sscanf(*shard, "%d/%d", &n, &m); err != nil || n < 1 || m < 1 {
|
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 {
|
if m > 0 && (matches-1)%m != n-1 {
|
||||||
continue // not in this shard
|
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 {
|
func isThirdParty(pkg string) bool {
|
||||||
return strings.HasPrefix(pkg, "tailscale.com/tempfork/")
|
return strings.HasPrefix(pkg, "tailscale.com/tempfork/")
|
||||||
}
|
}
|
||||||
@@ -194,7 +271,7 @@ func getFileTags(filename string) (tagSet, error) {
|
|||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
fileTags[filename] = ts
|
fileTags[filename] = ts
|
||||||
return tags, nil
|
return ts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileMentionsTag(filename, tag string) (bool, error) {
|
func fileMentionsTag(filename, tag string) (bool, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user