URI: 
       basefs.go - hugo - [fork] hugo port for 9front
  HTML git clone https://git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
       basefs.go (24362B)
       ---
            1 // Copyright 2024 The Hugo Authors. All rights reserved.
            2 //
            3 // Licensed under the Apache License, Version 2.0 (the "License");
            4 // you may not use this file except in compliance with the License.
            5 // You may obtain a copy of the License at
            6 // http://www.apache.org/licenses/LICENSE-2.0
            7 //
            8 // Unless required by applicable law or agreed to in writing, software
            9 // distributed under the License is distributed on an "AS IS" BASIS,
           10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           11 // See the License for the specific language governing permissions and
           12 // limitations under the License.
           13 
           14 // Package filesystems provides the fine grained file systems used by Hugo. These
           15 // are typically virtual filesystems that are composites of project and theme content.
           16 package filesystems
           17 
           18 import (
           19         "fmt"
           20         "io"
           21         "os"
           22         "path/filepath"
           23         "strings"
           24         "sync"
           25 
           26         "github.com/bep/overlayfs"
           27         "github.com/gohugoio/hugo/config"
           28         "github.com/gohugoio/hugo/htesting"
           29         "github.com/gohugoio/hugo/hugofs/glob"
           30 
           31         "github.com/gohugoio/hugo/common/herrors"
           32         "github.com/gohugoio/hugo/common/loggers"
           33         "github.com/gohugoio/hugo/common/types"
           34 
           35         "github.com/rogpeppe/go-internal/lockedfile"
           36 
           37         "github.com/gohugoio/hugo/hugofs/files"
           38 
           39         "github.com/gohugoio/hugo/modules"
           40 
           41         hpaths "github.com/gohugoio/hugo/common/paths"
           42         "github.com/gohugoio/hugo/hugofs"
           43         "github.com/gohugoio/hugo/hugolib/paths"
           44         "github.com/spf13/afero"
           45 )
           46 
           47 const (
           48         // Used to control concurrency between multiple Hugo instances, e.g.
           49         // a running server and building new content with 'hugo new'.
           50         // It's placed in the project root.
           51         lockFileBuild = ".hugo_build.lock"
           52 )
           53 
           54 var filePathSeparator = string(filepath.Separator)
           55 
           56 // BaseFs contains the core base filesystems used by Hugo. The name "base" is used
           57 // to underline that even if they can be composites, they all have a base path set to a specific
           58 // resource folder, e.g "/my-project/content". So, no absolute filenames needed.
           59 type BaseFs struct {
           60         // SourceFilesystems contains the different source file systems.
           61         *SourceFilesystems
           62 
           63         // The source filesystem (needs absolute filenames).
           64         SourceFs afero.Fs
           65 
           66         // The project source.
           67         ProjectSourceFs afero.Fs
           68 
           69         // The filesystem used to publish the rendered site.
           70         // This usually maps to /my-project/public.
           71         PublishFs afero.Fs
           72 
           73         // The filesystem used for static files.
           74         PublishFsStatic afero.Fs
           75 
           76         // A read-only filesystem starting from the project workDir.
           77         WorkDir afero.Fs
           78 
           79         theBigFs *filesystemsCollector
           80 
           81         workingDir string
           82 
           83         // Locks.
           84         buildMu Lockable // <project>/.hugo_build.lock
           85 }
           86 
           87 type Lockable interface {
           88         Lock() (unlock func(), err error)
           89 }
           90 
           91 type fakeLockfileMutex struct {
           92         mu sync.Mutex
           93 }
           94 
           95 func (f *fakeLockfileMutex) Lock() (func(), error) {
           96         f.mu.Lock()
           97         return func() { f.mu.Unlock() }, nil
           98 }
           99 
          100 // Tries to acquire a build lock.
          101 func (b *BaseFs) LockBuild() (unlock func(), err error) {
          102         return b.buildMu.Lock()
          103 }
          104 
          105 func (b *BaseFs) WatchFilenames() []string {
          106         var filenames []string
          107         sourceFs := b.SourceFs
          108 
          109         for _, rfs := range b.RootFss {
          110                 for _, component := range files.ComponentFolders {
          111                         fis, err := rfs.Mounts(component)
          112                         if err != nil {
          113                                 continue
          114                         }
          115 
          116                         for _, fim := range fis {
          117                                 meta := fim.Meta()
          118                                 if !meta.Watch {
          119                                         continue
          120                                 }
          121 
          122                                 if !fim.IsDir() {
          123                                         filenames = append(filenames, meta.Filename)
          124                                         continue
          125                                 }
          126 
          127                                 w := hugofs.NewWalkway(hugofs.WalkwayConfig{
          128                                         Fs:   sourceFs,
          129                                         Root: meta.Filename,
          130                                         WalkFn: func(path string, fi hugofs.FileMetaInfo) error {
          131                                                 if !fi.IsDir() {
          132                                                         return nil
          133                                                 }
          134                                                 if fi.Name() == ".git" ||
          135                                                         fi.Name() == "node_modules" || fi.Name() == "bower_components" {
          136                                                         return filepath.SkipDir
          137                                                 }
          138                                                 filenames = append(filenames, fi.Meta().Filename)
          139                                                 return nil
          140                                         },
          141                                 })
          142 
          143                                 w.Walk()
          144                         }
          145 
          146                 }
          147         }
          148 
          149         return filenames
          150 }
          151 
          152 func (b *BaseFs) mountsForComponent(component string) []hugofs.FileMetaInfo {
          153         var result []hugofs.FileMetaInfo
          154         for _, rfs := range b.RootFss {
          155                 dirs, err := rfs.Mounts(component)
          156                 if err == nil {
          157                         result = append(result, dirs...)
          158                 }
          159         }
          160         return result
          161 }
          162 
          163 // AbsProjectContentDir tries to construct a filename below the most
          164 // relevant content directory.
          165 func (b *BaseFs) AbsProjectContentDir(filename string) (string, string, error) {
          166         isAbs := filepath.IsAbs(filename)
          167         for _, fi := range b.mountsForComponent(files.ComponentFolderContent) {
          168                 if !fi.IsDir() {
          169                         continue
          170                 }
          171                 meta := fi.Meta()
          172                 if !meta.IsProject {
          173                         continue
          174                 }
          175 
          176                 if isAbs {
          177                         if strings.HasPrefix(filename, meta.Filename) {
          178                                 return strings.TrimPrefix(filename, meta.Filename+filePathSeparator), filename, nil
          179                         }
          180                 } else {
          181                         contentDir := strings.TrimPrefix(strings.TrimPrefix(meta.Filename, meta.BaseDir), filePathSeparator) + filePathSeparator
          182 
          183                         if strings.HasPrefix(filename, contentDir) {
          184                                 relFilename := strings.TrimPrefix(filename, contentDir)
          185                                 absFilename := filepath.Join(meta.Filename, relFilename)
          186                                 return relFilename, absFilename, nil
          187                         }
          188                 }
          189 
          190         }
          191 
          192         if !isAbs {
          193                 // A filename on the form "posts/mypage.md", put it inside
          194                 // the first content folder, usually <workDir>/content.
          195                 // Pick the first project dir (which is probably the most important one).
          196                 for _, dir := range b.SourceFilesystems.Content.mounts() {
          197                         if !dir.IsDir() {
          198                                 continue
          199                         }
          200                         meta := dir.Meta()
          201                         if meta.IsProject {
          202                                 return filename, filepath.Join(meta.Filename, filename), nil
          203                         }
          204                 }
          205         }
          206 
          207         return "", "", fmt.Errorf("could not determine content directory for %q", filename)
          208 }
          209 
          210 // ResolveJSConfigFile resolves the JS-related config file to a absolute
          211 // filename. One example of such would be postcss.config.js.
          212 func (b *BaseFs) ResolveJSConfigFile(name string) string {
          213         // First look in assets/_jsconfig
          214         fi, err := b.Assets.Fs.Stat(filepath.Join(files.FolderJSConfig, name))
          215         if err == nil {
          216                 return fi.(hugofs.FileMetaInfo).Meta().Filename
          217         }
          218         // Fall back to the work dir.
          219         fi, err = b.Work.Stat(name)
          220         if err == nil {
          221                 return fi.(hugofs.FileMetaInfo).Meta().Filename
          222         }
          223 
          224         return ""
          225 }
          226 
          227 // SourceFilesystems contains the different source file systems. These can be
          228 // composite file systems (theme and project etc.), and they have all root
          229 // set to the source type the provides: data, i18n, static, layouts.
          230 type SourceFilesystems struct {
          231         Content    *SourceFilesystem
          232         Data       *SourceFilesystem
          233         I18n       *SourceFilesystem
          234         Layouts    *SourceFilesystem
          235         Archetypes *SourceFilesystem
          236         Assets     *SourceFilesystem
          237 
          238         AssetsWithDuplicatesPreserved *SourceFilesystem
          239 
          240         RootFss []*hugofs.RootMappingFs
          241 
          242         // Writable filesystem on top the project's resources directory,
          243         // with any sub module's resource fs layered below.
          244         ResourcesCache afero.Fs
          245 
          246         // The work folder (may be a composite of project and theme components).
          247         Work afero.Fs
          248 
          249         // When in multihost we have one static filesystem per language. The sync
          250         // static files is currently done outside of the Hugo build (where there is
          251         // a concept of a site per language).
          252         // When in non-multihost mode there will be one entry in this map with a blank key.
          253         Static map[string]*SourceFilesystem
          254 
          255         conf config.AllProvider
          256 }
          257 
          258 // A SourceFilesystem holds the filesystem for a given source type in Hugo (data,
          259 // i18n, layouts, static) and additional metadata to be able to use that filesystem
          260 // in server mode.
          261 type SourceFilesystem struct {
          262         // Name matches one in files.ComponentFolders
          263         Name string
          264 
          265         // This is a virtual composite filesystem. It expects path relative to a context.
          266         Fs afero.Fs
          267 
          268         // The source filesystem (usually the OS filesystem).
          269         SourceFs afero.Fs
          270 
          271         // When syncing a source folder to the target (e.g. /public), this may
          272         // be set to publish into a subfolder. This is used for static syncing
          273         // in multihost mode.
          274         PublishFolder string
          275 }
          276 
          277 // StaticFs returns the static filesystem for the given language.
          278 // This can be a composite filesystem.
          279 func (s SourceFilesystems) StaticFs(lang string) afero.Fs {
          280         var staticFs afero.Fs = hugofs.NoOpFs
          281 
          282         if fs, ok := s.Static[lang]; ok {
          283                 staticFs = fs.Fs
          284         } else if fs, ok := s.Static[""]; ok {
          285                 staticFs = fs.Fs
          286         }
          287 
          288         return staticFs
          289 }
          290 
          291 // StatResource looks for a resource in these filesystems in order: static, assets and finally content.
          292 // If found in any of them, it returns FileInfo and the relevant filesystem.
          293 // Any non herrors.IsNotExist error will be returned.
          294 // An herrors.IsNotExist error will be returned only if all filesystems return such an error.
          295 // Note that if we only wanted to find the file, we could create a composite Afero fs,
          296 // but we also need to know which filesystem root it lives in.
          297 func (s SourceFilesystems) StatResource(lang, filename string) (fi os.FileInfo, fs afero.Fs, err error) {
          298         for _, fsToCheck := range []afero.Fs{s.StaticFs(lang), s.Assets.Fs, s.Content.Fs} {
          299                 fs = fsToCheck
          300                 fi, err = fs.Stat(filename)
          301                 if err == nil || !herrors.IsNotExist(err) {
          302                         return
          303                 }
          304         }
          305         // Not found.
          306         return
          307 }
          308 
          309 // IsStatic returns true if the given filename is a member of one of the static
          310 // filesystems.
          311 func (s SourceFilesystems) IsStatic(filename string) bool {
          312         for _, staticFs := range s.Static {
          313                 if staticFs.Contains(filename) {
          314                         return true
          315                 }
          316         }
          317         return false
          318 }
          319 
          320 // IsContent returns true if the given filename is a member of the content filesystem.
          321 func (s SourceFilesystems) IsContent(filename string) bool {
          322         return s.Content.Contains(filename)
          323 }
          324 
          325 // ResolvePaths resolves the given filename to a list of paths in the filesystems.
          326 func (s *SourceFilesystems) ResolvePaths(filename string) []hugofs.ComponentPath {
          327         var cpss []hugofs.ComponentPath
          328         for _, rfs := range s.RootFss {
          329                 cps, err := rfs.ReverseLookup(filename)
          330                 if err != nil {
          331                         panic(err)
          332                 }
          333                 cpss = append(cpss, cps...)
          334         }
          335         return cpss
          336 }
          337 
          338 // MakeStaticPathRelative makes an absolute static filename into a relative one.
          339 // It will return an empty string if the filename is not a member of a static filesystem.
          340 func (s SourceFilesystems) MakeStaticPathRelative(filename string) string {
          341         for _, staticFs := range s.Static {
          342                 rel, _ := staticFs.MakePathRelative(filename, true)
          343                 if rel != "" {
          344                         return rel
          345                 }
          346         }
          347         return ""
          348 }
          349 
          350 // MakePathRelative creates a relative path from the given filename.
          351 func (d *SourceFilesystem) MakePathRelative(filename string, checkExists bool) (string, bool) {
          352         cps, err := d.ReverseLookup(filename, checkExists)
          353         if err != nil {
          354                 panic(err)
          355         }
          356         if len(cps) == 0 {
          357                 return "", false
          358         }
          359 
          360         return filepath.FromSlash(cps[0].Path), true
          361 }
          362 
          363 // ReverseLookup returns the component paths for the given filename.
          364 func (d *SourceFilesystem) ReverseLookup(filename string, checkExists bool) ([]hugofs.ComponentPath, error) {
          365         var cps []hugofs.ComponentPath
          366         hugofs.WalkFilesystems(d.Fs, func(fs afero.Fs) bool {
          367                 if rfs, ok := fs.(hugofs.ReverseLookupProvder); ok {
          368                         if c, err := rfs.ReverseLookupComponent(d.Name, filename); err == nil {
          369                                 if checkExists {
          370                                         n := 0
          371                                         for _, cp := range c {
          372                                                 if _, err := d.Fs.Stat(filepath.FromSlash(cp.Path)); err == nil {
          373                                                         c[n] = cp
          374                                                         n++
          375                                                 }
          376                                         }
          377                                         c = c[:n]
          378                                 }
          379                                 cps = append(cps, c...)
          380                         }
          381                 }
          382                 return false
          383         })
          384         return cps, nil
          385 }
          386 
          387 func (d *SourceFilesystem) mounts() []hugofs.FileMetaInfo {
          388         var m []hugofs.FileMetaInfo
          389         hugofs.WalkFilesystems(d.Fs, func(fs afero.Fs) bool {
          390                 if rfs, ok := fs.(*hugofs.RootMappingFs); ok {
          391                         mounts, err := rfs.Mounts(d.Name)
          392                         if err == nil {
          393                                 m = append(m, mounts...)
          394                         }
          395                 }
          396                 return false
          397         })
          398 
          399         // Filter out any mounts not belonging to this filesystem.
          400         // TODO(bep) I think this is superfluous.
          401         n := 0
          402         for _, mm := range m {
          403                 if mm.Meta().Component == d.Name {
          404                         m[n] = mm
          405                         n++
          406                 }
          407         }
          408         m = m[:n]
          409 
          410         return m
          411 }
          412 
          413 func (d *SourceFilesystem) RealFilename(rel string) string {
          414         fi, err := d.Fs.Stat(rel)
          415         if err != nil {
          416                 return rel
          417         }
          418         if realfi, ok := fi.(hugofs.FileMetaInfo); ok {
          419                 return realfi.Meta().Filename
          420         }
          421 
          422         return rel
          423 }
          424 
          425 // Contains returns whether the given filename is a member of the current filesystem.
          426 func (d *SourceFilesystem) Contains(filename string) bool {
          427         for _, dir := range d.mounts() {
          428                 if !dir.IsDir() {
          429                         continue
          430                 }
          431                 if strings.HasPrefix(filename, dir.Meta().Filename) {
          432                         return true
          433                 }
          434         }
          435         return false
          436 }
          437 
          438 // RealDirs gets a list of absolute paths to directories starting from the given
          439 // path.
          440 func (d *SourceFilesystem) RealDirs(from string) []string {
          441         var dirnames []string
          442         for _, m := range d.mounts() {
          443                 if !m.IsDir() {
          444                         continue
          445                 }
          446                 dirname := filepath.Join(m.Meta().Filename, from)
          447                 if _, err := d.SourceFs.Stat(dirname); err == nil {
          448                         dirnames = append(dirnames, dirname)
          449                 }
          450         }
          451         return dirnames
          452 }
          453 
          454 // WithBaseFs allows reuse of some potentially expensive to create parts that remain
          455 // the same across sites/languages.
          456 func WithBaseFs(b *BaseFs) func(*BaseFs) error {
          457         return func(bb *BaseFs) error {
          458                 bb.theBigFs = b.theBigFs
          459                 bb.SourceFilesystems = b.SourceFilesystems
          460                 return nil
          461         }
          462 }
          463 
          464 // NewBase builds the filesystems used by Hugo given the paths and options provided.NewBase
          465 func NewBase(p *paths.Paths, logger loggers.Logger, options ...func(*BaseFs) error) (*BaseFs, error) {
          466         fs := p.Fs
          467         if logger == nil {
          468                 logger = loggers.NewDefault()
          469         }
          470 
          471         publishFs := hugofs.NewBaseFileDecorator(fs.PublishDir)
          472         projectSourceFs := hugofs.NewBaseFileDecorator(hugofs.NewBasePathFs(fs.Source, p.Cfg.BaseConfig().WorkingDir))
          473         sourceFs := hugofs.NewBaseFileDecorator(fs.Source)
          474         publishFsStatic := fs.PublishDirStatic
          475 
          476         var buildMu Lockable
          477         if p.Cfg.NoBuildLock() || htesting.IsTest {
          478                 buildMu = &fakeLockfileMutex{}
          479         } else {
          480                 buildMu = lockedfile.MutexAt(filepath.Join(p.Cfg.BaseConfig().WorkingDir, lockFileBuild))
          481         }
          482 
          483         b := &BaseFs{
          484                 SourceFs:        sourceFs,
          485                 ProjectSourceFs: projectSourceFs,
          486                 WorkDir:         fs.WorkingDirReadOnly,
          487                 PublishFs:       publishFs,
          488                 PublishFsStatic: publishFsStatic,
          489                 workingDir:      p.Cfg.BaseConfig().WorkingDir,
          490                 buildMu:         buildMu,
          491         }
          492 
          493         for _, opt := range options {
          494                 if err := opt(b); err != nil {
          495                         return nil, err
          496                 }
          497         }
          498 
          499         if b.theBigFs != nil && b.SourceFilesystems != nil {
          500                 return b, nil
          501         }
          502 
          503         builder := newSourceFilesystemsBuilder(p, logger, b)
          504         sourceFilesystems, err := builder.Build()
          505         if err != nil {
          506                 return nil, fmt.Errorf("build filesystems: %w", err)
          507         }
          508 
          509         b.SourceFilesystems = sourceFilesystems
          510         b.theBigFs = builder.theBigFs
          511 
          512         return b, nil
          513 }
          514 
          515 type sourceFilesystemsBuilder struct {
          516         logger   loggers.Logger
          517         p        *paths.Paths
          518         sourceFs afero.Fs
          519         result   *SourceFilesystems
          520         theBigFs *filesystemsCollector
          521 }
          522 
          523 func newSourceFilesystemsBuilder(p *paths.Paths, logger loggers.Logger, b *BaseFs) *sourceFilesystemsBuilder {
          524         sourceFs := hugofs.NewBaseFileDecorator(p.Fs.Source)
          525         return &sourceFilesystemsBuilder{
          526                 p: p, logger: logger, sourceFs: sourceFs, theBigFs: b.theBigFs,
          527                 result: &SourceFilesystems{
          528                         conf: p.Cfg,
          529                 },
          530         }
          531 }
          532 
          533 func (b *sourceFilesystemsBuilder) newSourceFilesystem(name string, fs afero.Fs) *SourceFilesystem {
          534         return &SourceFilesystem{
          535                 Name:     name,
          536                 Fs:       fs,
          537                 SourceFs: b.sourceFs,
          538         }
          539 }
          540 
          541 func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
          542         if b.theBigFs == nil {
          543                 theBigFs, err := b.createMainOverlayFs(b.p)
          544                 if err != nil {
          545                         return nil, fmt.Errorf("create main fs: %w", err)
          546                 }
          547 
          548                 b.theBigFs = theBigFs
          549         }
          550 
          551         createView := func(componentID string, overlayFs *overlayfs.OverlayFs) *SourceFilesystem {
          552                 if b.theBigFs == nil || b.theBigFs.overlayMounts == nil {
          553                         return b.newSourceFilesystem(componentID, hugofs.NoOpFs)
          554                 }
          555 
          556                 fs := hugofs.NewComponentFs(
          557                         hugofs.ComponentFsOptions{
          558                                 Fs:                     overlayFs,
          559                                 Component:              componentID,
          560                                 DefaultContentLanguage: b.p.Cfg.DefaultContentLanguage(),
          561                                 PathParser:             b.p.Cfg.PathParser(),
          562                         },
          563                 )
          564 
          565                 return b.newSourceFilesystem(componentID, fs)
          566         }
          567 
          568         b.result.Archetypes = createView(files.ComponentFolderArchetypes, b.theBigFs.overlayMounts)
          569         b.result.Layouts = createView(files.ComponentFolderLayouts, b.theBigFs.overlayMounts)
          570         b.result.Assets = createView(files.ComponentFolderAssets, b.theBigFs.overlayMounts)
          571         b.result.ResourcesCache = b.theBigFs.overlayResources
          572         b.result.RootFss = b.theBigFs.rootFss
          573 
          574         // data and i18n  needs a different merge strategy.
          575         overlayMountsPreserveDupes := b.theBigFs.overlayMounts.WithDirsMerger(hugofs.AppendDirsMerger)
          576         b.result.Data = createView(files.ComponentFolderData, overlayMountsPreserveDupes)
          577         b.result.I18n = createView(files.ComponentFolderI18n, overlayMountsPreserveDupes)
          578         b.result.AssetsWithDuplicatesPreserved = createView(files.ComponentFolderAssets, overlayMountsPreserveDupes)
          579 
          580         contentFs := hugofs.NewComponentFs(
          581                 hugofs.ComponentFsOptions{
          582                         Fs:                     b.theBigFs.overlayMountsContent,
          583                         Component:              files.ComponentFolderContent,
          584                         DefaultContentLanguage: b.p.Cfg.DefaultContentLanguage(),
          585                         PathParser:             b.p.Cfg.PathParser(),
          586                 },
          587         )
          588 
          589         b.result.Content = b.newSourceFilesystem(files.ComponentFolderContent, contentFs)
          590         b.result.Work = hugofs.NewReadOnlyFs(b.theBigFs.overlayFull)
          591 
          592         // Create static filesystem(s)
          593         ms := make(map[string]*SourceFilesystem)
          594         b.result.Static = ms
          595 
          596         if b.theBigFs.staticPerLanguage != nil {
          597                 // Multihost mode
          598                 for k, v := range b.theBigFs.staticPerLanguage {
          599                         sfs := b.newSourceFilesystem(files.ComponentFolderStatic, v)
          600                         sfs.PublishFolder = k
          601                         ms[k] = sfs
          602                 }
          603         } else {
          604                 bfs := hugofs.NewBasePathFs(b.theBigFs.overlayMountsStatic, files.ComponentFolderStatic)
          605                 ms[""] = b.newSourceFilesystem(files.ComponentFolderStatic, bfs)
          606         }
          607 
          608         return b.result, nil
          609 }
          610 
          611 func (b *sourceFilesystemsBuilder) createMainOverlayFs(p *paths.Paths) (*filesystemsCollector, error) {
          612         var staticFsMap map[string]*overlayfs.OverlayFs
          613         if b.p.Cfg.IsMultihost() {
          614                 languages := b.p.Cfg.Languages()
          615                 staticFsMap = make(map[string]*overlayfs.OverlayFs)
          616                 for _, l := range languages {
          617                         staticFsMap[l.Lang] = overlayfs.New(overlayfs.Options{})
          618                 }
          619         }
          620 
          621         collector := &filesystemsCollector{
          622                 sourceProject:     b.sourceFs,
          623                 sourceModules:     b.sourceFs,
          624                 staticPerLanguage: staticFsMap,
          625 
          626                 overlayMounts:        overlayfs.New(overlayfs.Options{}),
          627                 overlayMountsContent: overlayfs.New(overlayfs.Options{DirsMerger: hugofs.LanguageDirsMerger}),
          628                 overlayMountsStatic:  overlayfs.New(overlayfs.Options{DirsMerger: hugofs.LanguageDirsMerger}),
          629                 overlayFull:          overlayfs.New(overlayfs.Options{}),
          630                 overlayResources:     overlayfs.New(overlayfs.Options{FirstWritable: true}),
          631         }
          632 
          633         mods := p.AllModules()
          634 
          635         mounts := make([]mountsDescriptor, len(mods))
          636 
          637         for i := range mods {
          638                 mod := mods[i]
          639                 dir := mod.Dir()
          640 
          641                 isMainProject := mod.Owner() == nil
          642                 mounts[i] = mountsDescriptor{
          643                         Module:        mod,
          644                         dir:           dir,
          645                         isMainProject: isMainProject,
          646                         ordinal:       i,
          647                 }
          648 
          649         }
          650 
          651         err := b.createOverlayFs(collector, mounts)
          652 
          653         return collector, err
          654 }
          655 
          656 func (b *sourceFilesystemsBuilder) isContentMount(mnt modules.Mount) bool {
          657         return strings.HasPrefix(mnt.Target, files.ComponentFolderContent)
          658 }
          659 
          660 func (b *sourceFilesystemsBuilder) isStaticMount(mnt modules.Mount) bool {
          661         return strings.HasPrefix(mnt.Target, files.ComponentFolderStatic)
          662 }
          663 
          664 func (b *sourceFilesystemsBuilder) createOverlayFs(
          665         collector *filesystemsCollector,
          666         mounts []mountsDescriptor,
          667 ) error {
          668         if len(mounts) == 0 {
          669                 appendNopIfEmpty := func(ofs *overlayfs.OverlayFs) *overlayfs.OverlayFs {
          670                         if ofs.NumFilesystems() > 0 {
          671                                 return ofs
          672                         }
          673                         return ofs.Append(hugofs.NoOpFs)
          674                 }
          675                 collector.overlayMounts = appendNopIfEmpty(collector.overlayMounts)
          676                 collector.overlayMountsContent = appendNopIfEmpty(collector.overlayMountsContent)
          677                 collector.overlayMountsStatic = appendNopIfEmpty(collector.overlayMountsStatic)
          678                 collector.overlayMountsFull = appendNopIfEmpty(collector.overlayMountsFull)
          679                 collector.overlayFull = appendNopIfEmpty(collector.overlayFull)
          680                 collector.overlayResources = appendNopIfEmpty(collector.overlayResources)
          681 
          682                 return nil
          683         }
          684 
          685         for _, md := range mounts {
          686                 var (
          687                         fromTo        []hugofs.RootMapping
          688                         fromToContent []hugofs.RootMapping
          689                         fromToStatic  []hugofs.RootMapping
          690                 )
          691 
          692                 absPathify := func(path string) (string, string) {
          693                         if filepath.IsAbs(path) {
          694                                 return "", path
          695                         }
          696                         return md.dir, hpaths.AbsPathify(md.dir, path)
          697                 }
          698 
          699                 for i, mount := range md.Mounts() {
          700                         // Add more weight to early mounts.
          701                         // When two mounts contain the same filename,
          702                         // the first entry wins.
          703                         mountWeight := (10 + md.ordinal) * (len(md.Mounts()) - i)
          704 
          705                         inclusionFilter, err := glob.NewFilenameFilter(
          706                                 types.ToStringSlicePreserveString(mount.IncludeFiles),
          707                                 types.ToStringSlicePreserveString(mount.ExcludeFiles),
          708                         )
          709                         if err != nil {
          710                                 return err
          711                         }
          712 
          713                         base, filename := absPathify(mount.Source)
          714 
          715                         rm := hugofs.RootMapping{
          716                                 From:          mount.Target,
          717                                 To:            filename,
          718                                 ToBase:        base,
          719                                 Module:        md.Module.Path(),
          720                                 ModuleOrdinal: md.ordinal,
          721                                 IsProject:     md.isMainProject,
          722                                 Meta: &hugofs.FileMeta{
          723                                         Watch:           !mount.DisableWatch && md.Watch(),
          724                                         Weight:          mountWeight,
          725                                         InclusionFilter: inclusionFilter,
          726                                 },
          727                         }
          728 
          729                         isContentMount := b.isContentMount(mount)
          730 
          731                         lang := mount.Lang
          732                         if lang == "" && isContentMount {
          733                                 lang = b.p.Cfg.DefaultContentLanguage()
          734                         }
          735 
          736                         rm.Meta.Lang = lang
          737 
          738                         if isContentMount {
          739                                 fromToContent = append(fromToContent, rm)
          740                         } else if b.isStaticMount(mount) {
          741                                 fromToStatic = append(fromToStatic, rm)
          742                         } else {
          743                                 fromTo = append(fromTo, rm)
          744                         }
          745                 }
          746 
          747                 modBase := collector.sourceProject
          748                 if !md.isMainProject {
          749                         modBase = collector.sourceModules
          750                 }
          751 
          752                 sourceStatic := modBase
          753 
          754                 rmfs, err := hugofs.NewRootMappingFs(modBase, fromTo...)
          755                 if err != nil {
          756                         return err
          757                 }
          758                 rmfsContent, err := hugofs.NewRootMappingFs(modBase, fromToContent...)
          759                 if err != nil {
          760                         return err
          761                 }
          762                 rmfsStatic, err := hugofs.NewRootMappingFs(sourceStatic, fromToStatic...)
          763                 if err != nil {
          764                         return err
          765                 }
          766 
          767                 // We need to keep the list of directories for watching.
          768                 collector.addRootFs(rmfs)
          769                 collector.addRootFs(rmfsContent)
          770                 collector.addRootFs(rmfsStatic)
          771 
          772                 if collector.staticPerLanguage != nil {
          773                         for _, l := range b.p.Cfg.Languages() {
          774                                 lang := l.Lang
          775 
          776                                 lfs := rmfsStatic.Filter(func(rm hugofs.RootMapping) bool {
          777                                         rlang := rm.Meta.Lang
          778                                         return rlang == "" || rlang == lang
          779                                 })
          780                                 bfs := hugofs.NewBasePathFs(lfs, files.ComponentFolderStatic)
          781                                 collector.staticPerLanguage[lang] = collector.staticPerLanguage[lang].Append(bfs)
          782                         }
          783                 }
          784 
          785                 getResourcesDir := func() string {
          786                         if md.isMainProject {
          787                                 return b.p.AbsResourcesDir
          788                         }
          789                         _, filename := absPathify(files.FolderResources)
          790                         return filename
          791                 }
          792 
          793                 collector.overlayMounts = collector.overlayMounts.Append(rmfs)
          794                 collector.overlayMountsContent = collector.overlayMountsContent.Append(rmfsContent)
          795                 collector.overlayMountsStatic = collector.overlayMountsStatic.Append(rmfsStatic)
          796                 collector.overlayFull = collector.overlayFull.Append(hugofs.NewBasePathFs(modBase, md.dir))
          797                 collector.overlayResources = collector.overlayResources.Append(hugofs.NewBasePathFs(modBase, getResourcesDir()))
          798 
          799         }
          800 
          801         return nil
          802 }
          803 
          804 //lint:ignore U1000 useful for debugging
          805 func printFs(fs afero.Fs, path string, w io.Writer) {
          806         if fs == nil {
          807                 return
          808         }
          809         afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
          810                 if err != nil {
          811                         return err
          812                 }
          813                 if info.IsDir() {
          814                         return nil
          815                 }
          816                 var filename string
          817                 if fim, ok := info.(hugofs.FileMetaInfo); ok {
          818                         filename = fim.Meta().Filename
          819                 }
          820                 fmt.Fprintf(w, "    %q %q\n", path, filename)
          821                 return nil
          822         })
          823 }
          824 
          825 type filesystemsCollector struct {
          826         sourceProject afero.Fs // Source for project folders
          827         sourceModules afero.Fs // Source for modules/themes
          828 
          829         overlayMounts        *overlayfs.OverlayFs
          830         overlayMountsContent *overlayfs.OverlayFs
          831         overlayMountsStatic  *overlayfs.OverlayFs
          832         overlayMountsFull    *overlayfs.OverlayFs
          833         overlayFull          *overlayfs.OverlayFs
          834         overlayResources     *overlayfs.OverlayFs
          835 
          836         rootFss []*hugofs.RootMappingFs
          837 
          838         // Set if in multihost mode
          839         staticPerLanguage map[string]*overlayfs.OverlayFs
          840 }
          841 
          842 func (c *filesystemsCollector) addRootFs(rfs *hugofs.RootMappingFs) {
          843         c.rootFss = append(c.rootFss, rfs)
          844 }
          845 
          846 type mountsDescriptor struct {
          847         modules.Module
          848         dir           string
          849         isMainProject bool
          850         ordinal       int // zero based starting from the project.
          851 }