From d4e1018e19d1ec3dc3a084b1d29bbb3bc47ddde2 Mon Sep 17 00:00:00 2001 From: Jamie Gennis Date: Thu, 12 Jun 2014 20:06:50 -0700 Subject: [PATCH] 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 --- blueprint/context.go | 97 ++++++++++++++++++++- blueprint/globals.go | 188 +++++++++++++++++++++++++++++++++++++--- blueprint/ninja_defs.go | 47 ++++++---- 3 files changed, 298 insertions(+), 34 deletions(-) diff --git a/blueprint/context.go b/blueprint/context.go index e89be0f..6a5e68f 100644 --- a/blueprint/context.go +++ b/blueprint/context.go @@ -19,6 +19,30 @@ var ErrBuildActionsNotReady = errors.New("build actions are not ready") 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 { // set at instantiation moduleTypes map[string]ModuleType @@ -45,9 +69,11 @@ type Context struct { 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 { - Err error - Pos scanner.Position + Err error // the error that occurred + Pos scanner.Position // the relevant Blueprints file location } type localBuildActions struct { @@ -88,6 +114,9 @@ func (e *Error) Error() string { 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 { return &Context{ 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) { if _, present := c.moduleTypes[name]; present { 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 } +// 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) { if _, present := c.singletonInfo[name]; present { panic(errors.New("singleton name is already registered")) @@ -132,10 +176,30 @@ func singletonTypeName(singleton Singleton) string { 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) { 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, errs []error) { @@ -191,6 +255,15 @@ func (c *Context) Parse(rootDir, filename string, r io.Reader) (subdirs []string 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, errs []error) { @@ -433,6 +506,10 @@ func (c *Context) processModuleDef(moduleDef *parser.Module, 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 { errs := c.resolveDependencies() if len(errs) > 0 { @@ -559,6 +636,19 @@ func (c *Context) checkForDependencyCycles() (errs []error) { 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 { 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 { if !c.buildActionsReady { return ErrBuildActionsNotReady diff --git a/blueprint/globals.go b/blueprint/globals.go index 9bfe02c..7a061f3 100644 --- a/blueprint/globals.go +++ b/blueprint/globals.go @@ -86,13 +86,15 @@ func callerPackage() *pkg { return p } -// Import enables access to the global Ninja rules and variables that are -// exported by another Go package. It may only be called from a Go package's -// init() function. The Go package path passed to Import must have already been -// imported into the Go package using a Go import statement. The imported -// variables may then be 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 enables access to the exported Ninja pools, rules, and variables that +// are defined at the package scope of another Go package. Go's visibility +// rules apply to these references - capitalized names indicate that something +// is exported. It may only be called from a Go package's init() function. The +// Go package path passed to Import must have already been imported into the Go +// package using a Go import statement. The imported variables may then be +// 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 ( // "blueprint" @@ -111,6 +113,12 @@ func callerPackage() *pkg { // 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) { 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) { callerPkg := callerPackage() @@ -150,8 +161,15 @@ type staticVariable struct { value_ string } -// StaticVariable returns a Variable that does not depend on any configuration -// information. +// StaticVariable returns a Variable 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 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 { err := validateNinjaName(name) if err != nil { @@ -192,9 +210,19 @@ type variableFunc struct { } // 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 -// error. -func VariableFunc(name string, f func(interface{}) (string, error)) Variable { +// takes a config object as input and returns either the variable value 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 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) if err != nil { 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 -// a method on the interface{} object. The method must take no arguments and return -// a single string that will be the variable's value. +// a method on the config object. The method must take no arguments and return +// 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 { err := validateNinjaName(name) if err != nil { @@ -311,6 +347,15 @@ type staticPool struct { 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 { err := validateNinjaName(name) if err != nil { @@ -354,6 +399,16 @@ type poolFunc struct { 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 { err := validateNinjaName(name) if err != nil { @@ -403,6 +458,24 @@ type staticRule struct { 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 { pkg := callerPackage() @@ -474,6 +547,25 @@ type ruleFunc struct { 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), argNames ...string) Rule { @@ -575,6 +667,10 @@ func (r *builtinRule) isArg(argName string) bool { 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 { pkg() *pkg name() string @@ -587,6 +683,70 @@ type moduleTypeFunc struct { 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, new func() (m Module, properties interface{})) ModuleType { diff --git a/blueprint/ninja_defs.go b/blueprint/ninja_defs.go index 3f3329d..cc05085 100644 --- a/blueprint/ninja_defs.go +++ b/blueprint/ninja_defs.go @@ -6,6 +6,8 @@ import ( "strings" ) +// A Deps value indicates the dependency file format that Ninja should expect to +// be output by a compiler. type Deps int 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 { - Comment string - Depth int + Comment string // The comment that will appear above the definition. + 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 { - Comment string - Command string - Depfile string - Deps Deps - Description string - Generator bool - Pool Pool - Restat bool - Rspfile string - RspfileContent string + Comment string // The comment that will appear above the definition. + Command string // The command that Ninja will run for the rule. + Depfile string // The dependency file name. + Deps Deps // The format of the dependency file. + Description string // The description that Ninja will print for the rule. + Generator bool // Whether the rule generates the Ninja manifest file. + Pool Pool // The Ninja pool to which the rule belongs. + Restat bool // Whether Ninja should re-stat the rule's outputs. + Rspfile string // The response file. + 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 { - Rule Rule - Outputs []string - Inputs []string - Implicits []string - OrderOnly []string - Args map[string]string + Rule Rule // The rule to invoke. + Outputs []string // The list of output targets. + Inputs []string // The list of explicit input dependencies. + Implicits []string // The list of implicit dependencies. + OrderOnly []string // The list of order-only dependencies. + Args map[string]string // The variable/value pairs to set. } // A poolDef describes a pool definition. It does not include the name of the