github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/cue/load/loader.go (about) 1 // Copyright 2018 The CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package load 16 17 // Files in package are to a large extent based on Go files from the following 18 // Go packages: 19 // - cmd/go/internal/load 20 // - go/build 21 22 import ( 23 pathpkg "path" 24 "path/filepath" 25 "strings" 26 27 "github.com/joomcode/cue/cue/ast" 28 "github.com/joomcode/cue/cue/build" 29 "github.com/joomcode/cue/cue/errors" 30 "github.com/joomcode/cue/cue/registry" 31 "github.com/joomcode/cue/cue/token" 32 "github.com/joomcode/cue/internal/encoding" 33 "github.com/joomcode/cue/internal/filetypes" 34 35 // Trigger the unconditional loading of all core builtin packages if load 36 // is used. This was deemed the simplest way to avoid having to import 37 // this line explicitly, and thus breaking existing code, for the majority 38 // of cases, while not introducing an import cycle. 39 _ "github.com/joomcode/cue/pkg" 40 ) 41 42 // Instances returns the instances named by the command line arguments 'args'. 43 // If errors occur trying to load an instance it is returned with Incomplete 44 // set. Errors directly related to loading the instance are recorded in this 45 // instance, but errors that occur loading dependencies are recorded in these 46 // dependencies. 47 func Instances(args []string, c *Config) []*build.Instance { 48 if c == nil { 49 c = &Config{} 50 } 51 newC, err := c.complete() 52 if err != nil { 53 return []*build.Instance{c.newErrInstance(token.NoPos, "", err)} 54 } 55 c = newC 56 57 l := c.loader 58 59 // TODO: require packages to be placed before files. At some point this 60 // could be relaxed. 61 i := 0 62 for ; i < len(args) && filetypes.IsPackage(args[i]); i++ { 63 } 64 65 var r *registry.Registry 66 if c.StarlarkCodePath != "" { 67 r, err = registry.NewRegistry(c.StarlarkCodePath) 68 if err != nil { 69 return []*build.Instance{c.newErrInstance(token.NoPos, "", err)} 70 } 71 } 72 73 var a []*build.Instance 74 75 if len(args) == 0 || i > 0 { 76 for _, m := range l.importPaths(args[:i]) { 77 if m.Err != nil { 78 inst := c.newErrInstance(token.NoPos, "", m.Err) 79 a = append(a, inst) 80 continue 81 } 82 a = append(a, m.Pkgs...) 83 } 84 } 85 86 if args = args[i:]; len(args) > 0 { 87 files, err := filetypes.ParseArgs(args) 88 if err != nil { 89 return []*build.Instance{c.newErrInstance(token.NoPos, "", err)} 90 } 91 inst := l.cueFilesPackage(files) 92 inst.Context().StarlarkRegistry = r 93 a = append(a, inst) 94 } 95 96 for _, p := range a { 97 tags, err := findTags(p) 98 if err != nil { 99 p.ReportError(err) 100 } 101 l.tags = append(l.tags, tags...) 102 } 103 104 // TODO(api): have API call that returns an error which is the aggregate 105 // of all build errors. Certain errors, like these, hold across builds. 106 if err := injectTags(c.Tags, l); err != nil { 107 for _, p := range a { 108 p.ReportError(err) 109 } 110 return a 111 } 112 113 if l.replacements == nil { 114 return a 115 } 116 117 for _, p := range a { 118 for _, f := range p.Files { 119 ast.Walk(f, nil, func(n ast.Node) { 120 if ident, ok := n.(*ast.Ident); ok { 121 if v, ok := l.replacements[ident.Node]; ok { 122 ident.Node = v 123 } 124 } 125 }) 126 } 127 } 128 129 return a 130 } 131 132 // Mode flags for loadImport and download (in get.go). 133 const ( 134 // resolveImport means that loadImport should do import path expansion. 135 // That is, resolveImport means that the import path came from 136 // a source file and has not been expanded yet to account for 137 // vendoring or possible module adjustment. 138 // Every import path should be loaded initially with resolveImport, 139 // and then the expanded version (for example with the /vendor/ in it) 140 // gets recorded as the canonical import path. At that point, future loads 141 // of that package must not pass resolveImport, because 142 // disallowVendor will reject direct use of paths containing /vendor/. 143 resolveImport = 1 << iota 144 ) 145 146 type loader struct { 147 cfg *Config 148 stk importStack 149 tags []*tag // tags found in files 150 buildTags map[string]bool 151 replacements map[ast.Node]ast.Node 152 } 153 154 func (l *loader) abs(filename string) string { 155 if !isLocalImport(filename) { 156 return filename 157 } 158 return filepath.Join(l.cfg.Dir, filename) 159 } 160 161 // cueFilesPackage creates a package for building a collection of CUE files 162 // (typically named on the command line). 163 func (l *loader) cueFilesPackage(files []*build.File) *build.Instance { 164 pos := token.NoPos 165 cfg := l.cfg 166 cfg.filesMode = true 167 // ModInit() // TODO: support modules 168 pkg := l.cfg.Context.NewInstance(cfg.Dir, l.loadFunc()) 169 170 _, err := filepath.Abs(cfg.Dir) 171 if err != nil { 172 return cfg.newErrInstance(pos, toImportPath(cfg.Dir), 173 errors.Wrapf(err, pos, "could not convert '%s' to absolute path", cfg.Dir)) 174 } 175 176 for _, bf := range files { 177 f := bf.Filename 178 if f == "-" { 179 continue 180 } 181 if !filepath.IsAbs(f) { 182 f = filepath.Join(cfg.Dir, f) 183 } 184 fi, err := cfg.fileSystem.stat(f) 185 if err != nil { 186 return cfg.newErrInstance(pos, toImportPath(f), 187 errors.Wrapf(err, pos, "could not find file")) 188 } 189 if fi.IsDir() { 190 return cfg.newErrInstance(token.NoPos, toImportPath(f), 191 errors.Newf(pos, "file is a directory %v", f)) 192 } 193 } 194 195 fp := newFileProcessor(cfg, pkg) 196 for _, file := range files { 197 fp.add(pos, cfg.Dir, file, allowAnonymous) 198 } 199 200 // TODO: ModImportFromFiles(files) 201 pkg.Dir = cfg.Dir 202 rewriteFiles(pkg, pkg.Dir, true) 203 for _, err := range errors.Errors(fp.finalize(pkg)) { // ImportDir(&ctxt, dir, 0) 204 var x *NoFilesError 205 if len(pkg.OrphanedFiles) == 0 || !errors.As(err, &x) { 206 pkg.ReportError(err) 207 } 208 } 209 // TODO: Support module importing. 210 // if ModDirImportPath != nil { 211 // // Use the effective import path of the directory 212 // // for deciding visibility during pkg.load. 213 // bp.ImportPath = ModDirImportPath(dir) 214 // } 215 216 l.addFiles(cfg.Dir, pkg) 217 218 pkg.User = true 219 l.stk.Push("user") 220 _ = pkg.Complete() 221 l.stk.Pop() 222 pkg.User = true 223 //pkg.LocalPrefix = dirToImportPath(dir) 224 pkg.DisplayPath = "command-line-arguments" 225 226 return pkg 227 } 228 229 func (l *loader) addFiles(dir string, p *build.Instance) { 230 for _, f := range p.BuildFiles { 231 d := encoding.NewDecoder(f, &encoding.Config{ 232 Stdin: l.cfg.stdin(), 233 ParseFile: l.cfg.ParseFile, 234 }) 235 for ; !d.Done(); d.Next() { 236 _ = p.AddSyntax(d.File()) 237 } 238 if err := d.Err(); err != nil { 239 p.ReportError(errors.Promote(err, "load")) 240 } 241 d.Close() 242 } 243 } 244 245 func cleanImport(path string) string { 246 orig := path 247 path = pathpkg.Clean(path) 248 if strings.HasPrefix(orig, "./") && path != ".." && !strings.HasPrefix(path, "../") { 249 path = "./" + path 250 } 251 return path 252 } 253 254 // An importStack is a stack of import paths, possibly with the suffix " (test)" appended. 255 // The import path of a test package is the import path of the corresponding 256 // non-test package with the suffix "_test" added. 257 type importStack []string 258 259 func (s *importStack) Push(p string) { 260 *s = append(*s, p) 261 } 262 263 func (s *importStack) Pop() { 264 *s = (*s)[0 : len(*s)-1] 265 } 266 267 func (s *importStack) Copy() []string { 268 return append([]string{}, *s...) 269 }