From 49ee91c194d6712bf279887b7e778215f21be0be Mon Sep 17 00:00:00 2001 From: Martin Hutchinson Date: Wed, 1 Oct 2025 15:44:17 +0000 Subject: [PATCH] [SumDB] Support nested go.mod files If go.mod is not at the root, it will use the first go.mod file it finds when walking the tree. The location of the used modfile is printed out after the module name in the report to Stdout. --- vindex/cmd/sumdbverify/client.go | 53 +++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/vindex/cmd/sumdbverify/client.go b/vindex/cmd/sumdbverify/client.go index fe7cd2c..ab86f14 100644 --- a/vindex/cmd/sumdbverify/client.go +++ b/vindex/cmd/sumdbverify/client.go @@ -23,6 +23,7 @@ import ( "flag" "fmt" "io" + "io/fs" "net/http" "net/url" "os" @@ -93,7 +94,8 @@ func run(ctx context.Context) error { return fmt.Errorf("failed to compile report: %v", reportErr) } - fmt.Println(report.modName) + fmt.Printf("%s (./%s)\n", report.modName, report.modPath) + tw := tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', 0) if _, err := fmt.Fprintln(tw, "VERSION\tINDEX\tFOUND\tgo.mod\t"); err != nil { return fmt.Errorf("failed to output report: %v", err) @@ -128,22 +130,50 @@ func run(ctx context.Context) error { return reportErr } +func findGoMod(modRoot string) (string, error) { + goModPath := "go.mod" + if s, err := os.Stat(filepath.Join(modRoot, goModPath)); err == nil && !s.IsDir() { + return goModPath, nil + } + + err := filepath.WalkDir(modRoot, func(path string, d fs.DirEntry, err error) error { + if !d.IsDir() && d.Name() == "go.mod" { + relPath, err := filepath.Rel(modRoot, path) + if err != nil { + return err + } + goModPath = relPath + return os.ErrExist + } + return nil + }) + if errors.Is(err, os.ErrExist) { + return goModPath, nil + } + + return "", fmt.Errorf("failed to read go.mod file: %v", err) +} + func getReport(ctx context.Context, modRoot string, sumFetcher func(context.Context, string) (map[string]modData, error)) (diffReport, error) { if s, err := os.Stat(modRoot); err != nil || !s.IsDir() { return diffReport{}, errors.New("mod_root flag must be a directory") } - modPathBytes, err := os.ReadFile(filepath.Join(modRoot, "go.mod")) + goModPath, err := findGoMod(modRoot) if err != nil { - return diffReport{}, fmt.Errorf("failed to read go.mod file: %v", err) + return diffReport{}, fmt.Errorf("failed to find go.mod file in %s", modRoot) } - modPath, err := modfile.Parse("go.mod", modPathBytes, nil) + modPathBytes, err := os.ReadFile(filepath.Join(modRoot, goModPath)) if err != nil { + return diffReport{}, fmt.Errorf("failed to read go.mod file: %v", err) + } + modName := modfile.ModulePath(modPathBytes) + if modName == "" { return diffReport{}, fmt.Errorf("failed to parse go.mod file: %v", err) } - modName := modPath.Module.Mod.Path report := diffReport{ modName: modName, + modPath: goModPath, versions: make([]versionReport, 0), } @@ -193,7 +223,7 @@ func getReport(ctx context.Context, modRoot string, sumFetcher func(context.Cont for _, v := range sv { sumHashes := versions[v] - vr, err := reportVersion(ctx, repo, v) + vr, err := reportVersion(ctx, repo, goModPath, v) if err != nil { vErrors = append(vErrors, fmt.Errorf("failed to get report for version %q: %v", v, err)) } @@ -220,7 +250,7 @@ func getReport(ctx context.Context, modRoot string, sumFetcher func(context.Cont return report, errors.Join(vErrors...) } -func reportVersion(ctx context.Context, repo *git.Repository, v string) (versionReport, error) { +func reportVersion(ctx context.Context, repo *git.Repository, goModPath string, v string) (versionReport, error) { report := &versionReport{ version: v, } @@ -244,8 +274,14 @@ func reportVersion(ctx context.Context, repo *git.Repository, v string) (version if err != nil { return *report, fmt.Errorf("failed to get commit tree: %v", err) } + + // There is some gnarliness here: + // - the path passed in must only be "go.mod" as that is used in the hash construction + // - the path _may_ be nested, in reality + // The workaround is to pass in "go.mod" and then ignore it in the function, and just use + // the real path in the git directory when looking it up. hs, err := dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) { - modFile, err := tree.File("go.mod") + modFile, err := tree.File(goModPath) if err != nil { return nil, err } @@ -272,6 +308,7 @@ func reportVersion(ctx context.Context, repo *git.Repository, v string) (version type diffReport struct { modName string + modPath string versions []versionReport }