type BuildState struct {
counter uint64
+ // Tracks invocations of the Build method.
+ BuildCounter atomic.Uint64
+
mu sync.Mutex // protects state below.
OnSignalRebuild func(ids ...identity.Identity)
var _ identity.SignalRebuilder = (*BuildState)(nil)
+// IsRebuild reports whether this is a rebuild.
+func (b *BuildState) IsRebuild() bool {
+ return b.BuildCounter.Load() > 0
+}
+
// StartStageRender will be called before a stage is rendered.
func (b *BuildState) StartStageRender(stage tpl.RenderingContext) {
}
*progressReporter
*fatalErrorHandler
*buildCounters
- // Tracks invocations of the Build method.
- buildCounter atomic.Uint64
}
// hugoSitesSitesProvider is a wrapper that implements page.SitesProvider.
}
func (h *HugoSites) isRebuild() bool {
- return h.buildCounter.Load() > 0
+ return h.BuildState.IsRebuild()
}
func (h *HugoSites) resolveFirstSite(matrix sitesmatrix.VectorStore) *Site {
fastRenderMode := p.s.Conf.FastRenderMode()
- if !fastRenderMode || p.s.h.buildCounter.Load() == 0 {
+ if !fastRenderMode || !p.s.h.BuildState.IsRebuild() {
return shouldRender
}
h.reportProgress(func() (state terminal.ProgressState, progress float64) {
return terminal.ProgressHidden, 1.0
})
- h.buildCounter.Add(1)
+ h.BuildState.BuildCounter.Add(1)
}()
if h.Deps == nil {
func (ps *pageState) renderResources() error {
for _, r := range ps.Resources() {
if _, ok := r.(page.Page); ok {
- if ps.s.h.buildCounter.Load() == 0 {
+ if !ps.s.h.BuildState.IsRebuild() {
// Pages gets rendered with the owning page but we count them here.
ps.s.PathSpec.ProcessingStats.Incr(&ps.s.PathSpec.ProcessingStats.Pages)
}
}
}
- if !s.conf.DisableAliases && s.h.buildCounter.Load() == 0 {
+ if !s.conf.DisableAliases && !s.h.BuildState.IsRebuild() {
of := p.outputFormat()
if of.IsHTML && of.Permalinkable {
// Render any aliases for this page.
SignalRebuild(ids ...Identity)
}
+// IsRebuildProvider signals if we're in a rebuild or not.
+type IsRebuildProvider interface {
+ IsRebuild() bool
+}
+
// IncrementByOne implements Incrementer adding 1 every time Incr is called.
type IncrementByOne struct {
counter uint64
"strings"
"sync"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/cache/dynacache"
"/res1/tra",
dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnChange, Weight: 40},
),
+
+ cacheResourceTransformationPublished: hmaps.NewMap[string, string](),
}
}
cacheResources *dynacache.Partition[string, resource.Resources]
cacheResourceTransformation *dynacache.Partition[string, *resourceAdapterInner]
+ // Used in rebuilds. Maps the target path to the last published transformation key.
+ cacheResourceTransformationPublished *hmaps.Map[string, string]
+
fileCache *filecache.Cache
}
errorHandler herrors.ErrorSender,
execHelper *hexec.Exec,
buildClosers types.CloseAdder,
- rebuilder identity.SignalRebuilder,
+ rebuilder Rebuilder,
) (*Spec, error) {
conf := s.Cfg.GetConfig().(*allconfig.Config)
imgConfig := conf.Imaging
return rs, nil
}
+type Rebuilder interface {
+ identity.SignalRebuilder
+ identity.IsRebuildProvider
+}
+
type Spec struct {
*helpers.PathSpec
Logger loggers.Logger
ErrorSender herrors.ErrorSender
BuildClosers types.CloseAdder
- Rebuilder identity.SignalRebuilder
+ Rebuilder Rebuilder
Permalinks page.PermalinkExpander
func (r *resourceAdapter) getOrTransform(publish, setContent bool) error {
key := r.TransformationKey()
+
+ var created bool
res, err := r.spec.ResourceCache.cacheResourceTransformation.GetOrCreate(key, func(string) (*resourceAdapterInner, error) {
+ created = true
return r.transform(key, publish, setContent)
})
if err != nil {
}
r.resourceAdapterInner = res
+
+ if publish && r.spec.Rebuilder.IsRebuild() {
+ targetPath := r.target.TargetPath()
+ var republish bool
+
+ r.spec.ResourceCache.cacheResourceTransformationPublished.WithWriteLock(func(m map[string]string) error {
+ if created {
+ m[targetPath] = key
+ } else {
+ key2, found := m[targetPath]
+ republish = !found || key2 != key
+ m[targetPath] = key
+ }
+ return nil
+ })
+
+ if !created && republish {
+ src, err := contentReadSeekerCloser(r.target)
+ if err != nil {
+ return err
+ }
+ defer src.Close()
+ dest, err := r.target.openPublishFileForWriting(targetPath)
+ if err != nil {
+ return err
+ }
+ defer dest.Close()
+ _, err = io.Copy(dest, src)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
return nil
}
b.AssertFileContent("public/css/main.css", `{background:red}`)
}
+func TestCSSBuildEditOptionsMultiple(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ["taxonomy", "term", "rss"]
+disableLiveReload = true
+-- assets/css/main.css --
+body {
+ background: red;
+}
+-- layouts/_partials/css.html --
+{{ with resources.Get "css/main.css" }}
+{{ $opts := dict "minify" false }}
+{{ with . | css.Build $opts }}
+ <link rel="stylesheet" href="{{ .RelPermalink }}" />
+{{ end }}
+{{ end }}
+-- layouts/all.html --
+All. {{ partial "css.html" . }}
+-- content/p1.md --
+-- content/p2.md --
+-- content/p3.md --
+
+
+`
+
+ b := hugolib.TestRunning(t, files, hugolib.TestOptOsFs())
+
+ for range 3 {
+ b.AssertFileContent("public/css/main.css", ` background: red;`)
+ b.EditFileReplaceAll("layouts/_partials/css.html", `"minify" false`, `"minify" true`).Build()
+ b.AssertFileContent("public/css/main.css", `{background:red}`)
+ b.EditFileReplaceAll("layouts/_partials/css.html", `"minify" true`, `"minify" false`).Build()
+ }
+}
+
func TestCSSBuildSourceMaps(t *testing.T) {
t.Parallel()