b9ff002303
Bug: 323382414 Test: m nothing --no-skip-soong-tests Change-Id: I6e8a03c5785dbc3a90f458155dc4b9cd6ce7700b
266 lines
6.5 KiB
Go
266 lines
6.5 KiB
Go
// Copyright 2019 Google Inc. All rights reserved.
|
|
//
|
|
// 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 bpdoc
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/doc"
|
|
"go/parser"
|
|
"go/token"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/google/blueprint/proptools"
|
|
)
|
|
|
|
// Handles parsing and low-level processing of Blueprint module source files. Note that most getter
|
|
// functions associated with Reader only fill basic information that can be simply extracted from
|
|
// AST parsing results. More sophisticated processing is performed in bpdoc.go
|
|
type Reader struct {
|
|
pkgFiles map[string][]string // Map of package name to source files, provided by constructor
|
|
|
|
mutex sync.Mutex
|
|
goPkgs map[string]*doc.Package // Map of package name to parsed Go AST, protected by mutex
|
|
ps map[string]*PropertyStruct // Map of module type name to property struct, protected by mutex
|
|
}
|
|
|
|
func NewReader(pkgFiles map[string][]string) *Reader {
|
|
return &Reader{
|
|
pkgFiles: pkgFiles,
|
|
goPkgs: make(map[string]*doc.Package),
|
|
ps: make(map[string]*PropertyStruct),
|
|
}
|
|
}
|
|
|
|
func (r *Reader) Package(path string) (*Package, error) {
|
|
goPkg, err := r.goPkg(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Package{
|
|
Name: goPkg.Name,
|
|
Path: path,
|
|
Text: goPkg.Doc,
|
|
}, nil
|
|
}
|
|
|
|
func (r *Reader) ModuleType(name string, factory reflect.Value) (*ModuleType, error) {
|
|
f := runtime.FuncForPC(factory.Pointer())
|
|
|
|
pkgPath, err := funcNameToPkgPath(f.Name())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
factoryName := strings.TrimPrefix(f.Name(), pkgPath+".")
|
|
|
|
text, err := r.getModuleTypeDoc(pkgPath, factoryName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ModuleType{
|
|
Name: name,
|
|
PkgPath: pkgPath,
|
|
Text: formatText(text),
|
|
}, nil
|
|
}
|
|
|
|
// Return the PropertyStruct associated with a property struct type. The type should be in the
|
|
// format <package path>.<type name>
|
|
func (r *Reader) propertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) {
|
|
ps := r.getPropertyStruct(pkgPath, name)
|
|
|
|
if ps == nil {
|
|
pkg, err := r.goPkg(pkgPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, t := range pkg.Types {
|
|
if t.Name == name {
|
|
ps, err = newPropertyStruct(t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ps = r.putPropertyStruct(pkgPath, name, ps)
|
|
}
|
|
}
|
|
}
|
|
|
|
if ps == nil {
|
|
return nil, fmt.Errorf("package %q type %q not found", pkgPath, name)
|
|
}
|
|
|
|
ps = ps.Clone()
|
|
ps.SetDefaults(defaults)
|
|
|
|
return ps, nil
|
|
}
|
|
|
|
// Return the PropertyStruct associated with a struct type using recursion
|
|
// This method is useful since golang structs created using reflection have an empty PkgPath()
|
|
func (r *Reader) PropertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) {
|
|
var props []Property
|
|
|
|
// Base case: primitive type
|
|
if defaults.Kind() != reflect.Struct || proptools.IsConfigurable(defaults.Type()) {
|
|
props = append(props, Property{Name: name,
|
|
Type: defaults.Type().String()})
|
|
return &PropertyStruct{Properties: props}, nil
|
|
}
|
|
|
|
// Base case: use r.propertyStruct if struct has a non empty pkgpath
|
|
if pkgPath != "" {
|
|
return r.propertyStruct(pkgPath, name, defaults)
|
|
}
|
|
|
|
numFields := defaults.NumField()
|
|
for i := 0; i < numFields; i++ {
|
|
field := defaults.Type().Field(i)
|
|
// Recurse
|
|
ps, err := r.PropertyStruct(field.Type.PkgPath(), field.Type.Name(), reflect.New(field.Type).Elem())
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
prop := Property{
|
|
Name: strings.ToLower(field.Name),
|
|
Text: formatText(ps.Text),
|
|
Type: field.Type.Name(),
|
|
Properties: ps.Properties,
|
|
}
|
|
props = append(props, prop)
|
|
}
|
|
return &PropertyStruct{Properties: props}, nil
|
|
}
|
|
|
|
func (r *Reader) getModuleTypeDoc(pkgPath, factoryFuncName string) (string, error) {
|
|
goPkg, err := r.goPkg(pkgPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, fn := range goPkg.Funcs {
|
|
if fn.Name == factoryFuncName {
|
|
return fn.Doc, nil
|
|
}
|
|
}
|
|
|
|
// The doc package may associate the method with the type it returns, so iterate through those too
|
|
for _, typ := range goPkg.Types {
|
|
for _, fn := range typ.Funcs {
|
|
if fn.Name == factoryFuncName {
|
|
return fn.Doc, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func (r *Reader) getPropertyStruct(pkgPath, name string) *PropertyStruct {
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
|
|
name = pkgPath + "." + name
|
|
|
|
return r.ps[name]
|
|
}
|
|
|
|
func (r *Reader) putPropertyStruct(pkgPath, name string, ps *PropertyStruct) *PropertyStruct {
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
|
|
name = pkgPath + "." + name
|
|
|
|
if r.ps[name] != nil {
|
|
return r.ps[name]
|
|
} else {
|
|
r.ps[name] = ps
|
|
return ps
|
|
}
|
|
}
|
|
|
|
// Package AST generation and storage
|
|
func (r *Reader) goPkg(pkgPath string) (*doc.Package, error) {
|
|
pkg := r.getGoPkg(pkgPath)
|
|
if pkg == nil {
|
|
if files, ok := r.pkgFiles[pkgPath]; ok {
|
|
var err error
|
|
pkgAST, err := packageAST(files)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pkg = doc.New(pkgAST, pkgPath, doc.AllDecls)
|
|
pkg = r.putGoPkg(pkgPath, pkg)
|
|
} else {
|
|
return nil, fmt.Errorf("unknown package %q", pkgPath)
|
|
}
|
|
}
|
|
return pkg, nil
|
|
}
|
|
|
|
func (r *Reader) getGoPkg(pkgPath string) *doc.Package {
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
|
|
return r.goPkgs[pkgPath]
|
|
}
|
|
|
|
func (r *Reader) putGoPkg(pkgPath string, pkg *doc.Package) *doc.Package {
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
|
|
if r.goPkgs[pkgPath] != nil {
|
|
return r.goPkgs[pkgPath]
|
|
} else {
|
|
r.goPkgs[pkgPath] = pkg
|
|
return pkg
|
|
}
|
|
}
|
|
|
|
// A regex to find a package path within a function name. It finds the shortest string that is
|
|
// followed by '.' and doesn't have any '/'s left.
|
|
var pkgPathRe = regexp.MustCompile("^(.*?)\\.[^/]+$")
|
|
|
|
func funcNameToPkgPath(f string) (string, error) {
|
|
s := pkgPathRe.FindStringSubmatch(f)
|
|
if len(s) < 2 {
|
|
return "", fmt.Errorf("failed to extract package path from %q", f)
|
|
}
|
|
return s[1], nil
|
|
}
|
|
|
|
func packageAST(files []string) (*ast.Package, error) {
|
|
asts := make(map[string]*ast.File)
|
|
|
|
fset := token.NewFileSet()
|
|
for _, file := range files {
|
|
ast, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
asts[file] = ast
|
|
}
|
|
|
|
pkg, _ := ast.NewPackage(fset, asts, nil, nil)
|
|
return pkg, nil
|
|
}
|