// Copyright 2018 The CUE Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package load import ( // TODO: remove this usage "io/fs" "path" "path/filepath" "strings" "cuelang.org/go/cue/build" "cuelang.org/go/cue/errors" "cuelang.org/go/cue/token" ) // TODO: should be matched from module file only. // The pattern is either "all" (all packages), "std" (standard packages), // "cmd" (standard commands), or a path including "...". func (l *loader) matchPackages(pattern, pkgName string) *match { // cfg := l.cfg m := &match{ Pattern: pattern, Literal: false, } // match := func(string) bool { return true } // treeCanMatch := func(string) bool { return true } // if !isMetaPackage(pattern) { // match = matchPattern(pattern) // treeCanMatch = treeCanMatchPattern(pattern) // } // have := map[string]bool{ // "builtin": true, // ignore pseudo-package that exists only for documentation // } // for _, src := range cfg.srcDirs() { // if pattern == "std" || pattern == "cmd" { // continue // } // src = filepath.Clean(src) + string(filepath.Separator) // root := src // if pattern == "cmd" { // root += "cmd" + string(filepath.Separator) // } // filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { // if err != nil || path == src { // return nil // } // want := true // // Avoid .foo, _foo, and testdata directory trees. // _, elem := filepath.Split(path) // if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { // want = false // } // name := filepath.ToSlash(path[len(src):]) // if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { // // The name "std" is only the standard library. // // If the name is cmd, it's the root of the command tree. // want = false // } // if !treeCanMatch(name) { // want = false // } // if !fi.IsDir() { // if fi.Mode()&os.ModeSymlink != 0 && want { // if target, err := os.Stat(path); err == nil && target.IsDir() { // fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) // } // } // return nil // } // if !want { // return skipDir // } // if have[name] { // return nil // } // have[name] = true // if !match(name) { // return nil // } // pkg := l.importPkg(".", path) // if err := pkg.Error; err != nil { // if _, noGo := err.(*noCUEError); noGo { // return nil // } // } // // If we are expanding "cmd", skip main // // packages under cmd/vendor. At least as of // // March, 2017, there is one there for the // // vendored pprof tool. // if pattern == "cmd" && strings.HasPrefix(pkg.DisplayPath, "cmd/vendor") && pkg.PkgName == "main" { // return nil // } // m.Pkgs = append(m.Pkgs, pkg) // return nil // }) // } return m } // matchPackagesInFS is like allPackages but is passed a pattern // beginning ./ or ../, meaning it should scan the tree rooted // at the given directory. There are ... in the pattern too. // (See go help packages for pattern syntax.) func (l *loader) matchPackagesInFS(pattern, pkgName string) *match { c := l.cfg m := &match{ Pattern: pattern, Literal: false, } // Find directory to begin the scan. // Could be smarter but this one optimization // is enough for now, since ... is usually at the // end of a path. i := strings.Index(pattern, "...") dir, _ := path.Split(pattern[:i]) root := l.abs(dir) // Find new module root from here or check there are no additional // cue.mod files between here and the next module. if !hasFilepathPrefix(root, c.ModuleRoot) { m.Err = errors.Newf(token.NoPos, "cue: pattern %s refers to dir %s, outside module root %s", pattern, root, c.ModuleRoot) return m } pkgDir := filepath.Join(root, modDir) _ = c.fileSystem.walk(root, func(path string, entry fs.DirEntry, err errors.Error) errors.Error { if err != nil || !entry.IsDir() { return nil } if path == pkgDir { return skipDir } top := path == root // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". _, elem := filepath.Split(path) dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." if dot || strings.HasPrefix(elem, "_") || (elem == "testdata" && !top) { return skipDir } if !top { // Ignore other modules found in subdirectories. if _, err := c.fileSystem.stat(filepath.Join(path, modDir)); err == nil { return skipDir } } // name := prefix + filepath.ToSlash(path) // if !match(name) { // return nil // } // We keep the directory if we can import it, or if we can't import it // due to invalid CUE source files. This means that directories // containing parse errors will be built (and fail) instead of being // silently skipped as not matching the pattern. // Do not take root, as we want to stay relative // to one dir only. relPath, err2 := filepath.Rel(c.Dir, path) if err2 != nil { panic(err2) // Should never happen because c.Dir is absolute. } relPath = "./" + filepath.ToSlash(relPath) // TODO: consider not doing these checks here. inst := l.newRelInstance(token.NoPos, relPath, pkgName) pkgs := l.importPkg(token.NoPos, inst) for _, p := range pkgs { if err := p.Err; err != nil && (p == nil || len(p.InvalidFiles) == 0) { switch err.(type) { case *NoFilesError: if c.DataFiles && len(p.OrphanedFiles) > 0 { break } return nil default: m.Err = errors.Append(m.Err, err) } } } m.Pkgs = append(m.Pkgs, pkgs...) return nil }) return m } // importPaths returns the matching paths to use for the given command line. // It calls ImportPathsQuiet and then WarnUnmatched. func (l *loader) importPaths(patterns []string) []*match { matches := l.importPathsQuiet(patterns) warnUnmatched(matches) return matches } // importPathsQuiet is like ImportPaths but does not warn about patterns with no matches. func (l *loader) importPathsQuiet(patterns []string) []*match { var out []*match for _, a := range cleanPatterns(patterns) { if isMetaPackage(a) { out = append(out, l.matchPackages(a, l.cfg.Package)) continue } orig := a pkgName := l.cfg.Package switch p := strings.IndexByte(a, ':'); { case p < 0: case p == 0: pkgName = a[1:] a = "." default: pkgName = a[p+1:] a = a[:p] } if pkgName == "*" { pkgName = "" } if strings.Contains(a, "...") { if isLocalImport(a) { out = append(out, l.matchPackagesInFS(a, pkgName)) } else { out = append(out, l.matchPackages(a, pkgName)) } continue } var p *build.Instance if isLocalImport(a) { p = l.newRelInstance(token.NoPos, a, pkgName) } else { p = l.newInstance(token.NoPos, importPath(orig)) } pkgs := l.importPkg(token.NoPos, p) out = append(out, &match{Pattern: a, Literal: true, Pkgs: pkgs}) } return out }