hugo/commands/hugobuilder.go
Bjørn Erik Pedersen 241b21b0fd Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code.

Also,

* Lower case the default output format names; this is in line with the custom ones (map keys) and how
it's treated all the places. This avoids doing `stringds.EqualFold` everywhere.

Closes #10896
Closes #10620
2023-05-16 18:01:29 +02:00

994 lines
24 KiB
Go

// Copyright 2023 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
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"runtime/trace"
"strings"
"sync"
"time"
"github.com/bep/simplecobra"
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/terminal"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/hugolib/filesystems"
"github.com/gohugoio/hugo/livereload"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/watcher"
"github.com/spf13/fsync"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
)
type hugoBuilder struct {
r *rootCommand
cunfMu sync.Mutex
conf_ *commonConfig
// May be nil.
s *serverCommand
// Currently only set when in "fast render mode".
changeDetector *fileChangeDetector
visitedURLs *types.EvictingStringQueue
fullRebuildSem *semaphore.Weighted
debounce func(f func())
onConfigLoaded func(reloaded bool) error
fastRenderMode bool
buildWatch bool
showErrorInBrowser bool
errState hugoBuilderErrState
}
func (c *hugoBuilder) conf() *commonConfig {
c.cunfMu.Lock()
defer c.cunfMu.Unlock()
return c.conf_
}
func (c *hugoBuilder) setConf(conf *commonConfig) {
c.cunfMu.Lock()
defer c.cunfMu.Unlock()
c.conf_ = conf
}
type hugoBuilderErrState struct {
mu sync.Mutex
paused bool
builderr error
waserr bool
}
func (e *hugoBuilderErrState) setPaused(p bool) {
e.mu.Lock()
defer e.mu.Unlock()
e.paused = p
}
func (e *hugoBuilderErrState) isPaused() bool {
e.mu.Lock()
defer e.mu.Unlock()
return e.paused
}
func (e *hugoBuilderErrState) setBuildErr(err error) {
e.mu.Lock()
defer e.mu.Unlock()
e.builderr = err
}
func (e *hugoBuilderErrState) buildErr() error {
e.mu.Lock()
defer e.mu.Unlock()
return e.builderr
}
func (e *hugoBuilderErrState) setWasErr(w bool) {
e.mu.Lock()
defer e.mu.Unlock()
e.waserr = w
}
func (e *hugoBuilderErrState) wasErr() bool {
e.mu.Lock()
defer e.mu.Unlock()
return e.waserr
}
func (c *hugoBuilder) errCount() int {
return int(c.r.logger.LogCounters().ErrorCounter.Count())
}
// getDirList provides NewWatcher() with a list of directories to watch for changes.
func (c *hugoBuilder) getDirList() ([]string, error) {
var filenames []string
walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error {
if err != nil {
c.r.logger.Errorln("walker: ", err)
return nil
}
if fi.IsDir() {
if fi.Name() == ".git" ||
fi.Name() == "node_modules" || fi.Name() == "bower_components" {
return filepath.SkipDir
}
filenames = append(filenames, fi.Meta().Filename)
}
return nil
}
watchFiles := c.hugo().PathSpec.BaseFs.WatchDirs()
for _, fi := range watchFiles {
if !fi.IsDir() {
filenames = append(filenames, fi.Meta().Filename)
continue
}
w := hugofs.NewWalkway(hugofs.WalkwayConfig{Logger: c.r.logger, Info: fi, WalkFn: walkFn})
if err := w.Walk(); err != nil {
c.r.logger.Errorln("walker: ", err)
}
}
filenames = helpers.UniqueStringsSorted(filenames)
return filenames, nil
}
func (c *hugoBuilder) initCPUProfile() (func(), error) {
if c.r.cpuprofile == "" {
return nil, nil
}
f, err := os.Create(c.r.cpuprofile)
if err != nil {
return nil, fmt.Errorf("failed to create CPU profile: %w", err)
}
if err := pprof.StartCPUProfile(f); err != nil {
return nil, fmt.Errorf("failed to start CPU profile: %w", err)
}
return func() {
pprof.StopCPUProfile()
f.Close()
}, nil
}
func (c *hugoBuilder) initMemProfile() {
if c.r.memprofile == "" {
return
}
f, err := os.Create(c.r.memprofile)
if err != nil {
c.r.logger.Errorf("could not create memory profile: ", err)
}
defer f.Close()
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
c.r.logger.Errorf("could not write memory profile: ", err)
}
}
func (c *hugoBuilder) initMemTicker() func() {
memticker := time.NewTicker(5 * time.Second)
quit := make(chan struct{})
printMem := func() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("\n\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\n\n", formatByteCount(m.Alloc), formatByteCount(m.TotalAlloc), formatByteCount(m.Sys), m.NumGC)
}
go func() {
for {
select {
case <-memticker.C:
printMem()
case <-quit:
memticker.Stop()
printMem()
return
}
}
}()
return func() {
close(quit)
}
}
func (c *hugoBuilder) initMutexProfile() (func(), error) {
if c.r.mutexprofile == "" {
return nil, nil
}
f, err := os.Create(c.r.mutexprofile)
if err != nil {
return nil, err
}
runtime.SetMutexProfileFraction(1)
return func() {
pprof.Lookup("mutex").WriteTo(f, 0)
f.Close()
}, nil
}
func (c *hugoBuilder) initProfiling() (func(), error) {
stopCPUProf, err := c.initCPUProfile()
if err != nil {
return nil, err
}
stopMutexProf, err := c.initMutexProfile()
if err != nil {
return nil, err
}
stopTraceProf, err := c.initTraceProfile()
if err != nil {
return nil, err
}
var stopMemTicker func()
if c.r.printm {
stopMemTicker = c.initMemTicker()
}
return func() {
c.initMemProfile()
if stopCPUProf != nil {
stopCPUProf()
}
if stopMutexProf != nil {
stopMutexProf()
}
if stopTraceProf != nil {
stopTraceProf()
}
if stopMemTicker != nil {
stopMemTicker()
}
}, nil
}
func (c *hugoBuilder) initTraceProfile() (func(), error) {
if c.r.traceprofile == "" {
return nil, nil
}
f, err := os.Create(c.r.traceprofile)
if err != nil {
return nil, fmt.Errorf("failed to create trace file: %w", err)
}
if err := trace.Start(f); err != nil {
return nil, fmt.Errorf("failed to start trace: %w", err)
}
return func() {
trace.Stop()
f.Close()
}, nil
}
// newWatcher creates a new watcher to watch filesystem events.
func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*watcher.Batcher, error) {
staticSyncer := &staticSyncer{c: c}
var pollInterval time.Duration
poll := pollIntervalStr != ""
if poll {
pollInterval, err := types.ToDurationE(pollIntervalStr)
if err != nil {
return nil, fmt.Errorf("invalid value for flag poll: %s", err)
}
c.r.logger.Printf("Use watcher with poll interval %v", pollInterval)
}
if pollInterval == 0 {
pollInterval = 500 * time.Millisecond
}
watcher, err := watcher.New(500*time.Millisecond, pollInterval, poll)
if err != nil {
return nil, err
}
spec := c.hugo().Deps.SourceSpec
for _, d := range dirList {
if d != "" {
if spec.IgnoreFile(d) {
continue
}
_ = watcher.Add(d)
}
}
// Identifies changes to config (config.toml) files.
configSet := make(map[string]bool)
configFiles := c.conf().configs.LoadingInfo.ConfigFiles
c.r.logger.Println("Watching for config changes in", strings.Join(configFiles, ", "))
for _, configFile := range configFiles {
watcher.Add(configFile)
configSet[configFile] = true
}
go func() {
for {
select {
case evs := <-watcher.Events:
unlock, err := c.hugo().LockBuild()
if err != nil {
c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
return
}
c.handleEvents(watcher, staticSyncer, evs, configSet)
if c.showErrorInBrowser && c.errCount() > 0 {
// Need to reload browser to show the error
livereload.ForceRefresh()
}
unlock()
case err := <-watcher.Errors():
if err != nil && !herrors.IsNotExist(err) {
c.r.logger.Errorln("Error while watching:", err)
}
}
}
}()
return watcher, nil
}
func (c *hugoBuilder) build() error {
stopProfiling, err := c.initProfiling()
if err != nil {
return err
}
defer func() {
if stopProfiling != nil {
stopProfiling()
}
}()
if err := c.fullBuild(false); err != nil {
return err
}
if !c.r.quiet {
c.r.Println()
c.hugo().PrintProcessingStats(os.Stdout)
c.r.Println()
}
return nil
}
func (c *hugoBuilder) buildSites(noBuildLock bool) (err error) {
return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
}
func (c *hugoBuilder) copyStatic() (map[string]uint64, error) {
m, err := c.doWithPublishDirs(c.copyStaticTo)
if err == nil || herrors.IsNotExist(err) {
return m, nil
}
return m, err
}
func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
publishDir := helpers.FilePathSeparator
if sourceFs.PublishFolder != "" {
publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
}
fs := &countingStatFs{Fs: sourceFs.Fs}
syncer := fsync.NewSyncer()
syncer.NoTimes = c.conf().configs.Base.NoTimes
syncer.NoChmod = c.conf().configs.Base.NoChmod
syncer.ChmodFilter = chmodFilter
syncer.SrcFs = fs
syncer.DestFs = c.conf().fs.PublishDirStatic
// Now that we are using a unionFs for the static directories
// We can effectively clean the publishDir on initial sync
syncer.Delete = c.conf().configs.Base.CleanDestinationDir
if syncer.Delete {
c.r.logger.Infoln("removing all files from destination that don't exist in static dirs")
syncer.DeleteFilter = func(f os.FileInfo) bool {
return f.IsDir() && strings.HasPrefix(f.Name(), ".")
}
}
c.r.logger.Infoln("syncing static files to", publishDir)
// because we are using a baseFs (to get the union right).
// set sync src to root
err := syncer.Sync(publishDir, helpers.FilePathSeparator)
if err != nil {
return 0, err
}
// Sync runs Stat 3 times for every source file (which sounds much)
numFiles := fs.statCounter / 3
return numFiles, err
}
func (c *hugoBuilder) doWithPublishDirs(f func(sourceFs *filesystems.SourceFilesystem) (uint64, error)) (map[string]uint64, error) {
langCount := make(map[string]uint64)
staticFilesystems := c.hugo().BaseFs.SourceFilesystems.Static
if len(staticFilesystems) == 0 {
c.r.logger.Infoln("No static directories found to sync")
return langCount, nil
}
for lang, fs := range staticFilesystems {
cnt, err := f(fs)
if err != nil {
return langCount, err
}
if lang == "" {
// Not multihost
for _, l := range c.conf().configs.Languages {
langCount[l.Lang] = cnt
}
} else {
langCount[lang] = cnt
}
}
return langCount, nil
}
func (c *hugoBuilder) fullBuild(noBuildLock bool) error {
var (
g errgroup.Group
langCount map[string]uint64
)
if !c.r.quiet {
fmt.Println("Start building sites … ")
fmt.Println(hugo.BuildVersionString())
if terminal.IsTerminal(os.Stdout) {
defer func() {
fmt.Print(showCursor + clearLine)
}()
}
}
copyStaticFunc := func() error {
cnt, err := c.copyStatic()
if err != nil {
return fmt.Errorf("error copying static files: %w", err)
}
langCount = cnt
return nil
}
buildSitesFunc := func() error {
if err := c.buildSites(noBuildLock); err != nil {
return fmt.Errorf("error building site: %w", err)
}
return nil
}
// Do not copy static files and build sites in parallel if cleanDestinationDir is enabled.
// This flag deletes all static resources in /public folder that are missing in /static,
// and it does so at the end of copyStatic() call.
if c.conf().configs.Base.CleanDestinationDir {
if err := copyStaticFunc(); err != nil {
return err
}
if err := buildSitesFunc(); err != nil {
return err
}
} else {
g.Go(copyStaticFunc)
g.Go(buildSitesFunc)
if err := g.Wait(); err != nil {
return err
}
}
for _, s := range c.hugo().Sites {
s.ProcessingStats.Static = langCount[s.Language().Lang]
}
if c.r.gc {
count, err := c.hugo().GC()
if err != nil {
return err
}
for _, s := range c.hugo().Sites {
// We have no way of knowing what site the garbage belonged to.
s.ProcessingStats.Cleaned = uint64(count)
}
}
return nil
}
func (c *hugoBuilder) fullRebuild(changeType string) {
if changeType == configChangeGoMod {
// go.mod may be changed during the build itself, and
// we really want to prevent superfluous builds.
if !c.fullRebuildSem.TryAcquire(1) {
return
}
c.fullRebuildSem.Release(1)
}
c.fullRebuildSem.Acquire(context.Background(), 1)
go func() {
defer c.fullRebuildSem.Release(1)
c.printChangeDetected(changeType)
defer func() {
// Allow any file system events to arrive basimplecobra.
// This will block any rebuild on config changes for the
// duration of the sleep.
time.Sleep(2 * time.Second)
}()
defer c.r.timeTrack(time.Now(), "Rebuilt")
err := c.reloadConfig()
if err != nil {
// Set the processing on pause until the state is recovered.
c.errState.setPaused(true)
c.handleBuildErr(err, "Failed to reload config")
} else {
c.errState.setPaused(false)
}
if !c.errState.isPaused() {
_, err := c.copyStatic()
if err != nil {
c.r.logger.Errorln(err)
return
}
err = c.buildSites(false)
if err != nil {
c.r.logger.Errorln(err)
} else if c.s != nil && c.s.doLiveReload {
livereload.ForceRefresh()
}
}
}()
}
func (c *hugoBuilder) handleBuildErr(err error, msg string) {
c.errState.setBuildErr(err)
c.r.logger.Errorln(msg + ": " + cleanErrorLog(err.Error()))
}
func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
staticSyncer *staticSyncer,
evs []fsnotify.Event,
configSet map[string]bool) {
defer func() {
c.errState.setWasErr(false)
}()
var isHandled bool
for _, ev := range evs {
isConfig := configSet[ev.Name]
configChangeType := configChangeConfig
if isConfig {
if strings.Contains(ev.Name, "go.mod") {
configChangeType = configChangeGoMod
}
if strings.Contains(ev.Name, ".work") {
configChangeType = configChangeGoWork
}
}
if !isConfig {
// It may be one of the /config folders
dirname := filepath.Dir(ev.Name)
if dirname != "." && configSet[dirname] {
isConfig = true
}
}
if isConfig {
isHandled = true
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
continue
}
if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Rename == fsnotify.Rename {
configFiles := c.conf().configs.LoadingInfo.ConfigFiles
for _, configFile := range configFiles {
counter := 0
for watcher.Add(configFile) != nil {
counter++
if counter >= 100 {
break
}
time.Sleep(100 * time.Millisecond)
}
}
}
// Config file(s) changed. Need full rebuild.
c.fullRebuild(configChangeType)
return
}
}
if isHandled {
return
}
if c.errState.isPaused() {
// Wait for the server to get into a consistent state before
// we continue with processing.
return
}
if len(evs) > 50 {
// This is probably a mass edit of the content dir.
// Schedule a full rebuild for when it slows down.
c.debounce(func() {
c.fullRebuild("")
})
return
}
c.r.logger.Infoln("Received System Events:", evs)
staticEvents := []fsnotify.Event{}
dynamicEvents := []fsnotify.Event{}
filtered := []fsnotify.Event{}
for _, ev := range evs {
if c.hugo().ShouldSkipFileChangeEvent(ev) {
continue
}
// Check the most specific first, i.e. files.
contentMapped := c.hugo().ContentChanges.GetSymbolicLinkMappings(ev.Name)
if len(contentMapped) > 0 {
for _, mapped := range contentMapped {
filtered = append(filtered, fsnotify.Event{Name: mapped, Op: ev.Op})
}
continue
}
// Check for any symbolic directory mapping.
dir, name := filepath.Split(ev.Name)
contentMapped = c.hugo().ContentChanges.GetSymbolicLinkMappings(dir)
if len(contentMapped) == 0 {
filtered = append(filtered, ev)
continue
}
for _, mapped := range contentMapped {
mappedFilename := filepath.Join(mapped, name)
filtered = append(filtered, fsnotify.Event{Name: mappedFilename, Op: ev.Op})
}
}
evs = filtered
for _, ev := range evs {
ext := filepath.Ext(ev.Name)
baseName := filepath.Base(ev.Name)
istemp := strings.HasSuffix(ext, "~") ||
(ext == ".swp") || // vim
(ext == ".swx") || // vim
(ext == ".tmp") || // generic temp file
(ext == ".DS_Store") || // OSX Thumbnail
baseName == "4913" || // vim
strings.HasPrefix(ext, ".goutputstream") || // gnome
strings.HasSuffix(ext, "jb_old___") || // intelliJ
strings.HasSuffix(ext, "jb_tmp___") || // intelliJ
strings.HasSuffix(ext, "jb_bak___") || // intelliJ
strings.HasPrefix(ext, ".sb-") || // byword
strings.HasPrefix(baseName, ".#") || // emacs
strings.HasPrefix(baseName, "#") // emacs
if istemp {
continue
}
if c.hugo().Deps.SourceSpec.IgnoreFile(ev.Name) {
continue
}
// Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these
if ev.Name == "" {
continue
}
// Write and rename operations are often followed by CHMOD.
// There may be valid use cases for rebuilding the site on CHMOD,
// but that will require more complex logic than this simple conditional.
// On OS X this seems to be related to Spotlight, see:
// https://github.com/go-fsnotify/fsnotify/issues/15
// A workaround is to put your site(s) on the Spotlight exception list,
// but that may be a little mysterious for most end users.
// So, for now, we skip reload on CHMOD.
// We do have to check for WRITE though. On slower laptops a Chmod
// could be aggregated with other important events, and we still want
// to rebuild on those
if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod {
continue
}
walkAdder := func(path string, f hugofs.FileMetaInfo, err error) error {
if f.IsDir() {
c.r.logger.Println("adding created directory to watchlist", path)
if err := watcher.Add(path); err != nil {
return err
}
} else if !staticSyncer.isStatic(path) {
// Hugo's rebuilding logic is entirely file based. When you drop a new folder into
// /content on OSX, the above logic will handle future watching of those files,
// but the initial CREATE is lost.
dynamicEvents = append(dynamicEvents, fsnotify.Event{Name: path, Op: fsnotify.Create})
}
return nil
}
// recursively add new directories to watch list
// When mkdir -p is used, only the top directory triggers an event (at least on OSX)
if ev.Op&fsnotify.Create == fsnotify.Create {
if s, err := c.conf().fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
_ = helpers.SymbolicWalk(c.conf().fs.Source, ev.Name, walkAdder)
}
}
if staticSyncer.isStatic(ev.Name) {
staticEvents = append(staticEvents, ev)
} else {
dynamicEvents = append(dynamicEvents, ev)
}
}
if len(staticEvents) > 0 {
c.printChangeDetected("Static files")
if c.r.forceSyncStatic {
c.r.logger.Printf("Syncing all static files\n")
_, err := c.copyStatic()
if err != nil {
c.r.logger.Errorln("Error copying static files to publish dir:", err)
return
}
} else {
if err := staticSyncer.syncsStaticEvents(staticEvents); err != nil {
c.r.logger.Errorln("Error syncing static files to publish dir:", err)
return
}
}
if c.s != nil && c.s.doLiveReload {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
// force refresh when more than one file
if !c.errState.wasErr() && len(staticEvents) == 1 {
ev := staticEvents[0]
h := c.hugo()
path := h.BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
path = h.RelURL(helpers.ToSlashTrimLeading(path), false)
livereload.RefreshPath(path)
} else {
livereload.ForceRefresh()
}
}
}
if len(dynamicEvents) > 0 {
partitionedEvents := partitionDynamicEvents(
c.hugo().BaseFs.SourceFilesystems,
dynamicEvents)
onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
c.printChangeDetected("")
c.changeDetector.PrepareNew()
func() {
defer c.r.timeTrack(time.Now(), "Total")
if err := c.rebuildSites(dynamicEvents); err != nil {
c.handleBuildErr(err, "Rebuild failed")
}
}()
if c.s != nil && c.s.doLiveReload {
if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 {
if c.errState.wasErr() {
livereload.ForceRefresh()
return
}
changed := c.changeDetector.changed()
if c.changeDetector != nil && len(changed) == 0 {
// Nothing has changed.
return
} else if len(changed) == 1 {
pathToRefresh := c.hugo().PathSpec.RelURL(helpers.ToSlashTrimLeading(changed[0]), false)
livereload.RefreshPath(pathToRefresh)
} else {
livereload.ForceRefresh()
}
}
if len(partitionedEvents.ContentEvents) > 0 {
navigate := c.s != nil && c.s.navigateToChanged
// We have fetched the same page above, but it may have
// changed.
var p page.Page
if navigate {
if onePageName != "" {
p = c.hugo().GetContentPage(onePageName)
}
}
if p != nil {
livereload.NavigateToPathForPort(p.RelPermalink(), p.Site().ServerPort())
} else {
livereload.ForceRefresh()
}
}
}
}
}
func (c *hugoBuilder) hugo() *hugolib.HugoSites {
h, err := c.r.HugFromConfig(c.conf())
if err != nil {
panic(err)
}
if c.s != nil {
// A running server, register the media types.
for _, s := range h.Sites {
s.RegisterMediaTypes()
}
}
return h
}
func (c *hugoBuilder) hugoTry() *hugolib.HugoSites {
h, _ := c.r.HugFromConfig(c.conf())
return h
}
func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error {
cfg := config.New()
cfg.Set("renderToDisk", (c.s == nil && !c.r.renderToMemory) || (c.s != nil && c.s.renderToDisk))
watch := c.r.buildWatch || (c.s != nil && c.s.serverWatch)
cfg.Set("environment", c.r.environment)
cfg.Set("internal", maps.Params{
"running": running,
"watch": watch,
"verbose": c.r.verbose,
})
conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, cfg))
if err != nil {
return err
}
c.setConf(conf)
if c.onConfigLoaded != nil {
if err := c.onConfigLoaded(false); err != nil {
return err
}
}
return nil
}
func (c *hugoBuilder) printChangeDetected(typ string) {
msg := "\nChange"
if typ != "" {
msg += " of " + typ
}
msg += " detected, rebuilding site."
c.r.logger.Println(msg)
const layout = "2006-01-02 15:04:05.000 -0700"
c.r.logger.Println(htime.Now().Format(layout))
}
func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error {
if err := c.errState.buildErr(); err != nil {
ferrs := herrors.UnwrapFileErrorsWithErrorContext(err)
for _, err := range ferrs {
events = append(events, fsnotify.Event{Name: err.Position().Filename, Op: fsnotify.Write})
}
}
c.errState.setBuildErr(nil)
visited := c.visitedURLs.PeekAllSet()
h := c.hugo()
if c.fastRenderMode {
// Make sure we always render the home pages
for _, l := range c.conf().configs.Languages {
langPath := h.GetLangSubDir(l.Lang)
if langPath != "" {
langPath = langPath + "/"
}
home := h.PrependBasePath("/"+langPath, false)
visited[home] = true
}
}
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: visited, ErrRecovery: c.errState.wasErr()}, events...)
}
func (c *hugoBuilder) reloadConfig() error {
c.r.Reset()
c.r.configVersionID.Add(1)
oldConf := c.conf()
conf, err := c.r.ConfigFromConfig(c.r.configVersionID.Load(), c.conf())
if err != nil {
return err
}
sameLen := len(oldConf.configs.Languages) == len(conf.configs.Languages)
if !sameLen {
if oldConf.configs.IsMultihost || conf.configs.IsMultihost {
return errors.New("multihost change detected, please restart server")
}
}
c.setConf(conf)
if c.onConfigLoaded != nil {
if err := c.onConfigLoaded(true); err != nil {
return err
}
}
return nil
}