Document most of the blueprint package APIs.

This change adds docs to all the blueprint package APIs except for the Module
and Singleton types and their corresponding context types.

Change-Id: I74aa48c7743086ad79b3122d5813f5c4823c6519
This commit is contained in:
Jamie Gennis 2014-06-12 20:06:50 -07:00
parent ec7012824a
commit d4e1018e19
3 changed files with 298 additions and 34 deletions

View file

@ -19,6 +19,30 @@ var ErrBuildActionsNotReady = errors.New("build actions are not ready")
const maxErrors = 10 const maxErrors = 10
// A Context contains all the state needed to parse a set of Blueprints files
// and generate a Ninja file. The process of generating a Ninja file proceeds
// through a series of four phases. Each phase corresponds with a some methods
// on the Context object
//
// Phase Methods
// ------------ -------------------------------------------
// 1. Registration RegisterModuleType, RegisterSingleton
//
// 2. Parse ParseBlueprintsFiles, Parse
//
// 3. Generate ResovleDependencies, PrepareBuildActions
//
// 4. Write WriteBuildFile
//
// The registration phase prepares the context to process Blueprints files
// containing various types of modules. The parse phase reads in one or more
// Blueprints files and validates their contents against the module types that
// have been registered. The generate phase then analyzes the parsed Blueprints
// contents to create an internal representation for the build actions that must
// be performed. This phase also performs validation of the module dependencies
// and property values defined in the parsed Blueprints files. Finally, the
// write phase generates the Ninja manifest text based on the generated build
// actions.
type Context struct { type Context struct {
// set at instantiation // set at instantiation
moduleTypes map[string]ModuleType moduleTypes map[string]ModuleType
@ -45,9 +69,11 @@ type Context struct {
requiredNinjaMicro int // For the ninja_required_version variable requiredNinjaMicro int // For the ninja_required_version variable
} }
// An Error describes a problem that was encountered that is related to a
// particular location in a Blueprints file.
type Error struct { type Error struct {
Err error Err error // the error that occurred
Pos scanner.Position Pos scanner.Position // the relevant Blueprints file location
} }
type localBuildActions struct { type localBuildActions struct {
@ -88,6 +114,9 @@ func (e *Error) Error() string {
return fmt.Sprintf("%s: %s", e.Pos, e.Err) return fmt.Sprintf("%s: %s", e.Pos, e.Err)
} }
// NewContext creates a new Context object. The created context initially has
// no module types or singletons registered, so the RegisterModuleType and
// RegisterSingleton methods must be called before it can do anything useful.
func NewContext() *Context { func NewContext() *Context {
return &Context{ return &Context{
moduleTypes: make(map[string]ModuleType), moduleTypes: make(map[string]ModuleType),
@ -97,6 +126,18 @@ func NewContext() *Context {
} }
} }
// RegisterModuleType associates a module type name (which can appear in a
// Blueprints file) with a ModuleType object. When the given module type name
// is encountered in a Blueprints file during parsing, the ModuleType object
// will be used to instantiate a new Module object to handle the build action
// generation for the module.
//
// The module type names given here must be unique for the context. Note that
// these module type names are different from the name passed to MakeModuleType.
// The name given here is how the module type is referenced in a Blueprints
// file, while the name passed to MakeModuleType indicates the name of the Go
// ModuleType object (i.e. it's used to when reporting build logic problems to
// make finding the problematic code easier).
func (c *Context) RegisterModuleType(name string, typ ModuleType) { func (c *Context) RegisterModuleType(name string, typ ModuleType) {
if _, present := c.moduleTypes[name]; present { if _, present := c.moduleTypes[name]; present {
panic(errors.New("module type name is already registered")) panic(errors.New("module type name is already registered"))
@ -104,6 +145,9 @@ func (c *Context) RegisterModuleType(name string, typ ModuleType) {
c.moduleTypes[name] = typ c.moduleTypes[name] = typ
} }
// RegisterSingleton registers a singleton object that will be invoked to
// generate build actions. Each registered singleton is invoked exactly once as
// part of the generate phase.
func (c *Context) RegisterSingleton(name string, singleton Singleton) { func (c *Context) RegisterSingleton(name string, singleton Singleton) {
if _, present := c.singletonInfo[name]; present { if _, present := c.singletonInfo[name]; present {
panic(errors.New("singleton name is already registered")) panic(errors.New("singleton name is already registered"))
@ -132,10 +176,30 @@ func singletonTypeName(singleton Singleton) string {
return typ.PkgPath() + "." + typ.Name() return typ.PkgPath() + "." + typ.Name()
} }
// SetIgnoreUnknownModuleTypes sets the behavior of the context in the case
// where it encounters an unknown module type while parsing Blueprints files. By
// default, the context will report unknown module types as an error. If this
// method is called with ignoreUnknownModuleTypes set to true then the context
// will silently ignore unknown module types.
//
// This method should generally not be used. It exists to facilitate the
// bootstrapping process.
func (c *Context) SetIgnoreUnknownModuleTypes(ignoreUnknownModuleTypes bool) { func (c *Context) SetIgnoreUnknownModuleTypes(ignoreUnknownModuleTypes bool) {
c.ignoreUnknownModuleTypes = ignoreUnknownModuleTypes c.ignoreUnknownModuleTypes = ignoreUnknownModuleTypes
} }
// Parse parses a single Blueprints file from r, creating Module objects for
// each of the module definitions encountered. If the Blueprints file contains
// an assignment to the "subdirs" variable, then the subdirectories listed are
// returned in the subdirs first return value.
//
// rootDir specifies the path to the root directory of the source tree, while
// filename specifies the path to the Blueprints file. These paths are used for
// error reporting and for determining the module's directory.
//
// This method should probably not be used directly. It is provided to simplify
// testing. Instead ParseBlueprintsFiles should be called to parse a set of
// Blueprints files starting from a top-level Blueprints file.
func (c *Context) Parse(rootDir, filename string, r io.Reader) (subdirs []string, func (c *Context) Parse(rootDir, filename string, r io.Reader) (subdirs []string,
errs []error) { errs []error) {
@ -191,6 +255,15 @@ func (c *Context) Parse(rootDir, filename string, r io.Reader) (subdirs []string
return subdirs, errs return subdirs, errs
} }
// ParseBlueprintsFiles parses a set of Blueprints files starting with the file
// at rootFile. When it encounters a Blueprints file with a set of subdirs
// listed it recursively parses any Blueprints files found in those
// subdirectories.
//
// If no errors are encountered while parsing the files, the list of paths on
// which the future output will depend is returned. This list will include both
// Blueprints file paths as well as directory paths for cases where wildcard
// subdirs are found.
func (c *Context) ParseBlueprintsFiles(rootFile string) (deps []string, func (c *Context) ParseBlueprintsFiles(rootFile string) (deps []string,
errs []error) { errs []error) {
@ -433,6 +506,10 @@ func (c *Context) processModuleDef(moduleDef *parser.Module,
return nil return nil
} }
// ResolveDependencies checks that the dependencies specified by all of the
// modules defined in the parsed Blueprints files are valid. This means that
// the modules depended upon are defined and that no circular dependencies
// exist.
func (c *Context) ResolveDependencies() []error { func (c *Context) ResolveDependencies() []error {
errs := c.resolveDependencies() errs := c.resolveDependencies()
if len(errs) > 0 { if len(errs) > 0 {
@ -559,6 +636,19 @@ func (c *Context) checkForDependencyCycles() (errs []error) {
return return
} }
// PrepareBuildActions generates an internal representation of all the build
// actions that need to be performed. This process involves invoking the
// GenerateBuildActions method on each of the Module objects created during the
// parse phase and then on each of the registered Singleton objects.
//
// If the ResolveDependencies method has not already been called it is called
// automatically by this method.
//
// The config argument is made available to all of the Module and Singleton
// objects via the Config method on the ModuleContext and SingletonContext
// objects passed to GenerateBuildActions. It is also passed to the functions
// specified via PoolFunc, RuleFunc, and VariableFunc so that they can compute
// config-specific values.
func (c *Context) PrepareBuildActions(config interface{}) []error { func (c *Context) PrepareBuildActions(config interface{}) []error {
c.buildActionsReady = false c.buildActionsReady = false
@ -954,6 +1044,9 @@ func (c *Context) checkForVariableReferenceCycles(
} }
} }
// 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.
func (c *Context) WriteBuildFile(w io.Writer) error { func (c *Context) WriteBuildFile(w io.Writer) error {
if !c.buildActionsReady { if !c.buildActionsReady {
return ErrBuildActionsNotReady return ErrBuildActionsNotReady

View file

@ -86,13 +86,15 @@ func callerPackage() *pkg {
return p return p
} }
// Import enables access to the global Ninja rules and variables that are // Import enables access to the exported Ninja pools, rules, and variables that
// exported by another Go package. It may only be called from a Go package's // are defined at the package scope of another Go package. Go's visibility
// init() function. The Go package path passed to Import must have already been // rules apply to these references - capitalized names indicate that something
// imported into the Go package using a Go import statement. The imported // is exported. It may only be called from a Go package's init() function. The
// variables may then be accessed from Ninja strings as "${pkg.Variable}", while // Go package path passed to Import must have already been imported into the Go
// the imported rules can simply be accessed as exported Go variables from the // package using a Go import statement. The imported variables may then be
// package. For example: // accessed from Ninja strings as "${pkg.Variable}", while the imported rules
// can simply be accessed as exported Go variables from the package. For
// example:
// //
// import ( // import (
// "blueprint" // "blueprint"
@ -111,6 +113,12 @@ func callerPackage() *pkg {
// Outputs: []string{"${bar.SomeVariable}"}, // Outputs: []string{"${bar.SomeVariable}"},
// }) // })
// } // }
//
// Note that the local name used to refer to the package in Ninja variable names
// is derived from pkgPath by extracting the last path component. This differs
// from Go's import declaration, which derives the local name from the package
// clause in the imported package. By convention these names are made to match,
// but this is not required.
func Import(pkgPath string) { func Import(pkgPath string) {
callerPkg := callerPackage() callerPkg := callerPackage()
@ -125,6 +133,9 @@ func Import(pkgPath string) {
} }
} }
// ImportAs provides the same functionality as Import, but it allows the local
// name that will be used to refer to the package to be specified explicitly.
// It may only be called from a Go package's init() function.
func ImportAs(as, pkgPath string) { func ImportAs(as, pkgPath string) {
callerPkg := callerPackage() callerPkg := callerPackage()
@ -150,8 +161,15 @@ type staticVariable struct {
value_ string value_ string
} }
// StaticVariable returns a Variable that does not depend on any configuration // StaticVariable returns a Variable whose value does not depend on any
// information. // configuration information. It may only be called during a Go package's
// initialization - either from the init() function or as part of a package-
// scoped variable's initialization.
//
// This function is usually used to initialize a package-scoped Go variable that
// represents a Ninja variable that will be output. The name argument should
// exactly match the Go variable name, and the value string may reference other
// Ninja variables that are visible within the calling Go package.
func StaticVariable(name, value string) Variable { func StaticVariable(name, value string) Variable {
err := validateNinjaName(name) err := validateNinjaName(name)
if err != nil { if err != nil {
@ -192,9 +210,19 @@ type variableFunc struct {
} }
// VariableFunc returns a Variable whose value is determined by a function that // VariableFunc returns a Variable whose value is determined by a function that
// takes a interface{} object as input and returns either the variable value or an // takes a config object as input and returns either the variable value or an
// error. // error. It may only be called during a Go package's initialization - either
func VariableFunc(name string, f func(interface{}) (string, error)) Variable { // from the init() function or as part of a package-scoped variable's
// initialization.
//
// This function is usually used to initialize a package-scoped Go variable that
// represents a Ninja variable that will be output. The name argument should
// exactly match the Go variable name, and the value string returned by f may
// reference other Ninja variables that are visible within the calling Go
// package.
func VariableFunc(name string, f func(config interface{}) (string,
error)) Variable {
err := validateNinjaName(name) err := validateNinjaName(name)
if err != nil { if err != nil {
panic(err) panic(err)
@ -212,8 +240,16 @@ func VariableFunc(name string, f func(interface{}) (string, error)) Variable {
} }
// VariableConfigMethod returns a Variable whose value is determined by calling // VariableConfigMethod returns a Variable whose value is determined by calling
// a method on the interface{} object. The method must take no arguments and return // a method on the config object. The method must take no arguments and return
// a single string that will be the variable's value. // a single string that will be the variable's value. It may only be called
// during a Go package's initialization - either from the init() function or as
// part of a package-scoped variable's initialization.
//
// This function is usually used to initialize a package-scoped Go variable that
// represents a Ninja variable that will be output. The name argument should
// exactly match the Go variable name, and the value string returned by method
// may reference other Ninja variables that are visible within the calling Go
// package.
func VariableConfigMethod(name string, method interface{}) Variable { func VariableConfigMethod(name string, method interface{}) Variable {
err := validateNinjaName(name) err := validateNinjaName(name)
if err != nil { if err != nil {
@ -311,6 +347,15 @@ type staticPool struct {
params PoolParams params PoolParams
} }
// StaticPool returns a Pool whose value does not depend on any configuration
// information. It may only be called during a Go package's initialization -
// either from the init() function or as part of a package-scoped Go variable's
// initialization.
//
// This function is usually used to initialize a package-scoped Go variable that
// represents a Ninja pool that will be output. The name argument should
// exactly match the Go variable name, and the params fields may reference other
// Ninja variables that are visible within the calling Go package.
func StaticPool(name string, params PoolParams) Pool { func StaticPool(name string, params PoolParams) Pool {
err := validateNinjaName(name) err := validateNinjaName(name)
if err != nil { if err != nil {
@ -354,6 +399,16 @@ type poolFunc struct {
paramsFunc func(interface{}) (PoolParams, error) paramsFunc func(interface{}) (PoolParams, error)
} }
// PoolFunc returns a Pool whose value is determined by a function that takes a
// config object as input and returns either the pool parameters or an error. It
// may only be called during a Go package's initialization - either from the
// init() function or as part of a package-scoped variable's initialization.
//
// This function is usually used to initialize a package-scoped Go variable that
// represents a Ninja pool that will be output. The name argument should
// exactly match the Go variable name, and the string fields of the PoolParams
// returned by f may reference other Ninja variables that are visible within the
// calling Go package.
func PoolFunc(name string, f func(interface{}) (PoolParams, error)) Pool { func PoolFunc(name string, f func(interface{}) (PoolParams, error)) Pool {
err := validateNinjaName(name) err := validateNinjaName(name)
if err != nil { if err != nil {
@ -403,6 +458,24 @@ type staticRule struct {
scope_ *scope scope_ *scope
} }
// StaticRule returns a Rule whose value does not depend on any configuration
// information. It may only be called during a Go package's initialization -
// either from the init() function or as part of a package-scoped Go variable's
// initialization.
//
// This function is usually used to initialize a package-scoped Go variable that
// represents a Ninja rule that will be output. The name argument should
// exactly match the Go variable name, and the params fields may reference other
// Ninja variables that are visible within the calling Go package.
//
// The argNames arguments list Ninja variables that may be overridden by Ninja
// build statements that invoke the rule. These arguments may be referenced in
// any of the string fields of params. Arguments can shadow package-scoped
// variables defined within the caller's Go package, but they may not shadow
// those defined in another package. Shadowing a package-scoped variable
// results in the package-scoped variable's value being used for build
// statements that do not override the argument. For argument names that do not
// shadow package-scoped variables the default value is an empty string.
func StaticRule(name string, params RuleParams, argNames ...string) Rule { func StaticRule(name string, params RuleParams, argNames ...string) Rule {
pkg := callerPackage() pkg := callerPackage()
@ -474,6 +547,25 @@ type ruleFunc struct {
scope_ *scope scope_ *scope
} }
// RuleFunc returns a Rule whose value is determined by a function that takes a
// config object as input and returns either the rule parameters or an error. It
// may only be called during a Go package's initialization - either from the
// init() function or as part of a package-scoped variable's initialization.
//
// This function is usually used to initialize a package-scoped Go variable that
// represents a Ninja rule that will be output. The name argument should
// exactly match the Go variable name, and the string fields of the RuleParams
// returned by f may reference other Ninja variables that are visible within the
// calling Go package.
//
// The argNames arguments list Ninja variables that may be overridden by Ninja
// build statements that invoke the rule. These arguments may be referenced in
// any of the string fields of the RuleParams returned by f. Arguments can
// shadow package-scoped variables defined within the caller's Go package, but
// they may not shadow those defined in another package. Shadowing a package-
// scoped variable results in the package-scoped variable's value being used for
// build statements that do not override the argument. For argument names that
// do not shadow package-scoped variables the default value is an empty string.
func RuleFunc(name string, f func(interface{}) (RuleParams, error), func RuleFunc(name string, f func(interface{}) (RuleParams, error),
argNames ...string) Rule { argNames ...string) Rule {
@ -575,6 +667,10 @@ func (r *builtinRule) isArg(argName string) bool {
return false return false
} }
// A ModuleType represents a type of module that can be defined in a Blueprints
// file. In order for it to be used when interpreting Blueprints files, a
// ModuleType must first be registered with a Context object via the
// Context.RegisterModuleType method.
type ModuleType interface { type ModuleType interface {
pkg() *pkg pkg() *pkg
name() string name() string
@ -587,6 +683,70 @@ type moduleTypeFunc struct {
new_ func() (Module, interface{}) new_ func() (Module, interface{})
} }
// MakeModuleType returns a new ModuleType object that will instantiate new
// Module objects with the given new function. MakeModuleType may only be
// called during a Go package's initialization - either from the init() function
// or as part of a package-scoped variable's initialization.
//
// This function is usually used to initialize a package-scoped Go ModuleType
// variable that can then be passed to Context.RegisterModuleType. The name
// argument should exactly match the Go variable name. Note that this name is
// different than the one passed to Context.RegisterModuleType. This name is
// used to identify the Go object in error messages, making it easier to
// identify problematic build logic code. The name passed to
// Context.RegisterModuleType is the name that appear in Blueprints files to
// instantiate modules of this type.
//
// The new function passed to MakeModuleType returns two values. The first is
// the newly created Module object. The second is a pointer to that Module
// object's properties struct. This properties struct is examined when parsing
// a module definition of this type in a Blueprints file. Exported fields of
// the properties struct are automatically set to the property values specified
// in the Blueprints file. The properties struct field names determine the name
// of the Blueprints file properties that are used - the Blueprints property
// name matches that of the properties struct field name with the first letter
// converted to lower-case.
//
// The fields of the properties struct must either []string, a string, or bool.
// The Context will panic if a Module gets instantiated with a properties struct
// containing a field that is not one these supported types.
//
// Any properties that appear in the Blueprints files that are not built-in
// module properties (such as "name" and "deps") and do not have a corresponding
// field in the returned module properties struct result in an error during the
// Context's parse phase.
//
// As an example, the follow code:
//
// var MyModuleType = blueprint.MakeModuleType("MyModuleType", newMyModule)
//
// type myModule struct {
// properties struct {
// Foo string
// Bar []string
// }
// }
//
// func newMyModule() (blueprint.Module, interface{}) {
// module := new(myModule)
// properties := &module.properties
// return module, properties
// }
//
// func main() {
// ctx := blueprint.NewContext()
// ctx.RegisterModuleType("my_module", MyModuleType)
// // ...
// }
//
// would support parsing a module defined in a Blueprints file as follows:
//
// my_module {
// name: "myName",
// foo: "my foo string",
// bar: ["my", "bar", "strings"],
// }
//
func MakeModuleType(name string, func MakeModuleType(name string,
new func() (m Module, properties interface{})) ModuleType { new func() (m Module, properties interface{})) ModuleType {

View file

@ -6,6 +6,8 @@ import (
"strings" "strings"
) )
// A Deps value indicates the dependency file format that Ninja should expect to
// be output by a compiler.
type Deps int type Deps int
const ( const (
@ -27,31 +29,40 @@ func (d Deps) String() string {
} }
} }
// A PoolParams object contains the set of parameters that make up a Ninja pool
// definition.
type PoolParams struct { type PoolParams struct {
Comment string Comment string // The comment that will appear above the definition.
Depth int Depth int // The Ninja pool depth.
} }
// A RuleParams object contains the set of parameters that make up a Ninja rule
// definition. Each field except for Comment corresponds with a Ninja variable
// of the same name.
type RuleParams struct { type RuleParams struct {
Comment string Comment string // The comment that will appear above the definition.
Command string Command string // The command that Ninja will run for the rule.
Depfile string Depfile string // The dependency file name.
Deps Deps Deps Deps // The format of the dependency file.
Description string Description string // The description that Ninja will print for the rule.
Generator bool Generator bool // Whether the rule generates the Ninja manifest file.
Pool Pool Pool Pool // The Ninja pool to which the rule belongs.
Restat bool Restat bool // Whether Ninja should re-stat the rule's outputs.
Rspfile string Rspfile string // The response file.
RspfileContent string RspfileContent string // The response file content.
} }
// A BuildParams object contains the set of parameters that make up a Ninja
// build statement. Each field except for Args corresponds with a part of the
// Ninja build statement. The Args field contains variable names and values
// that are set within the build statement's scope in the Ninja file.
type BuildParams struct { type BuildParams struct {
Rule Rule Rule Rule // The rule to invoke.
Outputs []string Outputs []string // The list of output targets.
Inputs []string Inputs []string // The list of explicit input dependencies.
Implicits []string Implicits []string // The list of implicit dependencies.
OrderOnly []string OrderOnly []string // The list of order-only dependencies.
Args map[string]string Args map[string]string // The variable/value pairs to set.
} }
// A poolDef describes a pool definition. It does not include the name of the // A poolDef describes a pool definition. It does not include the name of the