// Command removedpackages refuses a Go source of the catalogue that imports what mesh-sdk gave up in the release that // narrowed it (novox/hq ADR 0593 §1): a package that left for its owner, and the build runner's names that left // mesh-sdk's buildstep for mesh-build-go's package. // // A module compiles against the mesh-sdk version it pins, so a source that still names a removed package builds until the // pin moves, and then breaks in a delivery of its own; and a later change could bring the import back with the old pin. // This check refuses it at once, from the source alone: // // - an import of a package in Removed (or one under it); // - in a file importing mesh-sdk's buildstep (under any name), a selector naming anything that is not in // BuildstepContract: what is left of that package is what the delivery parts read, and the runner is // mesh-build-go's package (git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep). // // It reads the non-test and test Go files under modules/ and checks/ (testdata, vendor and node_modules left out). // // go run . // // Exits 1 on a finding. The lists are the release's: a package that goes later is added here with the change. package main import ( "fmt" "go/ast" "go/parser" "go/token" "io/fs" "os" "path/filepath" "sort" "strconv" "strings" ) const sdk = "git.novox.be/novox/mesh-sdk/go" // Removed are the packages of mesh-sdk that left it, each with where its logic is now. var Removed = map[string]string{ "buildcpu": "the build runner's package, git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/buildcpu", "buildstep/conformance": "the build runner's package, git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance", "delivery/buildrun": "the builder's package (mesh-delivery-builder), imported by its repository's modules", "compose": "the node-engine, which alone composes (ADR 0593 §6)", "compose/frombus": "the node-engine, which alone composes (ADR 0593 §6)", "compose/fromruntime": "the node-engine, which alone composes (ADR 0593 §6)", "compose/judge": "the assigner's own package", "takein": "the node-engine, which alone composes (ADR 0593 §6)", "validate": "the node-engine, which alone composes (ADR 0593 §6)", "facts": "nothing: the controller they served is ended (ADR 0590)", "example": "nothing: no module imports it (ADR 0593 §12)", "packageguard": "nothing: a test helper of mesh-sdk's own", } // BuildstepContract are the names mesh-sdk's buildstep keeps: the failure contract, the fetch request, the stale-step sweep, // the step id and the module tree shape the delivery parts read. var BuildstepContract = map[string]bool{ "AskWhat": true, "Boot": true, "By": true, "CapacitySetting": true, "Clip": true, "ConsoleFailure": true, "CutLine": true, "EndOne": true, "EndStale": true, "EndStaleAtStart": true, "EndStaleRound": true, "Failure": true, "FetchRequest": true, "FetchRequestProperties": true, "Hidden": true, "Launchers": true, "LauncherSuffix": true, "LineBytes": true, "ModuleTree": true, "NewID": true, "OutputBytes": true, "Owner": true, "PackagesArg": true, "ParseFetchRequest": true, "PassingLine": true, "RestartReason": true, "Scan": true, "ScanOf": true, "StaleAttempts": true, "StaleRetry": true, "StaleServed": true, } // Findings is every refusal in the Go sources under root, sorted. func Findings(root string) ([]string, error) { var out []string for _, top := range []string{"modules", "checks"} { err := filepath.WalkDir(filepath.Join(root, top), func(path string, d fs.DirEntry, err error) error { if err != nil { if os.IsNotExist(err) { return nil } return err } if d.IsDir() { switch d.Name() { case "testdata", "vendor", "node_modules", ".mesh-build": return filepath.SkipDir } return nil } if !strings.HasSuffix(path, ".go") { return nil } rel, _ := filepath.Rel(root, path) found, err := fileFindings(path, filepath.ToSlash(rel)) if err != nil { return err } out = append(out, found...) return nil }) if err != nil { return nil, err } } sort.Strings(out) return out, nil } func fileFindings(path, rel string) ([]string, error) { file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.SkipObjectResolution) if err != nil { return nil, fmt.Errorf("%s: %w", rel, err) } var out []string buildstep := "" for _, imp := range file.Imports { p, err := strconv.Unquote(imp.Path.Value) if err != nil { continue } sub, ok := strings.CutPrefix(p, sdk+"/") if !ok { continue } for pkg, to := range Removed { if sub == pkg || strings.HasPrefix(sub, pkg+"/") { out = append(out, fmt.Sprintf("%s: imports %s, which left mesh-sdk; it is %s (novox/hq ADR 0593 §1)", rel, p, to)) break } } if sub == "buildstep" { buildstep = "buildstep" if imp.Name != nil { buildstep = imp.Name.Name } } } if buildstep == "" || buildstep == "_" || buildstep == "." { return out, nil } seen := map[string]bool{} ast.Inspect(file, func(n ast.Node) bool { sel, ok := n.(*ast.SelectorExpr) if !ok { return true } id, ok := sel.X.(*ast.Ident) if !ok || id.Name != buildstep || !sel.Sel.IsExported() || BuildstepContract[sel.Sel.Name] || seen[sel.Sel.Name] { return true } seen[sel.Sel.Name] = true out = append(out, fmt.Sprintf("%s: uses %s.%s, which is the build runner's, no longer mesh-sdk's buildstep; import git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep (novox/hq ADR 0593 §1)", rel, buildstep, sel.Sel.Name)) return true }) return out, nil } func main() { if len(os.Args) != 2 { fmt.Fprintln(os.Stderr, "usage: removedpackages ") os.Exit(2) } found, err := Findings(os.Args[1]) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(2) } for _, f := range found { fmt.Println(f) } if len(found) > 0 { os.Exit(1) } fmt.Println("no source imports a package mesh-sdk gave up") }