Add self-documenting support

The primary builder will now generate a rule to call itself with
--docs=.bootstrap/docs/<name>.html to produce an automatically
generated documentation file.

The documentation generation process is:
 - Call each factory once to get empty property structs associated
   with the module type
 - Use reflection to determine the names of the type of each property
   struct
 - Use the bootstrap_go_package modules from reading the Blueprints files
   to find the source files for each Go package used to build the primary
   builder
 - Use the go/parser module to find the type declaration for each
   property struct
 - Extract comments for the property struct and each property declaration
 - Format all the comments into HTML

Change-Id: Icae9307cc10549a30bfc14d6922824099de5a9b0
This commit is contained in:
Colin Cross 2015-05-13 14:36:24 -07:00
parent 4adc819490
commit 4572edddfa
10 changed files with 948 additions and 44 deletions

View file

@ -73,6 +73,7 @@ bootstrap_go_package(
"blueprint",
"blueprint-deptools",
"blueprint-pathtools",
"blueprint-bootstrap-bpdoc",
],
pkgPath = "github.com/google/blueprint/bootstrap",
srcs = [
@ -81,6 +82,19 @@ bootstrap_go_package(
"bootstrap/command.go",
"bootstrap/config.go",
"bootstrap/doc.go",
"bootstrap/writedocs.go",
],
)
bootstrap_go_package(
name = "blueprint-bootstrap-bpdoc",
deps = [
"blueprint",
"blueprint-proptools",
],
pkgPath = "github.com/google/blueprint/bootstrap/bpdoc",
srcs = [
"bootstrap/bpdoc/bpdoc.go",
],
)

View file

@ -16,12 +16,13 @@ package bootstrap
import (
"fmt"
"github.com/google/blueprint"
"github.com/google/blueprint/pathtools"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/google/blueprint"
"github.com/google/blueprint/pathtools"
)
const bootstrapDir = ".bootstrap"
@ -116,6 +117,8 @@ var (
BinDir = filepath.Join(bootstrapDir, "bin")
minibpFile = filepath.Join(BinDir, "minibp")
docsDir = filepath.Join(bootstrapDir, "docs")
)
type goPackageProducer interface {
@ -505,13 +508,13 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// creating the binary that we'll use to generate the non-bootstrap
// build.ninja file.
var primaryBuilders []*goBinary
var allGoBinaries []string
var rebootstrapDeps []string
ctx.VisitAllModulesIf(isBootstrapBinaryModule,
func(module blueprint.Module) {
binaryModule := module.(*goBinary)
binaryModuleName := ctx.ModuleName(binaryModule)
binaryModulePath := filepath.Join(BinDir, binaryModuleName)
allGoBinaries = append(allGoBinaries, binaryModulePath)
rebootstrapDeps = append(rebootstrapDeps, binaryModulePath)
if binaryModule.properties.PrimaryBuilder {
primaryBuilders = append(primaryBuilders, binaryModule)
}
@ -552,6 +555,9 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
mainNinjaFile := filepath.Join(bootstrapDir, "main.ninja.in")
mainNinjaDepFile := mainNinjaFile + ".d"
bootstrapNinjaFile := filepath.Join(bootstrapDir, "bootstrap.ninja.in")
docsFile := filepath.Join(docsDir, primaryBuilderName+".html")
rebootstrapDeps = append(rebootstrapDeps, docsFile)
if s.config.generatingBootstrapper {
// We're generating a bootstrapper Ninja file, so we need to set things
@ -564,6 +570,24 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// two Ninja processes try to write to the same log concurrently.
ctx.SetBuildDir(pctx, bootstrapDir)
// Generate build system docs for the primary builder. Generating docs reads the source
// files used to build the primary builder, but that dependency will be picked up through
// the dependency on the primary builder itself. There are no dependencies on the
// Blueprints files, as any relevant changes to the Blueprints files would have caused
// a rebuild of the primary builder.
bigbpDocs := ctx.Rule(pctx, "bigbpDocs",
blueprint.RuleParams{
Command: fmt.Sprintf("%s %s --docs $out %s", primaryBuilderFile,
primaryBuilderExtraFlags, topLevelBlueprints),
Description: fmt.Sprintf("%s docs $out", primaryBuilderName),
})
ctx.Build(pctx, blueprint.BuildParams{
Rule: bigbpDocs,
Outputs: []string{docsFile},
Implicits: []string{primaryBuilderFile},
})
// We generate the depfile here that includes the dependencies for all
// the Blueprints files that contribute to generating the big build
// manifest (build.ninja file). This depfile will be used by the non-
@ -584,7 +608,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
Rule: bigbp,
Outputs: []string{mainNinjaFile},
Inputs: []string{topLevelBlueprints},
Implicits: allGoBinaries,
Implicits: rebootstrapDeps,
})
// When the current build.ninja file is a bootstrapper, we always want
@ -639,13 +663,12 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
Args: args,
})
} else {
var allGoTests []string
ctx.VisitAllModulesIf(isGoTestProducer,
func(module blueprint.Module) {
testModule := module.(goTestProducer)
target := testModule.GoTestTarget()
if target != "" {
allGoTests = append(allGoTests, target)
rebootstrapDeps = append(rebootstrapDeps, target)
}
})
@ -661,8 +684,8 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// rule. We do this by depending on that file and then setting up a
// phony rule to generate it that uses the depfile.
buildNinjaDeps := []string{"$bootstrapCmd", mainNinjaFile}
buildNinjaDeps = append(buildNinjaDeps, allGoBinaries...)
buildNinjaDeps = append(buildNinjaDeps, allGoTests...)
buildNinjaDeps = append(buildNinjaDeps, rebootstrapDeps...)
ctx.Build(pctx, blueprint.BuildParams{
Rule: rebootstrap,
Outputs: []string{"build.ninja"},
@ -679,6 +702,12 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
},
})
ctx.Build(pctx, blueprint.BuildParams{
Rule: phony,
Outputs: []string{docsFile},
Implicits: []string{primaryBuilderFile},
})
// If the bootstrap Ninja invocation caused a new bootstrapNinjaFile to be
// generated then that means we need to rebootstrap using it instead of
// the current bootstrap manifest. We enable the Ninja "generator"

698
bootstrap/bpdoc/bpdoc.go Normal file
View file

@ -0,0 +1,698 @@
package bpdoc
import (
"bytes"
"fmt"
"go/ast"
"go/doc"
"go/parser"
"go/token"
"io/ioutil"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"text/template"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
)
type DocCollector struct {
pkgFiles map[string][]string // Map of package name to source files, provided by constructor
mutex sync.Mutex
pkgDocs map[string]*doc.Package // Map of package name to parsed Go AST, protected by mutex
docs map[string]*PropertyStructDocs // Map of type name to docs, protected by mutex
}
func NewDocCollector(pkgFiles map[string][]string) *DocCollector {
return &DocCollector{
pkgFiles: pkgFiles,
pkgDocs: make(map[string]*doc.Package),
docs: make(map[string]*PropertyStructDocs),
}
}
// Return the PropertyStructDocs associated with a property struct type. The type should be in the
// format <package path>.<type name>
func (dc *DocCollector) Docs(pkg, name string, defaults reflect.Value) (*PropertyStructDocs, error) {
docs := dc.getDocs(name)
if docs == nil {
pkgDocs, err := dc.packageDocs(pkg)
if err != nil {
return nil, err
}
for _, t := range pkgDocs.Types {
if t.Name == name {
docs, err = newDocs(t)
if err != nil {
return nil, err
}
docs = dc.putDocs(name, docs)
}
}
}
if docs == nil {
return nil, fmt.Errorf("package %q type %q not found", pkg, name)
}
docs = docs.Clone()
docs.SetDefaults(defaults)
return docs, nil
}
func (dc *DocCollector) getDocs(name string) *PropertyStructDocs {
dc.mutex.Lock()
defer dc.mutex.Unlock()
return dc.docs[name]
}
func (dc *DocCollector) putDocs(name string, docs *PropertyStructDocs) *PropertyStructDocs {
dc.mutex.Lock()
defer dc.mutex.Unlock()
if dc.docs[name] != nil {
return dc.docs[name]
} else {
dc.docs[name] = docs
return docs
}
}
type PropertyStructDocs struct {
Name string
Text string
Properties []PropertyDocs
}
type PropertyDocs struct {
Name string
OtherNames []string
Type string
Tag reflect.StructTag
Text string
OtherTexts []string
Properties []PropertyDocs
Default string
}
func (docs *PropertyStructDocs) Clone() *PropertyStructDocs {
ret := *docs
ret.Properties = append([]PropertyDocs(nil), ret.Properties...)
for i, prop := range ret.Properties {
ret.Properties[i] = prop.Clone()
}
return &ret
}
func (docs *PropertyDocs) Clone() PropertyDocs {
ret := *docs
ret.Properties = append([]PropertyDocs(nil), ret.Properties...)
for i, prop := range ret.Properties {
ret.Properties[i] = prop.Clone()
}
return ret
}
func (docs *PropertyDocs) Equal(other PropertyDocs) bool {
return docs.Name == other.Name && docs.Type == other.Type && docs.Tag == other.Tag &&
docs.Text == other.Text && docs.Default == other.Default &&
stringArrayEqual(docs.OtherNames, other.OtherNames) &&
stringArrayEqual(docs.OtherTexts, other.OtherTexts) &&
docs.SameSubProperties(other)
}
func (docs *PropertyStructDocs) SetDefaults(defaults reflect.Value) {
setDefaults(docs.Properties, defaults)
}
func setDefaults(properties []PropertyDocs, defaults reflect.Value) {
for i := range properties {
prop := &properties[i]
fieldName := proptools.FieldNameForProperty(prop.Name)
f := defaults.FieldByName(fieldName)
if (f == reflect.Value{}) {
panic(fmt.Errorf("property %q does not exist in %q", fieldName, defaults.Type()))
}
if reflect.DeepEqual(f.Interface(), reflect.Zero(f.Type()).Interface()) {
continue
}
if f.Type().Kind() == reflect.Interface {
f = f.Elem()
}
if f.Type().Kind() == reflect.Ptr {
f = f.Elem()
}
if f.Type().Kind() == reflect.Struct {
setDefaults(prop.Properties, f)
} else {
prop.Default = fmt.Sprintf("%v", f.Interface())
}
}
}
func stringArrayEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func (docs *PropertyDocs) SameSubProperties(other PropertyDocs) bool {
if len(docs.Properties) != len(other.Properties) {
return false
}
for i := range docs.Properties {
if !docs.Properties[i].Equal(other.Properties[i]) {
return false
}
}
return true
}
func (docs *PropertyStructDocs) GetByName(name string) *PropertyDocs {
return getByName(name, "", &docs.Properties)
}
func getByName(name string, prefix string, props *[]PropertyDocs) *PropertyDocs {
for i := range *props {
if prefix+(*props)[i].Name == name {
return &(*props)[i]
} else if strings.HasPrefix(name, prefix+(*props)[i].Name+".") {
return getByName(name, prefix+(*props)[i].Name+".", &(*props)[i].Properties)
}
}
return nil
}
func (prop *PropertyDocs) Nest(nested *PropertyStructDocs) {
//prop.Name += "(" + nested.Name + ")"
//prop.Text += "(" + nested.Text + ")"
prop.Properties = append(prop.Properties, nested.Properties...)
}
func newDocs(t *doc.Type) (*PropertyStructDocs, error) {
typeSpec := t.Decl.Specs[0].(*ast.TypeSpec)
docs := PropertyStructDocs{
Name: t.Name,
Text: t.Doc,
}
structType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
return nil, fmt.Errorf("type of %q is not a struct", t.Name)
}
var err error
docs.Properties, err = structProperties(structType)
if err != nil {
return nil, err
}
return &docs, nil
}
func structProperties(structType *ast.StructType) (props []PropertyDocs, err error) {
for _, f := range structType.Fields.List {
//fmt.Printf("%T %#v\n", f, f)
for _, n := range f.Names {
var name, typ, tag, text string
var innerProps []PropertyDocs
if n != nil {
name = proptools.PropertyNameForField(n.Name)
}
if f.Doc != nil {
text = f.Doc.Text()
}
if f.Tag != nil {
tag, err = strconv.Unquote(f.Tag.Value)
if err != nil {
return nil, err
}
}
switch a := f.Type.(type) {
case *ast.ArrayType:
typ = "list of strings"
case *ast.InterfaceType:
typ = "interface"
case *ast.Ident:
typ = a.Name
case *ast.StructType:
innerProps, err = structProperties(a)
if err != nil {
return nil, err
}
default:
typ = fmt.Sprintf("%T", f.Type)
}
props = append(props, PropertyDocs{
Name: name,
Type: typ,
Tag: reflect.StructTag(tag),
Text: text,
Properties: innerProps,
})
}
}
return props, nil
}
func (docs *PropertyStructDocs) ExcludeByTag(key, value string) {
filterPropsByTag(&docs.Properties, key, value, true)
}
func (docs *PropertyStructDocs) IncludeByTag(key, value string) {
filterPropsByTag(&docs.Properties, key, value, false)
}
func filterPropsByTag(props *[]PropertyDocs, key, value string, exclude bool) {
// Create a slice that shares the storage of props but has 0 length. Appending up to
// len(props) times to this slice will overwrite the original slice contents
filtered := (*props)[:0]
for _, x := range *props {
tag := x.Tag.Get(key)
for _, entry := range strings.Split(tag, ",") {
if (entry == value) == !exclude {
filtered = append(filtered, x)
}
}
}
*props = filtered
}
// Package AST generation and storage
func (dc *DocCollector) packageDocs(pkg string) (*doc.Package, error) {
pkgDocs := dc.getPackageDocs(pkg)
if pkgDocs == nil {
if files, ok := dc.pkgFiles[pkg]; ok {
var err error
pkgAST, err := NewPackageAST(files)
if err != nil {
return nil, err
}
pkgDocs = doc.New(pkgAST, pkg, doc.AllDecls)
pkgDocs = dc.putPackageDocs(pkg, pkgDocs)
} else {
return nil, fmt.Errorf("unknown package %q", pkg)
}
}
return pkgDocs, nil
}
func (dc *DocCollector) getPackageDocs(pkg string) *doc.Package {
dc.mutex.Lock()
defer dc.mutex.Unlock()
return dc.pkgDocs[pkg]
}
func (dc *DocCollector) putPackageDocs(pkg string, pkgDocs *doc.Package) *doc.Package {
dc.mutex.Lock()
defer dc.mutex.Unlock()
if dc.pkgDocs[pkg] != nil {
return dc.pkgDocs[pkg]
} else {
dc.pkgDocs[pkg] = pkgDocs
return pkgDocs
}
}
func NewPackageAST(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
}
func Write(filename string, pkgFiles map[string][]string,
moduleTypePropertyStructs map[string][]interface{}) error {
docSet := NewDocCollector(pkgFiles)
var moduleTypeList []*moduleTypeDoc
for moduleType, propertyStructs := range moduleTypePropertyStructs {
mtDoc, err := getModuleTypeDoc(docSet, moduleType, propertyStructs)
if err != nil {
return err
}
removeEmptyPropertyStructs(mtDoc)
collapseDuplicatePropertyStructs(mtDoc)
collapseNestedPropertyStructs(mtDoc)
combineDuplicateProperties(mtDoc)
moduleTypeList = append(moduleTypeList, mtDoc)
}
sort.Sort(moduleTypeByName(moduleTypeList))
buf := &bytes.Buffer{}
unique := 0
tmpl, err := template.New("file").Funcs(map[string]interface{}{
"unique": func() int {
unique++
return unique
}}).Parse(fileTemplate)
if err != nil {
return err
}
err = tmpl.Execute(buf, moduleTypeList)
if err != nil {
return err
}
err = ioutil.WriteFile(filename, buf.Bytes(), 0666)
if err != nil {
return err
}
return nil
}
func getModuleTypeDoc(docSet *DocCollector, moduleType string,
propertyStructs []interface{}) (*moduleTypeDoc, error) {
mtDoc := &moduleTypeDoc{
Name: moduleType,
//Text: docSet.ModuleTypeDocs(moduleType),
}
for _, s := range propertyStructs {
v := reflect.ValueOf(s).Elem()
t := v.Type()
// Ignore property structs with unexported or unnamed types
if t.PkgPath() == "" {
continue
}
psDoc, err := docSet.Docs(t.PkgPath(), t.Name(), v)
if err != nil {
return nil, err
}
psDoc.ExcludeByTag("blueprint", "mutated")
for nested, nestedValue := range nestedPropertyStructs(v) {
nestedType := nestedValue.Type()
// Ignore property structs with unexported or unnamed types
if nestedType.PkgPath() == "" {
continue
}
nestedDoc, err := docSet.Docs(nestedType.PkgPath(), nestedType.Name(), nestedValue)
if err != nil {
return nil, err
}
nestedDoc.ExcludeByTag("blueprint", "mutated")
nestPoint := psDoc.GetByName(nested)
if nestPoint == nil {
return nil, fmt.Errorf("nesting point %q not found", nested)
}
key, value, err := blueprint.HasFilter(nestPoint.Tag)
if err != nil {
return nil, err
}
if key != "" {
nestedDoc.IncludeByTag(key, value)
}
nestPoint.Nest(nestedDoc)
}
mtDoc.PropertyStructs = append(mtDoc.PropertyStructs, psDoc)
}
return mtDoc, nil
}
func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value {
ret := make(map[string]reflect.Value)
var walk func(structValue reflect.Value, prefix string)
walk = func(structValue reflect.Value, prefix string) {
typ := structValue.Type()
for i := 0; i < structValue.NumField(); i++ {
field := typ.Field(i)
if field.PkgPath != "" {
// The field is not exported so just skip it.
continue
}
fieldValue := structValue.Field(i)
switch fieldValue.Kind() {
case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
// Nothing
case reflect.Struct:
walk(fieldValue, prefix+proptools.PropertyNameForField(field.Name)+".")
case reflect.Ptr, reflect.Interface:
if !fieldValue.IsNil() {
// We leave the pointer intact and zero out the struct that's
// pointed to.
elem := fieldValue.Elem()
if fieldValue.Kind() == reflect.Interface {
if elem.Kind() != reflect.Ptr {
panic(fmt.Errorf("can't get type of field %q: interface "+
"refers to a non-pointer", field.Name))
}
elem = elem.Elem()
}
if elem.Kind() != reflect.Struct {
panic(fmt.Errorf("can't get type of field %q: points to a "+
"non-struct", field.Name))
}
nestPoint := prefix + proptools.PropertyNameForField(field.Name)
ret[nestPoint] = elem
walk(elem, nestPoint+".")
}
default:
panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
field.Name, fieldValue.Kind()))
}
}
}
walk(s, "")
return ret
}
// Remove any property structs that have no exported fields
func removeEmptyPropertyStructs(mtDoc *moduleTypeDoc) {
for i := 0; i < len(mtDoc.PropertyStructs); i++ {
if len(mtDoc.PropertyStructs[i].Properties) == 0 {
mtDoc.PropertyStructs = append(mtDoc.PropertyStructs[:i], mtDoc.PropertyStructs[i+1:]...)
i--
}
}
}
// Squashes duplicates of the same property struct into single entries
func collapseDuplicatePropertyStructs(mtDoc *moduleTypeDoc) {
var collapsedDocs []*PropertyStructDocs
propertyStructLoop:
for _, from := range mtDoc.PropertyStructs {
for _, to := range collapsedDocs {
if from.Name == to.Name {
collapseDuplicateProperties(&to.Properties, &from.Properties)
continue propertyStructLoop
}
}
collapsedDocs = append(collapsedDocs, from)
}
mtDoc.PropertyStructs = collapsedDocs
}
func collapseDuplicateProperties(to, from *[]PropertyDocs) {
propertyLoop:
for _, f := range *from {
for i := range *to {
t := &(*to)[i]
if f.Name == t.Name {
collapseDuplicateProperties(&t.Properties, &f.Properties)
continue propertyLoop
}
}
*to = append(*to, f)
}
}
// Find all property structs that only contain structs, and move their children up one with
// a prefixed name
func collapseNestedPropertyStructs(mtDoc *moduleTypeDoc) {
for _, ps := range mtDoc.PropertyStructs {
collapseNestedProperties(&ps.Properties)
}
}
func collapseNestedProperties(p *[]PropertyDocs) {
var n []PropertyDocs
for _, parent := range *p {
var containsProperty bool
for j := range parent.Properties {
child := &parent.Properties[j]
if len(child.Properties) > 0 {
collapseNestedProperties(&child.Properties)
} else {
containsProperty = true
}
}
if containsProperty || len(parent.Properties) == 0 {
n = append(n, parent)
} else {
for j := range parent.Properties {
child := parent.Properties[j]
child.Name = parent.Name + "." + child.Name
n = append(n, child)
}
}
}
*p = n
}
func combineDuplicateProperties(mtDoc *moduleTypeDoc) {
for _, ps := range mtDoc.PropertyStructs {
combineDuplicateSubProperties(&ps.Properties)
}
}
func combineDuplicateSubProperties(p *[]PropertyDocs) {
var n []PropertyDocs
propertyLoop:
for _, child := range *p {
if len(child.Properties) > 0 {
combineDuplicateSubProperties(&child.Properties)
for i := range n {
s := &n[i]
if s.SameSubProperties(child) {
s.OtherNames = append(s.OtherNames, child.Name)
s.OtherTexts = append(s.OtherTexts, child.Text)
continue propertyLoop
}
}
}
n = append(n, child)
}
*p = n
}
type moduleTypeByName []*moduleTypeDoc
func (l moduleTypeByName) Len() int { return len(l) }
func (l moduleTypeByName) Less(i, j int) bool { return l[i].Name < l[j].Name }
func (l moduleTypeByName) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
type moduleTypeDoc struct {
Name string
Text string
PropertyStructs []*PropertyStructDocs
}
var (
fileTemplate = `
<html>
<head>
<title>Build Docs</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head>
<body>
<h1>Build Docs</h1>
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
{{range .}}
{{ $collapseIndex := unique }}
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="heading{{$collapseIndex}}">
<h2 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{{$collapseIndex}}" aria-expanded="false" aria-controls="collapse{{$collapseIndex}}">
{{.Name}}
</a>
</h2>
</div>
</div>
<div id="collapse{{$collapseIndex}}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading{{$collapseIndex}}">
<div class="panel-body">
<p>{{.Text}}</p>
{{range .PropertyStructs}}
<p>{{.Text}}</p>
{{template "properties" .Properties}}
{{end}}
</div>
</div>
{{end}}
</div>
</body>
</html>
{{define "properties"}}
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
{{range .}}
{{$collapseIndex := unique}}
{{if .Properties}}
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="heading{{$collapseIndex}}">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{{$collapseIndex}}" aria-expanded="false" aria-controls="collapse{{$collapseIndex}}">
{{.Name}}{{range .OtherNames}}, {{.}}{{end}}
</a>
</h4>
</div>
</div>
<div id="collapse{{$collapseIndex}}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading{{$collapseIndex}}">
<div class="panel-body">
<p>{{.Text}}</p>
{{range .OtherTexts}}<p>{{.}}</p>{{end}}
{{template "properties" .Properties}}
</div>
</div>
{{else}}
<div>
<h4>{{.Name}}{{range .OtherNames}}, {{.}}{{end}}</h4>
<p>{{.Text}}</p>
{{range .OtherTexts}}<p>{{.}}</p>{{end}}
<p><i>Type: {{.Type}}</i></p>
{{if .Default}}<p><i>Default: {{.Default}}</i></p>{{end}}
</div>
{{end}}
{{end}}
</div>
{{end}}
`
)

View file

@ -33,6 +33,7 @@ var (
depFile string
checkFile string
manifestFile string
docFile string
cpuprofile string
runGoTests bool
)
@ -42,6 +43,7 @@ func init() {
flag.StringVar(&depFile, "d", "", "the dependency file to output")
flag.StringVar(&checkFile, "c", "", "the existing file to check against")
flag.StringVar(&manifestFile, "m", "", "the bootstrap manifest file")
flag.StringVar(&docFile, "docs", "", "build documentation file to output")
flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to file")
flag.BoolVar(&runGoTests, "t", false, "build and run go tests during bootstrap")
}
@ -90,6 +92,19 @@ func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...stri
// Add extra ninja file dependencies
deps = append(deps, extraNinjaFileDeps...)
errs = ctx.ResolveDependencies(config)
if len(errs) > 0 {
fatalErrors(errs)
}
if docFile != "" {
err := writeDocs(ctx, filepath.Dir(bootstrapConfig.topLevelBlueprintsFile), docFile)
if err != nil {
fatalErrors([]error{err})
}
return
}
extraDeps, errs := ctx.PrepareBuildActions(config)
if len(errs) > 0 {
fatalErrors(errs)

59
bootstrap/writedocs.go Normal file
View file

@ -0,0 +1,59 @@
package bootstrap
import (
"fmt"
"path/filepath"
"github.com/google/blueprint"
"github.com/google/blueprint/bootstrap/bpdoc"
"github.com/google/blueprint/pathtools"
)
func writeDocs(ctx *blueprint.Context, srcDir, filename string) error {
// Find the module that's marked as the "primary builder", which means it's
// creating the binary that we'll use to generate the non-bootstrap
// build.ninja file.
var primaryBuilders []*goBinary
var minibp *goBinary
ctx.VisitAllModulesIf(isBootstrapBinaryModule,
func(module blueprint.Module) {
binaryModule := module.(*goBinary)
if binaryModule.properties.PrimaryBuilder {
primaryBuilders = append(primaryBuilders, binaryModule)
}
if ctx.ModuleName(binaryModule) == "minibp" {
minibp = binaryModule
}
})
if minibp == nil {
panic("missing minibp")
}
var primaryBuilder *goBinary
switch len(primaryBuilders) {
case 0:
// If there's no primary builder module then that means we'll use minibp
// as the primary builder.
primaryBuilder = minibp
case 1:
primaryBuilder = primaryBuilders[0]
default:
return fmt.Errorf("multiple primary builder modules present")
}
pkgFiles := make(map[string][]string)
ctx.VisitDepsDepthFirst(primaryBuilder, func(module blueprint.Module) {
switch m := module.(type) {
case (*goPackage):
pkgFiles[m.properties.PkgPath] = pathtools.PrefixPaths(m.properties.Srcs,
filepath.Join(srcDir, ctx.ModuleDir(m)))
default:
panic(fmt.Errorf("unknown dependency type %T", module))
}
})
return bpdoc.Write(filename, pkgFiles, ctx.ModuleTypePropertyStructs())
}

View file

@ -84,17 +84,39 @@ build $
${g.bootstrap.srcDir}/bootstrap/cleanup.go $
${g.bootstrap.srcDir}/bootstrap/command.go $
${g.bootstrap.srcDir}/bootstrap/config.go $
${g.bootstrap.srcDir}/bootstrap/doc.go | ${g.bootstrap.gcCmd} $
${g.bootstrap.srcDir}/bootstrap/doc.go $
${g.bootstrap.srcDir}/bootstrap/writedocs.go | ${g.bootstrap.gcCmd} $
.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a
incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg
.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a
incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-bootstrap-bpdoc/pkg
pkgPath = github.com/google/blueprint/bootstrap
default $
.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Module: blueprint-bootstrap-bpdoc
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.func·002
# Defined: Blueprints:89:1
build $
.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
: g.bootstrap.gc ${g.bootstrap.srcDir}/bootstrap/bpdoc/bpdoc.go | $
${g.bootstrap.gcCmd} $
.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
.bootstrap/blueprint/pkg/github.com/google/blueprint.a
incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg
pkgPath = github.com/google/blueprint/bootstrap/bpdoc
default $
.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Module: blueprint-deptools
# Variant:
@ -159,7 +181,7 @@ default $
# Variant:
# Type: bootstrap_go_binary
# Factory: github.com/google/blueprint/bootstrap.func·003
# Defined: Blueprints:96:1
# Defined: Blueprints:110:1
build .bootstrap/bpfmt/obj/bpfmt.a: g.bootstrap.gc $
${g.bootstrap.srcDir}/bpfmt/bpfmt.go | ${g.bootstrap.gcCmd} $
@ -181,7 +203,7 @@ default .bootstrap/bin/bpfmt
# Variant:
# Type: bootstrap_go_binary
# Factory: github.com/google/blueprint/bootstrap.func·003
# Defined: Blueprints:102:1
# Defined: Blueprints:116:1
build .bootstrap/bpmodify/obj/bpmodify.a: g.bootstrap.gc $
${g.bootstrap.srcDir}/bpmodify/bpmodify.go | ${g.bootstrap.gcCmd} $
@ -203,7 +225,7 @@ default .bootstrap/bin/bpmodify
# Variant:
# Type: bootstrap_go_binary
# Factory: github.com/google/blueprint/bootstrap.func·003
# Defined: Blueprints:108:1
# Defined: Blueprints:122:1
build .bootstrap/gotestmain/obj/gotestmain.a: g.bootstrap.gc $
${g.bootstrap.srcDir}/gotestmain/gotestmain.go | ${g.bootstrap.gcCmd}
@ -222,7 +244,7 @@ default .bootstrap/bin/gotestmain
# Variant:
# Type: bootstrap_go_binary
# Factory: github.com/google/blueprint/bootstrap.func·003
# Defined: Blueprints:87:1
# Defined: Blueprints:101:1
build .bootstrap/minibp/obj/minibp.a: g.bootstrap.gc $
${g.bootstrap.srcDir}/bootstrap/minibp/main.go | ${g.bootstrap.gcCmd} $
@ -231,14 +253,15 @@ build .bootstrap/minibp/obj/minibp.a: g.bootstrap.gc $
.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a
incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-bootstrap/pkg
incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-bootstrap-bpdoc/pkg -I .bootstrap/blueprint-bootstrap/pkg
pkgPath = minibp
default .bootstrap/minibp/obj/minibp.a
build .bootstrap/minibp/obj/a.out: g.bootstrap.link $
.bootstrap/minibp/obj/minibp.a | ${g.bootstrap.linkCmd}
libDirFlags = -L .bootstrap/blueprint-parser/pkg -L .bootstrap/blueprint-pathtools/pkg -L .bootstrap/blueprint-proptools/pkg -L .bootstrap/blueprint/pkg -L .bootstrap/blueprint-deptools/pkg -L .bootstrap/blueprint-bootstrap/pkg
libDirFlags = -L .bootstrap/blueprint-parser/pkg -L .bootstrap/blueprint-pathtools/pkg -L .bootstrap/blueprint-proptools/pkg -L .bootstrap/blueprint/pkg -L .bootstrap/blueprint-deptools/pkg -L .bootstrap/blueprint-bootstrap-bpdoc/pkg -L .bootstrap/blueprint-bootstrap/pkg
default .bootstrap/minibp/obj/a.out
build .bootstrap/bin/minibp: g.bootstrap.cp .bootstrap/minibp/obj/a.out
@ -248,6 +271,10 @@ default .bootstrap/bin/minibp
# Singleton: bootstrap
# Factory: github.com/google/blueprint/bootstrap.func·008
rule s.bootstrap.bigbpDocs
command = .bootstrap/bin/minibp -p --docs ${out} ${g.bootstrap.srcDir}/Blueprints
description = minibp docs ${out}
rule s.bootstrap.bigbp
command = .bootstrap/bin/minibp -p -d .bootstrap/main.ninja.in.d -m ${g.bootstrap.bootstrapManifest} -o ${out} ${in}
depfile = .bootstrap/main.ninja.in.d
@ -259,10 +286,13 @@ rule s.bootstrap.minibp
description = minibp ${out}
generator = true
build .bootstrap/docs/minibp.html: s.bootstrap.bigbpDocs | $
.bootstrap/bin/minibp
default .bootstrap/docs/minibp.html
build .bootstrap/main.ninja.in: s.bootstrap.bigbp $
${g.bootstrap.srcDir}/Blueprints | .bootstrap/bin/bpfmt $
.bootstrap/bin/bpmodify .bootstrap/bin/gotestmain $
.bootstrap/bin/minibp
.bootstrap/bin/minibp .bootstrap/docs/minibp.html
default .bootstrap/main.ninja.in
build .bootstrap/notAFile: phony
default .bootstrap/notAFile

View file

@ -1000,7 +1000,12 @@ func (c *Context) addModules(modules []*moduleInfo) (errs []error) {
// objects via the Config method on the DynamicDependerModuleContext objects
// passed to their DynamicDependencies method.
func (c *Context) ResolveDependencies(config interface{}) []error {
errs := c.resolveDependencies(config)
errs := c.runEarlyMutators(config)
if len(errs) > 0 {
return errs
}
errs = c.resolveDependencies(config)
if len(errs) > 0 {
return errs
}
@ -1361,11 +1366,6 @@ func (c *Context) updateDependencies() (errs []error) {
func (c *Context) PrepareBuildActions(config interface{}) (deps []string, errs []error) {
c.buildActionsReady = false
errs = c.runEarlyMutators(config)
if len(errs) > 0 {
return nil, errs
}
if !c.dependenciesReady {
errs := c.ResolveDependencies(config)
if len(errs) > 0 {
@ -2006,6 +2006,64 @@ func (c *Context) AllTargets() (map[string]string, error) {
return targets, nil
}
// ModuleTypePropertyStructs returns a mapping from module type name to a list of pointers to
// property structs returned by the factory for that module type.
func (c *Context) ModuleTypePropertyStructs() map[string][]interface{} {
ret := make(map[string][]interface{})
for moduleType, factory := range c.moduleFactories {
_, ret[moduleType] = factory()
}
return ret
}
func (c *Context) ModuleName(logicModule Module) string {
module := c.moduleInfo[logicModule]
return module.properties.Name
}
func (c *Context) ModuleDir(logicModule Module) string {
module := c.moduleInfo[logicModule]
return filepath.Dir(module.relBlueprintsFile)
}
func (c *Context) BlueprintFile(logicModule Module) string {
module := c.moduleInfo[logicModule]
return module.relBlueprintsFile
}
func (c *Context) ModuleErrorf(logicModule Module, format string,
args ...interface{}) error {
module := c.moduleInfo[logicModule]
return &Error{
Err: fmt.Errorf(format, args...),
Pos: module.pos,
}
}
func (c *Context) VisitAllModules(visit func(Module)) {
c.visitAllModules(visit)
}
func (c *Context) VisitAllModulesIf(pred func(Module) bool,
visit func(Module)) {
c.visitAllModulesIf(pred, visit)
}
func (c *Context) VisitDepsDepthFirst(module Module,
visit func(Module)) {
c.visitDepsDepthFirst(c.moduleInfo[module], visit)
}
func (c *Context) VisitDepsDepthFirstIf(module Module,
pred func(Module) bool, visit func(Module)) {
c.visitDepsDepthFirstIf(c.moduleInfo[module], pred, visit)
}
// WriteBuildFile writes the Ninja manifeset text for the generated build
// actions to w. If this is called before PrepareBuildActions successfully
// completes then ErrBuildActionsNotReady is returned.

View file

@ -30,6 +30,15 @@ func PropertyNameForField(fieldName string) string {
return propertyName
}
func FieldNameForProperty(propertyName string) string {
r, size := utf8.DecodeRuneInString(propertyName)
fieldName := string(unicode.ToUpper(r))
if len(propertyName) > size {
fieldName += propertyName[size:]
}
return fieldName
}
func CloneProperties(structValue reflect.Value) reflect.Value {
result := reflect.New(structValue.Type())
CopyProperties(result.Elem(), structValue)

View file

@ -16,7 +16,6 @@ package blueprint
import (
"fmt"
"path/filepath"
)
type Singleton interface {
@ -71,28 +70,21 @@ func (s *singletonContext) Config() interface{} {
}
func (s *singletonContext) ModuleName(logicModule Module) string {
module := s.context.moduleInfo[logicModule]
return module.properties.Name
return s.context.ModuleName(logicModule)
}
func (s *singletonContext) ModuleDir(logicModule Module) string {
module := s.context.moduleInfo[logicModule]
return filepath.Dir(module.relBlueprintsFile)
return s.context.ModuleDir(logicModule)
}
func (s *singletonContext) BlueprintFile(logicModule Module) string {
module := s.context.moduleInfo[logicModule]
return module.relBlueprintsFile
return s.context.BlueprintFile(logicModule)
}
func (s *singletonContext) ModuleErrorf(logicModule Module, format string,
args ...interface{}) {
module := s.context.moduleInfo[logicModule]
s.errs = append(s.errs, &Error{
Err: fmt.Errorf(format, args...),
Pos: module.pos,
})
s.errs = append(s.errs, s.context.ModuleErrorf(logicModule, format, args...))
}
func (s *singletonContext) Errorf(format string, args ...interface{}) {
@ -153,25 +145,25 @@ func (s *singletonContext) SetBuildDir(pctx *PackageContext, value string) {
}
func (s *singletonContext) VisitAllModules(visit func(Module)) {
s.context.visitAllModules(visit)
s.context.VisitAllModules(visit)
}
func (s *singletonContext) VisitAllModulesIf(pred func(Module) bool,
visit func(Module)) {
s.context.visitAllModulesIf(pred, visit)
s.context.VisitAllModulesIf(pred, visit)
}
func (s *singletonContext) VisitDepsDepthFirst(module Module,
visit func(Module)) {
s.context.visitDepsDepthFirst(s.context.moduleInfo[module], visit)
s.context.VisitDepsDepthFirst(module, visit)
}
func (s *singletonContext) VisitDepsDepthFirstIf(module Module,
pred func(Module) bool, visit func(Module)) {
s.context.visitDepsDepthFirstIf(s.context.moduleInfo[module], pred, visit)
s.context.VisitDepsDepthFirstIf(module, pred, visit)
}
func (s *singletonContext) AddNinjaFileDeps(deps ...string) {

View file

@ -230,7 +230,7 @@ func unpackStructValue(namePrefix string, structValue reflect.Value,
fallthrough
case reflect.Struct:
localFilterKey, localFilterValue := filterKey, filterValue
if k, v, err := hasFilter(field); err != nil {
if k, v, err := HasFilter(field.Tag); err != nil {
errs = append(errs, err)
if len(errs) >= maxErrors {
return errs
@ -337,8 +337,8 @@ func hasTag(field reflect.StructField, name, value string) bool {
return false
}
func hasFilter(field reflect.StructField) (k, v string, err error) {
tag := field.Tag.Get("blueprint")
func HasFilter(field reflect.StructTag) (k, v string, err error) {
tag := field.Get("blueprint")
for _, entry := range strings.Split(tag, ",") {
if strings.HasPrefix(entry, "filter") {
if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") {