From 004b6943906471cff0d0232040d24cff6cb89e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sat, 20 Apr 2024 11:05:35 +0200 Subject: [PATCH] Fix partial rebuilds for SCSS fetched with GetMatch and similar Fixes #12395 --- cache/dynacache/dynacache.go | 16 ++++-- cache/dynacache/dynacache_test.go | 2 +- commands/commandeer.go | 50 +++++++++++-------- hugolib/content_map_page.go | 2 +- hugolib/hugo_sites_build.go | 46 ++++++++++++++--- .../tocss/scss/scss_integration_test.go | 31 ++++++++++++ 6 files changed, 114 insertions(+), 33 deletions(-) diff --git a/cache/dynacache/dynacache.go b/cache/dynacache/dynacache.go index eab251e5d..e79de5a5b 100644 --- a/cache/dynacache/dynacache.go +++ b/cache/dynacache/dynacache.go @@ -140,16 +140,25 @@ func (c *Cache) DrainEvictedIdentities() []identity.Identity { } // ClearMatching clears all partition for which the predicate returns true. -func (c *Cache) ClearMatching(predicate func(k, v any) bool) { +func (c *Cache) ClearMatching(predicatePartition func(k string, p PartitionManager) bool, predicateValue func(k, v any) bool) { + if predicatePartition == nil { + predicatePartition = func(k string, p PartitionManager) bool { return true } + } + if predicateValue == nil { + panic("nil predicateValue") + } g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{ NumWorkers: len(c.partitions), Handle: func(ctx context.Context, partition PartitionManager) error { - partition.clearMatching(predicate) + partition.clearMatching(predicateValue) return nil }, }) - for _, p := range c.partitions { + for k, p := range c.partitions { + if !predicatePartition(k, p) { + continue + } g.Enqueue(p) } @@ -356,6 +365,7 @@ func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts Optio trace: c.opts.Log.Logger().WithLevel(logg.LevelTrace).WithField("partition", name), opts: opts, } + c.partitions[name] = partition return partition diff --git a/cache/dynacache/dynacache_test.go b/cache/dynacache/dynacache_test.go index 53de2385e..275e63f0b 100644 --- a/cache/dynacache/dynacache_test.go +++ b/cache/dynacache/dynacache_test.go @@ -156,7 +156,7 @@ func TestClear(t *testing.T) { cache = newTestCache(t) - cache.ClearMatching(func(k, v any) bool { + cache.ClearMatching(nil, func(k, v any) bool { return k.(string) == "clearOnRebuild" }) diff --git a/commands/commandeer.go b/commands/commandeer.go index f18c3f813..616a3c867 100644 --- a/commands/commandeer.go +++ b/commands/commandeer.go @@ -128,6 +128,7 @@ type rootCommand struct { verbose bool debug bool quiet bool + devMode bool // Hidden flag. renderToMemory bool @@ -423,29 +424,33 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error { func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) { level := logg.LevelWarn - if r.logLevel != "" { - switch strings.ToLower(r.logLevel) { - case "debug": - level = logg.LevelDebug - case "info": - level = logg.LevelInfo - case "warn", "warning": - level = logg.LevelWarn - case "error": - level = logg.LevelError - default: - return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel) - } + if r.devMode { + level = logg.LevelTrace } else { - if r.verbose { - hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0") - hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0") - level = logg.LevelInfo - } + if r.logLevel != "" { + switch strings.ToLower(r.logLevel) { + case "debug": + level = logg.LevelDebug + case "info": + level = logg.LevelInfo + case "warn", "warning": + level = logg.LevelWarn + case "error": + level = logg.LevelError + default: + return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel) + } + } else { + if r.verbose { + hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0") + hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0") + level = logg.LevelInfo + } - if r.debug { - hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0") - level = logg.LevelDebug + if r.debug { + hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0") + level = logg.LevelDebug + } } } @@ -505,10 +510,13 @@ Complete documentation is available at https://gohugo.io/.` cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output") cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output") + cmd.PersistentFlags().BoolVarP(&r.devMode, "devMode", "", false, "only used for internal testing, flag hidden.") cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)") _ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp)) cmd.Flags().BoolVarP(&r.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed") + cmd.PersistentFlags().MarkHidden("devMode") + // Configure local flags applyLocalFlagsBuild(cmd, r) diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go index aa32b5320..50e1bc35d 100644 --- a/hugolib/content_map_page.go +++ b/hugolib/content_map_page.go @@ -1084,7 +1084,7 @@ func (h *HugoSites) resolveAndClearStateForIdentities( return b } - h.MemCache.ClearMatching(shouldDelete) + h.MemCache.ClearMatching(nil, shouldDelete) return ll, nil }); err != nil { diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index 411f90734..3beb072e3 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -23,6 +23,7 @@ import ( "path" "path/filepath" "strings" + "sync" "time" "github.com/bep/logg" @@ -46,6 +47,7 @@ import ( "github.com/gohugoio/hugo/resources/page" "github.com/gohugoio/hugo/resources/page/siteidentities" "github.com/gohugoio/hugo/resources/postpub" + "github.com/gohugoio/hugo/resources/resource" "github.com/spf13/afero" @@ -758,15 +760,45 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf } } case files.ComponentFolderAssets: - logger.Println("Asset changed", pathInfo.Path()) + p := pathInfo.Path() + logger.Println("Asset changed", p) + + var matches []any + var mu sync.Mutex + + h.MemCache.ClearMatching( + func(k string, pm dynacache.PartitionManager) bool { + // Avoid going through everything. + return strings.HasPrefix(k, "/res") + }, + func(k, v any) bool { + if strings.Contains(k.(string), p) { + mu.Lock() + defer mu.Unlock() + switch vv := v.(type) { + case resource.Resources: + // GetMatch/Match. + for _, r := range vv { + matches = append(matches, r) + } + return true + default: + matches = append(matches, vv) + return true + + } + } + return false + }) var hasID bool - r, _ := h.ResourceSpec.ResourceCache.Get(context.Background(), dynacache.CleanKey(pathInfo.Base())) - identity.WalkIdentitiesShallow(r, func(level int, rid identity.Identity) bool { - hasID = true - changes = append(changes, rid) - return false - }) + for _, r := range matches { + identity.WalkIdentitiesShallow(r, func(level int, rid identity.Identity) bool { + hasID = true + changes = append(changes, rid) + return false + }) + } if !hasID { changes = append(changes, pathInfo) } diff --git a/resources/resource_transformers/tocss/scss/scss_integration_test.go b/resources/resource_transformers/tocss/scss/scss_integration_test.go index c193ca8af..02e2b9200 100644 --- a/resources/resource_transformers/tocss/scss/scss_integration_test.go +++ b/resources/resource_transformers/tocss/scss/scss_integration_test.go @@ -327,3 +327,34 @@ Styles: {{ $r.RelPermalink }} b.AssertFileContent("public/index.html", "Styles: /scss/main.css") } + +func TestRebuildAssetGetMatch(t *testing.T) { + t.Parallel() + if !scss.Supports() { + t.Skip() + } + + files := ` +-- assets/scss/main.scss -- +b { + color: red; +} +-- layouts/index.html -- +{{ $r := resources.GetMatch "scss/main.scss" | toCSS }} +T1: {{ $r.Content }} + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + Running: true, + }).Build() + + b.AssertFileContent("public/index.html", `color: red`) + + b.EditFiles("assets/scss/main.scss", `b { color: blue; }`).Build() + + b.AssertFileContent("public/index.html", `color: blue`) +}