Compare commits

...

15 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen bd1657c360 Build without the deploy feature by default
Build tags setup changed to:

* !nodeploy => withdeploy
* nodeploy => !withdeploy

Also move the deploy feature out into its own release archives.

See #12994 for the primary motivation for this change. But this also greatly reduces the number of dependencies in Hugo when you don't need this feature and cuts the binary size greatly.

Fixes #12994
2024-10-30 10:45:40 +01:00
Bjørn Erik Pedersen 62567d3820 deps: Upgrade github.com/bep/lazycache v0.6.0 => v0.7.0 2024-10-27 12:43:36 +01:00
Bjørn Erik Pedersen e10915f80a dynacache: Fix potential deadlocks on panics in GetOrCreate 2024-10-26 18:27:10 +02:00
hugoreleaser ec3890affe releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-24 12:41:02 +00:00
hugoreleaser 46cccb021b releaser: Bump versions for release of 0.136.5
[ci skip]
2024-10-24 12:26:27 +00:00
Bjørn Erik Pedersen cb6e27b32a hugolib/commands: Fix stuck server error issues
Fixes #11378
2024-10-24 13:34:11 +02:00
Joe Mooring 5bbe95f9c5 tpl/transform: Revert unmarshal whitespace removal
Fixes #12977
2024-10-22 09:42:40 +02:00
hugoreleaser 31d19b505d releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-21 13:46:41 +00:00
hugoreleaser bfa2fd683e releaser: Bump versions for release of 0.136.3
[ci skip]
2024-10-21 13:31:54 +00:00
David Karlsson 88d598a049 docker: Fix permission issues in Dockerfile
Closes #12971
Closes #12970
2024-10-21 15:28:36 +02:00
Bjørn Erik Pedersen 352be5ba87 Make sure that HugoSites is always closed when done
Including all the integration tests.
2024-10-20 13:04:58 +02:00
Joe Mooring d37606d2c2 tpl/strings: Add TrimSpace function
Closes #12962
2024-10-19 21:10:00 +02:00
Bjørn Erik Pedersen f5e54d9c7d common/herrors: Fix the deferred error message cleaner regexp
Make it less gready.
2024-10-19 10:00:01 +02:00
Bjørn Erik Pedersen 42f37b4e98 tpl/transform: Don't fail on "no data to transform"
Fixes #12964
2024-10-18 10:30:36 +02:00
hugoreleaser e971b7d866 releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-17 14:44:10 +00:00
41 changed files with 404 additions and 148 deletions
+3 -3
View File
@@ -112,17 +112,17 @@ jobs:
sass --version; sass --version;
mage -v check; mage -v check;
env: env:
HUGO_BUILD_TAGS: extended HUGO_BUILD_TAGS: extended,withdeploy
- if: matrix.os == 'windows-latest' - if: matrix.os == 'windows-latest'
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now. # See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
name: Test name: Test
run: | run: |
mage -v test; mage -v test;
env: env:
HUGO_BUILD_TAGS: extended HUGO_BUILD_TAGS: extended,withdeploy
- name: Build tags - name: Build tags
run: | run: |
go install -tags extended,nodeploy go install -tags extended
- if: matrix.os == 'ubuntu-latest' - if: matrix.os == 'ubuntu-latest'
name: Build for dragonfly name: Build for dragonfly
run: | run: |
+3 -1
View File
@@ -1,3 +1,5 @@
*.test *.test
imports.* imports.*
dist/
public/
+4 -4
View File
@@ -21,8 +21,8 @@ COPY --from=xx / /
ARG TARGETPLATFORM ARG TARGETPLATFORM
RUN xx-apk add musl-dev gcc g++ RUN xx-apk add musl-dev gcc g++
# Optionally set HUGO_BUILD_TAGS to "none" or "nodeploy" when building like so: # Optionally set HUGO_BUILD_TAGS to "none" or "withdeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=nodeploy . # docker build --build-arg HUGO_BUILD_TAGS=withdeploy .
# #
# We build the extended version by default. # We build the extended version by default.
ARG HUGO_BUILD_TAGS="extended" ARG HUGO_BUILD_TAGS="extended"
@@ -76,14 +76,14 @@ RUN mkdir -p /var/hugo/bin /cache && \
# See https://github.com/gohugoio/hugo/issues/9810 # See https://github.com/gohugoio/hugo/issues/9810
runuser -u hugo -- git config --global core.quotepath false runuser -u hugo -- git config --global core.quotepath false
USER hugo:hugo
VOLUME /project VOLUME /project
WORKDIR /project WORKDIR /project
USER hugo:hugo
ENV HUGO_CACHEDIR=/cache ENV HUGO_CACHEDIR=/cache
ENV PATH="/var/hugo/bin:$PATH" ENV PATH="/var/hugo/bin:$PATH"
COPY scripts/docker/entrypoint.sh /entrypoint.sh COPY scripts/docker/entrypoint.sh /entrypoint.sh
COPY --link --from=dart-sass /out/dart-sass /var/hugo/bin/dart-sass COPY --from=dart-sass /out/dart-sass /var/hugo/bin/dart-sass
# Update PATH to reflect the new dependencies. # Update PATH to reflect the new dependencies.
# For more complex setups, we should probably find a way to # For more complex setups, we should probably find a way to
+19 -6
View File
@@ -430,12 +430,25 @@ func (p *Partition[K, V]) doGetOrCreateWitTimeout(key K, duration time.Duration,
errch := make(chan error, 1) errch := make(chan error, 1)
go func() { go func() {
v, _, err := p.c.GetOrCreate(key, create) var (
if err != nil { v V
errch <- err err error
return )
} defer func() {
resultch <- v if r := recover(); r != nil {
if rerr, ok := r.(error); ok {
err = rerr
} else {
err = fmt.Errorf("panic: %v", r)
}
}
if err != nil {
errch <- err
} else {
resultch <- v
}
}()
v, _, err = p.c.GetOrCreate(key, create)
}() }()
select { select {
+55
View File
@@ -14,8 +14,11 @@
package dynacache package dynacache
import ( import (
"errors"
"fmt"
"path/filepath" "path/filepath"
"testing" "testing"
"time"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/loggers"
@@ -165,6 +168,58 @@ func TestClear(t *testing.T) {
cache.adjustCurrentMaxSize() cache.adjustCurrentMaxSize()
} }
func TestPanicInCreate(t *testing.T) {
t.Parallel()
c := qt.New(t)
cache := newTestCache(t)
p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild})
willPanic := func(i int) func() {
return func() {
p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
panic(errors.New(key))
})
}
}
// GetOrCreateWitTimeout needs to recover from panics in the create func.
willErr := func(i int) error {
_, err := p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) {
return testItem{}, errors.New(key)
})
return err
}
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
c.Assert(willPanic(i), qt.PanicMatches, fmt.Sprintf("panic-%d", i))
c.Assert(willErr(i), qt.ErrorMatches, fmt.Sprintf("error-%d", i))
}
}
// Test the same keys again without the panic.
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
v, err := p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
return testItem{
name: key,
}, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v.name, qt.Equals, fmt.Sprintf("panic-%d", i))
v, err = p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) {
return testItem{
name: key,
}, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v.name, qt.Equals, fmt.Sprintf("error-%d", i))
}
}
}
func TestAdjustCurrentMaxSize(t *testing.T) { func TestAdjustCurrentMaxSize(t *testing.T) {
t.Parallel() t.Parallel()
c := qt.New(t) c := qt.New(t)
+20 -1
View File
@@ -42,6 +42,7 @@ import (
"github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig" "github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
@@ -66,6 +67,12 @@ func Execute(args []string) error {
} }
args = mapLegacyArgs(args) args = mapLegacyArgs(args)
cd, err := x.Execute(context.Background(), args) cd, err := x.Execute(context.Background(), args)
if cd != nil {
if closer, ok := cd.Root.Command.(types.Closer); ok {
closer.Close()
}
}
if err != nil { if err != nil {
if err == errHelp { if err == errHelp {
cd.CobraCommand.Help() cd.CobraCommand.Help()
@@ -149,6 +156,18 @@ func (r *rootCommand) isVerbose() bool {
return r.logger.Level() <= logg.LevelInfo return r.logger.Level() <= logg.LevelInfo
} }
func (r *rootCommand) Close() error {
if r.hugoSites != nil {
r.hugoSites.DeleteFunc(func(key configKey, value *hugolib.HugoSites) bool {
if value != nil {
value.Close()
}
return false
})
}
return nil
}
func (r *rootCommand) Build(cd *simplecobra.Commandeer, bcfg hugolib.BuildCfg, cfg config.Provider) (*hugolib.HugoSites, error) { func (r *rootCommand) Build(cd *simplecobra.Commandeer, bcfg hugolib.BuildCfg, cfg config.Provider) (*hugolib.HugoSites, error) {
h, err := r.Hugo(cfg) h, err := r.Hugo(cfg)
if err != nil { if err != nil {
@@ -488,7 +507,7 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
return loggers.New(optsLogger), nil return loggers.New(optsLogger), nil
} }
func (r *rootCommand) Reset() { func (r *rootCommand) resetLogs() {
r.logger.Reset() r.logger.Reset()
loggers.Log().Reset() loggers.Log().Reset()
} }
+2 -15
View File
@@ -11,21 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !nodeploy //go:build withdeploy
// +build !nodeploy // +build withdeploy
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands package commands
+4 -3
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build nodeploy //go:build !withdeploy
// +build nodeploy // +build !withdeploy
// Copyright 2024 The Hugo Authors. All rights reserved. // Copyright 2024 The Hugo Authors. All rights reserved.
// //
@@ -31,6 +31,7 @@ package commands
import ( import (
"context" "context"
"errors"
"github.com/bep/simplecobra" "github.com/bep/simplecobra"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -40,7 +41,7 @@ func newDeployCommand() simplecobra.Commander {
return &simpleCommand{ return &simpleCommand{
name: "deploy", name: "deploy",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
return nil return errors.New("deploy not supported in this version of Hugo; install a release with 'withdeploy' in the archive filename or build yourself with the 'withdeploy' build tag. Also see https://github.com/gohugoio/hugo/pull/12995")
}, },
withc: func(cmd *cobra.Command, r *rootCommand) { withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Hidden = true cmd.Hidden = true
+34 -24
View File
@@ -27,7 +27,6 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/bep/logg"
"github.com/bep/simplecobra" "github.com/bep/simplecobra"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/herrors"
@@ -136,10 +135,6 @@ func (e *hugoBuilderErrState) wasErr() bool {
return e.waserr return e.waserr
} }
func (c *hugoBuilder) errCount() int {
return c.r.logger.LoggCount(logg.LevelError) + loggers.Log().LoggCount(logg.LevelError)
}
// getDirList provides NewWatcher() with a list of directories to watch for changes. // getDirList provides NewWatcher() with a list of directories to watch for changes.
func (c *hugoBuilder) getDirList() ([]string, error) { func (c *hugoBuilder) getDirList() ([]string, error) {
h, err := c.hugo() h, err := c.hugo()
@@ -345,7 +340,6 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
for { for {
select { select {
case changes := <-c.r.changesFromBuild: case changes := <-c.r.changesFromBuild:
c.errState.setBuildErr(nil)
unlock, err := h.LockBuild() unlock, err := h.LockBuild()
if err != nil { if err != nil {
c.r.logger.Errorln("Failed to acquire a build lock: %s", err) c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
@@ -358,7 +352,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
} }
if c.s != nil && c.s.doLiveReload { if c.s != nil && c.s.doLiveReload {
doReload := c.changeDetector == nil || len(c.changeDetector.changed()) > 0 doReload := c.changeDetector == nil || len(c.changeDetector.changed()) > 0
doReload = doReload || c.showErrorInBrowser && c.errCount() > 0 doReload = doReload || c.showErrorInBrowser && c.errState.buildErr() != nil
if doReload { if doReload {
livereload.ForceRefresh() livereload.ForceRefresh()
} }
@@ -372,7 +366,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
return return
} }
c.handleEvents(watcher, staticSyncer, evs, configSet) c.handleEvents(watcher, staticSyncer, evs, configSet)
if c.showErrorInBrowser && c.errCount() > 0 { if c.showErrorInBrowser && c.errState.buildErr() != nil {
// Need to reload browser to show the error // Need to reload browser to show the error
livereload.ForceRefresh() livereload.ForceRefresh()
} }
@@ -419,11 +413,17 @@ func (c *hugoBuilder) build() error {
} }
func (c *hugoBuilder) buildSites(noBuildLock bool) (err error) { func (c *hugoBuilder) buildSites(noBuildLock bool) (err error) {
h, err := c.hugo() defer func() {
c.errState.setBuildErr(err)
}()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil { if err != nil {
return err return
} }
return h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock}) err = h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
return
} }
func (c *hugoBuilder) copyStatic() (map[string]uint64, error) { func (c *hugoBuilder) copyStatic() (map[string]uint64, error) {
@@ -619,6 +619,9 @@ func (c *hugoBuilder) fullRebuild(changeType string) {
// Set the processing on pause until the state is recovered. // Set the processing on pause until the state is recovered.
c.errState.setPaused(true) c.errState.setPaused(true)
c.handleBuildErr(err, "Failed to reload config") c.handleBuildErr(err, "Failed to reload config")
if c.s.doLiveReload {
livereload.ForceRefresh()
}
} else { } else {
c.errState.setPaused(false) c.errState.setPaused(false)
} }
@@ -1081,37 +1084,44 @@ func (c *hugoBuilder) printChangeDetected(typ string) {
c.r.logger.Println(htime.Now().Format(layout)) c.r.logger.Println(htime.Now().Format(layout))
} }
func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error { func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) (err error) {
defer func() {
c.errState.setBuildErr(err)
}()
if err := c.errState.buildErr(); err != nil { if err := c.errState.buildErr(); err != nil {
ferrs := herrors.UnwrapFileErrorsWithErrorContext(err) ferrs := herrors.UnwrapFileErrorsWithErrorContext(err)
for _, err := range ferrs { for _, err := range ferrs {
events = append(events, fsnotify.Event{Name: err.Position().Filename, Op: fsnotify.Write}) events = append(events, fsnotify.Event{Name: err.Position().Filename, Op: fsnotify.Write})
} }
} }
c.errState.setBuildErr(nil) var h *hugolib.HugoSites
h, err := c.hugo() h, err = c.hugo()
if err != nil { if err != nil {
return err return
} }
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...) return
} }
func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) error { func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) (err error) {
c.errState.setBuildErr(nil) defer func() {
h, err := c.hugo() c.errState.setBuildErr(err)
}()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil { if err != nil {
return err return
} }
whatChanged := &hugolib.WhatChanged{} whatChanged := &hugolib.WhatChanged{}
whatChanged.Add(ids...) whatChanged.Add(ids...)
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}) err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
c.errState.setBuildErr(err)
return err return
} }
func (c *hugoBuilder) reloadConfig() error { func (c *hugoBuilder) reloadConfig() error {
c.r.Reset() c.r.resetLogs()
c.r.configVersionID.Add(1) c.r.configVersionID.Add(1)
if err := c.withConfE(func(conf *commonConfig) error { if err := c.withConfE(func(conf *commonConfig) error {
+11 -13
View File
@@ -648,9 +648,8 @@ func (c *serverCommand) setServerInfoInConfig() error {
} }
func (c *serverCommand) getErrorWithContext() any { func (c *serverCommand) getErrorWithContext() any {
errCount := c.errCount() buildErr := c.errState.buildErr()
if buildErr == nil {
if errCount == 0 {
return nil return nil
} }
@@ -659,7 +658,7 @@ func (c *serverCommand) getErrorWithContext() any {
m["Error"] = cleanErrorLog(c.r.logger.Errors()) m["Error"] = cleanErrorLog(c.r.logger.Errors())
m["Version"] = hugo.BuildVersionString() m["Version"] = hugo.BuildVersionString()
ferrors := herrors.UnwrapFileErrorsWithErrorContext(c.errState.buildErr()) ferrors := herrors.UnwrapFileErrorsWithErrorContext(buildErr)
m["Files"] = ferrors m["Files"] = ferrors
return m return m
@@ -830,22 +829,25 @@ func (c *serverCommand) fixURL(baseURLFromConfig, baseURLFromFlag string, port i
return u.String(), nil return u.String(), nil
} }
func (c *serverCommand) partialReRender(urls ...string) error { func (c *serverCommand) partialReRender(urls ...string) (err error) {
defer func() { defer func() {
c.errState.setWasErr(false) c.errState.setWasErr(false)
}() }()
c.errState.setBuildErr(nil)
visited := types.NewEvictingStringQueue(len(urls)) visited := types.NewEvictingStringQueue(len(urls))
for _, url := range urls { for _, url := range urls {
visited.Add(url) visited.Add(url)
} }
h, err := c.hugo() var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil { if err != nil {
return err return
} }
// Note: We do not set NoBuildLock as the file lock is not acquired at this stage. // Note: We do not set NoBuildLock as the file lock is not acquired at this stage.
return h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()}) err = h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
return
} }
func (c *serverCommand) serve() error { func (c *serverCommand) serve() error {
@@ -1012,10 +1014,6 @@ func (c *serverCommand) serve() error {
c.r.Println("Error:", err) c.r.Println("Error:", err)
} }
if h := c.hugoTry(); h != nil {
h.Close()
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
wg2, ctx := errgroup.WithContext(ctx) wg2, ctx := errgroup.WithContext(ctx)
+1 -1
View File
@@ -137,7 +137,7 @@ var nilPointerErrRe = regexp.MustCompile(`at <(.*)>: error calling (.*?): runtim
const deferredPrefix = "__hdeferred/" const deferredPrefix = "__hdeferred/"
var deferredStringToRemove = regexp.MustCompile(`executing "__hdeferred/.*" `) var deferredStringToRemove = regexp.MustCompile(`executing "__hdeferred/.*?" `)
// ImproveRenderErr improves the error message for rendering errors. // ImproveRenderErr improves the error message for rendering errors.
func ImproveRenderErr(inErr error) (outErr error) { func ImproveRenderErr(inErr error) (outErr error) {
+3 -3
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one. // This should be the only one.
var CurrentVersion = Version{ var CurrentVersion = Version{
Major: 0, Major: 0,
Minor: 136, Minor: 137,
PatchLevel: 2, PatchLevel: 0,
Suffix: "", Suffix: "-DEV",
} }
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !nodeploy //go:build withdeploy
// +build !nodeploy // +build withdeploy
package deploy package deploy
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !nodeploy //go:build withdeploy
// +build !nodeploy // +build withdeploy
package deploy package deploy
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !solaris && !nodeploy //go:build !solaris && withdeploy
// +build !solaris,!nodeploy // +build !solaris,withdeploy
package deploy package deploy
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !nodeploy //go:build withdeploy
// +build !nodeploy // +build withdeploy
package deploy package deploy
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !nodeploy //go:build withdeploy
// +build !nodeploy // +build withdeploy
package deployconfig package deployconfig
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !nodeploy //go:build withdeploy
// +build !nodeploy // +build withdeploy
package deploy package deploy
+7
View File
@@ -98,6 +98,8 @@ type Deps struct {
// TODO(bep) rethink this re. a plugin setup, but this will have to do for now. // TODO(bep) rethink this re. a plugin setup, but this will have to do for now.
WasmDispatchers *warpc.Dispatchers WasmDispatchers *warpc.Dispatchers
isClosed bool
*globalErrHandler *globalErrHandler
} }
@@ -345,6 +347,11 @@ func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
} }
func (d *Deps) Close() error { func (d *Deps) Close() error {
if d.isClosed {
return nil
}
d.isClosed = true
if d.MemCache != nil { if d.MemCache != nil {
d.MemCache.Stop() d.MemCache.Stop()
} }
+1 -1
View File
@@ -16,7 +16,7 @@ require (
github.com/bep/gowebp v0.3.0 github.com/bep/gowebp v0.3.0
github.com/bep/helpers v0.5.0 github.com/bep/helpers v0.5.0
github.com/bep/imagemeta v0.8.1 github.com/bep/imagemeta v0.8.1
github.com/bep/lazycache v0.4.0 github.com/bep/lazycache v0.7.0
github.com/bep/logg v0.4.0 github.com/bep/logg v0.4.0
github.com/bep/mclib v1.20400.20402 github.com/bep/mclib v1.20400.20402
github.com/bep/overlayfs v0.9.2 github.com/bep/overlayfs v0.9.2
+4
View File
@@ -143,6 +143,10 @@ github.com/bep/imagemeta v0.8.1 h1:tjZLPRftjxU7PTI87o5e5WKOFQ4S9S0engiP1OTpJTI=
github.com/bep/imagemeta v0.8.1/go.mod h1:5piPAq5Qomh07m/dPPCLN3mDJyFusvUG7VwdRD/vX0s= github.com/bep/imagemeta v0.8.1/go.mod h1:5piPAq5Qomh07m/dPPCLN3mDJyFusvUG7VwdRD/vX0s=
github.com/bep/lazycache v0.4.0 h1:X8yVyWNVupPd4e1jV7efi3zb7ZV/qcjKQgIQ5aPbkYI= github.com/bep/lazycache v0.4.0 h1:X8yVyWNVupPd4e1jV7efi3zb7ZV/qcjKQgIQ5aPbkYI=
github.com/bep/lazycache v0.4.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc= github.com/bep/lazycache v0.4.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc=
github.com/bep/lazycache v0.6.0 h1:0vCgFo7TBtMQpSx64jnH1sagmw0ZougIFRpsqPHTa5U=
github.com/bep/lazycache v0.6.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc=
github.com/bep/lazycache v0.7.0 h1:VM257SkkjcR9z55eslXTkUIX8QMNKoqQRNKV/4xIkCY=
github.com/bep/lazycache v0.7.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc=
github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ= github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0= github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0=
github.com/bep/mclib v1.20400.20402 h1:olpCE2WSPpOAbFE1R4hnftSEmQ34+xzy2HRzd0m69rA= github.com/bep/mclib v1.20400.20402 h1:olpCE2WSPpOAbFE1R4hnftSEmQ34+xzy2HRzd0m69rA=
-3
View File
@@ -179,9 +179,6 @@ type hugoSitesInit struct {
// Loads the data from all of the /data folders. // Loads the data from all of the /data folders.
data *lazy.Init data *lazy.Init
// Performs late initialization (before render) of the templates.
layouts *lazy.Init
// Loads the Git info and CODEOWNERS for all the pages if enabled. // Loads the Git info and CODEOWNERS for all the pages if enabled.
gitInfo *lazy.Init gitInfo *lazy.Init
} }
-10
View File
@@ -250,10 +250,6 @@ func (h *HugoSites) process(ctx context.Context, l logg.LevelLogger, config *Bui
l = l.WithField("step", "process") l = l.WithField("step", "process")
defer loggers.TimeTrackf(l, time.Now(), nil, "") defer loggers.TimeTrackf(l, time.Now(), nil, "")
if _, err := h.init.layouts.Do(ctx); err != nil {
return err
}
if len(events) > 0 { if len(events) > 0 {
// This is a rebuild triggered from file events. // This is a rebuild triggered from file events.
return h.processPartialFileEvents(ctx, l, config, init, events) return h.processPartialFileEvents(ctx, l, config, init, events)
@@ -1067,8 +1063,6 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
} }
if tmplChanged || i18nChanged { if tmplChanged || i18nChanged {
// TODO(bep) we should split this, but currently the loading of i18n and layout files are tied together. See #12048.
h.init.layouts.Reset()
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) { if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
// TODO(bep) this could probably be optimized to somehow // TODO(bep) this could probably be optimized to somehow
// only load the changed templates and its dependencies, but that is non-trivial. // only load the changed templates and its dependencies, but that is non-trivial.
@@ -1141,10 +1135,6 @@ func (s *Site) handleContentAdapterChanges(bi pagesfromdata.BuildInfo, buildConf
} }
func (h *HugoSites) processContentAdaptersOnRebuild(ctx context.Context, buildConfig *BuildCfg) error { func (h *HugoSites) processContentAdaptersOnRebuild(ctx context.Context, buildConfig *BuildCfg) error {
// Make sure the layouts are initialized.
if _, err := h.init.layouts.Do(context.Background()); err != nil {
return err
}
g := rungroup.Run[*pagesfromdata.PagesFromTemplate](ctx, rungroup.Config[*pagesfromdata.PagesFromTemplate]{ g := rungroup.Run[*pagesfromdata.PagesFromTemplate](ctx, rungroup.Config[*pagesfromdata.PagesFromTemplate]{
NumWorkers: h.numWorkers, NumWorkers: h.numWorkers,
Handle: func(ctx context.Context, p *pagesfromdata.PagesFromTemplate) error { Handle: func(ctx context.Context, p *pagesfromdata.PagesFromTemplate) error {
+6 -5
View File
@@ -246,11 +246,6 @@ func (s *IntegrationTestBuilder) AssertBuildCountGitInfo(count int) {
s.Assert(s.H.init.gitInfo.InitCount(), qt.Equals, count) s.Assert(s.H.init.gitInfo.InitCount(), qt.Equals, count)
} }
func (s *IntegrationTestBuilder) AssertBuildCountLayouts(count int) {
s.Helper()
s.Assert(s.H.init.layouts.InitCount(), qt.Equals, count)
}
func (s *IntegrationTestBuilder) AssertFileCount(dirname string, expected int) { func (s *IntegrationTestBuilder) AssertFileCount(dirname string, expected int) {
s.Helper() s.Helper()
fs := s.fs.WorkingDirReadOnly fs := s.fs.WorkingDirReadOnly
@@ -421,6 +416,12 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
s.Assert(err, qt.IsNil) s.Assert(err, qt.IsNil)
} }
s.Cleanup(func() {
if h := s.H; h != nil {
s.Assert(h.Close(), qt.IsNil)
}
})
return s return s
} }
+9 -4
View File
@@ -34,6 +34,15 @@ import (
var pageIDCounter atomic.Uint64 var pageIDCounter atomic.Uint64
func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) { func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
p, pth, err := h.doNewPage(m)
if err != nil {
// Make sure that any partially created page part is marked as stale.
m.MarkStale()
}
return p, pth, err
}
func (h *HugoSites) doNewPage(m *pageMeta) (*pageState, *paths.Path, error) {
m.Staler = &resources.AtomicStaler{} m.Staler = &resources.AtomicStaler{}
if m.pageMetaParams == nil { if m.pageMetaParams == nil {
m.pageMetaParams = &pageMetaParams{ m.pageMetaParams = &pageMetaParams{
@@ -231,10 +240,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
} }
return ps, nil return ps, nil
}() }()
// Make sure to evict any cached and now stale data.
if err != nil {
m.MarkStale()
}
if ps == nil { if ps == nil {
return nil, nil, err return nil, nil, err
-10
View File
@@ -344,7 +344,6 @@ func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []
skipRebuildForFilenames: make(map[string]bool), skipRebuildForFilenames: make(map[string]bool),
init: &hugoSitesInit{ init: &hugoSitesInit{
data: lazy.New(), data: lazy.New(),
layouts: lazy.New(),
gitInfo: lazy.New(), gitInfo: lazy.New(),
}, },
} }
@@ -400,15 +399,6 @@ func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []
return nil, nil return nil, nil
}) })
h.init.layouts.Add(func(context.Context) (any, error) {
for _, s := range h.Sites {
if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil {
return nil, err
}
}
return nil, nil
})
h.init.gitInfo.Add(func(context.Context) (any, error) { h.init.gitInfo.Add(func(context.Context) (any, error) {
err := h.loadGitInfo() err := h.loadGitInfo()
if err != nil { if err != nil {
+5 -2
View File
@@ -1,7 +1,10 @@
# Release env. # Release env.
# These will be replaced by script before release. # These will be replaced by script before release.
HUGORELEASER_TAG=v0.136.1 HUGORELEASER_TAG=v0.136.5
HUGORELEASER_COMMITISH=64d1865c1e21c66feb96ffedda44a9eba2365af9 HUGORELEASER_COMMITISH=46cccb021bc6425455f4eec093f5cc4a32f1d12c
+22
View File
@@ -119,6 +119,24 @@ archive_alias_replacements = { "linux-amd64.tar.gz" = "Linux-64bit.tar.gz" }
[[builds.os.archs]] [[builds.os.archs]]
goarch = "amd64" goarch = "amd64"
[[builds]]
path = "container1/unix/extended-withdeploy"
[builds.build_settings]
flags = ["-buildmode", "exe", "-tags", "extended,withdeploy"]
env = ["CGO_ENABLED=1"]
[[builds.os]]
goos = "darwin"
[builds.os.build_settings]
env = ["CGO_ENABLED=1", "CC=o64-clang", "CXX=o64-clang++"]
[[builds.os.archs]]
goarch = "universal"
[[builds.os]]
goos = "linux"
[[builds.os.archs]]
goarch = "amd64"
[[builds]] [[builds]]
path = "container2/linux/extended" path = "container2/linux/extended"
@@ -173,6 +191,10 @@ archive_alias_replacements = { "linux-amd64.tar.gz" = "Linux-64bit.tar.gz" }
paths = ["builds/container1/unix/extended/**"] paths = ["builds/container1/unix/extended/**"]
[archives.archive_settings] [archives.archive_settings]
name_template = "{{ .Project }}_extended_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}" name_template = "{{ .Project }}_extended_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}"
[[archives]]
paths = ["builds/container1/unix/extended-withdeploy/**"]
[archives.archive_settings]
name_template = "{{ .Project }}_extended_withdeploy_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}"
[[archives]] [[archives]]
# Only extended builds in container2. # Only extended builds in container2.
paths = ["builds/container2/**"] paths = ["builds/container2/**"]
+1 -1
View File
@@ -334,7 +334,7 @@ func buildFlags() []string {
func buildTags() string { func buildTags() string {
// To build the extended Hugo SCSS/SASS enabled version, build with // To build the extended Hugo SCSS/SASS enabled version, build with
// HUGO_BUILD_TAGS=extended mage install etc. // HUGO_BUILD_TAGS=extended mage install etc.
// To build without `hugo deploy` for smaller binary, use HUGO_BUILD_TAGS=nodeploy // To build with `hugo deploy`, use HUGO_BUILD_TAGS=withdeploy
if envtags := os.Getenv("HUGO_BUILD_TAGS"); envtags != "" { if envtags := os.Getenv("HUGO_BUILD_TAGS"); envtags != "" {
return envtags return envtags
} }
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package main
import (
"testing"
"github.com/rogpeppe/go-internal/testscript"
)
func TestWithdeploy(t *testing.T) {
p := commonTestScriptsParam
p.Dir = "testscripts/withdeploy"
testscript.Run(t, p)
}
-5
View File
@@ -1,17 +1,12 @@
# Test the gen commands. # Test the gen commands.
# Note that adding new commands will require updating the NUM_COMMANDS value.
env NUM_COMMANDS=44
hugo gen -h hugo gen -h
stdout 'Generate documentation for your project using Hugo''s documentation engine, including syntax highlighting for various programming languages\.' stdout 'Generate documentation for your project using Hugo''s documentation engine, including syntax highlighting for various programming languages\.'
hugo gen doc --dir clidocs hugo gen doc --dir clidocs
checkfilecount $NUM_COMMANDS clidocs
hugo gen man -h hugo gen man -h
stdout 'up-to-date man pages' stdout 'up-to-date man pages'
hugo gen man --dir manpages hugo gen man --dir manpages
checkfilecount $NUM_COMMANDS manpages
hugo gen chromastyles -h hugo gen chromastyles -h
stdout 'Generate CSS stylesheet for the Chroma code highlighter' stdout 'Generate CSS stylesheet for the Chroma code highlighter'
@@ -0,0 +1,42 @@
# Test the hugo server command when adding an error to a config file
# and then fixing it.
hugo server &
waitServer
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
replace $WORK/hugo.toml 'title =' 'titlefoo'
httpget ${HUGOTEST_BASEURL_0}p1/ 'failed'
replace $WORK/hugo.toml 'titlefoo' 'title ='
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
stopServer
-- hugo.toml --
title = "Hugo Server Test"
baseURL = "https://example.org/"
disableKinds = ["taxonomy", "term", "sitemap"]
-- layouts/index.html --
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
-- layouts/_default/single.html --
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
-- content/_index.md --
---
title: Hugo Home
---
-- content/p1/index.md --
---
title: P1
---
-- content/p2/index.md --
---
title: P2
---
-- static/staticfiles/static.txt --
static
@@ -0,0 +1,42 @@
# Test the hugo server command when adding a front matter error to a content file
# and then fixing it.
hugo server &
waitServer
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
replace $WORK/content/p1/index.md 'title:' 'titlecolon'
httpget ${HUGOTEST_BASEURL_0}p1/ 'failed'
replace $WORK/content/p1/index.md 'titlecolon' 'title:'
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
stopServer
-- hugo.toml --
title = "Hugo Server Test"
baseURL = "https://example.org/"
disableKinds = ["taxonomy", "term", "sitemap"]
-- layouts/index.html --
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
-- layouts/_default/single.html --
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
-- content/_index.md --
---
title: Hugo Home
---
-- content/p1/index.md --
---
title: P1
---
-- content/p2/index.md --
---
title: P2
---
-- static/staticfiles/static.txt --
static
+11
View File
@@ -450,6 +450,17 @@ func (ns *Namespace) Trim(s, cutset any) (string, error) {
return strings.Trim(ss, sc), nil return strings.Trim(ss, sc), nil
} }
// TrimSpace returns the given string, removing leading and trailing whitespace
// as defined by Unicode.
func (ns *Namespace) TrimSpace(s any) (string, error) {
ss, err := cast.ToStringE(s)
if err != nil {
return "", err
}
return strings.TrimSpace(ss), nil
}
// TrimLeft returns a slice of the string s with all leading characters // TrimLeft returns a slice of the string s with all leading characters
// contained in cutset removed. // contained in cutset removed.
func (ns *Namespace) TrimLeft(cutset, s any) (string, error) { func (ns *Namespace) TrimLeft(cutset, s any) (string, error) {
+27
View File
@@ -854,3 +854,30 @@ func TestDiff(t *testing.T) {
} }
} }
func TestTrimSpace(t *testing.T) {
t.Parallel()
c := qt.New(t)
for _, test := range []struct {
s any
expect any
}{
{"\n\r test \n\r", "test"},
{template.HTML("\n\r test \n\r"), "test"},
{[]byte("\n\r test \n\r"), "test"},
// errors
{tstNoStringer{}, false},
} {
result, err := ns.TrimSpace(test.s)
if b, ok := test.expect.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil))
continue
}
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, test.expect)
}
}
-1
View File
@@ -40,7 +40,6 @@ type TemplateManager interface {
TemplateHandler TemplateHandler
TemplateFuncGetter TemplateFuncGetter
AddTemplate(name, tpl string) error AddTemplate(name, tpl string) error
MarkReady() error
} }
// TemplateVariants describes the possible variants of a template. // TemplateVariants describes the possible variants of a template.
+4 -17
View File
@@ -168,6 +168,10 @@ func newTemplateHandlers(d *deps.Deps) (*tpl.TemplateHandlers, error) {
return nil, err return nil, err
} }
if err := h.main.createPrototypes(); err != nil {
return nil, err
}
e := &templateExec{ e := &templateExec{
d: d, d: d,
executor: exec, executor: exec,
@@ -312,28 +316,11 @@ func (t *templateExec) GetFunc(name string) (reflect.Value, bool) {
return v, found return v, found
} }
func (t *templateExec) MarkReady() error {
var err error
t.readyInit.Do(func() {
// We only need the clones if base templates are in use.
if len(t.needsBaseof) > 0 {
err = t.main.createPrototypes()
if err != nil {
return
}
}
})
return err
}
type templateHandler struct { type templateHandler struct {
main *templateNamespace main *templateNamespace
needsBaseof map[string]templateInfo needsBaseof map[string]templateInfo
baseof map[string]templateInfo baseof map[string]templateInfo
readyInit sync.Once
// This is the filesystem to load the templates from. All the templates are // This is the filesystem to load the templates from. All the templates are
// stored in the root of this filesystem. // stored in the root of this filesystem.
layoutsFs afero.Fs layoutsFs afero.Fs
+19 -1
View File
@@ -248,7 +248,7 @@ func TestToMathMacros(t *testing.T) {
-- hugo.toml -- -- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term'] disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- layouts/index.html -- -- layouts/index.html --
{{ $macros := dict {{ $macros := dict
"\\addBar" "\\bar{#1}" "\\addBar" "\\bar{#1}"
"\\bold" "\\mathbf{#1}" "\\bold" "\\mathbf{#1}"
}} }}
@@ -261,3 +261,21 @@ disableKinds = ['page','rss','section','sitemap','taxonomy','term']
<mi>y</mi> <mi>y</mi>
`) `)
} }
// Issue #12977
func TestUnmarshalWithIndentedYAML(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- layouts/index.html --
{{ $yaml := "\n a:\n b: 1\n c:\n d: 2\n" }}
{{ $yaml | transform.Unmarshal | encoding.Jsonify }}
`
b := hugolib.Test(t, files)
b.AssertFileExists("public/index.html", true)
b.AssertFileContent("public/index.html", `{"a":{"b":1},"c":{"d":2}}`)
}
+2 -2
View File
@@ -113,8 +113,8 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) {
return nil, fmt.Errorf("type %T not supported", data) return nil, fmt.Errorf("type %T not supported", data)
} }
if dataStr == "" { if strings.TrimSpace(dataStr) == "" {
return nil, errors.New("no data to transform") return nil, nil
} }
key := hashing.MD5FromStringHexEncoded(dataStr) key := hashing.MD5FromStringHexEncoded(dataStr)
+2
View File
@@ -139,6 +139,8 @@ func TestUnmarshal(t *testing.T) {
a;b;c`, mime: media.Builtin.CSVType}, map[string]any{"DElimiter": ";", "Comment": "%"}, func(r [][]string) { a;b;c`, mime: media.Builtin.CSVType}, map[string]any{"DElimiter": ";", "Comment": "%"}, func(r [][]string) {
b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r) b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
}}, }},
{``, nil, nil},
{` `, nil, nil},
// errors // errors
{"thisisnotavaliddataformat", nil, false}, {"thisisnotavaliddataformat", nil, false},
{testContentResource{key: "r1", content: `invalid&toml"`, mime: media.Builtin.TOMLType}, nil, false}, {testContentResource{key: "r1", content: `invalid&toml"`, mime: media.Builtin.TOMLType}, nil, false},