From 94f0f7e59788e802e706a55cac0d52a9e70ff745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 14 Nov 2018 17:18:32 +0100 Subject: [PATCH] cache/filecache: Add a :project placeholder This allows for "cache per Hugo project", making `hugo --gc` work as expected, even if you have several Hugo projects running on the same PC. See #5439 --- cache/filecache/filecache.go | 5 ++- cache/filecache/filecache_config.go | 41 +++++++++++-------- cache/filecache/filecache_config_test.go | 32 +++++++++++++++ .../en/getting-started/configuration.md | 7 +++- hugolib/site_test.go | 11 ----- 5 files changed, 66 insertions(+), 30 deletions(-) diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go index e9f72cb92..9f50ad6b6 100644 --- a/cache/filecache/filecache.go +++ b/cache/filecache/filecache.go @@ -272,7 +272,10 @@ func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser { } func (c *Cache) isExpired(modTime time.Time) bool { - return c.maxAge >= 0 && time.Now().Sub(modTime) > c.maxAge + if c.maxAge < 0 { + return false + } + return c.maxAge == 0 || time.Now().Sub(modTime) > c.maxAge } // For testing diff --git a/cache/filecache/filecache_config.go b/cache/filecache/filecache_config.go index f0dd7295a..bb2cc36e7 100644 --- a/cache/filecache/filecache_config.go +++ b/cache/filecache/filecache_config.go @@ -35,7 +35,7 @@ const ( var defaultCacheConfig = cacheConfig{ MaxAge: -1, // Never expire - Dir: ":cacheDir", + Dir: ":cacheDir/:project", } const ( @@ -139,26 +139,33 @@ func decodeConfig(p *paths.Paths) (cachesConfig, error) { disabled := cfg.GetBool("ignoreCache") for k, v := range c { - v.Dir = filepath.Clean(v.Dir) - dir := filepath.ToSlash(v.Dir) + dir := filepath.ToSlash(filepath.Clean(v.Dir)) + hadSlash := strings.HasPrefix(dir, "/") parts := strings.Split(dir, "/") - first := parts[0] - if strings.HasPrefix(first, ":") { - resolved, err := resolveDirPlaceholder(p, first) - if err != nil { - return c, err + for i, part := range parts { + if strings.HasPrefix(part, ":") { + resolved, err := resolveDirPlaceholder(p, part) + if err != nil { + return c, err + } + parts[i] = resolved } - resolved = filepath.ToSlash(resolved) - - v.Dir = filepath.FromSlash(path.Join((append([]string{resolved}, parts[1:]...))...)) - - } else if isOsFs && !path.IsAbs(dir) { - return c, errors.Errorf("%q must either start with a placeholder (e.g. :cacheDir, :resourceDir) or be absolute", v.Dir) } - if len(v.Dir) < 5 { - return c, errors.Errorf("%q is not a valid cache dir", v.Dir) + dir = path.Join(parts...) + if hadSlash { + dir = "/" + dir + } + v.Dir = filepath.Clean(filepath.FromSlash(dir)) + + if isOsFs && !filepath.IsAbs(v.Dir) { + return c, errors.Errorf("%q must resolve to an absolute directory", v.Dir) + } + + // Avoid cache in root, e.g. / (Unix) or c:\ (Windows) + if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 { + return c, errors.Errorf("%q is a root folder and not allowed as cache dir", v.Dir) } if disabled { @@ -178,6 +185,8 @@ func resolveDirPlaceholder(p *paths.Paths, placeholder string) (string, error) { return p.AbsResourcesDir, nil case ":cachedir": return helpers.GetCacheDir(p.Fs.Source, p.Cfg) + case ":project": + return filepath.Base(p.WorkingDir), nil } return "", errors.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder) diff --git a/cache/filecache/filecache_config_test.go b/cache/filecache/filecache_config_test.go index abba6c25f..51126f080 100644 --- a/cache/filecache/filecache_config_test.go +++ b/cache/filecache/filecache_config_test.go @@ -16,6 +16,7 @@ package filecache import ( "path/filepath" "runtime" + "strings" "testing" "time" @@ -107,6 +108,8 @@ dir = "/path/to/c3" func TestDecodeConfigDefault(t *testing.T) { assert := require.New(t) cfg := viper.New() + cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject")) + if runtime.GOOS == "windows" { cfg.Set("resourceDir", "c:\\cache\\resources") cfg.Set("cacheDir", "c:\\cache\\thecache") @@ -130,5 +133,34 @@ func TestDecodeConfigDefault(t *testing.T) { assert.Equal("c:\\cache\\resources\\_gen", decoded[cacheKeyImages].Dir) } else { assert.Equal("/cache/resources/_gen", decoded[cacheKeyImages].Dir) + assert.Equal("/cache/thecache/hugoproject", decoded[cacheKeyGetJSON].Dir) } } + +func TestDecodeConfigInvalidDir(t *testing.T) { + t.Parallel() + + assert := require.New(t) + + configStr := ` +resourceDir = "myresources" +[caches] +[caches.getJSON] +maxAge = "10m" +dir = "/" + +` + if runtime.GOOS == "windows" { + configStr = strings.Replace(configStr, "/", "c:\\\\", 1) + } + + cfg, err := config.FromConfigString(configStr, "toml") + assert.NoError(err) + fs := hugofs.NewMem(cfg) + p, err := paths.New(fs, cfg) + assert.NoError(err) + + _, err = decodeConfig(p) + assert.Error(err) + +} diff --git a/docs/content/en/getting-started/configuration.md b/docs/content/en/getting-started/configuration.md index cb2eed4cd..f67b9370b 100644 --- a/docs/content/en/getting-started/configuration.md +++ b/docs/content/en/getting-started/configuration.md @@ -413,10 +413,10 @@ Since Hugo 0.52 you can configure more than just the `cacheDir`. This is the def ```toml [caches] [caches.getjson] -dir = ":cacheDir" +dir = ":cacheDir/:project" maxAge = -1 [caches.getcsv] -dir = ":cacheDir" +dir = ":cacheDir/:project" maxAge = -1 [caches.images] dir = ":resourceDir/_gen" @@ -434,6 +434,9 @@ You can override any of these cache setting in your own `config.toml`. :cacheDir : This is the value of the `cacheDir` config option if set (can also be set via OS env variable `HUGO_CACHEDIR`). It will fall back to `/opt/build/cache/hugo_cache/` on Netlify, or a `hugo_cache` directory below the OS temp dir for the others. This means that if you run your builds on Netlify, all caches configured with `:cacheDir` will be saved and restored on the next build. For other CI vendors, please read their documentation. For an CircleCI example, see [this configuration](https://github.com/bep/hugo-sass-test/blob/6c3960a8f4b90e8938228688bc49bdcdd6b2d99e/.circleci/config.yml). +:project + +The base directory name of the current Hugo project. This means that, in its default setting, every project will have separated file caches, which means that when you do `hugo --gc` you will not touch files related to other Hugo projects running on the same PC. :resourceDir : This is the value of the `resourceDir` config option. diff --git a/hugolib/site_test.go b/hugolib/site_test.go index 0fd3a397a..4f8d43122 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -15,7 +15,6 @@ package hugolib import ( "fmt" - "os" "path/filepath" "strings" "testing" @@ -25,7 +24,6 @@ import ( "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/hugofs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -351,15 +349,6 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) { } -func TestNewSiteDefaultLang(t *testing.T) { - t.Parallel() - defer os.Remove("resources") - s, err := NewSiteDefaultLang() - require.NoError(t, err) - require.Equal(t, hugofs.Os, s.Fs.Source) - require.Equal(t, hugofs.Os, s.Fs.Destination) -} - // Issue #3355 func TestShouldNotWriteZeroLengthFilesToDestination(t *testing.T) { cfg, fs := newTestCfg()