]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Fix and add integration test for the Bootstrap SCSS module for both Dart Sass and...
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 29 Feb 2024 18:05:23 +0000 (19:05 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 1 Mar 2024 13:18:52 +0000 (14:18 +0100)
This fixes the reverse filesystem lookup (absolute filename to path relative to the composite filesystem).

The old logic had some assumptions about the locality of the actual files that didn't work in more complex scenarios.

This commit now also adds the popular Bootstrap SCSS Hugo module to the CI build (both for libsass and dartsass transpiler), so we can hopefully avoid similar future breakage.

Fixes #12178

hugofs/rootmapping_fs.go
hugofs/rootmapping_fs_test.go
hugolib/filesystems/basefs.go
hugolib/hugo_sites_build.go
hugolib/integrationtest_builder.go
resources/resource_transformers/tocss/dartsass/dartsass_integration_test.go
resources/resource_transformers/tocss/scss/scss_integration_test.go

index a5bf9aadfe8704f8978b65f97f05446ab153623a..336c8b4e71a2707e20f75f66375396409b2b0f30 100644 (file)
@@ -21,6 +21,7 @@ import (
        "path"
        "path/filepath"
        "strings"
+       "sync/atomic"
 
        "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/common/paths"
@@ -43,6 +44,7 @@ var _ ReverseLookupProvder = (*RootMappingFs)(nil)
 func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
        rootMapToReal := radix.New()
        realMapToRoot := radix.New()
+       id := fmt.Sprintf("rfs-%d", rootMappingFsCounter.Add(1))
 
        addMapping := func(key string, rm RootMapping, to *radix.Tree) {
                var mappings []RootMapping
@@ -76,6 +78,16 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
                        rm.Meta = NewFileMeta()
                }
 
+               if rm.FromBase == "" {
+                       panic(" rm.FromBase is empty")
+               }
+
+               rm.Meta.Component = rm.FromBase
+               rm.Meta.Module = rm.Module
+               rm.Meta.ModuleOrdinal = rm.ModuleOrdinal
+               rm.Meta.IsProject = rm.IsProject
+               rm.Meta.BaseDir = rm.ToBase
+
                if !fi.IsDir() {
                        // We do allow single file mounts.
                        // However, the file system logic will be much simpler with just directories.
@@ -122,19 +134,9 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
                        }
                }
 
-               if rm.FromBase == "" {
-                       panic(" rm.FromBase is empty")
-               }
-
                // Extract "blog" from "content/blog"
                rm.path = strings.TrimPrefix(strings.TrimPrefix(rm.From, rm.FromBase), filepathSeparator)
-
                rm.Meta.SourceRoot = fi.(MetaProvider).Meta().Filename
-               rm.Meta.BaseDir = rm.ToBase
-               rm.Meta.Module = rm.Module
-               rm.Meta.ModuleOrdinal = rm.ModuleOrdinal
-               rm.Meta.Component = rm.FromBase
-               rm.Meta.IsProject = rm.IsProject
 
                meta := rm.Meta.Copy()
 
@@ -156,6 +158,7 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
        }
 
        rfs := &RootMappingFs{
+               id:            id,
                Fs:            fs,
                rootMapToReal: rootMapToReal,
                realMapToRoot: realMapToRoot,
@@ -227,11 +230,14 @@ var _ FilesystemUnwrapper = (*RootMappingFs)(nil)
 // is directories only, and they will be returned in Readdir and Readdirnames
 // in the order given.
 type RootMappingFs struct {
+       id string
        afero.Fs
        rootMapToReal *radix.Tree
        realMapToRoot *radix.Tree
 }
 
+var rootMappingFsCounter atomic.Int32
+
 func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
        base = filepathSeparator + fs.cleanName(base)
        roots := fs.getRootsWithPrefix(base)
@@ -263,6 +269,10 @@ func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
        return fss, nil
 }
 
+func (fs *RootMappingFs) Key() string {
+       return fs.id
+}
+
 func (fs *RootMappingFs) UnwrapFilesystem() afero.Fs {
        return fs.Fs
 }
@@ -320,16 +330,16 @@ func (c ComponentPath) ComponentPathJoined() string {
 }
 
 type ReverseLookupProvder interface {
-       ReverseLookup(filename string, checkExists bool) ([]ComponentPath, error)
-       ReverseLookupComponent(component, filename string, checkExists bool) ([]ComponentPath, error)
+       ReverseLookup(filename string) ([]ComponentPath, error)
+       ReverseLookupComponent(component, filename string) ([]ComponentPath, error)
 }
 
 // func (fs *RootMappingFs) ReverseStat(filename string) ([]FileMetaInfo, error)
-func (fs *RootMappingFs) ReverseLookup(filename string, checkExists bool) ([]ComponentPath, error) {
-       return fs.ReverseLookupComponent("", filename, checkExists)
+func (fs *RootMappingFs) ReverseLookup(filename string) ([]ComponentPath, error) {
+       return fs.ReverseLookupComponent("", filename)
 }
 
-func (fs *RootMappingFs) ReverseLookupComponent(component, filename string, checkExists bool) ([]ComponentPath, error) {
+func (fs *RootMappingFs) ReverseLookupComponent(component, filename string) ([]ComponentPath, error) {
        filename = fs.cleanName(filename)
        key := filepathSeparator + filename
 
@@ -360,14 +370,6 @@ func (fs *RootMappingFs) ReverseLookupComponent(component, filename string, chec
                } else {
                        // Now we know that this file _could_ be in this fs.
                        filename = filepathSeparator + filepath.Join(first.path, dir, name)
-
-                       if checkExists {
-                               // Confirm that it exists.
-                               _, err := fs.Stat(first.FromBase + filename)
-                               if err != nil {
-                                       continue
-                               }
-                       }
                }
 
                cps = append(cps, ComponentPath{
@@ -667,6 +669,7 @@ func (fs *RootMappingFs) doStat(name string) ([]FileMetaInfo, error) {
                var fis []FileMetaInfo
 
                for _, rm := range roots {
+
                        var fi FileMetaInfo
                        fi, err = fs.statRoot(rm, name)
                        if err == nil {
index b1ef102d36e1808c90c81e4d31df90cc00059c5a..83a95d648c22fe3c21766f708e62e0a56a2301f3 100644 (file)
@@ -276,20 +276,20 @@ func TestRootMappingFsMount(t *testing.T) {
 
        // Test ReverseLookup.
        // Single file mounts.
-       cps, err := rfs.ReverseLookup(filepath.FromSlash("singlefiles/no.txt"), true)
+       cps, err := rfs.ReverseLookup(filepath.FromSlash("singlefiles/no.txt"))
        c.Assert(err, qt.IsNil)
        c.Assert(cps, qt.DeepEquals, []ComponentPath{
                {Component: "content", Path: "singles/p1.md", Lang: "no"},
        })
 
-       cps, err = rfs.ReverseLookup(filepath.FromSlash("singlefiles/sv.txt"), true)
+       cps, err = rfs.ReverseLookup(filepath.FromSlash("singlefiles/sv.txt"))
        c.Assert(err, qt.IsNil)
        c.Assert(cps, qt.DeepEquals, []ComponentPath{
                {Component: "content", Path: "singles/p1.md", Lang: "sv"},
        })
 
        // File inside directory mount.
-       cps, err = rfs.ReverseLookup(filepath.FromSlash("mynoblogcontent/test.txt"), true)
+       cps, err = rfs.ReverseLookup(filepath.FromSlash("mynoblogcontent/test.txt"))
        c.Assert(err, qt.IsNil)
        c.Assert(cps, qt.DeepEquals, []ComponentPath{
                {Component: "content", Path: "blog/test.txt", Lang: "no"},
index aa466e0eb2a6fd8e99b8c8eceec5af23b95f7b4d..b3e3284d5361cfd5652b981eb93d7478dbbd5956 100644 (file)
@@ -265,6 +265,9 @@ type SourceFilesystem struct {
        // This is a virtual composite filesystem. It expects path relative to a context.
        Fs afero.Fs
 
+       // The source filesystem (usually the OS filesystem).
+       SourceFs afero.Fs
+
        // When syncing a source folder to the target (e.g. /public), this may
        // be set to publish into a subfolder. This is used for static syncing
        // in multihost mode.
@@ -320,10 +323,10 @@ func (s SourceFilesystems) IsContent(filename string) bool {
 }
 
 // ResolvePaths resolves the given filename to a list of paths in the filesystems.
-func (s *SourceFilesystems) ResolvePaths(filename string, checkExists bool) []hugofs.ComponentPath {
+func (s *SourceFilesystems) ResolvePaths(filename string) []hugofs.ComponentPath {
        var cpss []hugofs.ComponentPath
        for _, rfs := range s.RootFss {
-               cps, err := rfs.ReverseLookup(filename, checkExists)
+               cps, err := rfs.ReverseLookup(filename)
                if err != nil {
                        panic(err)
                }
@@ -362,7 +365,17 @@ func (d *SourceFilesystem) ReverseLookup(filename string, checkExists bool) ([]h
        var cps []hugofs.ComponentPath
        hugofs.WalkFilesystems(d.Fs, func(fs afero.Fs) bool {
                if rfs, ok := fs.(hugofs.ReverseLookupProvder); ok {
-                       if c, err := rfs.ReverseLookupComponent(d.Name, filename, checkExists); err == nil {
+                       if c, err := rfs.ReverseLookupComponent(d.Name, filename); err == nil {
+                               if checkExists {
+                                       n := 0
+                                       for _, cp := range c {
+                                               if _, err := d.Fs.Stat(filepath.FromSlash(cp.Path)); err == nil {
+                                                       c[n] = cp
+                                                       n++
+                                               }
+                                       }
+                                       c = c[:n]
+                               }
                                cps = append(cps, c...)
                        }
                }
@@ -379,11 +392,12 @@ func (d *SourceFilesystem) mounts() []hugofs.FileMetaInfo {
                        if err == nil {
                                m = append(m, mounts...)
                        }
-
                }
                return false
        })
+
        // Filter out any mounts not belonging to this filesystem.
+       // TODO(bep) I think this is superflous.
        n := 0
        for _, mm := range m {
                if mm.Meta().Component == d.Name {
@@ -392,6 +406,7 @@ func (d *SourceFilesystem) mounts() []hugofs.FileMetaInfo {
                }
        }
        m = m[:n]
+
        return m
 }
 
@@ -428,10 +443,8 @@ func (d *SourceFilesystem) RealDirs(from string) []string {
                if !m.IsDir() {
                        continue
                }
-               meta := m.Meta()
-               _, err := d.Fs.Stat(from)
-               if err == nil {
-                       dirname := filepath.Join(meta.Filename, from)
+               dirname := filepath.Join(m.Meta().Filename, from)
+               if _, err := d.SourceFs.Stat(dirname); err == nil {
                        dirnames = append(dirnames, dirname)
                }
        }
@@ -519,8 +532,9 @@ func newSourceFilesystemsBuilder(p *paths.Paths, logger loggers.Logger, b *BaseF
 
 func (b *sourceFilesystemsBuilder) newSourceFilesystem(name string, fs afero.Fs) *SourceFilesystem {
        return &SourceFilesystem{
-               Name: name,
-               Fs:   fs,
+               Name:     name,
+               Fs:       fs,
+               SourceFs: b.sourceFs,
        }
 }
 
index 93acdbf6bfa2e2606369fc5320242cee940ae873..382b1eed02b7759b4050c7afdc3c2d42e6dbfd74 100644 (file)
@@ -656,7 +656,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
 
                isChangedDir := statErr == nil && fi.IsDir()
 
-               cpss := h.BaseFs.ResolvePaths(ev.Name, !removed)
+               cpss := h.BaseFs.ResolvePaths(ev.Name)
                pss := make([]*paths.Path, len(cpss))
                for i, cps := range cpss {
                        p := cps.Path
index 642bac7abf13dbea65d1cbb6ede3be45d25967f6..be11c18d6921a9a0dffe0d921515942edfd4ffba 100644 (file)
@@ -183,6 +183,13 @@ type lockingBuffer struct {
        bytes.Buffer
 }
 
+func (b *lockingBuffer) ReadFrom(r io.Reader) (n int64, err error) {
+       b.Lock()
+       n, err = b.Buffer.ReadFrom(r)
+       b.Unlock()
+       return
+}
+
 func (b *lockingBuffer) Write(p []byte) (n int, err error) {
        b.Lock()
        n, err = b.Buffer.Write(p)
index dd4c1e5cae3afb80311fe72ec62896b83aea7bbf..4d48b3b6a6bb08c1ceaf77a8cc08aec337e29b87 100644 (file)
@@ -19,6 +19,7 @@ import (
 
        "github.com/bep/logg"
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/htesting"
        "github.com/gohugoio/hugo/hugolib"
        "github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
 )
@@ -525,3 +526,39 @@ T1: {{ $r.Content }}
        b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:13:0: number`)
        b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:14:0: number`)
 }
+
+// Note: This test is more or less duplicated in both of the SCSS packages (libsass and dartsass).
+func TestBootstrap(t *testing.T) {
+       t.Parallel()
+       if !dartsass.Supports() {
+               t.Skip()
+       }
+       if !htesting.IsCI() {
+               t.Skip("skip (slow) test in non-CI environment")
+       }
+
+       files := `
+-- hugo.toml --
+disableKinds = ["term", "taxonomy", "section", "page"]
+[module]
+[[module.imports]]
+path="github.com/gohugoio/hugo-mod-bootstrap-scss/v5"
+-- go.mod --
+module github.com/gohugoio/tests/testHugoModules
+-- assets/scss/main.scss --
+@import "bootstrap/bootstrap";
+-- layouts/index.html --
+{{ $cssOpts := (dict "transpiler" "dartsass" ) }}
+{{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts }}
+Styles: {{ $r.RelPermalink }}
+               `
+
+       b := hugolib.NewIntegrationTestBuilder(
+               hugolib.IntegrationTestConfig{
+                       T:           t,
+                       TxtarString: files,
+                       NeedsOsFS:   true,
+               }).Build()
+
+       b.AssertFileContent("public/index.html", "Styles: /scss/main.css")
+}
index 4d7d9d710abe555bb2f5dabbcb16da96f7a3e5be..c193ca8af6b987115ae9a96d9dd23085e874257a 100644 (file)
@@ -20,6 +20,7 @@ import (
 
        qt "github.com/frankban/quicktest"
 
+       "github.com/gohugoio/hugo/htesting"
        "github.com/gohugoio/hugo/hugolib"
        "github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
 )
@@ -290,3 +291,39 @@ T1: {{ $r.Content }}
 
        b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover;font-family:Hugo&#39;s New Roman}p{color:blue;font-size:var 24px}b{color:green}`)
 }
+
+// Note: This test is more or less duplicated in both of the SCSS packages (libsass and dartsass).
+func TestBootstrap(t *testing.T) {
+       t.Parallel()
+       if !scss.Supports() {
+               t.Skip()
+       }
+       if !htesting.IsCI() {
+               t.Skip("skip (slow) test in non-CI environment")
+       }
+
+       files := `
+-- hugo.toml --
+disableKinds = ["term", "taxonomy", "section", "page"]
+[module]
+[[module.imports]]
+path="github.com/gohugoio/hugo-mod-bootstrap-scss/v5"
+-- go.mod --
+module github.com/gohugoio/tests/testHugoModules
+-- assets/scss/main.scss --
+@import "bootstrap/bootstrap";
+-- layouts/index.html --
+{{ $cssOpts := (dict "transpiler" "libsass" ) }}
+{{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts }}
+Styles: {{ $r.RelPermalink }}
+               `
+
+       b := hugolib.NewIntegrationTestBuilder(
+               hugolib.IntegrationTestConfig{
+                       T:           t,
+                       TxtarString: files,
+                       NeedsOsFS:   true,
+               }).Build()
+
+       b.AssertFileContent("public/index.html", "Styles: /scss/main.css")
+}