1// Copyright 2019 Google Inc. All rights reserved. 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 15package bpdoc 16 17import ( 18 "fmt" 19 "go/ast" 20 "go/doc" 21 "go/parser" 22 "go/token" 23 "reflect" 24 "regexp" 25 "runtime" 26 "strings" 27 "sync" 28 29 "github.com/google/blueprint/proptools" 30) 31 32// Handles parsing and low-level processing of Blueprint module source files. Note that most getter 33// functions associated with Reader only fill basic information that can be simply extracted from 34// AST parsing results. More sophisticated processing is performed in bpdoc.go 35type Reader struct { 36 pkgFiles map[string][]string // Map of package name to source files, provided by constructor 37 38 mutex sync.Mutex 39 goPkgs map[string]*doc.Package // Map of package name to parsed Go AST, protected by mutex 40 ps map[string]*PropertyStruct // Map of module type name to property struct, protected by mutex 41} 42 43func NewReader(pkgFiles map[string][]string) *Reader { 44 return &Reader{ 45 pkgFiles: pkgFiles, 46 goPkgs: make(map[string]*doc.Package), 47 ps: make(map[string]*PropertyStruct), 48 } 49} 50 51func (r *Reader) Package(path string) (*Package, error) { 52 goPkg, err := r.goPkg(path) 53 if err != nil { 54 return nil, err 55 } 56 57 return &Package{ 58 Name: goPkg.Name, 59 Path: path, 60 Text: goPkg.Doc, 61 }, nil 62} 63 64func (r *Reader) ModuleType(name string, factory reflect.Value) (*ModuleType, error) { 65 f := runtime.FuncForPC(factory.Pointer()) 66 67 pkgPath, err := funcNameToPkgPath(f.Name()) 68 if err != nil { 69 return nil, err 70 } 71 72 factoryName := strings.TrimPrefix(f.Name(), pkgPath+".") 73 74 text, err := r.getModuleTypeDoc(pkgPath, factoryName) 75 if err != nil { 76 return nil, err 77 } 78 79 return &ModuleType{ 80 Name: name, 81 PkgPath: pkgPath, 82 Text: formatText(text), 83 }, nil 84} 85 86// Return the PropertyStruct associated with a property struct type. The type should be in the 87// format <package path>.<type name> 88func (r *Reader) propertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) { 89 ps := r.getPropertyStruct(pkgPath, name) 90 91 if ps == nil { 92 pkg, err := r.goPkg(pkgPath) 93 if err != nil { 94 return nil, err 95 } 96 97 for _, t := range pkg.Types { 98 if t.Name == name { 99 ps, err = newPropertyStruct(t) 100 if err != nil { 101 return nil, err 102 } 103 ps = r.putPropertyStruct(pkgPath, name, ps) 104 } 105 } 106 } 107 108 if ps == nil { 109 return nil, fmt.Errorf("package %q type %q not found", pkgPath, name) 110 } 111 112 ps = ps.Clone() 113 ps.SetDefaults(defaults) 114 115 return ps, nil 116} 117 118// Return the PropertyStruct associated with a struct type using recursion 119// This method is useful since golang structs created using reflection have an empty PkgPath() 120func (r *Reader) PropertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) { 121 var props []Property 122 123 // Base case: primitive type 124 if defaults.Kind() != reflect.Struct || proptools.IsConfigurable(defaults.Type()) { 125 props = append(props, Property{Name: name, 126 Type: defaults.Type().String()}) 127 return &PropertyStruct{Properties: props}, nil 128 } 129 130 // Base case: use r.propertyStruct if struct has a non empty pkgpath 131 if pkgPath != "" { 132 return r.propertyStruct(pkgPath, name, defaults) 133 } 134 135 numFields := defaults.NumField() 136 for i := 0; i < numFields; i++ { 137 field := defaults.Type().Field(i) 138 // Recurse 139 ps, err := r.PropertyStruct(field.Type.PkgPath(), field.Type.Name(), reflect.New(field.Type).Elem()) 140 141 if err != nil { 142 return nil, err 143 } 144 prop := Property{ 145 Name: strings.ToLower(field.Name), 146 Text: formatText(ps.Text), 147 Type: field.Type.Name(), 148 Properties: ps.Properties, 149 } 150 props = append(props, prop) 151 } 152 return &PropertyStruct{Properties: props}, nil 153} 154 155func (r *Reader) getModuleTypeDoc(pkgPath, factoryFuncName string) (string, error) { 156 goPkg, err := r.goPkg(pkgPath) 157 if err != nil { 158 return "", err 159 } 160 161 for _, fn := range goPkg.Funcs { 162 if fn.Name == factoryFuncName { 163 return fn.Doc, nil 164 } 165 } 166 167 // The doc package may associate the method with the type it returns, so iterate through those too 168 for _, typ := range goPkg.Types { 169 for _, fn := range typ.Funcs { 170 if fn.Name == factoryFuncName { 171 return fn.Doc, nil 172 } 173 } 174 } 175 176 return "", nil 177} 178 179func (r *Reader) getPropertyStruct(pkgPath, name string) *PropertyStruct { 180 r.mutex.Lock() 181 defer r.mutex.Unlock() 182 183 name = pkgPath + "." + name 184 185 return r.ps[name] 186} 187 188func (r *Reader) putPropertyStruct(pkgPath, name string, ps *PropertyStruct) *PropertyStruct { 189 r.mutex.Lock() 190 defer r.mutex.Unlock() 191 192 name = pkgPath + "." + name 193 194 if r.ps[name] != nil { 195 return r.ps[name] 196 } else { 197 r.ps[name] = ps 198 return ps 199 } 200} 201 202// Package AST generation and storage 203func (r *Reader) goPkg(pkgPath string) (*doc.Package, error) { 204 pkg := r.getGoPkg(pkgPath) 205 if pkg == nil { 206 if files, ok := r.pkgFiles[pkgPath]; ok { 207 var err error 208 pkgAST, err := packageAST(files) 209 if err != nil { 210 return nil, err 211 } 212 pkg = doc.New(pkgAST, pkgPath, doc.AllDecls) 213 pkg = r.putGoPkg(pkgPath, pkg) 214 } else { 215 return nil, fmt.Errorf("unknown package %q", pkgPath) 216 } 217 } 218 return pkg, nil 219} 220 221func (r *Reader) getGoPkg(pkgPath string) *doc.Package { 222 r.mutex.Lock() 223 defer r.mutex.Unlock() 224 225 return r.goPkgs[pkgPath] 226} 227 228func (r *Reader) putGoPkg(pkgPath string, pkg *doc.Package) *doc.Package { 229 r.mutex.Lock() 230 defer r.mutex.Unlock() 231 232 if r.goPkgs[pkgPath] != nil { 233 return r.goPkgs[pkgPath] 234 } else { 235 r.goPkgs[pkgPath] = pkg 236 return pkg 237 } 238} 239 240// A regex to find a package path within a function name. It finds the shortest string that is 241// followed by '.' and doesn't have any '/'s left. 242var pkgPathRe = regexp.MustCompile("^(.*?)\\.[^/]+$") 243 244func funcNameToPkgPath(f string) (string, error) { 245 s := pkgPathRe.FindStringSubmatch(f) 246 if len(s) < 2 { 247 return "", fmt.Errorf("failed to extract package path from %q", f) 248 } 249 return s[1], nil 250} 251 252func packageAST(files []string) (*ast.Package, error) { 253 asts := make(map[string]*ast.File) 254 255 fset := token.NewFileSet() 256 for _, file := range files { 257 ast, err := parser.ParseFile(fset, file, nil, parser.ParseComments) 258 if err != nil { 259 return nil, err 260 } 261 asts[file] = ast 262 } 263 264 pkg, _ := ast.NewPackage(fset, asts, nil, nil) 265 return pkg, nil 266} 267