Use apiextension v1
- upgrade from apiextension v1beta1 to v1 - generate yaml manifest for crd intead of applying it at runtime - users will have to apply the manifest with kubectl - kg and kgctl log an error if the crd is not present - now validation should actually work Signed-off-by: leonnicolas <leonloechner@gmx.de>
This commit is contained in:
60
vendor/sigs.k8s.io/controller-tools/pkg/loader/doc.go
generated
vendored
Normal file
60
vendor/sigs.k8s.io/controller-tools/pkg/loader/doc.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 loader defines helpers for loading packages from sources. It wraps
|
||||
// go/packages, allow incremental loading of source code and manual control
|
||||
// over which packages get type-checked. This allows for faster loading in
|
||||
// cases where you don't actually care about certain imports.
|
||||
//
|
||||
// Because it uses go/packages, it's modules-aware, and works in both modules-
|
||||
// and non-modules environments.
|
||||
//
|
||||
// Loading
|
||||
//
|
||||
// The main entrypoint for loading is LoadRoots, which traverse the package
|
||||
// graph starting at the given patterns (file, package, path, or ...-wildcard,
|
||||
// as one might pass to go list). Packages beyond the roots can be accessed
|
||||
// via the Imports() method. Packages are initially loaded with export data
|
||||
// paths, filenames, and imports.
|
||||
//
|
||||
// Packages are suitable for comparison, as each unique package only ever has
|
||||
// one *Package object returned.
|
||||
//
|
||||
// Syntax and TypeChecking
|
||||
//
|
||||
// ASTs and type-checking information can be loaded with NeedSyntax and
|
||||
// NeedTypesInfo, respectively. Both are idempotent -- repeated calls will
|
||||
// simply re-use the cached contents. Note that NeedTypesInfo will *only* type
|
||||
// check the current package -- if you want to type-check imports as well,
|
||||
// you'll need to type-check them first.
|
||||
//
|
||||
// Reference Pruning and Recursive Checking
|
||||
//
|
||||
// In order to type-check using only the packages you care about, you can use a
|
||||
// TypeChecker. TypeChecker will visit each top-level type declaration,
|
||||
// collect (optionally filtered) references, and type-check references
|
||||
// packages.
|
||||
//
|
||||
// Errors
|
||||
//
|
||||
// Errors can be added to each package. Use ErrFromNode to create an error
|
||||
// from an AST node. Errors can then be printed (complete with file and
|
||||
// position information) using PrintErrors, optionally filtered by error type.
|
||||
// It's generally a good idea to filter out TypeErrors when doing incomplete
|
||||
// type-checking with TypeChecker. You can use MaybeErrList to return multiple
|
||||
// errors if you need to return an error instead of adding it to a package.
|
||||
// AddError will later unroll it into individual errors.
|
||||
package loader
|
67
vendor/sigs.k8s.io/controller-tools/pkg/loader/errors.go
generated
vendored
Normal file
67
vendor/sigs.k8s.io/controller-tools/pkg/loader/errors.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// PositionedError represents some error with an associated position.
|
||||
// The position is tied to some external token.FileSet.
|
||||
type PositionedError struct {
|
||||
Pos token.Pos
|
||||
error
|
||||
}
|
||||
|
||||
// Node is the intersection of go/ast.Node and go/types.Var.
|
||||
type Node interface {
|
||||
Pos() token.Pos // position of first character belonging to the node
|
||||
}
|
||||
|
||||
// ErrFromNode returns the given error, with additional information
|
||||
// attaching it to the given AST node. It will automatically map
|
||||
// over error lists.
|
||||
func ErrFromNode(err error, node Node) error {
|
||||
if asList, isList := err.(ErrList); isList {
|
||||
resList := make(ErrList, len(asList))
|
||||
for i, baseErr := range asList {
|
||||
resList[i] = ErrFromNode(baseErr, node)
|
||||
}
|
||||
return resList
|
||||
}
|
||||
return PositionedError{
|
||||
Pos: node.Pos(),
|
||||
error: err,
|
||||
}
|
||||
}
|
||||
|
||||
// MaybeErrList constructs an ErrList if the given list of
|
||||
// errors has any errors, otherwise returning nil.
|
||||
func MaybeErrList(errs []error) error {
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return ErrList(errs)
|
||||
}
|
||||
|
||||
// ErrList is a list of errors aggregated together into a single error.
|
||||
type ErrList []error
|
||||
|
||||
func (l ErrList) Error() string {
|
||||
return fmt.Sprintf("%v", []error(l))
|
||||
}
|
360
vendor/sigs.k8s.io/controller-tools/pkg/loader/loader.go
generated
vendored
Normal file
360
vendor/sigs.k8s.io/controller-tools/pkg/loader/loader.go
generated
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// Much of this is strongly inspired by the contents of go/packages,
|
||||
// except that it allows for lazy loading of syntax and type-checking
|
||||
// information to speed up cases where full traversal isn't needed.
|
||||
|
||||
// PrintErrors print errors associated with all packages
|
||||
// in the given package graph, starting at the given root
|
||||
// packages and traversing through all imports. It will skip
|
||||
// any errors of the kinds specified in filterKinds. It will
|
||||
// return true if any errors were printed.
|
||||
func PrintErrors(pkgs []*Package, filterKinds ...packages.ErrorKind) bool {
|
||||
pkgsRaw := make([]*packages.Package, len(pkgs))
|
||||
for i, pkg := range pkgs {
|
||||
pkgsRaw[i] = pkg.Package
|
||||
}
|
||||
toSkip := make(map[packages.ErrorKind]struct{})
|
||||
for _, errKind := range filterKinds {
|
||||
toSkip[errKind] = struct{}{}
|
||||
}
|
||||
hadErrors := false
|
||||
packages.Visit(pkgsRaw, nil, func(pkgRaw *packages.Package) {
|
||||
for _, err := range pkgRaw.Errors {
|
||||
if _, skip := toSkip[err.Kind]; skip {
|
||||
continue
|
||||
}
|
||||
hadErrors = true
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
})
|
||||
return hadErrors
|
||||
}
|
||||
|
||||
// Package is a single, unique Go package that can be
|
||||
// lazily parsed and type-checked. Packages should not
|
||||
// be constructed directly -- instead, use LoadRoots.
|
||||
// For a given call to LoadRoots, only a single instance
|
||||
// of each package exists, and thus they may be used as keys
|
||||
// and for comparison.
|
||||
type Package struct {
|
||||
*packages.Package
|
||||
|
||||
imports map[string]*Package
|
||||
|
||||
loader *loader
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Imports returns the imports for the given package, indexed by
|
||||
// package path (*not* name in any particular file).
|
||||
func (p *Package) Imports() map[string]*Package {
|
||||
if p.imports == nil {
|
||||
p.imports = p.loader.packagesFor(p.Package.Imports)
|
||||
}
|
||||
|
||||
return p.imports
|
||||
}
|
||||
|
||||
// NeedTypesInfo indicates that type-checking information is needed for this package.
|
||||
// Actual type-checking information can be accessed via the Types and TypesInfo fields.
|
||||
func (p *Package) NeedTypesInfo() {
|
||||
if p.TypesInfo != nil {
|
||||
return
|
||||
}
|
||||
p.NeedSyntax()
|
||||
p.loader.typeCheck(p)
|
||||
}
|
||||
|
||||
// NeedSyntax indicates that a parsed AST is needed for this package.
|
||||
// Actual ASTs can be accessed via the Syntax field.
|
||||
func (p *Package) NeedSyntax() {
|
||||
if p.Syntax != nil {
|
||||
return
|
||||
}
|
||||
out := make([]*ast.File, len(p.CompiledGoFiles))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(p.CompiledGoFiles))
|
||||
for i, filename := range p.CompiledGoFiles {
|
||||
go func(i int, filename string) {
|
||||
defer wg.Done()
|
||||
src, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
p.AddError(err)
|
||||
return
|
||||
}
|
||||
out[i], err = p.loader.parseFile(filename, src)
|
||||
if err != nil {
|
||||
p.AddError(err)
|
||||
return
|
||||
}
|
||||
}(i, filename)
|
||||
}
|
||||
wg.Wait()
|
||||
for _, file := range out {
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
p.Syntax = out
|
||||
}
|
||||
|
||||
// AddError adds an error to the errors associated with the given package.
|
||||
func (p *Package) AddError(err error) {
|
||||
switch typedErr := err.(type) {
|
||||
case *os.PathError:
|
||||
// file-reading errors
|
||||
p.Errors = append(p.Errors, packages.Error{
|
||||
Pos: typedErr.Path + ":1",
|
||||
Msg: typedErr.Err.Error(),
|
||||
Kind: packages.ParseError,
|
||||
})
|
||||
case scanner.ErrorList:
|
||||
// parsing/scanning errors
|
||||
for _, subErr := range typedErr {
|
||||
p.Errors = append(p.Errors, packages.Error{
|
||||
Pos: subErr.Pos.String(),
|
||||
Msg: subErr.Msg,
|
||||
Kind: packages.ParseError,
|
||||
})
|
||||
}
|
||||
case types.Error:
|
||||
// type-checking errors
|
||||
p.Errors = append(p.Errors, packages.Error{
|
||||
Pos: typedErr.Fset.Position(typedErr.Pos).String(),
|
||||
Msg: typedErr.Msg,
|
||||
Kind: packages.TypeError,
|
||||
})
|
||||
case ErrList:
|
||||
for _, subErr := range typedErr {
|
||||
p.AddError(subErr)
|
||||
}
|
||||
case PositionedError:
|
||||
p.Errors = append(p.Errors, packages.Error{
|
||||
Pos: p.loader.cfg.Fset.Position(typedErr.Pos).String(),
|
||||
Msg: typedErr.Error(),
|
||||
Kind: packages.UnknownError,
|
||||
})
|
||||
default:
|
||||
// should only happen for external errors, like ref checking
|
||||
p.Errors = append(p.Errors, packages.Error{
|
||||
Pos: p.ID + ":-",
|
||||
Msg: err.Error(),
|
||||
Kind: packages.UnknownError,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// loader loads packages and their imports. Loaded packages will have
|
||||
// type size, imports, and exports file information populated. Additional
|
||||
// information, like ASTs and type-checking information, can be accessed
|
||||
// via methods on individual packages.
|
||||
type loader struct {
|
||||
// Roots are the loaded "root" packages in the package graph loaded via
|
||||
// LoadRoots.
|
||||
Roots []*Package
|
||||
|
||||
// cfg contains the package loading config (initialized on demand)
|
||||
cfg *packages.Config
|
||||
// packages contains the cache of Packages indexed by the underlying
|
||||
// package.Package, so that we don't ever produce two Packages with
|
||||
// the same underlying packages.Package.
|
||||
packages map[*packages.Package]*Package
|
||||
packagesMu sync.Mutex
|
||||
}
|
||||
|
||||
// packageFor returns a wrapped Package for the given packages.Package,
|
||||
// ensuring that there's a one-to-one mapping between the two.
|
||||
// It's *not* threadsafe -- use packagesFor for that.
|
||||
func (l *loader) packageFor(pkgRaw *packages.Package) *Package {
|
||||
if l.packages[pkgRaw] == nil {
|
||||
l.packages[pkgRaw] = &Package{
|
||||
Package: pkgRaw,
|
||||
loader: l,
|
||||
}
|
||||
}
|
||||
return l.packages[pkgRaw]
|
||||
}
|
||||
|
||||
// packagesFor returns a map of Package objects for each packages.Package in the input
|
||||
// map, ensuring that there's a one-to-one mapping between package.Package and Package
|
||||
// (as per packageFor).
|
||||
func (l *loader) packagesFor(pkgsRaw map[string]*packages.Package) map[string]*Package {
|
||||
l.packagesMu.Lock()
|
||||
defer l.packagesMu.Unlock()
|
||||
|
||||
out := make(map[string]*Package, len(pkgsRaw))
|
||||
for name, rawPkg := range pkgsRaw {
|
||||
out[name] = l.packageFor(rawPkg)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// typeCheck type-checks the given package.
|
||||
func (l *loader) typeCheck(pkg *Package) {
|
||||
// don't conflict with typeCheckFromExportData
|
||||
|
||||
pkg.TypesInfo = &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
}
|
||||
|
||||
pkg.Fset = l.cfg.Fset
|
||||
pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
|
||||
|
||||
importer := importerFunc(func(path string) (*types.Package, error) {
|
||||
if path == "unsafe" {
|
||||
return types.Unsafe, nil
|
||||
}
|
||||
|
||||
// The imports map is keyed by import path.
|
||||
importedPkg := pkg.Imports()[path]
|
||||
if importedPkg == nil {
|
||||
return nil, fmt.Errorf("package %q possibly creates an import loop", path)
|
||||
}
|
||||
|
||||
// it's possible to have a call to check in parallel to a call to this
|
||||
// if one package in the package graph gets its dependency filtered out,
|
||||
// but another doesn't (so one wants a "dummy" package here, and another
|
||||
// wants the full check).
|
||||
//
|
||||
// Thus, we need to lock here (at least for the time being) to avoid
|
||||
// races between the above write to `pkg.Types` and this checking of
|
||||
// importedPkg.Types.
|
||||
importedPkg.Lock()
|
||||
defer importedPkg.Unlock()
|
||||
|
||||
if importedPkg.Types != nil && importedPkg.Types.Complete() {
|
||||
return importedPkg.Types, nil
|
||||
}
|
||||
|
||||
// if we haven't already loaded typecheck data, we don't care about this package's types
|
||||
return types.NewPackage(importedPkg.PkgPath, importedPkg.Name), nil
|
||||
})
|
||||
|
||||
var errs []error
|
||||
|
||||
// type-check
|
||||
checkConfig := &types.Config{
|
||||
Importer: importer,
|
||||
|
||||
IgnoreFuncBodies: true, // we only need decl-level info
|
||||
|
||||
Error: func(err error) {
|
||||
errs = append(errs, err)
|
||||
},
|
||||
|
||||
Sizes: pkg.TypesSizes,
|
||||
}
|
||||
if err := types.NewChecker(checkConfig, l.cfg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// make sure that if a given sub-import is ill-typed, we mark this package as ill-typed as well.
|
||||
illTyped := len(errs) > 0
|
||||
if !illTyped {
|
||||
for _, importedPkg := range pkg.Imports() {
|
||||
if importedPkg.IllTyped {
|
||||
illTyped = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
pkg.IllTyped = illTyped
|
||||
|
||||
// publish errors to the package error list.
|
||||
for _, err := range errs {
|
||||
pkg.AddError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// parseFile parses the given file, including comments.
|
||||
func (l *loader) parseFile(filename string, src []byte) (*ast.File, error) {
|
||||
// skip function bodies
|
||||
file, err := parser.ParseFile(l.cfg.Fset, filename, src, parser.AllErrors|parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// LoadRoots loads the given "root" packages by path, transitively loading
|
||||
// and all imports as well.
|
||||
//
|
||||
// Loaded packages will have type size, imports, and exports file information
|
||||
// populated. Additional information, like ASTs and type-checking information,
|
||||
// can be accessed via methods on individual packages.
|
||||
func LoadRoots(roots ...string) ([]*Package, error) {
|
||||
return LoadRootsWithConfig(&packages.Config{}, roots...)
|
||||
}
|
||||
|
||||
// LoadRootsWithConfig functions like LoadRoots, except that it allows passing
|
||||
// a custom loading config. The config will be modified to suit the needs of
|
||||
// the loader.
|
||||
//
|
||||
// This is generally only useful for use in testing when you need to modify
|
||||
// loading settings to load from a fake location.
|
||||
func LoadRootsWithConfig(cfg *packages.Config, roots ...string) ([]*Package, error) {
|
||||
l := &loader{
|
||||
cfg: cfg,
|
||||
packages: make(map[*packages.Package]*Package),
|
||||
}
|
||||
l.cfg.Mode |= packages.LoadImports | packages.NeedTypesSizes
|
||||
if l.cfg.Fset == nil {
|
||||
l.cfg.Fset = token.NewFileSet()
|
||||
}
|
||||
// put our build flags first so that callers can override them
|
||||
l.cfg.BuildFlags = append([]string{"-tags", "ignore_autogenerated"}, l.cfg.BuildFlags...)
|
||||
|
||||
rawPkgs, err := packages.Load(l.cfg, roots...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rawPkg := range rawPkgs {
|
||||
l.Roots = append(l.Roots, l.packageFor(rawPkg))
|
||||
}
|
||||
|
||||
return l.Roots, nil
|
||||
}
|
||||
|
||||
// importFunc is an implementation of the single-method
|
||||
// types.Importer interface based on a function value.
|
||||
type importerFunc func(path string) (*types.Package, error)
|
||||
|
||||
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
|
32
vendor/sigs.k8s.io/controller-tools/pkg/loader/paths.go
generated
vendored
Normal file
32
vendor/sigs.k8s.io/controller-tools/pkg/loader/paths.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 loader
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NonVendorPath returns a package path that does not include anything before the
|
||||
// last vendor directory. This is useful for when using vendor directories,
|
||||
// and using go/types.Package.Path(), which returns the full path including vendor.
|
||||
//
|
||||
// If you're using this, make sure you really need it -- it's better to index by
|
||||
// the actual Package object when you can.
|
||||
func NonVendorPath(rawPath string) string {
|
||||
parts := strings.Split(rawPath, "/vendor/")
|
||||
return parts[len(parts)-1]
|
||||
}
|
268
vendor/sigs.k8s.io/controller-tools/pkg/loader/refs.go
generated
vendored
Normal file
268
vendor/sigs.k8s.io/controller-tools/pkg/loader/refs.go
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go/ast"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NB(directxman12): most of this is done by the typechecker,
|
||||
// but it's a bit slow/heavyweight for what we want -- we want
|
||||
// to resolve external imports *only* if we actually need them.
|
||||
|
||||
// Basically, what we do is:
|
||||
// 1. Map imports to names
|
||||
// 2. Find all explicit external references (`name.type`)
|
||||
// 3. Find all referenced packages by merging explicit references and dot imports
|
||||
// 4. Only type-check those packages
|
||||
// 5. Ignore type-checking errors from the missing packages, because we won't ever
|
||||
// touch unloaded types (they're probably used in ignored fields/types, variables, or functions)
|
||||
// (done using PrintErrors with an ignore argument from the caller).
|
||||
// 6. Notice any actual type-checking errors via invalid types
|
||||
|
||||
// importsMap saves import aliases, mapping them to underlying packages.
|
||||
type importsMap struct {
|
||||
// dotImports maps package IDs to packages for any packages that have/ been imported as `.`
|
||||
dotImports map[string]*Package
|
||||
// byName maps package aliases or names to the underlying package.
|
||||
byName map[string]*Package
|
||||
}
|
||||
|
||||
// mapImports maps imports from the names they use in the given file to the underlying package,
|
||||
// using a map of package import paths to packages (generally from Package.Imports()).
|
||||
func mapImports(file *ast.File, importedPkgs map[string]*Package) (*importsMap, error) {
|
||||
m := &importsMap{
|
||||
dotImports: make(map[string]*Package),
|
||||
byName: make(map[string]*Package),
|
||||
}
|
||||
for _, importSpec := range file.Imports {
|
||||
path, err := strconv.Unquote(importSpec.Path.Value)
|
||||
if err != nil {
|
||||
return nil, ErrFromNode(err, importSpec.Path)
|
||||
}
|
||||
importedPkg := importedPkgs[path]
|
||||
if importedPkg == nil {
|
||||
return nil, ErrFromNode(fmt.Errorf("no such package located"), importSpec.Path)
|
||||
}
|
||||
if importSpec.Name == nil {
|
||||
m.byName[importedPkg.Name] = importedPkg
|
||||
continue
|
||||
}
|
||||
if importSpec.Name.Name == "." {
|
||||
m.dotImports[importedPkg.ID] = importedPkg
|
||||
continue
|
||||
}
|
||||
m.byName[importSpec.Name.Name] = importedPkg
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// referenceSet finds references to external packages' types in the given file,
|
||||
// without otherwise calling into the type-checker. When checking structs,
|
||||
// it only checks fields with JSON tags.
|
||||
type referenceSet struct {
|
||||
file *ast.File
|
||||
imports *importsMap
|
||||
pkg *Package
|
||||
|
||||
externalRefs map[*Package]struct{}
|
||||
}
|
||||
|
||||
func (r *referenceSet) init() {
|
||||
if r.externalRefs == nil {
|
||||
r.externalRefs = make(map[*Package]struct{})
|
||||
}
|
||||
}
|
||||
|
||||
// NodeFilter filters nodes, accepting them for reference collection
|
||||
// when true is returned and rejecting them when false is returned.
|
||||
type NodeFilter func(ast.Node) bool
|
||||
|
||||
// collectReferences saves all references to external types in the given info.
|
||||
func (r *referenceSet) collectReferences(rawType ast.Expr, filterNode NodeFilter) {
|
||||
r.init()
|
||||
col := &referenceCollector{
|
||||
refs: r,
|
||||
filterNode: filterNode,
|
||||
}
|
||||
ast.Walk(col, rawType)
|
||||
}
|
||||
|
||||
// external saves an external reference to the given named package.
|
||||
func (r *referenceSet) external(pkgName string) {
|
||||
pkg := r.imports.byName[pkgName]
|
||||
if pkg == nil {
|
||||
r.pkg.AddError(fmt.Errorf("use of unimported package %q", pkgName))
|
||||
return
|
||||
}
|
||||
r.externalRefs[pkg] = struct{}{}
|
||||
}
|
||||
|
||||
// referenceCollector visits nodes in an AST, adding external references to a
|
||||
// referenceSet.
|
||||
type referenceCollector struct {
|
||||
refs *referenceSet
|
||||
filterNode NodeFilter
|
||||
}
|
||||
|
||||
func (c *referenceCollector) Visit(node ast.Node) ast.Visitor {
|
||||
if !c.filterNode(node) {
|
||||
return nil
|
||||
}
|
||||
switch typedNode := node.(type) {
|
||||
case *ast.Ident:
|
||||
// local reference or dot-import, ignore
|
||||
return nil
|
||||
case *ast.SelectorExpr:
|
||||
pkgName := typedNode.X.(*ast.Ident).Name
|
||||
c.refs.external(pkgName)
|
||||
return nil
|
||||
default:
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
// allReferencedPackages finds all directly referenced packages in the given package.
|
||||
func allReferencedPackages(pkg *Package, filterNodes NodeFilter) []*Package {
|
||||
pkg.NeedSyntax()
|
||||
refsByFile := make(map[*ast.File]*referenceSet)
|
||||
for _, file := range pkg.Syntax {
|
||||
imports, err := mapImports(file, pkg.Imports())
|
||||
if err != nil {
|
||||
pkg.AddError(err)
|
||||
return nil
|
||||
}
|
||||
refs := &referenceSet{
|
||||
file: file,
|
||||
imports: imports,
|
||||
pkg: pkg,
|
||||
}
|
||||
refsByFile[file] = refs
|
||||
}
|
||||
|
||||
EachType(pkg, func(file *ast.File, decl *ast.GenDecl, spec *ast.TypeSpec) {
|
||||
refs := refsByFile[file]
|
||||
refs.collectReferences(spec.Type, filterNodes)
|
||||
})
|
||||
|
||||
allPackages := make(map[*Package]struct{})
|
||||
for _, refs := range refsByFile {
|
||||
for _, pkg := range refs.imports.dotImports {
|
||||
allPackages[pkg] = struct{}{}
|
||||
}
|
||||
for ref := range refs.externalRefs {
|
||||
allPackages[ref] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
res := make([]*Package, 0, len(allPackages))
|
||||
for pkg := range allPackages {
|
||||
res = append(res, pkg)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// TypeChecker performs type-checking on a limitted subset of packages by
|
||||
// checking each package's types' externally-referenced types, and only
|
||||
// type-checking those packages.
|
||||
type TypeChecker struct {
|
||||
// NodeFilters are used to filter the set of references that are followed
|
||||
// when typechecking. If any of the filters returns true for a given node,
|
||||
// its package will be added to the set of packages to check.
|
||||
//
|
||||
// If no filters are specified, all references are followed (this may be slow).
|
||||
//
|
||||
// Modifying this after the first call to check may yield strange/invalid
|
||||
// results.
|
||||
NodeFilters []NodeFilter
|
||||
|
||||
checkedPackages map[*Package]struct{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Check type-checks the given package and all packages referenced by types
|
||||
// that pass through (have true returned by) any of the NodeFilters.
|
||||
func (c *TypeChecker) Check(root *Package) {
|
||||
c.init()
|
||||
|
||||
// use a sub-checker with the appropriate settings
|
||||
(&TypeChecker{
|
||||
NodeFilters: c.NodeFilters,
|
||||
checkedPackages: c.checkedPackages,
|
||||
}).check(root)
|
||||
}
|
||||
|
||||
func (c *TypeChecker) isNodeInteresting(node ast.Node) bool {
|
||||
// no filters --> everything is important
|
||||
if len(c.NodeFilters) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// otherwise, passing through any one filter means this node is important
|
||||
for _, filter := range c.NodeFilters {
|
||||
if filter(node) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *TypeChecker) init() {
|
||||
if c.checkedPackages == nil {
|
||||
c.checkedPackages = make(map[*Package]struct{})
|
||||
}
|
||||
}
|
||||
|
||||
// check recursively type-checks the given package, only loading packages that
|
||||
// are actually referenced by our types (it's the actual implementation of Check,
|
||||
// without initialization).
|
||||
func (c *TypeChecker) check(root *Package) {
|
||||
root.Lock()
|
||||
defer root.Unlock()
|
||||
|
||||
c.Lock()
|
||||
_, ok := c.checkedPackages[root]
|
||||
c.Unlock()
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
refedPackages := allReferencedPackages(root, c.isNodeInteresting)
|
||||
|
||||
// first, resolve imports for all leaf packages...
|
||||
var wg sync.WaitGroup
|
||||
for _, pkg := range refedPackages {
|
||||
wg.Add(1)
|
||||
go func(pkg *Package) {
|
||||
defer wg.Done()
|
||||
c.check(pkg)
|
||||
}(pkg)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// ...then, we can safely type-check ourself
|
||||
root.NeedTypesInfo()
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.checkedPackages[root] = struct{}{}
|
||||
}
|
81
vendor/sigs.k8s.io/controller-tools/pkg/loader/visit.go
generated
vendored
Normal file
81
vendor/sigs.k8s.io/controller-tools/pkg/loader/visit.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 loader
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// TypeCallback is a callback called for each raw AST (gendecl, typespec) combo.
|
||||
type TypeCallback func(file *ast.File, decl *ast.GenDecl, spec *ast.TypeSpec)
|
||||
|
||||
// EachType calls the given callback for each (gendecl, typespec) combo in the
|
||||
// given package. Generally, using markers.EachType is better when working
|
||||
// with marker data, and has a more convinient representation.
|
||||
func EachType(pkg *Package, cb TypeCallback) {
|
||||
visitor := &typeVisitor{
|
||||
callback: cb,
|
||||
}
|
||||
pkg.NeedSyntax()
|
||||
for _, file := range pkg.Syntax {
|
||||
visitor.file = file
|
||||
ast.Walk(visitor, file)
|
||||
}
|
||||
}
|
||||
|
||||
// typeVisitor visits all TypeSpecs, calling the given callback for each.
|
||||
type typeVisitor struct {
|
||||
callback TypeCallback
|
||||
decl *ast.GenDecl
|
||||
file *ast.File
|
||||
}
|
||||
|
||||
// Visit visits all TypeSpecs.
|
||||
func (v *typeVisitor) Visit(node ast.Node) ast.Visitor {
|
||||
if node == nil {
|
||||
v.decl = nil
|
||||
return v
|
||||
}
|
||||
|
||||
switch typedNode := node.(type) {
|
||||
case *ast.File:
|
||||
v.file = typedNode
|
||||
return v
|
||||
case *ast.GenDecl:
|
||||
v.decl = typedNode
|
||||
return v
|
||||
case *ast.TypeSpec:
|
||||
v.callback(v.file, v.decl, typedNode)
|
||||
return nil // don't recurse
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ParseAstTag parses the given raw tag literal into a reflect.StructTag.
|
||||
func ParseAstTag(tag *ast.BasicLit) reflect.StructTag {
|
||||
if tag == nil {
|
||||
return reflect.StructTag("")
|
||||
}
|
||||
tagStr, err := strconv.Unquote(tag.Value)
|
||||
if err != nil {
|
||||
return reflect.StructTag("")
|
||||
}
|
||||
return reflect.StructTag(tagStr)
|
||||
}
|
Reference in New Issue
Block a user