shithub: hugo

Download patch

ref: 4b6c5eba306e6e69f3dd07a6c102bfc8040b38c9
parent: edf9f0a354e5eaa556f8faed70b5243b7273b35c
author: Bjørn Erik Pedersen <[email protected]>
date: Wed Jul 31 04:21:17 EDT 2019

Move the mount duplicate filter to the modules package

Also simplify the mount validation logic. There are plenty of ways a user can create mount configs that behaves oddly.

--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -207,17 +207,25 @@
 		return v, configFiles, err
 	}
 
-	mods, modulesConfigFiles, err := l.collectModules(modulesConfig, v)
-	if err != nil {
-		return v, configFiles, err
-	}
+	// Need to run these after the modules are loaded, but before
+	// they are finalized.
+	collectHook := func(m *modules.ModulesConfig) error {
+		if err := loadLanguageSettings(v, nil); err != nil {
+			return err
+		}
 
-	if err := loadLanguageSettings(v, nil); err != nil {
-		return v, configFiles, err
+		mods := m.ActiveModules
+
+		// Apply default project mounts.
+		if err := modules.ApplyProjectConfigDefaults(v, mods[len(mods)-1]); err != nil {
+			return err
+		}
+
+		return nil
 	}
 
-	// Apply default project mounts.
-	if err := modules.ApplyProjectConfigDefaults(v, mods[len(mods)-1]); err != nil {
+	_, modulesConfigFiles, err := l.collectModules(modulesConfig, v, collectHook)
+	if err != nil {
 		return v, configFiles, err
 	}
 
@@ -406,7 +414,7 @@
 	return modConfig, nil
 }
 
-func (l configLoader) collectModules(modConfig modules.Config, v1 *viper.Viper) (modules.Modules, []string, error) {
+func (l configLoader) collectModules(modConfig modules.Config, v1 *viper.Viper, hookBeforeFinalize func(m *modules.ModulesConfig) error) (modules.Modules, []string, error) {
 	workingDir := l.WorkingDir
 	if workingDir == "" {
 		workingDir = v1.GetString("workingDir")
@@ -420,16 +428,40 @@
 	if err != nil {
 		return nil, nil, err
 	}
+
 	v1.Set("filecacheConfigs", filecacheConfigs)
 
+	var configFilenames []string
+
+	hook := func(m *modules.ModulesConfig) error {
+		for _, tc := range m.ActiveModules {
+			if tc.ConfigFilename() != "" {
+				if tc.Watch() {
+					configFilenames = append(configFilenames, tc.ConfigFilename())
+				}
+				if err := l.applyThemeConfig(v1, tc); err != nil {
+					return err
+				}
+			}
+		}
+
+		if hookBeforeFinalize != nil {
+			return hookBeforeFinalize(m)
+		}
+
+		return nil
+
+	}
+
 	modulesClient := modules.NewClient(modules.ClientConfig{
-		Fs:           l.Fs,
-		Logger:       l.Logger,
-		WorkingDir:   workingDir,
-		ThemesDir:    themesDir,
-		CacheDir:     filecacheConfigs.CacheDirModules(),
-		ModuleConfig: modConfig,
-		IgnoreVendor: ignoreVendor,
+		Fs:                 l.Fs,
+		Logger:             l.Logger,
+		HookBeforeFinalize: hook,
+		WorkingDir:         workingDir,
+		ThemesDir:          themesDir,
+		CacheDir:           filecacheConfigs.CacheDirModules(),
+		ModuleConfig:       modConfig,
+		IgnoreVendor:       ignoreVendor,
 	})
 
 	v1.Set("modulesClient", modulesClient)
@@ -441,22 +473,6 @@
 
 	// Avoid recreating these later.
 	v1.Set("allModules", moduleConfig.ActiveModules)
-
-	if len(moduleConfig.ActiveModules) == 0 {
-		return nil, nil, nil
-	}
-
-	var configFilenames []string
-	for _, tc := range moduleConfig.ActiveModules {
-		if tc.ConfigFilename() != "" {
-			if tc.Watch() {
-				configFilenames = append(configFilenames, tc.ConfigFilename())
-			}
-			if err := l.applyThemeConfig(v1, tc); err != nil {
-				return nil, nil, err
-			}
-		}
-	}
 
 	if moduleConfig.GoModulesFilename != "" {
 		// We want to watch this for changes and trigger rebuild on version
--- a/hugolib/filesystems/basefs.go
+++ b/hugolib/filesystems/basefs.go
@@ -18,7 +18,6 @@
 import (
 	"io"
 	"os"
-	"path"
 	"path/filepath"
 	"strings"
 	"sync"
@@ -454,7 +453,6 @@
 	// The theme components are ordered from left to right.
 	// We need to revert it to get the
 	// overlay logic below working as expected, with the project on top (last).
-
 	for i, mod := range mods {
 		dir := mod.Dir()
 
@@ -463,11 +461,9 @@
 		}
 
 		isMainProject := mod.Owner() == nil
-		// TODO(bep) embed mount + move any duplicate/overlap
 		modsReversed[i] = mountsDescriptor{
-			mounts:        mod.Mounts(),
+			Module:        mod,
 			dir:           dir,
-			watch:         mod.Watch(),
 			isMainProject: isMainProject,
 		}
 	}
@@ -500,37 +496,8 @@
 		return paths.AbsPathify(md.dir, path)
 	}
 
-	seen := make(map[string]bool)
+	for _, mount := range md.Mounts() {
 
-	var mounts []modules.Mount
-
-OUTER:
-	for i, mount := range md.mounts {
-		key := path.Join(mount.Lang, mount.Source, mount.Target)
-		if seen[key] {
-			continue
-		}
-		seen[key] = true
-
-		// Prevent overlapping mounts
-		for j, mount2 := range md.mounts {
-			if j == i || mount2.Target != mount.Target {
-				continue
-			}
-			source := mount.Source
-			if !strings.HasSuffix(source, filePathSeparator) {
-				source += filePathSeparator
-			}
-			if strings.HasPrefix(mount2.Source, source) {
-				continue OUTER
-			}
-		}
-
-		mounts = append(mounts, mount)
-	}
-
-	for _, mount := range mounts {
-
 		mountWeight := 1
 		if md.isMainProject {
 			mountWeight++
@@ -540,7 +507,7 @@
 			From: mount.Target,
 			To:   absPathify(mount.Source),
 			Meta: hugofs.FileMeta{
-				"watch":       md.watch,
+				"watch":       md.Watch(),
 				"mountWeight": mountWeight,
 			},
 		}
@@ -703,9 +670,8 @@
 }
 
 type mountsDescriptor struct {
-	mounts        []modules.Mount
+	modules.Module
 	dir           string
-	watch         bool // whether this is a candidate for watching in server mode.
 	isMainProject bool
 }
 
--- a/modules/client.go
+++ b/modules/client.go
@@ -66,7 +66,6 @@
 // level imports to start out.
 func NewClient(cfg ClientConfig) *Client {
 	fs := cfg.Fs
-
 	n := filepath.Join(cfg.WorkingDir, goModFilename)
 	goModEnabled, _ := afero.Exists(fs, n)
 	var goModFilename string
@@ -97,9 +96,7 @@
 
 	return &Client{
 		fs:                fs,
-		ignoreVendor:      cfg.IgnoreVendor,
-		workingDir:        cfg.WorkingDir,
-		themesDir:         cfg.ThemesDir,
+		ccfg:              cfg,
 		logger:            logger,
 		moduleConfig:      mcfg,
 		environ:           env,
@@ -111,15 +108,8 @@
 	fs     afero.Fs
 	logger *loggers.Logger
 
-	// Ignore any _vendor directory.
-	ignoreVendor bool
+	ccfg ClientConfig
 
-	// Absolute path to the project dir.
-	workingDir string
-
-	// Absolute path to the project's themes dir.
-	themesDir string
-
 	// The top level module config
 	moduleConfig Config
 
@@ -194,7 +184,7 @@
 // meaning that if the top-level module is vendored, that will be the full
 // set of dependencies.
 func (c *Client) Vendor() error {
-	vendorDir := filepath.Join(c.workingDir, vendord)
+	vendorDir := filepath.Join(c.ccfg.WorkingDir, vendord)
 	if err := c.rmVendorDir(vendorDir); err != nil {
 		return err
 	}
@@ -284,7 +274,7 @@
 		return errors.Wrap(err, "failed to init modules")
 	}
 
-	c.GoModulesFilename = filepath.Join(c.workingDir, goModFilename)
+	c.GoModulesFilename = filepath.Join(c.ccfg.WorkingDir, goModFilename)
 
 	return nil
 }
@@ -335,7 +325,7 @@
 		return err
 	}
 	if data != nil {
-		if err := afero.WriteFile(c.fs, filepath.Join(c.workingDir, name), data, 0666); err != nil {
+		if err := afero.WriteFile(c.fs, filepath.Join(c.ccfg.WorkingDir, name), data, 0666); err != nil {
 			return err
 		}
 	}
@@ -352,7 +342,7 @@
 	modlineSplitter := getModlineSplitter(name == goModFilename)
 
 	b := &bytes.Buffer{}
-	f, err := c.fs.Open(filepath.Join(c.workingDir, name))
+	f, err := c.fs.Open(filepath.Join(c.ccfg.WorkingDir, name))
 	if err != nil {
 		if os.IsNotExist(err) {
 			// It's been deleted.
@@ -424,7 +414,7 @@
 	cmd := exec.CommandContext(ctx, "go", args...)
 
 	cmd.Env = c.environ
-	cmd.Dir = c.workingDir
+	cmd.Dir = c.ccfg.WorkingDir
 	cmd.Stdout = stdout
 	cmd.Stderr = io.MultiWriter(stderr, os.Stderr)
 
@@ -482,11 +472,22 @@
 
 // ClientConfig configures the module Client.
 type ClientConfig struct {
-	Fs           afero.Fs
-	Logger       *loggers.Logger
+	Fs     afero.Fs
+	Logger *loggers.Logger
+
+	// If set, it will be run before we do any duplicate checks for modules
+	// etc.
+	HookBeforeFinalize func(m *ModulesConfig) error
+
+	// Ignore any _vendor directory.
 	IgnoreVendor bool
-	WorkingDir   string
-	ThemesDir    string // Absolute directory path
+
+	// Absolute path to the project dir.
+	WorkingDir string
+
+	// Absolute path to the project's themes dir.
+	ThemesDir string
+
 	CacheDir     string // Module cache
 	ModuleConfig Config
 }
--- a/modules/collect.go
+++ b/modules/collect.go
@@ -20,6 +20,8 @@
 	"path/filepath"
 	"strings"
 
+	"github.com/gohugoio/hugo/common/loggers"
+
 	"github.com/spf13/cast"
 
 	"github.com/gohugoio/hugo/common/maps"
@@ -62,8 +64,25 @@
 
 func (h *Client) Collect() (ModulesConfig, error) {
 	mc, coll := h.collect(true)
-	return mc, coll.err
+	if coll.err != nil {
+		return mc, coll.err
+	}
 
+	if err := (&mc).setActiveMods(h.logger); err != nil {
+		return mc, err
+	}
+
+	if h.ccfg.HookBeforeFinalize != nil {
+		if err := h.ccfg.HookBeforeFinalize(&mc); err != nil {
+			return mc, err
+		}
+	}
+
+	if err := (&mc).finalize(h.logger); err != nil {
+		return mc, err
+	}
+
+	return mc, nil
 }
 
 func (h *Client) collect(tidy bool) (ModulesConfig, *collector) {
@@ -83,20 +102,8 @@
 		}
 	}
 
-	// TODO(bep) consider --ignoreVendor vs removing from go.mod
-	var activeMods Modules
-	for _, mod := range c.modules {
-		if !mod.Config().HugoVersion.IsValid() {
-			h.logger.WARN.Printf(`Module %q is not compatible with this Hugo version; run "hugo mod graph" for more information.`, mod.Path())
-		}
-		if !mod.Disabled() {
-			activeMods = append(activeMods, mod)
-		}
-	}
-
 	return ModulesConfig{
 		AllModules:        c.modules,
-		ActiveModules:     activeMods,
 		GoModulesFilename: c.GoModulesFilename,
 	}, c
 
@@ -113,6 +120,43 @@
 	GoModulesFilename string
 }
 
+func (m *ModulesConfig) setActiveMods(logger *loggers.Logger) error {
+	var activeMods Modules
+	for _, mod := range m.AllModules {
+		if !mod.Config().HugoVersion.IsValid() {
+			logger.WARN.Printf(`Module %q is not compatible with this Hugo version; run "hugo mod graph" for more information.`, mod.Path())
+		}
+		if !mod.Disabled() {
+			activeMods = append(activeMods, mod)
+		}
+	}
+
+	m.ActiveModules = activeMods
+
+	return nil
+}
+
+func (m *ModulesConfig) finalize(logger *loggers.Logger) error {
+	for _, mod := range m.AllModules {
+		m := mod.(*moduleAdapter)
+		m.mounts = filterUnwantedMounts(m.mounts)
+	}
+	return nil
+}
+
+func filterUnwantedMounts(mounts []Mount) []Mount {
+	// Remove duplicates
+	seen := make(map[Mount]bool)
+	tmp := mounts[:0]
+	for _, m := range mounts {
+		if !seen[m] {
+			tmp = append(tmp, m)
+		}
+		seen[m] = true
+	}
+	return tmp
+}
+
 type collected struct {
 	// Pick the first and prevent circular loops.
 	seen map[string]bool
@@ -177,7 +221,7 @@
 	modulePath := moduleImport.Path
 	var realOwner Module = owner
 
-	if !c.ignoreVendor {
+	if !c.ccfg.IgnoreVendor {
 		if err := c.collectModulesTXT(owner); err != nil {
 			return nil, err
 		}
@@ -223,10 +267,10 @@
 
 			// Fall back to /themes/<mymodule>
 			if moduleDir == "" {
-				moduleDir = filepath.Join(c.themesDir, modulePath)
+				moduleDir = filepath.Join(c.ccfg.ThemesDir, modulePath)
 
 				if found, _ := afero.Exists(c.fs, moduleDir); !found {
-					c.err = c.wrapModuleNotFound(errors.Errorf(`module %q not found; either add it as a Hugo Module or store it in %q.`, modulePath, c.themesDir))
+					c.err = c.wrapModuleNotFound(errors.Errorf(`module %q not found; either add it as a Hugo Module or store it in %q.`, modulePath, c.ccfg.ThemesDir))
 					return nil, nil
 				}
 			}
@@ -427,7 +471,7 @@
 		return
 	}
 
-	projectMod := createProjectModule(c.gomods.GetMain(), c.workingDir, c.moduleConfig)
+	projectMod := createProjectModule(c.gomods.GetMain(), c.ccfg.WorkingDir, c.moduleConfig)
 
 	if err := c.addAndRecurse(projectMod, false); err != nil {
 		c.err = err
--- a/modules/collect_test.go
+++ b/modules/collect_test.go
@@ -36,3 +36,19 @@
 	}
 
 }
+
+func TestFilterUnwantedMounts(t *testing.T) {
+
+	mounts := []Mount{
+		Mount{Source: "a", Target: "b", Lang: "en"},
+		Mount{Source: "a", Target: "b", Lang: "en"},
+		Mount{Source: "b", Target: "c", Lang: "en"},
+	}
+
+	filtered := filterUnwantedMounts(mounts)
+
+	assert := require.New(t)
+	assert.Len(filtered, 2)
+	assert.Equal([]Mount{Mount{Source: "a", Target: "b", Lang: "en"}, Mount{Source: "b", Target: "c", Lang: "en"}}, filtered)
+
+}
--- a/modules/config.go
+++ b/modules/config.go
@@ -15,7 +15,6 @@
 
 import (
 	"fmt"
-	"path"
 	"path/filepath"
 	"strings"
 
@@ -174,18 +173,7 @@
 	// Prepend the mounts from configuration.
 	mounts = append(moda.mounts, mounts...)
 
-	// Remove duplicates
-	seen := make(map[string]bool)
-	tmp := mounts[:0]
-	for _, m := range mounts {
-		key := path.Join(m.Lang, m.Source, m.Target)
-		if !seen[key] {
-			tmp = append(tmp, m)
-		}
-		seen[key] = true
-	}
-
-	moda.mounts = tmp
+	moda.mounts = mounts
 
 	return nil
 }