...

Source file src/cuelang.org/go/cue/load/search.go

Documentation: cuelang.org/go/cue/load

     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  

View as plain text