this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue/build: add package

Change-Id: I572b8279b51b8816425923dc7f2a7f3d31853ad3

+546
+136
cue/build/context.go
··· 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 defines data types and utilities for defining CUE configuration 16 + // instances. 17 + // 18 + // This package enforces the rules regarding packages and instances as defined 19 + // in the spec, but it leaves any other details, as well as handling of modules, 20 + // up to the implementation. 21 + // 22 + // A full implementation of instance loading can be found in the loader package. 23 + // 24 + // WARNING: this packages may change. It is fine to use load and cue, who both 25 + // use this package. 26 + package build 27 + 28 + import ( 29 + "context" 30 + 31 + "cuelang.org/go/cue/parser" 32 + "cuelang.org/go/cue/token" 33 + ) 34 + 35 + // A Context keeps track of state of building instances and caches work. 36 + type Context struct { 37 + ctxt context.Context 38 + fset *token.FileSet 39 + 40 + loader LoadFunc 41 + parseOptions []parser.Option 42 + 43 + initialized bool 44 + 45 + imports map[string]*Instance 46 + } 47 + 48 + // NewInstance creates an instance for this Context. 49 + func (c *Context) NewInstance(dir string, f LoadFunc) *Instance { 50 + if f == nil { 51 + f = c.loader 52 + } 53 + return &Instance{ 54 + ctxt: c, 55 + loadFunc: f, 56 + Dir: dir, 57 + } 58 + } 59 + 60 + // Complete finishes the initialization of an instance. All files must have 61 + // been added with AddFile before this call. 62 + func (inst *Instance) Complete() error { 63 + if inst.done { 64 + return inst.Err 65 + } 66 + inst.done = true 67 + 68 + err := inst.complete() 69 + if err != nil { 70 + inst.Err = err 71 + inst.Incomplete = true 72 + } 73 + return err 74 + } 75 + 76 + func (c *Context) init() { 77 + if !c.initialized { 78 + c.initialized = true 79 + c.ctxt = context.Background() 80 + c.initialized = true 81 + c.imports = map[string]*Instance{} 82 + c.fset = token.NewFileSet() 83 + } 84 + } 85 + 86 + // Options: 87 + // - certain parse modes 88 + // - parallellism 89 + // - error handler (allows cancelling the context) 90 + // - file set. 91 + 92 + // NewContext creates a new build context. 93 + // 94 + // All instances must be created with a context. 95 + func NewContext(opts ...Option) *Context { 96 + c := &Context{} 97 + for _, o := range opts { 98 + o(c) 99 + } 100 + c.init() 101 + return c 102 + } 103 + 104 + // Pos returns position information for a token.Pos. 105 + func (c *Context) Pos(pos token.Pos) token.Position { 106 + if c.fset == nil { 107 + return token.Position{} 108 + } 109 + return c.fset.Position(pos) 110 + } 111 + 112 + // FileSet reports the file set used for parsing files. 113 + func (c *Context) FileSet() *token.FileSet { 114 + c.init() 115 + return c.fset 116 + } 117 + 118 + // PurgeCache purges the instance cache. 119 + func (c *Context) PurgeCache() { 120 + for name := range c.imports { 121 + delete(c.imports, name) 122 + } 123 + } 124 + 125 + // Option define build options. 126 + type Option func(c *Context) 127 + 128 + // ParseOptions sets parsing options. 129 + func ParseOptions(mode ...parser.Option) Option { 130 + return func(c *Context) { c.parseOptions = mode } 131 + } 132 + 133 + // Loader sets parsing options. 134 + func Loader(f LoadFunc) Option { 135 + return func(c *Context) { c.loader = f } 136 + }
+16
cue/build/doc.go
··· 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 defines collections of CUE files to build an instance. 16 + package build // import "cuelang.org/go/cue/build"
+154
cue/build/import.go
··· 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 + "log" 19 + "sort" 20 + "strconv" 21 + 22 + "cuelang.org/go/cue/ast" 23 + "cuelang.org/go/cue/errors" 24 + "cuelang.org/go/cue/token" 25 + ) 26 + 27 + type LoadFunc func(path string) *Instance 28 + 29 + func (inst *Instance) complete() error { 30 + // TODO: handle case-insensitive collisions. 31 + // dir := inst.Dir 32 + // names := []string{} 33 + // for _, src := range sources { 34 + // names = append(names, src.path) 35 + // } 36 + // f1, f2 := str.FoldDup(names) 37 + // if f1 != "" { 38 + // return nil, fmt.Errorf("case-insensitive file name collision: %q and %q", f1, f2) 39 + // } 40 + 41 + var ( 42 + c = inst.ctxt 43 + fset = c.FileSet() 44 + imported = map[string][]token.Position{} 45 + ) 46 + 47 + for _, f := range inst.Files { 48 + for _, decl := range f.Decls { 49 + d, ok := decl.(*ast.ImportDecl) 50 + if !ok { 51 + continue 52 + } 53 + for _, spec := range d.Specs { 54 + quoted := spec.Path.Value 55 + path, err := strconv.Unquote(quoted) 56 + if err != nil { 57 + // TODO: remove panic 58 + log.Panicf("%s: parser returned invalid quoted string: <%s>", f.Filename, quoted) 59 + } 60 + imported[path] = append(imported[path], fset.Position(spec.Pos())) 61 + } 62 + } 63 + } 64 + 65 + paths := make([]string, 0, len(imported)) 66 + for path := range imported { 67 + paths = append(paths, path) 68 + if path == "" { 69 + return errors.E(imported[path], "empty import path") 70 + } 71 + } 72 + 73 + sort.Strings(paths) 74 + 75 + if inst.loadFunc != nil { 76 + for i, path := range paths { 77 + isLocal := IsLocalImport(path) 78 + if isLocal { 79 + // path = dirToImportPath(filepath.Join(dir, path)) 80 + } 81 + 82 + imp := c.imports[path] 83 + if imp == nil { 84 + imp = inst.loadFunc(path) 85 + if imp == nil { 86 + continue 87 + } 88 + if imp.Err != nil { 89 + if len(imported[path]) > 0 { 90 + imp.Err = errors.Augment(imp.Err, imported[path][0]) 91 + } 92 + return imp.Err 93 + } 94 + imp.ImportPath = path 95 + // imp.parent = inst 96 + c.imports[path] = imp 97 + // imp.parent = nil 98 + } else if imp.parent != nil { 99 + // TODO: report a standard cycle message. 100 + // cycle is now handled explicitly in loader 101 + } 102 + paths[i] = imp.ImportPath 103 + 104 + inst.addImport(imp) 105 + if imp.Incomplete { 106 + inst.Incomplete = true 107 + } 108 + } 109 + } 110 + 111 + inst.ImportPaths = paths 112 + inst.ImportPos = imported 113 + 114 + // Build full dependencies 115 + deps := make(map[string]*Instance) 116 + var q []*Instance 117 + q = append(q, inst.Imports...) 118 + for i := 0; i < len(q); i++ { 119 + p1 := q[i] 120 + path := p1.ImportPath 121 + // The same import path could produce an error or not, 122 + // depending on what tries to import it. 123 + // Prefer to record entries with errors, so we can report them. 124 + // p0 := deps[path] 125 + // if err0, err1 := lastError(p0), lastError(p1); p0 == nil || err1 != nil && (err0 == nil || len(err0.ImportStack) > len(err1.ImportStack)) { 126 + // deps[path] = p1 127 + // for _, p2 := range p1.Imports { 128 + // if deps[p2.ImportPath] != p2 { 129 + // q = append(q, p2) 130 + // } 131 + // } 132 + // } 133 + if _, ok := deps[path]; !ok { 134 + deps[path] = p1 135 + } 136 + } 137 + inst.Deps = make([]string, 0, len(deps)) 138 + for dep := range deps { 139 + inst.Deps = append(inst.Deps, dep) 140 + } 141 + sort.Strings(inst.Deps) 142 + 143 + for _, dep := range inst.Deps { 144 + p1 := deps[dep] 145 + if p1 == nil { 146 + panic("impossible: missing entry in package cache for " + dep + " imported by " + inst.ImportPath) 147 + } 148 + if p1.Err != nil { 149 + inst.DepsErrors = append(inst.DepsErrors, p1.Err) 150 + } 151 + } 152 + 153 + return nil 154 + }
+240
cue/build/instance.go
··· 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 + "fmt" 19 + pathpkg "path" 20 + "path/filepath" 21 + "strings" 22 + "unicode" 23 + 24 + "cuelang.org/go/cue/ast" 25 + "cuelang.org/go/cue/errors" 26 + "cuelang.org/go/cue/parser" 27 + "cuelang.org/go/cue/token" 28 + ) 29 + 30 + // An Instance describes the collection of files, and its imports, necessary 31 + // to build a CUE instance. 32 + // 33 + // A typical way to create an Instance is to use the loader package. 34 + type Instance struct { 35 + ctxt *Context 36 + 37 + // Files contains the AST for all files part of this instance. 38 + Files []*ast.File 39 + 40 + loadFunc LoadFunc 41 + done bool 42 + 43 + // Scope is another instance that may be used to resolve any unresolved 44 + // reference of this instance. For instance, tool and test instances 45 + // may refer to top-level fields in their package scope. 46 + Scope *Instance 47 + 48 + // PkgName is the name specified in the package clause. 49 + PkgName string 50 + hasName bool 51 + 52 + // ImportPath returns the unique path to identify an imported instance. 53 + // 54 + // Instances created with NewInstance do not have an import path. 55 + ImportPath string 56 + 57 + // Imports lists the instances of all direct imports of this instance. 58 + Imports []*Instance 59 + 60 + // The Err for loading this package or nil on success. This does not 61 + // include any errors of dependencies. Incomplete will be set if there 62 + // were any errors in dependencies. 63 + Err error 64 + 65 + // Incomplete reports whether any dependencies had an error. 66 + Incomplete bool 67 + 68 + parent *Instance // TODO: for cycle detection 69 + 70 + // The following fields are for informative purposes and are not used by 71 + // the cue package to create an instance. 72 + 73 + // ImportComment is the path in the import comment on the package statement. 74 + ImportComment string 75 + 76 + // DisplayPath is a user-friendly version of the package or import path. 77 + DisplayPath string 78 + 79 + // Dir is the package directory. Note that a package may also include files 80 + // from ancestor directories, up to the module file. 81 + Dir string 82 + 83 + Root string // module root directory ("" if unknown) 84 + 85 + // AllTags are the build tags that can influence file selection in this 86 + // directory. 87 + AllTags []string 88 + 89 + Standard bool // Is a builtin package 90 + Local bool 91 + localPrefix string 92 + 93 + // Relative to Dir 94 + CUEFiles []string // .cue source files 95 + DataFiles []string // recognized data files (.json, .yaml, etc.) 96 + TestCUEFiles []string // .cue test files (_test.cue) 97 + ToolCUEFiles []string // .cue tool files (_tool.cue) 98 + IgnoredCUEFiles []string // .cue source files ignored for this build 99 + InvalidCUEFiles []string // .cue source files with detected problems (parse error, wrong package name, and so on) 100 + 101 + // Dependencies 102 + ImportPaths []string 103 + ImportPos map[string][]token.Position // line information for Imports 104 + 105 + Deps []string 106 + DepsErrors []error 107 + Match []string 108 + } 109 + 110 + // Abs converts relative path used in the one of the file fields to an 111 + // absolute one. 112 + func (inst *Instance) Abs(path string) string { 113 + if filepath.IsAbs(path) { 114 + return path 115 + } 116 + return filepath.Join(inst.Root, path) 117 + } 118 + 119 + func (inst *Instance) chkErr(err error) error { 120 + if err != nil { 121 + inst.ReportError(err) 122 + } 123 + return err 124 + } 125 + 126 + func (inst *Instance) setPkg(pkg string) bool { 127 + if !inst.hasName { 128 + inst.hasName = true 129 + inst.PkgName = pkg 130 + return true 131 + } 132 + return false 133 + } 134 + 135 + // ReportError reports an error processing this instance. 136 + func (inst *Instance) ReportError(err error) { 137 + if inst.Err == nil { 138 + inst.Err = err 139 + } 140 + } 141 + 142 + func (inst *Instance) errorf(pos token.Pos, format string, args ...interface{}) error { 143 + return inst.chkErr(errors.E(inst.ctxt.Pos(pos), fmt.Sprintf(format, args...))) 144 + } 145 + 146 + // Context defines the build context for this instance. All files defined 147 + // in Syntax as well as all imported instances must be created using the 148 + // same build context. 149 + func (inst *Instance) Context() *Context { 150 + return inst.ctxt 151 + } 152 + 153 + // LookupImport defines a mapping from an ImportSpec's ImportPath to Instance. 154 + func (inst *Instance) LookupImport(path string) *Instance { 155 + path = inst.expandPath(path) 156 + for _, inst := range inst.Imports { 157 + if inst.ImportPath == path { 158 + return inst 159 + } 160 + } 161 + return nil 162 + } 163 + 164 + func (inst *Instance) addImport(imp *Instance) { 165 + for _, inst := range inst.Imports { 166 + if inst.ImportPath == imp.ImportPath { 167 + if inst != imp { 168 + panic("import added multiple times with different instances") 169 + } 170 + return 171 + } 172 + } 173 + inst.Imports = append(inst.Imports, imp) 174 + } 175 + 176 + // AddFile adds the file with the given name to the list of files for this 177 + // instance. The file may be loaded from the cache of the instance's context. 178 + // It does not process the file's imports. The package name of the file must 179 + // match the package name of the instance. 180 + func (inst *Instance) AddFile(filename string, src interface{}) error { 181 + c := inst.ctxt 182 + file, err := parser.ParseFile(c.FileSet(), filename, src, c.parseOptions...) 183 + if err == nil { 184 + err = inst.addSyntax(file) 185 + } 186 + return inst.chkErr(err) 187 + } 188 + 189 + // addSyntax adds the given file to list of files for this instance. The package 190 + // name of the file must match the package name of the instance. 191 + func (inst *Instance) addSyntax(file *ast.File) error { 192 + pkg := "" 193 + pos := file.Pos() 194 + if file.Name != nil { 195 + pkg = file.Name.Name 196 + pos = file.Name.Pos() 197 + } 198 + if !inst.setPkg(pkg) && pkg != inst.PkgName { 199 + return inst.errorf(pos, 200 + "package name %q conflicts with previous package name %q", 201 + pkg, inst.PkgName) 202 + } 203 + inst.Files = append(inst.Files, file) 204 + return nil 205 + } 206 + 207 + func (inst *Instance) expandPath(path string) string { 208 + isLocal := IsLocalImport(path) 209 + if isLocal { 210 + path = dirToImportPath(filepath.Join(inst.Dir, path)) 211 + } 212 + return path 213 + } 214 + 215 + // dirToImportPath returns the pseudo-import path we use for a package 216 + // outside the CUE path. It begins with _/ and then contains the full path 217 + // to the directory. If the package lives in c:\home\gopher\my\pkg then 218 + // the pseudo-import path is _/c_/home/gopher/my/pkg. 219 + // Using a pseudo-import path like this makes the ./ imports no longer 220 + // a special case, so that all the code to deal with ordinary imports works 221 + // automatically. 222 + func dirToImportPath(dir string) string { 223 + return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir))) 224 + } 225 + 226 + func makeImportValid(r rune) rune { 227 + // Should match Go spec, compilers, and ../../go/parser/parser.go:/isValidImport. 228 + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" 229 + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { 230 + return '_' 231 + } 232 + return r 233 + } 234 + 235 + // IsLocalImport reports whether the import path is 236 + // a local import path, like ".", "..", "./foo", or "../foo". 237 + func IsLocalImport(path string) bool { 238 + return path == "." || path == ".." || 239 + strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") 240 + }