blob: 996edb0afe09ed339ca99666704a9b4ab2cac1a3 [file] [log] [blame]
// 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 build
import (
"sort"
"strconv"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/token"
)
type LoadFunc func(pos token.Pos, path string) *Instance
type cueError = errors.Error
type buildError struct {
cueError
inputs []token.Pos
}
func (e *buildError) InputPositions() []token.Pos {
return e.inputs
}
func (inst *Instance) complete() errors.Error {
// TODO: handle case-insensitive collisions.
// dir := inst.Dir
// names := []string{}
// for _, src := range sources {
// names = append(names, src.path)
// }
// f1, f2 := str.FoldDup(names)
// if f1 != "" {
// return nil, fmt.Errorf("case-insensitive file name collision: %q and %q", f1, f2)
// }
var (
c = inst.ctxt
imported = map[string][]token.Pos{}
)
for _, f := range inst.Files {
for _, decl := range f.Decls {
d, ok := decl.(*ast.ImportDecl)
if !ok {
continue
}
for _, spec := range d.Specs {
quoted := spec.Path.Value
path, err := strconv.Unquote(quoted)
if err != nil {
inst.Err = errors.Append(inst.Err,
errors.Newf(
spec.Path.Pos(),
"%s: parser returned invalid quoted string: <%s>",
f.Filename, quoted))
}
imported[path] = append(imported[path], spec.Pos())
}
}
}
paths := make([]string, 0, len(imported))
for path := range imported {
paths = append(paths, path)
if path == "" {
return &buildError{
errors.Newf(token.NoPos, "empty import path"),
imported[path],
}
}
}
sort.Strings(paths)
if inst.loadFunc != nil {
for i, path := range paths {
isLocal := IsLocalImport(path)
if isLocal {
// path = dirToImportPath(filepath.Join(dir, path))
}
imp := c.imports[path]
if imp == nil {
pos := token.NoPos
if len(imported[path]) > 0 {
pos = imported[path][0]
}
imp = inst.loadFunc(pos, path)
if imp == nil {
continue
}
if imp.Err != nil {
return errors.Wrapf(imp.Err, pos, "import failed")
}
imp.ImportPath = path
// imp.parent = inst
c.imports[path] = imp
// imp.parent = nil
} else if imp.parent != nil {
// TODO: report a standard cycle message.
// cycle is now handled explicitly in loader
}
paths[i] = imp.ImportPath
inst.addImport(imp)
if imp.Incomplete {
inst.Incomplete = true
}
}
}
inst.ImportPaths = paths
inst.ImportPos = imported
// Build full dependencies
deps := make(map[string]*Instance)
var q []*Instance
q = append(q, inst.Imports...)
for i := 0; i < len(q); i++ {
p1 := q[i]
path := p1.ImportPath
// The same import path could produce an error or not,
// depending on what tries to import it.
// Prefer to record entries with errors, so we can report them.
// p0 := deps[path]
// if err0, err1 := lastError(p0), lastError(p1); p0 == nil || err1 != nil && (err0 == nil || len(err0.ImportStack) > len(err1.ImportStack)) {
// deps[path] = p1
// for _, p2 := range p1.Imports {
// if deps[p2.ImportPath] != p2 {
// q = append(q, p2)
// }
// }
// }
if _, ok := deps[path]; !ok {
deps[path] = p1
}
}
inst.Deps = make([]string, 0, len(deps))
for dep := range deps {
inst.Deps = append(inst.Deps, dep)
}
sort.Strings(inst.Deps)
for _, dep := range inst.Deps {
p1 := deps[dep]
if p1 == nil {
panic("impossible: missing entry in package cache for " + dep + " imported by " + inst.ImportPath)
}
if p1.Err != nil {
inst.DepsErrors = append(inst.DepsErrors, p1.Err)
}
}
return nil
}