diff --git a/commands/commands.go b/commands/commands.go index 09e0c84553d..ddacc7cf3ba 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -273,6 +273,7 @@ func (cc *hugoBuilderCommon) handleCommonBuilderFlags(cmd *cobra.Command) { cmd.PersistentFlags().StringVarP(&cc.environment, "environment", "e", "", "build environment") cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory") cmd.PersistentFlags().BoolP("ignoreVendor", "", false, "ignores any _vendor directory") + cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern") } func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) { diff --git a/commands/commands_test.go b/commands/commands_test.go index 3b1944891bb..22e9968535e 100644 --- a/commands/commands_test.go +++ b/commands/commands_test.go @@ -151,7 +151,7 @@ func readFileFrom(c *qt.C, filename string) string { return string(b) } -func TestCommandsPersistentFlags(t *testing.T) { +func TestFlags(t *testing.T) { c := qt.New(t) noOpRunE := func(cmd *cobra.Command, args []string) error { @@ -159,90 +159,103 @@ func TestCommandsPersistentFlags(t *testing.T) { } tests := []struct { + name string args []string - check func(command []cmder) - }{{[]string{"server", - "--config=myconfig.toml", - "--configDir=myconfigdir", - "--contentDir=mycontent", - "--disableKinds=page,home", - "--environment=testing", - "--configDir=myconfigdir", - "--layoutDir=mylayouts", - "--theme=mytheme", - "--gc", - "--themesDir=mythemes", - "--cleanDestinationDir", - "--navigateToChanged", - "--disableLiveReload", - "--noHTTPCache", - "--i18n-warnings", - "--destination=/tmp/mydestination", - "-b=https://example.com/b/", - "--port=1366", - "--renderToDisk", - "--source=mysource", - "--path-warnings", - }, func(commands []cmder) { - var sc *serverCmd - for _, command := range commands { - if b, ok := command.(commandsBuilderGetter); ok { - v := b.getCommandsBuilder().hugoBuilderCommon - c.Assert(v.cfgFile, qt.Equals, "myconfig.toml") - c.Assert(v.cfgDir, qt.Equals, "myconfigdir") - c.Assert(v.source, qt.Equals, "mysource") - c.Assert(v.baseURL, qt.Equals, "https://example.com/b/") - } - - if srvCmd, ok := command.(*serverCmd); ok { - sc = srvCmd - } - } - - c.Assert(sc, qt.Not(qt.IsNil)) - c.Assert(sc.navigateToChanged, qt.Equals, true) - c.Assert(sc.disableLiveReload, qt.Equals, true) - c.Assert(sc.noHTTPCache, qt.Equals, true) - c.Assert(sc.renderToDisk, qt.Equals, true) - c.Assert(sc.serverPort, qt.Equals, 1366) - c.Assert(sc.environment, qt.Equals, "testing") - - cfg := viper.New() - sc.flagsToConfig(cfg) - c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination") - c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent") - c.Assert(cfg.GetString("layoutDir"), qt.Equals, "mylayouts") - c.Assert(cfg.GetStringSlice("theme"), qt.DeepEquals, []string{"mytheme"}) - c.Assert(cfg.GetString("themesDir"), qt.Equals, "mythemes") - c.Assert(cfg.GetString("baseURL"), qt.Equals, "https://example.com/b/") - - c.Assert(cfg.Get("disableKinds"), qt.DeepEquals, []string{"page", "home"}) - - c.Assert(cfg.GetBool("gc"), qt.Equals, true) - - // The flag is named path-warnings - c.Assert(cfg.GetBool("logPathWarnings"), qt.Equals, true) - - // The flag is named i18n-warnings - c.Assert(cfg.GetBool("logI18nWarnings"), qt.Equals, true) - - }}} + check func(c *qt.C, cmd *serverCmd) + }{ + { + // https://github.com/gohugoio/hugo/issues/7642 + name: "ignoreVendor as bool", + args: []string{"server", "--ignoreVendor"}, + check: func(c *qt.C, cmd *serverCmd) { + cfg := viper.New() + cmd.flagsToConfig(cfg) + c.Assert(cfg.Get("ignoreVendor"), qt.Equals, true) + }, + }, + { + // https://github.com/gohugoio/hugo/issues/7642 + name: "ignoreVendorPaths", + args: []string{"server", "--ignoreVendorPaths=github.com/**"}, + check: func(c *qt.C, cmd *serverCmd) { + cfg := viper.New() + cmd.flagsToConfig(cfg) + c.Assert(cfg.Get("ignoreVendorPaths"), qt.Equals, "github.com/**") + }, + }, + { + name: "Persistent flags", + args: []string{"server", + "--config=myconfig.toml", + "--configDir=myconfigdir", + "--contentDir=mycontent", + "--disableKinds=page,home", + "--environment=testing", + "--configDir=myconfigdir", + "--layoutDir=mylayouts", + "--theme=mytheme", + "--gc", + "--themesDir=mythemes", + "--cleanDestinationDir", + "--navigateToChanged", + "--disableLiveReload", + "--noHTTPCache", + "--i18n-warnings", + "--destination=/tmp/mydestination", + "-b=https://example.com/b/", + "--port=1366", + "--renderToDisk", + "--source=mysource", + "--path-warnings", + }, + check: func(c *qt.C, sc *serverCmd) { + c.Assert(sc, qt.Not(qt.IsNil)) + c.Assert(sc.navigateToChanged, qt.Equals, true) + c.Assert(sc.disableLiveReload, qt.Equals, true) + c.Assert(sc.noHTTPCache, qt.Equals, true) + c.Assert(sc.renderToDisk, qt.Equals, true) + c.Assert(sc.serverPort, qt.Equals, 1366) + c.Assert(sc.environment, qt.Equals, "testing") + + cfg := viper.New() + sc.flagsToConfig(cfg) + c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination") + c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent") + c.Assert(cfg.GetString("layoutDir"), qt.Equals, "mylayouts") + c.Assert(cfg.GetStringSlice("theme"), qt.DeepEquals, []string{"mytheme"}) + c.Assert(cfg.GetString("themesDir"), qt.Equals, "mythemes") + c.Assert(cfg.GetString("baseURL"), qt.Equals, "https://example.com/b/") + + c.Assert(cfg.Get("disableKinds"), qt.DeepEquals, []string{"page", "home"}) + + c.Assert(cfg.GetBool("gc"), qt.Equals, true) + + // The flag is named path-warnings + c.Assert(cfg.GetBool("logPathWarnings"), qt.Equals, true) + + // The flag is named i18n-warnings + c.Assert(cfg.GetBool("logI18nWarnings"), qt.Equals, true) + + }}} for _, test := range tests { - b := newCommandsBuilder() - root := b.addAll().build() + c.Run(test.name, func(c *qt.C) { - for _, c := range b.commands { - if c.getCommand() == nil { - continue + b := newCommandsBuilder() + root := b.addAll().build() + + for _, cmd := range b.commands { + if cmd.getCommand() == nil { + continue + } + // We are only intereseted in the flag handling here. + cmd.getCommand().RunE = noOpRunE } - // We are only intereseted in the flag handling here. - c.getCommand().RunE = noOpRunE - } - rootCmd := root.getCommand() - rootCmd.SetArgs(test.args) - c.Assert(rootCmd.Execute(), qt.IsNil) - test.check(b.commands) + rootCmd := root.getCommand() + rootCmd.SetArgs(test.args) + c.Assert(rootCmd.Execute(), qt.IsNil) + test.check(c, b.commands[0].(*serverCmd)) + }) } } diff --git a/commands/hugo.go b/commands/hugo.go index de4e3fbb253..7eaaedbc9a6 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -200,6 +200,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) { "noTimes", "noChmod", "ignoreVendor", + "ignoreVendorPaths", "templateMetrics", "templateMetricsHints", diff --git a/docs/content/en/hugo-modules/use-modules.md b/docs/content/en/hugo-modules/use-modules.md index 5f16d5675df..b6fd3a0aabf 100644 --- a/docs/content/en/hugo-modules/use-modules.md +++ b/docs/content/en/hugo-modules/use-modules.md @@ -120,7 +120,7 @@ Note that: * You can run `hugo mod vendor` on any level in the module tree. * Vendoring will not store modules stored in your `themes` folder. -* Most commands accept a `--ignoreVendor` flag, which will then run as if the none of the `_vendor` folders in the module tree existed. +* Most commands accept a `--ignoreVendorPaths` flag, which will then not use the vendored modules in `_vendor` for the module paths matching the [Glob](https://github.com/gobwas/glob) pattern given. Note that before Hugo 0.75 this flag was named `--ignoreVendor` and was a "all or nothing". {{< new-in "0.75.0" >}} Also see the [CLI Doc](/commands/hugo_mod_vendor/). diff --git a/hugolib/config.go b/hugolib/config.go index 841bd5193a3..cab2013ca35 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -18,6 +18,9 @@ import ( "path/filepath" "strings" + "github.com/gobwas/glob" + hglob "github.com/gohugoio/hugo/hugofs/glob" + "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/cache/filecache" @@ -202,6 +205,12 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid } } + // We made this a Glob pattern in Hugo 0.75, we don't need both. + if v.GetBool("ignoreVendor") { + helpers.Deprecated("--ignoreVendor", "--ignoreVendorPaths **", false) + v.Set("ignoreVendorPaths", "**") + } + modulesConfig, err := l.loadModulesConfig(v) if err != nil { return v, configFiles, err @@ -417,7 +426,10 @@ func (l configLoader) collectModules(modConfig modules.Config, v1 *viper.Viper, themesDir := paths.AbsPathify(l.WorkingDir, v1.GetString("themesDir")) - ignoreVendor := v1.GetBool("ignoreVendor") + var ignoreVendor glob.Glob + if s := v1.GetString("ignoreVendorPaths"); s != "" { + ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s)) + } filecacheConfigs, err := filecache.DecodeConfig(l.Fs, v1) if err != nil { diff --git a/hugolib/hugo_modules_test.go b/hugolib/hugo_modules_test.go index b69503021aa..03768486203 100644 --- a/hugolib/hugo_modules_test.go +++ b/hugolib/hugo_modules_test.go @@ -126,11 +126,15 @@ baseURL = "https://example.com" title = "My Modular Site" workingDir = %q theme = %q -ignoreVendor = %t +ignoreVendorPaths = %q ` - config := fmt.Sprintf(configTemplate, workingDir, m.Path(), ignoreVendor) + ignoreVendorPaths := "" + if ignoreVendor { + ignoreVendorPaths = "github.com/**" + } + config := fmt.Sprintf(configTemplate, workingDir, m.Path(), ignoreVendorPaths) b := newTestSitesBuilder(t) diff --git a/modules/client.go b/modules/client.go index c66311d0577..914d06a4e65 100644 --- a/modules/client.go +++ b/modules/client.go @@ -605,8 +605,9 @@ type ClientConfig struct { // etc. HookBeforeFinalize func(m *ModulesConfig) error - // Ignore any _vendor directory. - IgnoreVendor bool + // Ignore any _vendor directory for module paths matching the given pattern. + // This can be nil. + IgnoreVendor glob.Glob // Absolute path to the project dir. WorkingDir string @@ -618,6 +619,10 @@ type ClientConfig struct { ModuleConfig Config } +func (c ClientConfig) shouldIgnoreVendor(path string) bool { + return c.IgnoreVendor != nil && c.IgnoreVendor.Match(path) +} + type goBinaryStatus int type goModule struct { diff --git a/modules/client_test.go b/modules/client_test.go index 07b71c4091d..d5da621d1d2 100644 --- a/modules/client_test.go +++ b/modules/client_test.go @@ -17,6 +17,8 @@ import ( "bytes" "testing" + "github.com/gohugoio/hugo/hugofs/glob" + "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/htesting" @@ -89,7 +91,7 @@ project github.com/gohugoio/hugoTestModules1_darwin/modh2_2_2@v1.3.0+vendor Fs: hugofs.Os, WorkingDir: workingDir, ModuleConfig: modConfig, - IgnoreVendor: true, + IgnoreVendor: globAll, }) graphb.Reset() @@ -101,6 +103,8 @@ project github.com/gohugoio/hugoTestModules1_darwin/modh2_2_2@v1.3.0+vendor } +var globAll, _ = glob.GetGlob("**") + func TestGetModlineSplitter(t *testing.T) { c := qt.New(t) diff --git a/modules/collect.go b/modules/collect.go index 0ac766fb944..f87ed248429 100644 --- a/modules/collect.go +++ b/modules/collect.go @@ -196,7 +196,8 @@ func (c *collector) initModules() error { gomods: goModules{}, } - if !c.ccfg.IgnoreVendor && c.isVendored(c.ccfg.WorkingDir) { + // If both these are true, we don't even need Go installed to build. + if c.ccfg.IgnoreVendor == nil && c.isVendored(c.ccfg.WorkingDir) { return nil } @@ -229,7 +230,7 @@ func (c *collector) add(owner *moduleAdapter, moduleImport Import, disabled bool modulePath := moduleImport.Path var realOwner Module = owner - if !c.ccfg.IgnoreVendor { + if !c.ccfg.shouldIgnoreVendor(modulePath) { if err := c.collectModulesTXT(owner); err != nil { return nil, err }