// Command toolchains refuses any manifest but the owner's declaring a language's toolchain package (novox/hq // ADR 0477 clause 4, issue 1573): the package go only in modules/go, nodejs and npm only in modules/nodejs, and buildah (issue 1702) only in modules/buildah. // Everything that compiles or runs that language requires the owner's provision instead (go-toolchain, // node-toolchain), so the package is declared once, and a node without the owner refuses the module that // needs it at resolution rather than failing at its first build. The owners must declare their packages. // // go run . // // Exits 1 on a finding. package main import ( "encoding/json" "fmt" "os" "path/filepath" "sort" ) // Owners names, for each toolchain package, the one module that declares it. var Owners = map[string]string{ "buildah": "buildah", "go": "go", "nodejs": "nodejs", "npm": "nodejs", } // Finding is one refusal: Module declares Package, which Owner alone may, or (Missing) Owner does not declare // the Package it owns. type Finding struct { Module, Package, Owner string Missing bool } // Check reads every manifest under root/modules and answers what it refuses. func Check(root string) ([]Finding, error) { manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } sort.Strings(manifests) var out []Finding declared := map[string]map[string]bool{} // module -> packages for _, path := range manifests { raw, err := os.ReadFile(path) if err != nil { return nil, err } var m struct { Module string `json:"module"` Resources []struct { Type string `json:"type"` Package string `json:"package"` } `json:"resources"` } if err := json.Unmarshal(raw, &m); err != nil { return nil, fmt.Errorf("%s: %w", path, err) } declared[m.Module] = map[string]bool{} for _, r := range m.Resources { if r.Type != "package" { continue } declared[m.Module][r.Package] = true if owner, ok := Owners[r.Package]; ok && owner != m.Module { out = append(out, Finding{Module: m.Module, Package: r.Package, Owner: owner}) } } } var pkgs []string for p := range Owners { pkgs = append(pkgs, p) } sort.Strings(pkgs) for _, p := range pkgs { if !declared[Owners[p]][p] { out = append(out, Finding{Module: Owners[p], Package: p, Owner: Owners[p], Missing: true}) } } return out, nil } func main() { root := "." if len(os.Args) > 1 { root = os.Args[1] } found, err := Check(root) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } for _, f := range found { if f.Missing { fmt.Printf("%s does not declare the package %s it owns (novox/hq ADR 0477 clause 4)\n", f.Owner, f.Package) continue } fmt.Printf("%s declares the package %s: only %s may; require its provision instead (novox/hq ADR 0477 clause 4)\n", f.Module, f.Package, f.Owner) } if len(found) > 0 { os.Exit(1) } fmt.Println("toolchains: go, nodejs, npm and buildah are declared only by their owners") }