cuelang.org/go@v0.10.1/cue/load/instances.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 import ( 18 "context" 19 "fmt" 20 "io/fs" 21 "sort" 22 "strconv" 23 "strings" 24 25 "cuelang.org/go/cue/ast" 26 "cuelang.org/go/cue/build" 27 "cuelang.org/go/internal/cueexperiment" 28 "cuelang.org/go/internal/filetypes" 29 "cuelang.org/go/internal/mod/modimports" 30 "cuelang.org/go/internal/mod/modpkgload" 31 "cuelang.org/go/internal/mod/modrequirements" 32 "cuelang.org/go/mod/module" 33 34 // Trigger the unconditional loading of all core builtin packages if load 35 // is used. This was deemed the simplest way to avoid having to import 36 // this line explicitly, and thus breaking existing code, for the majority 37 // of cases, while not introducing an import cycle. 38 _ "cuelang.org/go/pkg" 39 ) 40 41 // Instances returns the instances named by the command line arguments 'args'. 42 // If errors occur trying to load an instance it is returned with Incomplete 43 // set. Errors directly related to loading the instance are recorded in this 44 // instance, but errors that occur loading dependencies are recorded in these 45 // dependencies. 46 func Instances(args []string, c *Config) []*build.Instance { 47 ctx := context.TODO() 48 if c == nil { 49 c = &Config{} 50 } 51 // We want to consult the CUE_EXPERIMENT flag to see whether 52 // consult external registries by default. 53 if err := cueexperiment.Init(); err != nil { 54 return []*build.Instance{c.newErrInstance(err)} 55 } 56 newC, err := c.complete() 57 if err != nil { 58 return []*build.Instance{c.newErrInstance(err)} 59 } 60 c = newC 61 if len(args) == 0 { 62 args = []string{"."} 63 } 64 // TODO: This requires packages to be placed before files. At some point this 65 // could be relaxed. 66 i := 0 67 for ; i < len(args) && filetypes.IsPackage(args[i]); i++ { 68 } 69 pkgArgs := args[:i] 70 otherArgs := args[i:] 71 otherFiles, err := filetypes.ParseArgs(otherArgs) 72 if err != nil { 73 return []*build.Instance{c.newErrInstance(err)} 74 } 75 for _, f := range otherFiles { 76 if err := setFileSource(c, f); err != nil { 77 return []*build.Instance{c.newErrInstance(err)} 78 } 79 } 80 if c.Package != "" && c.Package != "_" && c.Package != "*" { 81 // The caller has specified an explicit package to load. 82 // This is essentially the same as passing an explicit package 83 // qualifier to all package arguments that don't already have 84 // one. We add that qualifier here so that there's a distinction 85 // between package paths specified as arguments, which 86 // have the qualifier added, and package paths that are dependencies 87 // of those, which don't. 88 pkgArgs1 := make([]string, 0, len(pkgArgs)) 89 for _, p := range pkgArgs { 90 if ip := module.ParseImportPath(p); !ip.ExplicitQualifier { 91 ip.Qualifier = c.Package 92 p = ip.String() 93 } 94 pkgArgs1 = append(pkgArgs1, p) 95 } 96 pkgArgs = pkgArgs1 97 } 98 99 tg := newTagger(c) 100 // Pass all arguments that look like packages to loadPackages 101 // so that they'll be available when looking up the packages 102 // that are specified on the command line. 103 expandedPaths, err := expandPackageArgs(c, pkgArgs, c.Package, tg) 104 if err != nil { 105 return []*build.Instance{c.newErrInstance(err)} 106 } 107 108 var pkgs *modpkgload.Packages 109 if !c.SkipImports { 110 pkgs, err = loadPackages(ctx, c, expandedPaths, otherFiles, tg) 111 if err != nil { 112 return []*build.Instance{c.newErrInstance(err)} 113 } 114 } 115 l := newLoader(c, tg, pkgs) 116 117 if c.Context == nil { 118 opts := []build.Option{ 119 build.ParseFile(c.ParseFile), 120 } 121 if f := l.loadFunc(); l != nil { 122 opts = append(opts, build.Loader(f)) 123 } 124 c.Context = build.NewContext(opts...) 125 } 126 127 a := []*build.Instance{} 128 if len(pkgArgs) > 0 { 129 for _, m := range l.importPaths(pkgArgs) { 130 if m.Err != nil { 131 inst := c.newErrInstance(m.Err) 132 a = append(a, inst) 133 continue 134 } 135 a = append(a, m.Pkgs...) 136 } 137 } 138 139 if len(otherFiles) > 0 { 140 a = append(a, l.cueFilesPackage(otherFiles)) 141 } 142 143 for _, p := range a { 144 tags, err := findTags(p) 145 if err != nil { 146 p.ReportError(err) 147 } 148 tg.tags = append(tg.tags, tags...) 149 } 150 151 // TODO(api): have API call that returns an error which is the aggregate 152 // of all build errors. Certain errors, like these, hold across builds. 153 if err := tg.injectTags(c.Tags); err != nil { 154 for _, p := range a { 155 p.ReportError(err) 156 } 157 return a 158 } 159 160 if tg.replacements == nil { 161 return a 162 } 163 164 for _, p := range a { 165 for _, f := range p.Files { 166 ast.Walk(f, nil, func(n ast.Node) { 167 if ident, ok := n.(*ast.Ident); ok { 168 if v, ok := tg.replacements[ident.Node]; ok { 169 ident.Node = v 170 } 171 } 172 }) 173 } 174 } 175 176 return a 177 } 178 179 // loadPackages returns packages loaded from the given package list and also 180 // including imports from the given build files. 181 func loadPackages( 182 ctx context.Context, 183 cfg *Config, 184 pkgs []resolvedPackageArg, 185 otherFiles []*build.File, 186 tg *tagger, 187 ) (*modpkgload.Packages, error) { 188 if cfg.Registry == nil || cfg.modFile == nil || cfg.modFile.Module == "" { 189 return nil, nil 190 } 191 mainModPath := cfg.modFile.QualifiedModule() 192 reqs := modrequirements.NewRequirements( 193 mainModPath, 194 cfg.Registry, 195 cfg.modFile.DepVersions(), 196 cfg.modFile.DefaultMajorVersions(), 197 ) 198 mainModLoc := module.SourceLoc{ 199 FS: cfg.fileSystem.ioFS(cfg.ModuleRoot), 200 Dir: ".", 201 } 202 pkgPaths := make(map[string]bool) 203 // Add any packages specified directly on the command line. 204 for _, pkg := range pkgs { 205 pkgPaths[pkg.resolvedCanonical] = true 206 } 207 // Add any imports found in other files. 208 for _, f := range otherFiles { 209 if f.Encoding != build.CUE { 210 // not a CUE file; assume it has no imports for now. 211 continue 212 } 213 syntax, err := cfg.fileSystem.getCUESyntax(f) 214 if err != nil { 215 return nil, fmt.Errorf("cannot get syntax for %q: %w", f.Filename, err) 216 } 217 for _, imp := range syntax.Imports { 218 pkgPath, err := strconv.Unquote(imp.Path.Value) 219 if err != nil { 220 // Should never happen. 221 return nil, fmt.Errorf("invalid import path %q in %s", imp.Path.Value, f.Filename) 222 } 223 // Canonicalize the path. 224 pkgPath = module.ParseImportPath(pkgPath).Canonical().String() 225 pkgPaths[pkgPath] = true 226 } 227 } 228 // TODO use maps.Keys when we can. 229 pkgPathSlice := make([]string, 0, len(pkgPaths)) 230 for p := range pkgPaths { 231 pkgPathSlice = append(pkgPathSlice, p) 232 } 233 sort.Strings(pkgPathSlice) 234 return modpkgload.LoadPackages( 235 ctx, 236 cfg.Module, 237 mainModLoc, 238 reqs, 239 cfg.Registry, 240 pkgPathSlice, 241 func(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) bool { 242 if !cfg.Tools && strings.HasSuffix(mf.FilePath, "_tool.cue") { 243 return false 244 } 245 isTest := strings.HasSuffix(mf.FilePath, "_test.cue") 246 var tagIsSet func(string) bool 247 if mod.Path() == mainModPath { 248 // In the main module. 249 if isTest && !cfg.Tests { 250 return false 251 } 252 tagIsSet = tg.tagIsSet 253 } else { 254 // Outside the main module. 255 if isTest { 256 // Don't traverse test files outside the main module 257 return false 258 } 259 // Treat all build tag keys as unset. 260 tagIsSet = func(string) bool { 261 return false 262 } 263 } 264 if err := shouldBuildFile(mf.Syntax, tagIsSet); err != nil { 265 // Later build logic should pick up and report the same error. 266 return false 267 } 268 return true 269 }, 270 ), nil 271 }