cuelang.org/go@v0.13.0/cue/build/import.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 build
    16  
    17  import (
    18  	"maps"
    19  	"slices"
    20  	"strconv"
    21  
    22  	"cuelang.org/go/cue/errors"
    23  	"cuelang.org/go/cue/token"
    24  )
    25  
    26  type LoadFunc func(pos token.Pos, path string) *Instance
    27  
    28  type cueError = errors.Error
    29  
    30  type buildError struct {
    31  	cueError
    32  	inputs []token.Pos
    33  }
    34  
    35  func (e *buildError) InputPositions() []token.Pos {
    36  	return e.inputs
    37  }
    38  
    39  func (inst *Instance) complete() errors.Error {
    40  	// TODO: handle case-insensitive collisions.
    41  	// dir := inst.Dir
    42  	// names := []string{}
    43  	// for _, src := range sources {
    44  	// 	names = append(names, src.path)
    45  	// }
    46  	// f1, f2 := str.FoldDup(names)
    47  	// if f1 != "" {
    48  	// 	return nil, fmt.Errorf("case-insensitive file name collision: %q and %q", f1, f2)
    49  	// }
    50  
    51  	var (
    52  		c        = inst.ctxt
    53  		imported = map[string][]token.Pos{}
    54  	)
    55  
    56  	for _, f := range inst.Files {
    57  		for _, spec := range f.Imports {
    58  			quoted := spec.Path.Value
    59  			path, err := strconv.Unquote(quoted)
    60  			if err != nil {
    61  				inst.Err = errors.Append(inst.Err,
    62  					errors.Newf(
    63  						spec.Path.Pos(),
    64  						"%s: parser returned invalid quoted string: <%s>",
    65  						f.Filename, quoted))
    66  			}
    67  			imported[path] = append(imported[path], spec.Pos())
    68  		}
    69  	}
    70  
    71  	paths := make([]string, 0, len(imported))
    72  	for path := range imported {
    73  		paths = append(paths, path)
    74  		if path == "" {
    75  			return &buildError{
    76  				errors.Newf(token.NoPos, "empty import path"),
    77  				imported[path],
    78  			}
    79  		}
    80  	}
    81  
    82  	slices.Sort(paths)
    83  
    84  	if inst.loadFunc != nil {
    85  		for i, path := range paths {
    86  			// isLocal := IsLocalImport(path)
    87  			// if isLocal {
    88  			// 	path = dirToImportPath(filepath.Join(dir, path))
    89  			// }
    90  
    91  			imp := c.imports[path]
    92  			if imp == nil {
    93  				pos := token.NoPos
    94  				if len(imported[path]) > 0 {
    95  					pos = imported[path][0]
    96  				}
    97  				imp = inst.loadFunc(pos, path)
    98  				if imp == nil {
    99  					continue
   100  				}
   101  				if imp.Err != nil {
   102  					return errors.Wrapf(imp.Err, pos, "import failed")
   103  				}
   104  				imp.ImportPath = path
   105  				// imp.parent = inst
   106  				c.imports[path] = imp
   107  				// imp.parent = nil
   108  			} else if imp.parent != nil {
   109  				// TODO: report a standard cycle message.
   110  				//       cycle is now handled explicitly in loader
   111  			}
   112  			paths[i] = imp.ImportPath
   113  
   114  			inst.addImport(imp)
   115  			if imp.Incomplete {
   116  				inst.Incomplete = true
   117  			}
   118  		}
   119  	}
   120  
   121  	inst.ImportPaths = paths
   122  	inst.ImportPos = imported
   123  
   124  	// Build full dependencies
   125  	deps := make(map[string]*Instance)
   126  	var q []*Instance
   127  	q = append(q, inst.Imports...)
   128  	for i := 0; i < len(q); i++ {
   129  		p1 := q[i]
   130  		path := p1.ImportPath
   131  		// The same import path could produce an error or not,
   132  		// depending on what tries to import it.
   133  		// Prefer to record entries with errors, so we can report them.
   134  		// p0 := deps[path]
   135  		// if err0, err1 := lastError(p0), lastError(p1); p0 == nil || err1 != nil && (err0 == nil || len(err0.ImportStack) > len(err1.ImportStack)) {
   136  		// 	deps[path] = p1
   137  		// 	for _, p2 := range p1.Imports {
   138  		// 		if deps[p2.ImportPath] != p2 {
   139  		// 			q = append(q, p2)
   140  		// 		}
   141  		// 	}
   142  		// }
   143  		if _, ok := deps[path]; !ok {
   144  			deps[path] = p1
   145  		}
   146  	}
   147  	inst.Deps = slices.Sorted(maps.Keys(deps))
   148  
   149  	for _, dep := range inst.Deps {
   150  		p1 := deps[dep]
   151  		if p1 == nil {
   152  			panic("impossible: missing entry in package cache for " + dep + " imported by " + inst.ImportPath)
   153  		}
   154  		if p1.Err != nil {
   155  			inst.DepsErrors = append(inst.DepsErrors, p1.Err)
   156  		}
   157  	}
   158  
   159  	return nil
   160  }