...

Source file src/cuelang.org/go/cue/load/import.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  	"fmt"
    19  	"os"
    20  	pathpkg "path"
    21  	"path/filepath"
    22  	"sort"
    23  	"strings"
    24  
    25  	"cuelang.org/go/cue/ast"
    26  	"cuelang.org/go/cue/build"
    27  	"cuelang.org/go/cue/errors"
    28  	"cuelang.org/go/cue/token"
    29  	"cuelang.org/go/internal/filetypes"
    30  	"cuelang.org/go/mod/module"
    31  )
    32  
    33  // importPkg returns details about the CUE package named by the import path,
    34  // interpreting local import paths relative to l.cfg.Dir.
    35  // If the path is a local import path naming a package that can be imported
    36  // using a standard import path, the returned package will set p.ImportPath
    37  // to that path.
    38  //
    39  // In the directory and ancestor directories up to including one with a
    40  // cue.mod file, all .cue files are considered part of the package except for:
    41  //
    42  //   - files starting with _ or . (likely editor temporary files)
    43  //   - files with build constraints not satisfied by the context
    44  //
    45  // If an error occurs, importPkg sets the error in the returned instance,
    46  // which then may contain partial information.
    47  //
    48  // pkgName indicates which packages to load. It supports the following
    49  // values:
    50  //
    51  //	""      the default package for the directory, if only one
    52  //	        is present.
    53  //	_       anonymous files (which may be marked with _)
    54  //	*       all packages
    55  func (l *loader) importPkg(pos token.Pos, p *build.Instance) []*build.Instance {
    56  	retErr := func(errs errors.Error) []*build.Instance {
    57  		// XXX: move this loop to ReportError
    58  		for _, err := range errors.Errors(errs) {
    59  			p.ReportError(err)
    60  		}
    61  		return []*build.Instance{p}
    62  	}
    63  
    64  	for _, item := range l.stk {
    65  		if item == p.ImportPath {
    66  			return retErr(&PackageError{Message: errors.NewMessagef("package import cycle not allowed")})
    67  		}
    68  	}
    69  	l.stk.Push(p.ImportPath)
    70  	defer l.stk.Pop()
    71  
    72  	cfg := l.cfg
    73  	ctxt := &cfg.fileSystem
    74  
    75  	if p.Err != nil {
    76  		return []*build.Instance{p}
    77  	}
    78  
    79  	fp := newFileProcessor(cfg, p, l.tagger)
    80  
    81  	if p.PkgName == "" {
    82  		if l.cfg.Package == "*" {
    83  			fp.ignoreOther = true
    84  			fp.allPackages = true
    85  			p.PkgName = "_"
    86  		} else {
    87  			p.PkgName = l.cfg.Package
    88  		}
    89  	}
    90  	if p.PkgName != "" {
    91  		// If we have an explicit package name, we can ignore other packages.
    92  		fp.ignoreOther = true
    93  	}
    94  
    95  	var dirs [][2]string
    96  	genDir := GenPath(cfg.ModuleRoot)
    97  	if strings.HasPrefix(p.Dir, genDir) {
    98  		dirs = append(dirs, [2]string{genDir, p.Dir})
    99  		// && p.PkgName != "_"
   100  		for _, sub := range []string{"pkg", "usr"} {
   101  			rel, err := filepath.Rel(genDir, p.Dir)
   102  			if err != nil {
   103  				// should not happen
   104  				return retErr(errors.Wrapf(err, token.NoPos, "invalid path"))
   105  			}
   106  			base := filepath.Join(cfg.ModuleRoot, modDir, sub)
   107  			dir := filepath.Join(base, rel)
   108  			dirs = append(dirs, [2]string{base, dir})
   109  		}
   110  	} else {
   111  		dirs = append(dirs, [2]string{cfg.ModuleRoot, p.Dir})
   112  	}
   113  
   114  	found := false
   115  	for _, d := range dirs {
   116  		info, err := ctxt.stat(d[1])
   117  		if err == nil && info.IsDir() {
   118  			found = true
   119  			break
   120  		}
   121  	}
   122  
   123  	if !found {
   124  		return retErr(
   125  			&PackageError{
   126  				Message: errors.NewMessagef("cannot find package %q", p.DisplayPath),
   127  			})
   128  	}
   129  
   130  	// This algorithm assumes that multiple directories within cue.mod/*/
   131  	// have the same module scope and that there are no invalid modules.
   132  	inModule := false // if pkg == "_"
   133  	for _, d := range dirs {
   134  		if l.cfg.findRoot(d[1]) != "" {
   135  			inModule = true
   136  			break
   137  		}
   138  	}
   139  
   140  	for _, d := range dirs {
   141  		for dir := filepath.Clean(d[1]); ctxt.isDir(dir); {
   142  			files, err := ctxt.readDir(dir)
   143  			if err != nil && !os.IsNotExist(err) {
   144  				return retErr(errors.Wrapf(err, pos, "import failed reading dir %v", dirs[0][1]))
   145  			}
   146  			for _, f := range files {
   147  				if f.IsDir() {
   148  					continue
   149  				}
   150  				if f.Name() == "-" {
   151  					if _, err := cfg.fileSystem.stat("-"); !os.IsNotExist(err) {
   152  						continue
   153  					}
   154  				}
   155  				file, err := filetypes.ParseFile(f.Name(), filetypes.Input)
   156  				if err != nil {
   157  					p.UnknownFiles = append(p.UnknownFiles, &build.File{
   158  						Filename:      f.Name(),
   159  						ExcludeReason: errors.Newf(token.NoPos, "unknown filetype"),
   160  					})
   161  					continue // skip unrecognized file types
   162  				}
   163  				fp.add(dir, file, importComment)
   164  			}
   165  
   166  			if p.PkgName == "" || !inModule || l.cfg.isRoot(dir) || dir == d[0] {
   167  				break
   168  			}
   169  
   170  			// From now on we just ignore files that do not belong to the same
   171  			// package.
   172  			fp.ignoreOther = true
   173  
   174  			parent, _ := filepath.Split(dir)
   175  			parent = filepath.Clean(parent)
   176  
   177  			if parent == dir || len(parent) < len(d[0]) {
   178  				break
   179  			}
   180  			dir = parent
   181  		}
   182  	}
   183  
   184  	all := []*build.Instance{}
   185  
   186  	for _, p := range fp.pkgs {
   187  		impPath, err := addImportQualifier(importPath(p.ImportPath), p.PkgName)
   188  		p.ImportPath = string(impPath)
   189  		if err != nil {
   190  			p.ReportError(err)
   191  		}
   192  
   193  		all = append(all, p)
   194  		rewriteFiles(p, cfg.ModuleRoot, false)
   195  		if errs := fp.finalize(p); errs != nil {
   196  			p.ReportError(errs)
   197  			return all
   198  		}
   199  
   200  		l.addFiles(cfg.ModuleRoot, p)
   201  		_ = p.Complete()
   202  	}
   203  	sort.Slice(all, func(i, j int) bool {
   204  		return all[i].Dir < all[j].Dir
   205  	})
   206  	return all
   207  }
   208  
   209  // _loadFunc is the method used for the value of l.loadFunc.
   210  func (l *loader) _loadFunc(pos token.Pos, path string) *build.Instance {
   211  	impPath := importPath(path)
   212  	if isLocalImport(path) {
   213  		return l.cfg.newErrInstance(errors.Newf(pos, "relative import paths not allowed (%q)", path))
   214  	}
   215  
   216  	// is it a builtin?
   217  	if strings.IndexByte(strings.Split(path, "/")[0], '.') == -1 {
   218  		if l.cfg.StdRoot != "" {
   219  			p := l.newInstance(pos, impPath)
   220  			_ = l.importPkg(pos, p)
   221  			return p
   222  		}
   223  		return nil
   224  	}
   225  
   226  	p := l.newInstance(pos, impPath)
   227  	_ = l.importPkg(pos, p)
   228  	return p
   229  }
   230  
   231  // newRelInstance returns a build instance from the given
   232  // relative import path.
   233  func (l *loader) newRelInstance(pos token.Pos, path, pkgName string) *build.Instance {
   234  	if !isLocalImport(path) {
   235  		panic(fmt.Errorf("non-relative import path %q passed to newRelInstance", path))
   236  	}
   237  
   238  	var err errors.Error
   239  	dir := path
   240  
   241  	p := l.cfg.Context.NewInstance(path, l.loadFunc)
   242  	p.PkgName = pkgName
   243  	p.DisplayPath = filepath.ToSlash(path)
   244  	// p.ImportPath = string(dir) // compute unique ID.
   245  	p.Root = l.cfg.ModuleRoot
   246  	p.Module = l.cfg.Module
   247  
   248  	dir = filepath.Join(l.cfg.Dir, filepath.FromSlash(path))
   249  
   250  	if path != cleanImport(path) {
   251  		err = errors.Append(err, l.errPkgf(nil,
   252  			"non-canonical import path: %q should be %q", path, pathpkg.Clean(path)))
   253  	}
   254  
   255  	if importPath, e := l.importPathFromAbsDir(fsPath(dir), path); e != nil {
   256  		// Detect later to keep error messages consistent.
   257  	} else {
   258  		p.ImportPath = string(importPath)
   259  	}
   260  
   261  	p.Dir = dir
   262  
   263  	if filepath.IsAbs(path) || strings.HasPrefix(path, "/") {
   264  		err = errors.Append(err, errors.Newf(pos,
   265  			"absolute import path %q not allowed", path))
   266  	}
   267  	if err != nil {
   268  		p.Err = errors.Append(p.Err, err)
   269  		p.Incomplete = true
   270  	}
   271  
   272  	return p
   273  }
   274  
   275  func (l *loader) importPathFromAbsDir(absDir fsPath, key string) (importPath, errors.Error) {
   276  	if l.cfg.ModuleRoot == "" {
   277  		return "", errors.Newf(token.NoPos,
   278  			"cannot determine import path for %q (root undefined)", key)
   279  	}
   280  
   281  	dir := filepath.Clean(string(absDir))
   282  	if !strings.HasPrefix(dir, l.cfg.ModuleRoot) {
   283  		return "", errors.Newf(token.NoPos,
   284  			"cannot determine import path for %q (dir outside of root)", key)
   285  	}
   286  
   287  	pkg := filepath.ToSlash(dir[len(l.cfg.ModuleRoot):])
   288  	switch {
   289  	case strings.HasPrefix(pkg, "/cue.mod/"):
   290  		pkg = pkg[len("/cue.mod/"):]
   291  		if pkg == "" {
   292  			return "", errors.Newf(token.NoPos,
   293  				"invalid package %q (root of %s)", key, modDir)
   294  		}
   295  
   296  	case l.cfg.Module == "":
   297  		return "", errors.Newf(token.NoPos,
   298  			"cannot determine import path for %q (no module)", key)
   299  	default:
   300  		pkg = l.cfg.Module + pkg
   301  	}
   302  
   303  	name := l.cfg.Package
   304  	switch name {
   305  	case "_", "*":
   306  		name = ""
   307  	}
   308  
   309  	return addImportQualifier(importPath(pkg), name)
   310  }
   311  
   312  func (l *loader) newInstance(pos token.Pos, p importPath) *build.Instance {
   313  	dir, name, err := l.absDirFromImportPath(pos, p)
   314  	i := l.cfg.Context.NewInstance(dir, l.loadFunc)
   315  	i.Dir = dir
   316  	i.PkgName = name
   317  	i.DisplayPath = string(p)
   318  	i.ImportPath = string(p)
   319  	i.Root = l.cfg.ModuleRoot
   320  	i.Module = l.cfg.Module
   321  	i.Err = errors.Append(i.Err, err)
   322  
   323  	return i
   324  }
   325  
   326  // absDirFromImportPath converts a giving import path to an absolute directory
   327  // and a package name. The root directory must be set.
   328  //
   329  // The returned directory may not exist.
   330  func (l *loader) absDirFromImportPath(pos token.Pos, p importPath) (absDir, name string, err errors.Error) {
   331  	if l.cfg.ModuleRoot == "" {
   332  		return "", "", errors.Newf(pos, "cannot import %q (root undefined)", p)
   333  	}
   334  	origp := p
   335  	// Extract the package name.
   336  	parts := module.ParseImportPath(string(p))
   337  	name = parts.Qualifier
   338  	p = importPath(parts.Unqualified().String())
   339  	if name == "" {
   340  		err = errors.Newf(pos, "empty package name in import path %q", p)
   341  	} else if strings.IndexByte(name, '.') >= 0 {
   342  		err = errors.Newf(pos,
   343  			"cannot determine package name for %q (set explicitly with ':')", p)
   344  	} else if !ast.IsValidIdent(name) {
   345  		err = errors.Newf(pos,
   346  			"implied package identifier %q from import path %q is not valid", name, p)
   347  	}
   348  	if l.cfg.Registry != nil {
   349  		if l.pkgs == nil {
   350  			return "", name, errors.Newf(pos, "imports are unavailable because there is no cue.mod/module.cue file")
   351  		}
   352  		// TODO predicate registry-aware lookup on module.cue-declared CUE version?
   353  
   354  		// Note: use the original form of the import path because
   355  		// that's the form passed to modpkgload.LoadPackages
   356  		// and hence it's available by that name via Pkg.
   357  		pkg := l.pkgs.Pkg(string(origp))
   358  		if pkg == nil {
   359  			return "", name, errors.Newf(pos, "no dependency found for package %q", p)
   360  		}
   361  		if err := pkg.Error(); err != nil {
   362  			return "", name, errors.Newf(pos, "cannot find package %q: %v", p, err)
   363  		}
   364  		if mv := pkg.Mod(); mv.IsLocal() {
   365  			// It's a local package that's present inside one or both of the gen, usr or pkg
   366  			// directories. Even though modpkgload tells us exactly what those directories
   367  			// are, the rest of the cue/load logic expects only a single directory for now,
   368  			// so just use that.
   369  			absDir = filepath.Join(GenPath(l.cfg.ModuleRoot), parts.Path)
   370  		} else {
   371  			locs := pkg.Locations()
   372  			if len(locs) > 1 {
   373  				return "", "", errors.Newf(pos, "package %q unexpectedly found in multiple locations", p)
   374  			}
   375  			var err error
   376  			absDir, err = absPathForSourceLoc(locs[0])
   377  			if err != nil {
   378  				return "", name, errors.Newf(pos, "cannot determine source directory for package %q: %v", p, err)
   379  			}
   380  		}
   381  		return absDir, name, nil
   382  	}
   383  
   384  	// Determine the directory without using the registry.
   385  
   386  	sub := filepath.FromSlash(string(p))
   387  	switch hasPrefix := strings.HasPrefix(string(p), l.cfg.Module); {
   388  	case hasPrefix && len(sub) == len(l.cfg.Module):
   389  		absDir = l.cfg.ModuleRoot
   390  
   391  	case hasPrefix && p[len(l.cfg.Module)] == '/':
   392  		absDir = filepath.Join(l.cfg.ModuleRoot, sub[len(l.cfg.Module)+1:])
   393  
   394  	default:
   395  		absDir = filepath.Join(GenPath(l.cfg.ModuleRoot), sub)
   396  	}
   397  	return absDir, name, err
   398  }
   399  
   400  func absPathForSourceLoc(loc module.SourceLoc) (string, error) {
   401  	osfs, ok := loc.FS.(module.OSRootFS)
   402  	if !ok {
   403  		return "", fmt.Errorf("cannot get absolute path for FS of type %T", loc.FS)
   404  	}
   405  	osPath := osfs.OSRoot()
   406  	if osPath == "" {
   407  		return "", fmt.Errorf("cannot get absolute path for FS of type %T", loc.FS)
   408  	}
   409  	return filepath.Join(osPath, loc.Dir), nil
   410  }
   411  

View as plain text