Skip to content

Commit afa222f

Browse files
committed
cue/load: move syntax cache into fileSystem
In order for the CUE syntax to be shared between cue/load and internal/mod/modpkgload, it needs to be available in the `io.FS` implementation passed to `modpkgload`, and thus it can't be inside the `loader` because that gets created too late, after `modpkgload` has already been invoked. So we move the syntax cache into `fileSystem` so it will be easily available to the `io.FS` implementation. Also be a little more careful about caching: we make sure that we only cache file syntax when the parsing is absolutely vanilla, which might be slightly less efficient, but is very unlikely to make any significant difference as non-vanilla loading only happens for files explicitly mentioned on the command line, and those are usually not shared with files used elsewhere in the build. Signed-off-by: Roger Peppe <[email protected]> Change-Id: I282557ffde6af410a237f0a13f91c7c87abf1cef Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1197529 Reviewed-by: Paul Jolly <[email protected]> Unity-Result: CUE porcuepine <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent f787af8 commit afa222f

File tree

3 files changed

+61
-112
lines changed

3 files changed

+61
-112
lines changed

cue/load/fs.go

Lines changed: 56 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ import (
2626
"strings"
2727
"time"
2828

29+
"cuelang.org/go/cue"
2930
"cuelang.org/go/cue/ast"
3031
"cuelang.org/go/cue/build"
32+
"cuelang.org/go/cue/cuecontext"
3133
"cuelang.org/go/cue/errors"
32-
"cuelang.org/go/cue/parser"
3334
"cuelang.org/go/cue/token"
34-
"cuelang.org/go/internal/cueimports"
35+
"cuelang.org/go/internal/encoding"
3536
"cuelang.org/go/mod/module"
3637
)
3738

@@ -59,6 +60,7 @@ func (f *overlayFile) Sys() interface{} { return nil }
5960
type fileSystem struct {
6061
overlayDirs map[string]map[string]*overlayFile
6162
cwd string
63+
fileCache *fileCache
6264
}
6365

6466
func (fs *fileSystem) getDir(dir string, create bool) map[string]*overlayFile {
@@ -136,6 +138,7 @@ func newFileSystem(cfg *Config) (*fileSystem, error) {
136138
}
137139
}
138140
}
141+
fs.fileCache = newFileCache(cfg)
139142
return fs, nil
140143
}
141144

@@ -184,58 +187,6 @@ func (fs *fileSystem) getOverlay(path string) *overlayFile {
184187
return nil
185188
}
186189

187-
func (fs *fileSystem) getCUESyntax(fi *build.File) (*ast.File, error) {
188-
switch src := fi.Source.(type) {
189-
case []byte, string:
190-
return parser.ParseFile(fi.Filename, src, parser.ImportsOnly)
191-
case *ast.File:
192-
return src, nil
193-
case nil:
194-
if fi.Filename == "-" {
195-
panic("source unexpectedly not provided for stdin")
196-
}
197-
return fs.readCUE(fi.Filename, true)
198-
}
199-
return nil, errors.Newf(token.NoPos, "unsupported source type %T", fi.Source)
200-
}
201-
202-
func (fs *fileSystem) readCUE(path string, importsOnly bool) (*ast.File, error) {
203-
var parseMode parser.Option
204-
if importsOnly {
205-
parseMode = parser.ImportsOnly
206-
}
207-
var data []byte
208-
if f := fs.getOverlay(path); f != nil {
209-
if f.file != nil {
210-
return f.file, nil
211-
}
212-
if f.isDir {
213-
return nil, fmt.Errorf("%q is a directory", path)
214-
}
215-
data = f.contents
216-
} else {
217-
var err error
218-
f, err := fs.openFile(path)
219-
if err != nil {
220-
return nil, err
221-
}
222-
defer f.Close()
223-
if importsOnly {
224-
data, err = cueimports.Read(f)
225-
} else {
226-
data, err = io.ReadAll(f)
227-
}
228-
if err != nil {
229-
return nil, fmt.Errorf("read %s: %v", path, err)
230-
}
231-
}
232-
pf, err := parser.ParseFile(path, data, parseMode)
233-
if err != nil {
234-
return nil, err
235-
}
236-
return pf, nil
237-
}
238-
239190
func (fs *fileSystem) stat(path string) (iofs.FileInfo, errors.Error) {
240191
path = fs.makeAbs(path)
241192
if fi := fs.getOverlay(path); fi != nil {
@@ -441,3 +392,54 @@ func (f *ioFSFile) ReadDir(n int) ([]iofs.DirEntry, error) {
441392
f.entries = f.entries[n:]
442393
return entries, err
443394
}
395+
396+
func (fs *fileSystem) getCUESyntax(bf *build.File) (*ast.File, error) {
397+
if bf.Encoding != build.CUE {
398+
panic("getCUESyntax called with non-CUE file encoding")
399+
}
400+
// When it's a regular CUE file with no funny stuff going on, we
401+
// check and update the syntax cache.
402+
useCache := bf.Form == build.Schema && bf.Interpretation == ""
403+
if useCache {
404+
if syntax, ok := fs.fileCache.entries[bf.Filename]; ok {
405+
return syntax.file, syntax.err
406+
}
407+
}
408+
d := encoding.NewDecoder(fs.fileCache.ctx, bf, &fs.fileCache.config)
409+
defer d.Close()
410+
// Note: CUE files can never have multiple file parts.
411+
f, err := d.File(), d.Err()
412+
if useCache {
413+
fs.fileCache.entries[bf.Filename] = fileCacheEntry{f, err}
414+
}
415+
return f, err
416+
}
417+
418+
func newFileCache(c *Config) *fileCache {
419+
return &fileCache{
420+
config: encoding.Config{
421+
// Note: no need to pass Stdin, as we take care
422+
// always to pass a non-nil source when the file is "-".
423+
ParseFile: c.ParseFile,
424+
},
425+
ctx: cuecontext.New(),
426+
entries: make(map[string]fileCacheEntry),
427+
}
428+
}
429+
430+
// fileCache caches data derived from the file system.
431+
type fileCache struct {
432+
config encoding.Config
433+
ctx *cue.Context
434+
entries map[string]fileCacheEntry
435+
}
436+
437+
type fileCacheEntry struct {
438+
// TODO cache directory information too.
439+
440+
// file caches the work involved when decoding a file into an *ast.File.
441+
// This can happen multiple times for the same file, for example when it is present in
442+
// multiple different build instances in the same directory hierarchy.
443+
file *ast.File
444+
err error
445+
}

cue/load/instances.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ func Instances(args []string, c *Config) []*build.Instance {
9696
pkgArgs = pkgArgs1
9797
}
9898

99-
synCache := newSyntaxCache(c)
10099
tg := newTagger(c)
101100
// Pass all arguments that look like packages to loadPackages
102101
// so that they'll be available when looking up the packages
@@ -105,11 +104,11 @@ func Instances(args []string, c *Config) []*build.Instance {
105104
if err != nil {
106105
return []*build.Instance{c.newErrInstance(err)}
107106
}
108-
pkgs, err := loadPackages(ctx, c, synCache, expandedPaths, otherFiles, tg)
107+
pkgs, err := loadPackages(ctx, c, expandedPaths, otherFiles, tg)
109108
if err != nil {
110109
return []*build.Instance{c.newErrInstance(err)}
111110
}
112-
l := newLoader(c, tg, synCache, pkgs)
111+
l := newLoader(c, tg, pkgs)
113112

114113
if c.Context == nil {
115114
c.Context = build.NewContext(
@@ -175,7 +174,6 @@ func Instances(args []string, c *Config) []*build.Instance {
175174
func loadPackages(
176175
ctx context.Context,
177176
cfg *Config,
178-
synCache *syntaxCache,
179177
pkgs []resolvedPackageArg,
180178
otherFiles []*build.File,
181179
tg *tagger,
@@ -205,7 +203,7 @@ func loadPackages(
205203
// not a CUE file; assume it has no imports for now.
206204
continue
207205
}
208-
syntax, err := synCache.getSyntax(f)
206+
syntax, err := cfg.fileSystem.getCUESyntax(f)
209207
if err != nil {
210208
return nil, fmt.Errorf("cannot get syntax for %q: %v", f.Filename, err)
211209
}

cue/load/loader.go

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,9 @@ package load
2222
import (
2323
"path/filepath"
2424

25-
"cuelang.org/go/cue"
26-
"cuelang.org/go/cue/ast"
2725
"cuelang.org/go/cue/build"
28-
"cuelang.org/go/cue/cuecontext"
2926
"cuelang.org/go/cue/errors"
3027
"cuelang.org/go/cue/token"
31-
"cuelang.org/go/internal/encoding"
3228
"cuelang.org/go/internal/mod/modpkgload"
3329

3430
// Trigger the unconditional loading of all core builtin packages if load
@@ -44,11 +40,6 @@ type loader struct {
4440
stk importStack
4541
pkgs *modpkgload.Packages
4642

47-
// syntaxCache caches the work involved when decoding a file into an *ast.File.
48-
// This can happen multiple times for the same file, for example when it is present in
49-
// multiple different build instances in the same directory hierarchy.
50-
syntaxCache *syntaxCache
51-
5243
// dirCachedBuildFiles caches the work involved when reading a
5344
// directory. It is keyed by directory name. When we descend into
5445
// subdirectories to load patterns such as ./... we often end up
@@ -62,13 +53,12 @@ type cachedDirFiles struct {
6253
filenames []string
6354
}
6455

65-
func newLoader(c *Config, tg *tagger, syntaxCache *syntaxCache, pkgs *modpkgload.Packages) *loader {
56+
func newLoader(c *Config, tg *tagger, pkgs *modpkgload.Packages) *loader {
6657
return &loader{
6758
cfg: c,
6859
tagger: tg,
6960
pkgs: pkgs,
7061
dirCachedBuildFiles: make(map[string]cachedDirFiles),
71-
syntaxCache: syntaxCache,
7262
}
7363
}
7464

@@ -148,51 +138,10 @@ func (l *loader) cueFilesPackage(files []*build.File) *build.Instance {
148138
// addFiles populates p.Files by reading CUE syntax from p.BuildFiles.
149139
func (l *loader) addFiles(p *build.Instance) {
150140
for _, bf := range p.BuildFiles {
151-
f, err := l.syntaxCache.getSyntax(bf)
141+
f, err := l.cfg.fileSystem.getCUESyntax(bf)
152142
if err != nil {
153143
p.ReportError(errors.Promote(err, "load"))
154144
}
155145
_ = p.AddSyntax(f)
156146
}
157147
}
158-
159-
type syntaxCache struct {
160-
config encoding.Config
161-
ctx *cue.Context
162-
cache map[string]syntaxCacheEntry
163-
}
164-
165-
type syntaxCacheEntry struct {
166-
err error
167-
file *ast.File
168-
}
169-
170-
func newSyntaxCache(cfg *Config) *syntaxCache {
171-
return &syntaxCache{
172-
config: encoding.Config{
173-
// Note: no need to pass Stdin, as we take care
174-
// always to pass a non-nil source when the file is "-".
175-
ParseFile: cfg.ParseFile,
176-
},
177-
ctx: cuecontext.New(),
178-
cache: make(map[string]syntaxCacheEntry),
179-
}
180-
}
181-
182-
// getSyntax returns the CUE syntax corresponding to the file argument f.
183-
func (c *syntaxCache) getSyntax(bf *build.File) (*ast.File, error) {
184-
syntax, ok := c.cache[bf.Filename]
185-
if ok {
186-
return syntax.file, syntax.err
187-
}
188-
if bf.Encoding != build.CUE {
189-
panic("syntax for non-CUE file")
190-
}
191-
d := encoding.NewDecoder(c.ctx, bf, &c.config)
192-
defer d.Close()
193-
// Note: CUE files can never have multiple file parts.
194-
syntax.file = d.File()
195-
syntax.err = d.Err()
196-
c.cache[bf.Filename] = syntax
197-
return syntax.file, syntax.err
198-
}

0 commit comments

Comments
 (0)