// 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 filesystems provides the fine grained file systems used by Hugo. These
// are typically virtual filesystems that are composites of project and theme content.
package filesystems
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"github.com/bep/overlayfs"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/hugofs/glob"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/types"
"github.com/rogpeppe/go-internal/lockedfile"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/modules"
hpaths "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib/paths"
"github.com/spf13/afero"
)
const (
// Used to control concurrency between multiple Hugo instances, e.g.
// a running server and building new content with 'hugo new'.
// It's placed in the project root.
lockFileBuild = ".hugo_build.lock"
)
var filePathSeparator = string(filepath.Separator)
// BaseFs contains the core base filesystems used by Hugo. The name "base" is used
// to underline that even if they can be composites, they all have a base path set to a specific
// resource folder, e.g "/my-project/content". So, no absolute filenames needed.
type BaseFs struct {
// SourceFilesystems contains the different source file systems.
*SourceFilesystems
// The source filesystem (needs absolute filenames).
SourceFs afero.Fs
// The project source.
ProjectSourceFs afero.Fs
// The filesystem used to publish the rendered site.
// This usually maps to /my-project/public.
PublishFs afero.Fs
// The filesystem used for static files.
PublishFsStatic afero.Fs
// A read-only filesystem starting from the project workDir.
WorkDir afero.Fs
theBigFs *filesystemsCollector
workingDir string
// Locks.
buildMu Lockable // <project>/.hugo_build.lock
}
type Lockable interface {
Lock() (unlock func(), err error)
}
type fakeLockfileMutex struct {
mu sync.Mutex
}
func (f *fakeLockfileMutex) Lock() (func(), error) {
f.mu.Lock()
return func() { f.mu.Unlock() }, nil
}
// Tries to acquire a build lock.
func (b *BaseFs) LockBuild() (unlock func(), err error) {
return b.buildMu.Lock()
}
func (b *BaseFs) WatchFilenames() []string {
var filenames []string
sourceFs := b.SourceFs
for _, rfs := range b.RootFss {
for _, component := range files.ComponentFolders {
fis, err := rfs.Mounts(component)
if err != nil {
continue
}
for _, fim := range fis {
meta := fim.Meta()
if !meta.Watch {
continue
}
if !fim.IsDir() {
filenames = append(filenames, meta.Filename)
continue
}
w := hugofs.NewWalkway(hugofs.WalkwayConfig{
Fs: sourceFs,
Root: meta.Filename,
WalkFn: func(path string, fi hugofs.FileMetaInfo) error {
if !fi.IsDir() {
return nil
}
if fi.Name() == ".git" ||
fi.Name() == "node_modules" || fi.Name() == "bower_components" {
return filepath.SkipDir
}
filenames = append(filenames, fi.Meta().Filename)
return nil
},
})
w.Walk()
}
}
}
return filenames
}
func (b *BaseFs) mountsForComponent(component string) []hugofs.FileMetaInfo {
var result []hugofs.FileMetaInfo
for _, rfs := range b.RootFss {
dirs, err := rfs.Mounts(component)
if err == nil {
result = append(result, dirs...)
}
}
return