From: Bjørn Erik Pedersen Date: Sat, 24 Jan 2026 13:25:34 +0000 (+0100) Subject: Add modulequeries file cache for module version queries X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=b441472b6907079ac58baa9b7c79f5d7ac7ce31c;p=brevno-suite%2Fhugo Add modulequeries file cache for module version queries This adds a new file cache named 'modulequeries' with a 24h maxAge that caches JSON responses from 'go mod download -json' when querying module versions with constraints (e.g., version = " 1 { - continue - } - if pkg != "." { - pkg += "/..." - }*/ if err := cmp.Or(CleanTest(), UninstallAll()); err != nil { return err } @@ -209,7 +220,6 @@ func TestRace() error { } } return nil - } else { return runCmd(env, goexe, "test", "-p", "2", "-race", "./...", "-tags", buildTags()) } diff --git a/modules/client.go b/modules/client.go index c3d568188..dbad4d35c 100644 --- a/modules/client.go +++ b/modules/client.go @@ -30,8 +30,10 @@ import ( "time" "github.com/gohugoio/hugo/common/collections" + "github.com/gohugoio/hugo/common/hashing" "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/hexec" + "github.com/gohugoio/hugo/common/hugio" "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/config" @@ -45,8 +47,6 @@ import ( "golang.org/x/mod/module" - "github.com/gohugoio/hugo/common/hugio" - "github.com/spf13/afero" ) @@ -440,16 +440,31 @@ func isProbablyModule(path string) bool { } func (c *Client) downloadModuleVersion(path, version string) (*goModule, error) { + // If it's already cached, use it. + const keyPrefix = "downloadmoduleversion_" + cacheKey := keyPrefix + hashing.XxHashFromStringHexEncoded(c.ccfg.CacheDir, path, version) + ".json" + if b, _ := c.ccfg.ModuleQueriesCache.GetBytes(cacheKey); len(b) > 0 { + var cm goModule + if err := json.Unmarshal(b, &cm); err == nil && cm.Dir != "" { + if c.isDirNonEmpty(cm.Dir) { + return &cm, nil + } + } + } + + // No valid cache, need to query. args := []string{"mod", "download", "-json", fmt.Sprintf("%s@%s", path, version)} - b := &bytes.Buffer{} + b := &bytes.Buffer{} err := c.runGo(context.Background(), b, args...) if err != nil { return nil, fmt.Errorf("failed to download module %s@%s: %w", path, version, err) } + jsonResult := b.Bytes() + m := &goModule{} - if err := json.NewDecoder(b).Decode(m); err != nil { + if err := json.NewDecoder(bytes.NewReader(jsonResult)).Decode(m); err != nil { return nil, fmt.Errorf("failed to decode module download result: %w", err) } @@ -457,9 +472,26 @@ func (c *Client) downloadModuleVersion(path, version string) (*goModule, error) return nil, errors.New(m.Error.Err) } + // Cache the successful result if it has a Dir field. + if m.Dir != "" { + _ = c.ccfg.ModuleQueriesCache.SetBytes(cacheKey, jsonResult) + } + return m, nil } +// isDirNonEmpty returns true if dir exists and contains at least one file or subdirectory. +func (c *Client) isDirNonEmpty(dir string) bool { + f, err := c.fs.Open(dir) + if err != nil { + return false + } + defer f.Close() + // Read just one entry to check if directory is non-empty. + _, err = f.Readdirnames(1) + return err == nil +} + func (c *Client) listGoMods() (goModules, error) { if c.GoModulesFilename == "" || !c.moduleConfig.hasModuleImport() { return nil, nil @@ -865,8 +897,15 @@ type ClientConfig struct { Exec *hexec.Exec - CacheDir string // Module cache - ModuleConfig Config + CacheDir string // Module cache + ModuleQueriesCache ModuleQueriesCache + ModuleConfig Config +} + +// ModuleQueriesCache is a cache for module version queries. +type ModuleQueriesCache interface { + GetBytes(id string) ([]byte, error) + SetBytes(id string, data []byte) error } func (c ClientConfig) shouldIgnoreVendor(path string) bool { diff --git a/modules/collect.go b/modules/collect.go index de803dbd4..add5ae69a 100644 --- a/modules/collect.go +++ b/modules/collect.go @@ -391,14 +391,11 @@ func (c *collector) addAndRecurse(owner *moduleAdapter) error { pk := pathVersionKey{path: tc.Path(), version: tc.Version()} seenInCurrent := seen[pk] if seenInCurrent { - // Only one import of the same module per project. - if owner.projectMod { - // In Hugo v0.150.0 we introduced direct dependencies, and it may be tempting to import the same version - // with different mount setups. We may allow that in the future, but we need to get some experience first. - // For now, we just warn. The user needs to add multiple mount points in the same import. - c.logger.Warnf("module with path %q is imported for the same version %q more than once", tc.Path(), tc.Version()) + // Only one import of the same module per project, unless this is the main project. + if !owner.projectMod { + c.logger.Warnf("module with path %q is imported for the same version %q more than once; this is currently only allowed in the main project's config.", tc.Path(), tc.Version()) + continue } - continue } seen[pk] = true diff --git a/resources/resource_cache.go b/resources/resource_cache.go index 898cd4c31..e91b0ea7e 100644 --- a/resources/resource_cache.go +++ b/resources/resource_cache.go @@ -111,7 +111,7 @@ func (c *ResourceCache) getFromFile(key string) (filecache.ItemInfo, io.ReadClos var meta transformedResourceMetadata filenameMeta, filenameContent := c.getFilenames(key) - _, jsonContent, _ := c.fileCache.GetBytes(filenameMeta) + jsonContent, _ := c.fileCache.GetBytes(filenameMeta) if jsonContent == nil { return filecache.ItemInfo{}, nil, meta, false } diff --git a/resources/resource_transformers/babel/babel_integration_test.go b/resources/resource_transformers/babel/babel_integration_test.go index 4344af2be..c08114b6c 100644 --- a/resources/resource_transformers/babel/babel_integration_test.go +++ b/resources/resource_transformers/babel/babel_integration_test.go @@ -67,9 +67,9 @@ Transpiled3: {{ $transpiled.Permalink }} "scripts": {}, "devDependencies": { - "@babel/cli": "7.8.4", - "@babel/core": "7.9.0", - "@babel/preset-env": "7.9.5" + "@babel/cli": "7.28.6", + "@babel/core": "7.28.6", + "@babel/preset-env": "7.28.6" } }