mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-18 21:11:19 +02:00
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/rungroup"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
@@ -1002,7 +1003,7 @@ func (m *pageMap) debugPrint(prefix string, maxLevel int, w io.Writer) {
|
||||
}
|
||||
const indentStr = " "
|
||||
p := n.(*pageState)
|
||||
s := strings.TrimPrefix(keyPage, paths.CommonDir(prevKey, keyPage))
|
||||
s := strings.TrimPrefix(keyPage, paths.CommonDirPath(prevKey, keyPage))
|
||||
lenIndent := len(keyPage) - len(s)
|
||||
fmt.Fprint(w, strings.Repeat(indentStr, lenIndent))
|
||||
info := fmt.Sprintf("%s lm: %s (%s)", s, p.Lastmod().Format("2006-01-02"), p.Kind())
|
||||
@@ -1047,6 +1048,59 @@ func (m *pageMap) debugPrint(prefix string, maxLevel int, w io.Writer) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HugoSites) dynacacheGCFilenameIfNotWatchedAndDrainMatching(filename string) {
|
||||
cpss := h.BaseFs.ResolvePaths(filename)
|
||||
if len(cpss) == 0 {
|
||||
return
|
||||
}
|
||||
// Compile cache busters.
|
||||
var cacheBusters []func(string) bool
|
||||
for _, cps := range cpss {
|
||||
if cps.Watch {
|
||||
continue
|
||||
}
|
||||
np := glob.NormalizePath(path.Join(cps.Component, cps.Path))
|
||||
g, err := h.ResourceSpec.BuildConfig().MatchCacheBuster(h.Log, np)
|
||||
if err == nil && g != nil {
|
||||
cacheBusters = append(cacheBusters, g)
|
||||
}
|
||||
}
|
||||
if len(cacheBusters) == 0 {
|
||||
return
|
||||
}
|
||||
cacheBusterOr := func(s string) bool {
|
||||
for _, cb := range cacheBusters {
|
||||
if cb(s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
h.dynacacheGCCacheBuster(cacheBusterOr)
|
||||
|
||||
// We want to avoid that evicted items in the above is considered in the next step server change.
|
||||
_ = h.MemCache.DrainEvictedIdentitiesMatching(func(ki dynacache.KeyIdentity) bool {
|
||||
return cacheBusterOr(ki.Key.(string))
|
||||
})
|
||||
}
|
||||
|
||||
func (h *HugoSites) dynacacheGCCacheBuster(cachebuster func(s string) bool) {
|
||||
if cachebuster == nil {
|
||||
return
|
||||
}
|
||||
shouldDelete := func(k, v any) bool {
|
||||
var b bool
|
||||
if s, ok := k.(string); ok {
|
||||
b = cachebuster(s)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
h.MemCache.ClearMatching(nil, shouldDelete)
|
||||
}
|
||||
|
||||
func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
ctx context.Context,
|
||||
l logg.LevelLogger,
|
||||
@@ -1095,25 +1149,10 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
// 1. Handle the cache busters first, as those may produce identities for the page reset step.
|
||||
// 2. Then reset the page outputs, which may mark some resources as stale.
|
||||
// 3. Then GC the cache.
|
||||
// TOOD1
|
||||
if cachebuster != nil {
|
||||
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
|
||||
ll := l.WithField("substep", "gc dynacache cachebuster")
|
||||
|
||||
shouldDelete := func(k, v any) bool {
|
||||
if cachebuster == nil {
|
||||
return false
|
||||
}
|
||||
var b bool
|
||||
if s, ok := k.(string); ok {
|
||||
b = cachebuster(s)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
h.MemCache.ClearMatching(nil, shouldDelete)
|
||||
|
||||
h.dynacacheGCCacheBuster(cachebuster)
|
||||
return ll, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
@@ -1123,7 +1162,9 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
// Drain the cache eviction stack.
|
||||
evicted := h.Deps.MemCache.DrainEvictedIdentities()
|
||||
if len(evicted) < 200 {
|
||||
changes = append(changes, evicted...)
|
||||
for _, c := range evicted {
|
||||
changes = append(changes, c.Identity)
|
||||
}
|
||||
} else {
|
||||
// Mass eviction, we might as well invalidate everything.
|
||||
changes = []identity.Identity{identity.GenghisKhan}
|
||||
|
@@ -720,7 +720,7 @@ func (b *sourceFilesystemsBuilder) createOverlayFs(
|
||||
ModuleOrdinal: md.ordinal,
|
||||
IsProject: md.isMainProject,
|
||||
Meta: &hugofs.FileMeta{
|
||||
Watch: md.Watch(),
|
||||
Watch: !mount.DisableWatch && md.Watch(),
|
||||
Weight: mountWeight,
|
||||
InclusionFilter: inclusionFilter,
|
||||
},
|
||||
|
@@ -26,6 +26,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/bufferpool"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
@@ -173,6 +174,16 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
|
||||
h.SendError(fmt.Errorf("postRenderOnce: %w", err))
|
||||
}
|
||||
|
||||
// Make sure to write any build stats to disk first so it's available
|
||||
// to the post processors.
|
||||
if err := h.writeBuildStats(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := h.renderDeferred(infol); err != nil {
|
||||
h.SendError(fmt.Errorf("renderDeferred: %w", err))
|
||||
}
|
||||
|
||||
if err := h.postProcess(infol); err != nil {
|
||||
h.SendError(fmt.Errorf("postProcess: %w", err))
|
||||
}
|
||||
@@ -352,49 +363,174 @@ func (h *HugoSites) render(l logg.LevelLogger, config *BuildCfg) error {
|
||||
continue
|
||||
}
|
||||
|
||||
siteRenderContext.outIdx = siteOutIdx
|
||||
siteRenderContext.sitesOutIdx = i
|
||||
i++
|
||||
if err := func() error {
|
||||
rc := tpl.RenderingContext{Site: s, SiteOutIdx: siteOutIdx}
|
||||
h.BuildState.StartStageRender(rc)
|
||||
defer h.BuildState.StopStageRender(rc)
|
||||
|
||||
select {
|
||||
case <-h.Done():
|
||||
siteRenderContext.outIdx = siteOutIdx
|
||||
siteRenderContext.sitesOutIdx = i
|
||||
i++
|
||||
|
||||
select {
|
||||
case <-h.Done():
|
||||
return nil
|
||||
default:
|
||||
for _, s2 := range h.Sites {
|
||||
if err := s2.preparePagesForRender(s == s2, siteRenderContext.sitesOutIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !config.SkipRender {
|
||||
ll := l.WithField("substep", "pages").
|
||||
WithField("site", s.language.Lang).
|
||||
WithField("outputFormat", renderFormat.Name)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
if config.PartialReRender {
|
||||
if err := s.renderPages(siteRenderContext); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := s.render(siteRenderContext); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
loggers.TimeTrackf(ll, start, nil, "")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
for _, s2 := range h.Sites {
|
||||
// We render site by site, but since the content is lazily rendered
|
||||
// and a site can "borrow" content from other sites, every site
|
||||
// needs this set.
|
||||
s2.rc = &siteRenderingContext{Format: renderFormat}
|
||||
|
||||
if err := s2.preparePagesForRender(s == s2, siteRenderContext.sitesOutIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !config.SkipRender {
|
||||
ll := l.WithField("substep", "pages").
|
||||
WithField("site", s.language.Lang).
|
||||
WithField("outputFormat", renderFormat.Name)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
if config.PartialReRender {
|
||||
if err := s.renderPages(siteRenderContext); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := s.render(siteRenderContext); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
loggers.TimeTrackf(ll, start, nil, "")
|
||||
}
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HugoSites) renderDeferred(l logg.LevelLogger) error {
|
||||
l = l.WithField("step", "render deferred")
|
||||
start := time.Now()
|
||||
|
||||
var deferredCount int
|
||||
|
||||
for rc, de := range h.Deps.BuildState.DeferredExecutionsGroupedByRenderingContext {
|
||||
if de.FilenamesWithPostPrefix.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
deferredCount += de.FilenamesWithPostPrefix.Len()
|
||||
|
||||
s := rc.Site.(*Site)
|
||||
for _, s2 := range h.Sites {
|
||||
if err := s2.preparePagesForRender(s == s2, rc.SiteOutIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := s.executeDeferredTemplates(de); err != nil {
|
||||
return herrors.ImproveRenderErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
loggers.TimeTrackf(l, start, logg.Fields{
|
||||
logg.Field{Name: "count", Value: deferredCount},
|
||||
}, "")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Site) executeDeferredTemplates(de *deps.DeferredExecutions) error {
|
||||
handleFile := func(filename string) error {
|
||||
content, err := afero.ReadFile(s.BaseFs.PublishFs, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k := 0
|
||||
changed := false
|
||||
|
||||
for {
|
||||
if k >= len(content) {
|
||||
break
|
||||
}
|
||||
l := bytes.Index(content[k:], []byte(tpl.HugoDeferredTemplatePrefix))
|
||||
if l == -1 {
|
||||
break
|
||||
}
|
||||
m := bytes.Index(content[k+l:], []byte(tpl.HugoDeferredTemplateSuffix)) + len(tpl.HugoDeferredTemplateSuffix)
|
||||
|
||||
low, high := k+l, k+l+m
|
||||
|
||||
forward := l + m
|
||||
id := string(content[low:high])
|
||||
|
||||
if err := func() error {
|
||||
deferred, found := de.Executions.Get(id)
|
||||
if !found {
|
||||
panic(fmt.Sprintf("deferred execution with id %q not found", id))
|
||||
}
|
||||
deferred.Mu.Lock()
|
||||
defer deferred.Mu.Unlock()
|
||||
|
||||
if !deferred.Executed {
|
||||
tmpl := s.Deps.Tmpl()
|
||||
templ, found := tmpl.Lookup(deferred.TemplateName)
|
||||
if !found {
|
||||
panic(fmt.Sprintf("template %q not found", deferred.TemplateName))
|
||||
}
|
||||
|
||||
if err := func() error {
|
||||
buf := bufferpool.GetBuffer()
|
||||
defer bufferpool.PutBuffer(buf)
|
||||
|
||||
err = tmpl.ExecuteWithContext(deferred.Ctx, templ, buf, deferred.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deferred.Result = buf.String()
|
||||
deferred.Executed = true
|
||||
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
content = append(content[:low], append([]byte(deferred.Result), content[high:]...)...)
|
||||
changed = true
|
||||
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k += forward
|
||||
}
|
||||
|
||||
if changed {
|
||||
return afero.WriteFile(s.BaseFs.PublishFs, filename, content, 0o666)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
g := rungroup.Run[string](context.Background(), rungroup.Config[string]{
|
||||
NumWorkers: s.h.numWorkers,
|
||||
Handle: func(ctx context.Context, filename string) error {
|
||||
return handleFile(filename)
|
||||
},
|
||||
})
|
||||
|
||||
de.FilenamesWithPostPrefix.ForEeach(func(filename string, _ bool) {
|
||||
g.Enqueue(filename)
|
||||
})
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// / postRenderOnce runs some post processing that only needs to be done once, e.g. printing of unused templates.
|
||||
func (h *HugoSites) postRenderOnce() error {
|
||||
h.postRenderInit.Do(func() {
|
||||
@@ -428,12 +564,6 @@ func (h *HugoSites) postProcess(l logg.LevelLogger) error {
|
||||
l = l.WithField("step", "postProcess")
|
||||
defer loggers.TimeTrackf(l, time.Now(), nil, "")
|
||||
|
||||
// Make sure to write any build stats to disk first so it's available
|
||||
// to the post processors.
|
||||
if err := h.writeBuildStats(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// This will only be set when js.Build have been triggered with
|
||||
// imports that resolves to the project or a module.
|
||||
// Write a jsconfig.json file to the project's /asset directory
|
||||
@@ -600,6 +730,10 @@ func (h *HugoSites) writeBuildStats() error {
|
||||
}
|
||||
}
|
||||
|
||||
// This step may be followed by a post process step that may
|
||||
// rebuild e.g. CSS, so clear any cache that's defined for the hugo_stats.json.
|
||||
h.dynacacheGCFilenameIfNotWatchedAndDrainMatching(filename)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -628,3 +628,20 @@ title: "A page"
|
||||
|
||||
b.CreateSites().BuildFail(BuildCfg{})
|
||||
}
|
||||
|
||||
func TestErrorTemplateRuntime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
{{ .ThisDoesNotExist }}
|
||||
`
|
||||
|
||||
b, err := TestE(t, files)
|
||||
|
||||
b.Assert(err, qt.Not(qt.IsNil))
|
||||
b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`/layouts/index.html:2:3`))
|
||||
b.Assert(err.Error(), qt.Contains, `can't evaluate field ThisDoesNotExist`)
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *Site) Taxonomies() page.TaxonomyList {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
s.init.taxonomies.Do(context.Background())
|
||||
return s.taxonomies
|
||||
}
|
||||
@@ -200,12 +200,8 @@ func (s *Site) prepareInits() {
|
||||
})
|
||||
}
|
||||
|
||||
type siteRenderingContext struct {
|
||||
output.Format
|
||||
}
|
||||
|
||||
func (s *Site) Menus() navigation.Menus {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
s.init.menus.Do(context.Background())
|
||||
return s.menus
|
||||
}
|
||||
@@ -810,7 +806,7 @@ func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
|
||||
// as possible for existing sites. Most sites will use {{ .Site.GetPage "section" "my/section" }},
|
||||
// i.e. 2 arguments, so we test for that.
|
||||
func (s *Site) GetPage(ref ...string) (page.Page, error) {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
p, err := s.s.getPageForRefs(ref...)
|
||||
|
||||
if p == nil {
|
||||
|
@@ -88,10 +88,6 @@ type Site struct {
|
||||
publisher publisher.Publisher
|
||||
frontmatterHandler pagemeta.FrontMatterHandler
|
||||
|
||||
// We render each site for all the relevant output formats in serial with
|
||||
// this rendering context pointing to the current one.
|
||||
rc *siteRenderingContext
|
||||
|
||||
// The output formats that we need to render this site in. This slice
|
||||
// will be fixed once set.
|
||||
// This will be the union of Site.Pages' outputFormats.
|
||||
@@ -439,7 +435,7 @@ func (s *Site) Current() page.Site {
|
||||
|
||||
// MainSections returns the list of main sections.
|
||||
func (s *Site) MainSections() []string {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
return s.conf.C.MainSections
|
||||
}
|
||||
|
||||
@@ -458,7 +454,7 @@ func (s *Site) BaseURL() string {
|
||||
|
||||
// Deprecated: Use .Site.Lastmod instead.
|
||||
func (s *Site) LastChange() time.Time {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
hugo.Deprecate(".Site.LastChange", "Use .Site.Lastmod instead.", "v0.123.0")
|
||||
return s.lastmod
|
||||
}
|
||||
@@ -547,7 +543,7 @@ func (s *Site) ForEeachIdentityByName(name string, f func(identity.Identity) boo
|
||||
// Pages returns all pages.
|
||||
// This is for the current language only.
|
||||
func (s *Site) Pages() page.Pages {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
return s.pageMap.getPagesInSection(
|
||||
pageMapQueryPagesInSection{
|
||||
pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
|
||||
@@ -564,7 +560,7 @@ func (s *Site) Pages() page.Pages {
|
||||
// RegularPages returns all the regular pages.
|
||||
// This is for the current language only.
|
||||
func (s *Site) RegularPages() page.Pages {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
return s.pageMap.getPagesInSection(
|
||||
pageMapQueryPagesInSection{
|
||||
pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
|
||||
@@ -579,17 +575,17 @@ func (s *Site) RegularPages() page.Pages {
|
||||
|
||||
// AllPages returns all pages for all sites.
|
||||
func (s *Site) AllPages() page.Pages {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
return s.h.Pages()
|
||||
}
|
||||
|
||||
// AllRegularPages returns all regular pages for all sites.
|
||||
func (s *Site) AllRegularPages() page.Pages {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
return s.h.RegularPages()
|
||||
}
|
||||
|
||||
func (s *Site) checkReady() {
|
||||
func (s *Site) CheckReady() {
|
||||
if s.state != siteStateReady {
|
||||
panic("this method cannot be called before the site is fully initialized")
|
||||
}
|
||||
|
@@ -111,7 +111,7 @@ func (s *Site) renderPages(ctx *siteRenderContext) error {
|
||||
|
||||
err := <-errs
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to render pages: %w", herrors.ImproveIfNilPointer(err))
|
||||
return fmt.Errorf("failed to render pages: %w", herrors.ImproveRenderErr(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -226,7 +226,7 @@ func (s *Site) renderPaginator(p *pageState, templ tpl.Template) error {
|
||||
paginatePath := s.Conf.Pagination().Path
|
||||
|
||||
d := p.targetPathDescriptor
|
||||
f := p.s.rc.Format
|
||||
f := p.outputFormat()
|
||||
d.Type = f
|
||||
|
||||
if p.paginator.current == nil || p.paginator.current != p.paginator.current.First() {
|
||||
|
@@ -19,12 +19,12 @@ import (
|
||||
|
||||
// Sections returns the top level sections.
|
||||
func (s *Site) Sections() page.Pages {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
return s.Home().Sections()
|
||||
}
|
||||
|
||||
// Home is a shortcut to the home page, equivalent to .Site.GetPage "home".
|
||||
func (s *Site) Home() page.Page {
|
||||
s.checkReady()
|
||||
s.CheckReady()
|
||||
return s.s.home
|
||||
}
|
||||
|
Reference in New Issue
Block a user