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 // TODO: remove this usage 19 20 "io/fs" 21 "path" 22 "path/filepath" 23 "strings" 24 25 "cuelang.org/go/cue/build" 26 "cuelang.org/go/cue/errors" 27 "cuelang.org/go/cue/token" 28 ) 29 30 // TODO: should be matched from module file only. 31 // The pattern is either "all" (all packages), "std" (standard packages), 32 // "cmd" (standard commands), or a path including "...". 33 func (l *loader) matchPackages(pattern, pkgName string) *match { 34 // cfg := l.cfg 35 m := &match{ 36 Pattern: pattern, 37 Literal: false, 38 } 39 // match := func(string) bool { return true } 40 // treeCanMatch := func(string) bool { return true } 41 // if !isMetaPackage(pattern) { 42 // match = matchPattern(pattern) 43 // treeCanMatch = treeCanMatchPattern(pattern) 44 // } 45 46 // have := map[string]bool{ 47 // "builtin": true, // ignore pseudo-package that exists only for documentation 48 // } 49 50 // for _, src := range cfg.srcDirs() { 51 // if pattern == "std" || pattern == "cmd" { 52 // continue 53 // } 54 // src = filepath.Clean(src) + string(filepath.Separator) 55 // root := src 56 // if pattern == "cmd" { 57 // root += "cmd" + string(filepath.Separator) 58 // } 59 // filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 60 // if err != nil || path == src { 61 // return nil 62 // } 63 64 // want := true 65 // // Avoid .foo, _foo, and testdata directory trees. 66 // _, elem := filepath.Split(path) 67 // if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 68 // want = false 69 // } 70 71 // name := filepath.ToSlash(path[len(src):]) 72 // if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { 73 // // The name "std" is only the standard library. 74 // // If the name is cmd, it's the root of the command tree. 75 // want = false 76 // } 77 // if !treeCanMatch(name) { 78 // want = false 79 // } 80 81 // if !fi.IsDir() { 82 // if fi.Mode()&os.ModeSymlink != 0 && want { 83 // if target, err := os.Stat(path); err == nil && target.IsDir() { 84 // fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) 85 // } 86 // } 87 // return nil 88 // } 89 // if !want { 90 // return skipDir 91 // } 92 93 // if have[name] { 94 // return nil 95 // } 96 // have[name] = true 97 // if !match(name) { 98 // return nil 99 // } 100 // pkg := l.importPkg(".", path) 101 // if err := pkg.Error; err != nil { 102 // if _, noGo := err.(*noCUEError); noGo { 103 // return nil 104 // } 105 // } 106 107 // // If we are expanding "cmd", skip main 108 // // packages under cmd/vendor. At least as of 109 // // March, 2017, there is one there for the 110 // // vendored pprof tool. 111 // if pattern == "cmd" && strings.HasPrefix(pkg.DisplayPath, "cmd/vendor") && pkg.PkgName == "main" { 112 // return nil 113 // } 114 115 // m.Pkgs = append(m.Pkgs, pkg) 116 // return nil 117 // }) 118 // } 119 return m 120 } 121 122 // matchPackagesInFS is like allPackages but is passed a pattern 123 // beginning ./ or ../, meaning it should scan the tree rooted 124 // at the given directory. There are ... in the pattern too. 125 // (See go help packages for pattern syntax.) 126 func (l *loader) matchPackagesInFS(pattern, pkgName string) *match { 127 c := l.cfg 128 m := &match{ 129 Pattern: pattern, 130 Literal: false, 131 } 132 133 // Find directory to begin the scan. 134 // Could be smarter but this one optimization 135 // is enough for now, since ... is usually at the 136 // end of a path. 137 i := strings.Index(pattern, "...") 138 dir, _ := path.Split(pattern[:i]) 139 140 root := l.abs(dir) 141 142 // Find new module root from here or check there are no additional 143 // cue.mod files between here and the next module. 144 145 if !hasFilepathPrefix(root, c.ModuleRoot) { 146 m.Err = errors.Newf(token.NoPos, 147 "cue: pattern %s refers to dir %s, outside module root %s", 148 pattern, root, c.ModuleRoot) 149 return m 150 } 151 152 pkgDir := filepath.Join(root, modDir) 153 154 _ = c.fileSystem.walk(root, func(path string, entry fs.DirEntry, err errors.Error) errors.Error { 155 if err != nil || !entry.IsDir() { 156 return nil 157 } 158 if path == pkgDir { 159 return skipDir 160 } 161 162 top := path == root 163 164 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 165 _, elem := filepath.Split(path) 166 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 167 if dot || strings.HasPrefix(elem, "_") || (elem == "testdata" && !top) { 168 return skipDir 169 } 170 171 if !top { 172 // Ignore other modules found in subdirectories. 173 if _, err := c.fileSystem.stat(filepath.Join(path, modDir)); err == nil { 174 return skipDir 175 } 176 } 177 178 // name := prefix + filepath.ToSlash(path) 179 // if !match(name) { 180 // return nil 181 // } 182 183 // We keep the directory if we can import it, or if we can't import it 184 // due to invalid CUE source files. This means that directories 185 // containing parse errors will be built (and fail) instead of being 186 // silently skipped as not matching the pattern. 187 // Do not take root, as we want to stay relative 188 // to one dir only. 189 relPath, err2 := filepath.Rel(c.Dir, path) 190 if err2 != nil { 191 panic(err2) // Should never happen because c.Dir is absolute. 192 } 193 relPath = "./" + filepath.ToSlash(relPath) 194 // TODO: consider not doing these checks here. 195 inst := l.newRelInstance(token.NoPos, relPath, pkgName) 196 pkgs := l.importPkg(token.NoPos, inst) 197 for _, p := range pkgs { 198 if err := p.Err; err != nil && (p == nil || len(p.InvalidFiles) == 0) { 199 switch err.(type) { 200 case *NoFilesError: 201 if c.DataFiles && len(p.OrphanedFiles) > 0 { 202 break 203 } 204 return nil 205 default: 206 m.Err = errors.Append(m.Err, err) 207 } 208 } 209 } 210 211 m.Pkgs = append(m.Pkgs, pkgs...) 212 return nil 213 }) 214 return m 215 } 216 217 // importPaths returns the matching paths to use for the given command line. 218 // It calls ImportPathsQuiet and then WarnUnmatched. 219 func (l *loader) importPaths(patterns []string) []*match { 220 matches := l.importPathsQuiet(patterns) 221 warnUnmatched(matches) 222 return matches 223 } 224 225 // importPathsQuiet is like ImportPaths but does not warn about patterns with no matches. 226 func (l *loader) importPathsQuiet(patterns []string) []*match { 227 var out []*match 228 for _, a := range cleanPatterns(patterns) { 229 if isMetaPackage(a) { 230 out = append(out, l.matchPackages(a, l.cfg.Package)) 231 continue 232 } 233 234 orig := a 235 pkgName := l.cfg.Package 236 switch p := strings.IndexByte(a, ':'); { 237 case p < 0: 238 case p == 0: 239 pkgName = a[1:] 240 a = "." 241 default: 242 pkgName = a[p+1:] 243 a = a[:p] 244 } 245 if pkgName == "*" { 246 pkgName = "" 247 } 248 249 if strings.Contains(a, "...") { 250 if isLocalImport(a) { 251 out = append(out, l.matchPackagesInFS(a, pkgName)) 252 } else { 253 out = append(out, l.matchPackages(a, pkgName)) 254 } 255 continue 256 } 257 258 var p *build.Instance 259 if isLocalImport(a) { 260 p = l.newRelInstance(token.NoPos, a, pkgName) 261 } else { 262 p = l.newInstance(token.NoPos, importPath(orig)) 263 } 264 265 pkgs := l.importPkg(token.NoPos, p) 266 out = append(out, &match{Pattern: a, Literal: true, Pkgs: pkgs}) 267 } 268 return out 269 } 270