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