xref: /aosp_15_r20/build/blueprint/bootstrap/bpdoc/reader.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
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