···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+// Package build defines data types and utilities for defining CUE configuration
1616+// instances.
1717+//
1818+// This package enforces the rules regarding packages and instances as defined
1919+// in the spec, but it leaves any other details, as well as handling of modules,
2020+// up to the implementation.
2121+//
2222+// A full implementation of instance loading can be found in the loader package.
2323+//
2424+// WARNING: this packages may change. It is fine to use load and cue, who both
2525+// use this package.
2626+package build
2727+2828+import (
2929+ "context"
3030+3131+ "cuelang.org/go/cue/parser"
3232+ "cuelang.org/go/cue/token"
3333+)
3434+3535+// A Context keeps track of state of building instances and caches work.
3636+type Context struct {
3737+ ctxt context.Context
3838+ fset *token.FileSet
3939+4040+ loader LoadFunc
4141+ parseOptions []parser.Option
4242+4343+ initialized bool
4444+4545+ imports map[string]*Instance
4646+}
4747+4848+// NewInstance creates an instance for this Context.
4949+func (c *Context) NewInstance(dir string, f LoadFunc) *Instance {
5050+ if f == nil {
5151+ f = c.loader
5252+ }
5353+ return &Instance{
5454+ ctxt: c,
5555+ loadFunc: f,
5656+ Dir: dir,
5757+ }
5858+}
5959+6060+// Complete finishes the initialization of an instance. All files must have
6161+// been added with AddFile before this call.
6262+func (inst *Instance) Complete() error {
6363+ if inst.done {
6464+ return inst.Err
6565+ }
6666+ inst.done = true
6767+6868+ err := inst.complete()
6969+ if err != nil {
7070+ inst.Err = err
7171+ inst.Incomplete = true
7272+ }
7373+ return err
7474+}
7575+7676+func (c *Context) init() {
7777+ if !c.initialized {
7878+ c.initialized = true
7979+ c.ctxt = context.Background()
8080+ c.initialized = true
8181+ c.imports = map[string]*Instance{}
8282+ c.fset = token.NewFileSet()
8383+ }
8484+}
8585+8686+// Options:
8787+// - certain parse modes
8888+// - parallellism
8989+// - error handler (allows cancelling the context)
9090+// - file set.
9191+9292+// NewContext creates a new build context.
9393+//
9494+// All instances must be created with a context.
9595+func NewContext(opts ...Option) *Context {
9696+ c := &Context{}
9797+ for _, o := range opts {
9898+ o(c)
9999+ }
100100+ c.init()
101101+ return c
102102+}
103103+104104+// Pos returns position information for a token.Pos.
105105+func (c *Context) Pos(pos token.Pos) token.Position {
106106+ if c.fset == nil {
107107+ return token.Position{}
108108+ }
109109+ return c.fset.Position(pos)
110110+}
111111+112112+// FileSet reports the file set used for parsing files.
113113+func (c *Context) FileSet() *token.FileSet {
114114+ c.init()
115115+ return c.fset
116116+}
117117+118118+// PurgeCache purges the instance cache.
119119+func (c *Context) PurgeCache() {
120120+ for name := range c.imports {
121121+ delete(c.imports, name)
122122+ }
123123+}
124124+125125+// Option define build options.
126126+type Option func(c *Context)
127127+128128+// ParseOptions sets parsing options.
129129+func ParseOptions(mode ...parser.Option) Option {
130130+ return func(c *Context) { c.parseOptions = mode }
131131+}
132132+133133+// Loader sets parsing options.
134134+func Loader(f LoadFunc) Option {
135135+ return func(c *Context) { c.loader = f }
136136+}
+16
cue/build/doc.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+// Package build defines collections of CUE files to build an instance.
1616+package build // import "cuelang.org/go/cue/build"
+154
cue/build/import.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package build
1616+1717+import (
1818+ "log"
1919+ "sort"
2020+ "strconv"
2121+2222+ "cuelang.org/go/cue/ast"
2323+ "cuelang.org/go/cue/errors"
2424+ "cuelang.org/go/cue/token"
2525+)
2626+2727+type LoadFunc func(path string) *Instance
2828+2929+func (inst *Instance) complete() error {
3030+ // TODO: handle case-insensitive collisions.
3131+ // dir := inst.Dir
3232+ // names := []string{}
3333+ // for _, src := range sources {
3434+ // names = append(names, src.path)
3535+ // }
3636+ // f1, f2 := str.FoldDup(names)
3737+ // if f1 != "" {
3838+ // return nil, fmt.Errorf("case-insensitive file name collision: %q and %q", f1, f2)
3939+ // }
4040+4141+ var (
4242+ c = inst.ctxt
4343+ fset = c.FileSet()
4444+ imported = map[string][]token.Position{}
4545+ )
4646+4747+ for _, f := range inst.Files {
4848+ for _, decl := range f.Decls {
4949+ d, ok := decl.(*ast.ImportDecl)
5050+ if !ok {
5151+ continue
5252+ }
5353+ for _, spec := range d.Specs {
5454+ quoted := spec.Path.Value
5555+ path, err := strconv.Unquote(quoted)
5656+ if err != nil {
5757+ // TODO: remove panic
5858+ log.Panicf("%s: parser returned invalid quoted string: <%s>", f.Filename, quoted)
5959+ }
6060+ imported[path] = append(imported[path], fset.Position(spec.Pos()))
6161+ }
6262+ }
6363+ }
6464+6565+ paths := make([]string, 0, len(imported))
6666+ for path := range imported {
6767+ paths = append(paths, path)
6868+ if path == "" {
6969+ return errors.E(imported[path], "empty import path")
7070+ }
7171+ }
7272+7373+ sort.Strings(paths)
7474+7575+ if inst.loadFunc != nil {
7676+ for i, path := range paths {
7777+ isLocal := IsLocalImport(path)
7878+ if isLocal {
7979+ // path = dirToImportPath(filepath.Join(dir, path))
8080+ }
8181+8282+ imp := c.imports[path]
8383+ if imp == nil {
8484+ imp = inst.loadFunc(path)
8585+ if imp == nil {
8686+ continue
8787+ }
8888+ if imp.Err != nil {
8989+ if len(imported[path]) > 0 {
9090+ imp.Err = errors.Augment(imp.Err, imported[path][0])
9191+ }
9292+ return imp.Err
9393+ }
9494+ imp.ImportPath = path
9595+ // imp.parent = inst
9696+ c.imports[path] = imp
9797+ // imp.parent = nil
9898+ } else if imp.parent != nil {
9999+ // TODO: report a standard cycle message.
100100+ // cycle is now handled explicitly in loader
101101+ }
102102+ paths[i] = imp.ImportPath
103103+104104+ inst.addImport(imp)
105105+ if imp.Incomplete {
106106+ inst.Incomplete = true
107107+ }
108108+ }
109109+ }
110110+111111+ inst.ImportPaths = paths
112112+ inst.ImportPos = imported
113113+114114+ // Build full dependencies
115115+ deps := make(map[string]*Instance)
116116+ var q []*Instance
117117+ q = append(q, inst.Imports...)
118118+ for i := 0; i < len(q); i++ {
119119+ p1 := q[i]
120120+ path := p1.ImportPath
121121+ // The same import path could produce an error or not,
122122+ // depending on what tries to import it.
123123+ // Prefer to record entries with errors, so we can report them.
124124+ // p0 := deps[path]
125125+ // if err0, err1 := lastError(p0), lastError(p1); p0 == nil || err1 != nil && (err0 == nil || len(err0.ImportStack) > len(err1.ImportStack)) {
126126+ // deps[path] = p1
127127+ // for _, p2 := range p1.Imports {
128128+ // if deps[p2.ImportPath] != p2 {
129129+ // q = append(q, p2)
130130+ // }
131131+ // }
132132+ // }
133133+ if _, ok := deps[path]; !ok {
134134+ deps[path] = p1
135135+ }
136136+ }
137137+ inst.Deps = make([]string, 0, len(deps))
138138+ for dep := range deps {
139139+ inst.Deps = append(inst.Deps, dep)
140140+ }
141141+ sort.Strings(inst.Deps)
142142+143143+ for _, dep := range inst.Deps {
144144+ p1 := deps[dep]
145145+ if p1 == nil {
146146+ panic("impossible: missing entry in package cache for " + dep + " imported by " + inst.ImportPath)
147147+ }
148148+ if p1.Err != nil {
149149+ inst.DepsErrors = append(inst.DepsErrors, p1.Err)
150150+ }
151151+ }
152152+153153+ return nil
154154+}
+240
cue/build/instance.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package build
1616+1717+import (
1818+ "fmt"
1919+ pathpkg "path"
2020+ "path/filepath"
2121+ "strings"
2222+ "unicode"
2323+2424+ "cuelang.org/go/cue/ast"
2525+ "cuelang.org/go/cue/errors"
2626+ "cuelang.org/go/cue/parser"
2727+ "cuelang.org/go/cue/token"
2828+)
2929+3030+// An Instance describes the collection of files, and its imports, necessary
3131+// to build a CUE instance.
3232+//
3333+// A typical way to create an Instance is to use the loader package.
3434+type Instance struct {
3535+ ctxt *Context
3636+3737+ // Files contains the AST for all files part of this instance.
3838+ Files []*ast.File
3939+4040+ loadFunc LoadFunc
4141+ done bool
4242+4343+ // Scope is another instance that may be used to resolve any unresolved
4444+ // reference of this instance. For instance, tool and test instances
4545+ // may refer to top-level fields in their package scope.
4646+ Scope *Instance
4747+4848+ // PkgName is the name specified in the package clause.
4949+ PkgName string
5050+ hasName bool
5151+5252+ // ImportPath returns the unique path to identify an imported instance.
5353+ //
5454+ // Instances created with NewInstance do not have an import path.
5555+ ImportPath string
5656+5757+ // Imports lists the instances of all direct imports of this instance.
5858+ Imports []*Instance
5959+6060+ // The Err for loading this package or nil on success. This does not
6161+ // include any errors of dependencies. Incomplete will be set if there
6262+ // were any errors in dependencies.
6363+ Err error
6464+6565+ // Incomplete reports whether any dependencies had an error.
6666+ Incomplete bool
6767+6868+ parent *Instance // TODO: for cycle detection
6969+7070+ // The following fields are for informative purposes and are not used by
7171+ // the cue package to create an instance.
7272+7373+ // ImportComment is the path in the import comment on the package statement.
7474+ ImportComment string
7575+7676+ // DisplayPath is a user-friendly version of the package or import path.
7777+ DisplayPath string
7878+7979+ // Dir is the package directory. Note that a package may also include files
8080+ // from ancestor directories, up to the module file.
8181+ Dir string
8282+8383+ Root string // module root directory ("" if unknown)
8484+8585+ // AllTags are the build tags that can influence file selection in this
8686+ // directory.
8787+ AllTags []string
8888+8989+ Standard bool // Is a builtin package
9090+ Local bool
9191+ localPrefix string
9292+9393+ // Relative to Dir
9494+ CUEFiles []string // .cue source files
9595+ DataFiles []string // recognized data files (.json, .yaml, etc.)
9696+ TestCUEFiles []string // .cue test files (_test.cue)
9797+ ToolCUEFiles []string // .cue tool files (_tool.cue)
9898+ IgnoredCUEFiles []string // .cue source files ignored for this build
9999+ InvalidCUEFiles []string // .cue source files with detected problems (parse error, wrong package name, and so on)
100100+101101+ // Dependencies
102102+ ImportPaths []string
103103+ ImportPos map[string][]token.Position // line information for Imports
104104+105105+ Deps []string
106106+ DepsErrors []error
107107+ Match []string
108108+}
109109+110110+// Abs converts relative path used in the one of the file fields to an
111111+// absolute one.
112112+func (inst *Instance) Abs(path string) string {
113113+ if filepath.IsAbs(path) {
114114+ return path
115115+ }
116116+ return filepath.Join(inst.Root, path)
117117+}
118118+119119+func (inst *Instance) chkErr(err error) error {
120120+ if err != nil {
121121+ inst.ReportError(err)
122122+ }
123123+ return err
124124+}
125125+126126+func (inst *Instance) setPkg(pkg string) bool {
127127+ if !inst.hasName {
128128+ inst.hasName = true
129129+ inst.PkgName = pkg
130130+ return true
131131+ }
132132+ return false
133133+}
134134+135135+// ReportError reports an error processing this instance.
136136+func (inst *Instance) ReportError(err error) {
137137+ if inst.Err == nil {
138138+ inst.Err = err
139139+ }
140140+}
141141+142142+func (inst *Instance) errorf(pos token.Pos, format string, args ...interface{}) error {
143143+ return inst.chkErr(errors.E(inst.ctxt.Pos(pos), fmt.Sprintf(format, args...)))
144144+}
145145+146146+// Context defines the build context for this instance. All files defined
147147+// in Syntax as well as all imported instances must be created using the
148148+// same build context.
149149+func (inst *Instance) Context() *Context {
150150+ return inst.ctxt
151151+}
152152+153153+// LookupImport defines a mapping from an ImportSpec's ImportPath to Instance.
154154+func (inst *Instance) LookupImport(path string) *Instance {
155155+ path = inst.expandPath(path)
156156+ for _, inst := range inst.Imports {
157157+ if inst.ImportPath == path {
158158+ return inst
159159+ }
160160+ }
161161+ return nil
162162+}
163163+164164+func (inst *Instance) addImport(imp *Instance) {
165165+ for _, inst := range inst.Imports {
166166+ if inst.ImportPath == imp.ImportPath {
167167+ if inst != imp {
168168+ panic("import added multiple times with different instances")
169169+ }
170170+ return
171171+ }
172172+ }
173173+ inst.Imports = append(inst.Imports, imp)
174174+}
175175+176176+// AddFile adds the file with the given name to the list of files for this
177177+// instance. The file may be loaded from the cache of the instance's context.
178178+// It does not process the file's imports. The package name of the file must
179179+// match the package name of the instance.
180180+func (inst *Instance) AddFile(filename string, src interface{}) error {
181181+ c := inst.ctxt
182182+ file, err := parser.ParseFile(c.FileSet(), filename, src, c.parseOptions...)
183183+ if err == nil {
184184+ err = inst.addSyntax(file)
185185+ }
186186+ return inst.chkErr(err)
187187+}
188188+189189+// addSyntax adds the given file to list of files for this instance. The package
190190+// name of the file must match the package name of the instance.
191191+func (inst *Instance) addSyntax(file *ast.File) error {
192192+ pkg := ""
193193+ pos := file.Pos()
194194+ if file.Name != nil {
195195+ pkg = file.Name.Name
196196+ pos = file.Name.Pos()
197197+ }
198198+ if !inst.setPkg(pkg) && pkg != inst.PkgName {
199199+ return inst.errorf(pos,
200200+ "package name %q conflicts with previous package name %q",
201201+ pkg, inst.PkgName)
202202+ }
203203+ inst.Files = append(inst.Files, file)
204204+ return nil
205205+}
206206+207207+func (inst *Instance) expandPath(path string) string {
208208+ isLocal := IsLocalImport(path)
209209+ if isLocal {
210210+ path = dirToImportPath(filepath.Join(inst.Dir, path))
211211+ }
212212+ return path
213213+}
214214+215215+// dirToImportPath returns the pseudo-import path we use for a package
216216+// outside the CUE path. It begins with _/ and then contains the full path
217217+// to the directory. If the package lives in c:\home\gopher\my\pkg then
218218+// the pseudo-import path is _/c_/home/gopher/my/pkg.
219219+// Using a pseudo-import path like this makes the ./ imports no longer
220220+// a special case, so that all the code to deal with ordinary imports works
221221+// automatically.
222222+func dirToImportPath(dir string) string {
223223+ return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir)))
224224+}
225225+226226+func makeImportValid(r rune) rune {
227227+ // Should match Go spec, compilers, and ../../go/parser/parser.go:/isValidImport.
228228+ const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
229229+ if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
230230+ return '_'
231231+ }
232232+ return r
233233+}
234234+235235+// IsLocalImport reports whether the import path is
236236+// a local import path, like ".", "..", "./foo", or "../foo".
237237+func IsLocalImport(path string) bool {
238238+ return path == "." || path == ".." ||
239239+ strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
240240+}