You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
958 lines
26 KiB
958 lines
26 KiB
// Copyright 2014 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package blueprint
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// A PackageContext provides a way to create package-scoped Ninja pools,
|
|
// rules, and variables. A Go package should create a single unexported
|
|
// package-scoped PackageContext variable that it uses to create all package-
|
|
// scoped Ninja object definitions. This PackageContext object should then be
|
|
// passed to all calls to define module- or singleton-specific Ninja
|
|
// definitions. For example:
|
|
//
|
|
// package blah
|
|
//
|
|
// import (
|
|
// "blueprint"
|
|
// )
|
|
//
|
|
// var (
|
|
// pctx = NewPackageContext("path/to/blah")
|
|
//
|
|
// myPrivateVar = pctx.StaticVariable("myPrivateVar", "abcdef")
|
|
// MyExportedVar = pctx.StaticVariable("MyExportedVar", "$myPrivateVar 123456!")
|
|
//
|
|
// SomeRule = pctx.StaticRule(...)
|
|
// )
|
|
//
|
|
// // ...
|
|
//
|
|
// func (m *MyModule) GenerateBuildActions(ctx blueprint.Module) {
|
|
// ctx.Build(pctx, blueprint.BuildParams{
|
|
// Rule: SomeRule,
|
|
// Outputs: []string{"$myPrivateVar"},
|
|
// })
|
|
// }
|
|
type PackageContext interface {
|
|
Import(pkgPath string)
|
|
ImportAs(as, pkgPath string)
|
|
|
|
StaticVariable(name, value string) Variable
|
|
VariableFunc(name string, f func(config interface{}) (string, error)) Variable
|
|
VariableConfigMethod(name string, method interface{}) Variable
|
|
|
|
StaticPool(name string, params PoolParams) Pool
|
|
PoolFunc(name string, f func(interface{}) (PoolParams, error)) Pool
|
|
|
|
StaticRule(name string, params RuleParams, argNames ...string) Rule
|
|
RuleFunc(name string, f func(interface{}) (RuleParams, error), argNames ...string) Rule
|
|
|
|
AddNinjaFileDeps(deps ...string)
|
|
|
|
getScope() *basicScope
|
|
}
|
|
|
|
type packageContext struct {
|
|
fullName string
|
|
shortName string
|
|
pkgPath string
|
|
scope *basicScope
|
|
ninjaFileDeps []string
|
|
}
|
|
|
|
var _ PackageContext = &packageContext{}
|
|
|
|
func (p *packageContext) getScope() *basicScope {
|
|
return p.scope
|
|
}
|
|
|
|
var packageContexts = map[string]*packageContext{}
|
|
|
|
// NewPackageContext creates a PackageContext object for a given package. The
|
|
// pkgPath argument should always be set to the full path used to import the
|
|
// package. This function may only be called from a Go package's init()
|
|
// function or as part of a package-scoped variable initialization.
|
|
func NewPackageContext(pkgPath string) PackageContext {
|
|
checkCalledFromInit()
|
|
|
|
if _, present := packageContexts[pkgPath]; present {
|
|
panic(fmt.Errorf("package %q already has a package context", pkgPath))
|
|
}
|
|
|
|
pkgName := pkgPathToName(pkgPath)
|
|
err := validateNinjaName(pkgName)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
i := strings.LastIndex(pkgPath, "/")
|
|
shortName := pkgPath[i+1:]
|
|
|
|
p := &packageContext{
|
|
fullName: pkgName,
|
|
shortName: shortName,
|
|
pkgPath: pkgPath,
|
|
scope: newScope(nil),
|
|
}
|
|
|
|
packageContexts[pkgPath] = p
|
|
|
|
return p
|
|
}
|
|
|
|
var Phony Rule = NewBuiltinRule("phony")
|
|
|
|
var Console Pool = NewBuiltinPool("console")
|
|
|
|
var errRuleIsBuiltin = errors.New("the rule is a built-in")
|
|
var errPoolIsBuiltin = errors.New("the pool is a built-in")
|
|
var errVariableIsArg = errors.New("argument variables have no value")
|
|
|
|
// checkCalledFromInit panics if a Go package's init function is not on the
|
|
// call stack.
|
|
func checkCalledFromInit() {
|
|
for skip := 3; ; skip++ {
|
|
_, funcName, ok := callerName(skip)
|
|
if !ok {
|
|
panic("not called from an init func")
|
|
}
|
|
|
|
if funcName == "init" || strings.HasPrefix(funcName, "init·") ||
|
|
funcName == "init.ializers" || strings.HasPrefix(funcName, "init.") {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// A regex to find a package path within a function name. It finds the shortest string that is
|
|
// followed by '.' and doesn't have any '/'s left.
|
|
var pkgPathRe = regexp.MustCompile(`^(.*?)\.([^/]+)$`)
|
|
|
|
// callerName returns the package path and function name of the calling
|
|
// function. The skip argument has the same meaning as the skip argument of
|
|
// runtime.Callers.
|
|
func callerName(skip int) (pkgPath, funcName string, ok bool) {
|
|
var pc [1]uintptr
|
|
n := runtime.Callers(skip+1, pc[:])
|
|
if n != 1 {
|
|
return "", "", false
|
|
}
|
|
frames := runtime.CallersFrames(pc[:])
|
|
frame, _ := frames.Next()
|
|
f := frame.Function
|
|
s := pkgPathRe.FindStringSubmatch(f)
|
|
if len(s) < 3 {
|
|
panic(fmt.Errorf("failed to extract package path and function name from %q", f))
|
|
}
|
|
|
|
return s[1], s[2], true
|
|
}
|
|
|
|
// pkgPathToName makes a Ninja-friendly name out of a Go package name by
|
|
// replaceing all the '/' characters with '.'. We assume the results are
|
|
// unique, though this is not 100% guaranteed for Go package names that
|
|
// already contain '.' characters. Disallowing package names with '.' isn't
|
|
// reasonable since many package names contain the name of the hosting site
|
|
// (e.g. "code.google.com"). In practice this probably isn't really a
|
|
// problem.
|
|
func pkgPathToName(pkgPath string) string {
|
|
return strings.Replace(pkgPath, "/", ".", -1)
|
|
}
|
|
|
|
// 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"
|
|
// "foo/bar"
|
|
// )
|
|
//
|
|
// var pctx = NewPackagePath("blah")
|
|
//
|
|
// func init() {
|
|
// pctx.Import("foo/bar")
|
|
// }
|
|
//
|
|
// ...
|
|
//
|
|
// func (m *MyModule) GenerateBuildActions(ctx blueprint.Module) {
|
|
// ctx.Build(pctx, blueprint.BuildParams{
|
|
// Rule: bar.SomeRule,
|
|
// 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 (p *packageContext) Import(pkgPath string) {
|
|
checkCalledFromInit()
|
|
importPkg, ok := packageContexts[pkgPath]
|
|
if !ok {
|
|
panic(fmt.Errorf("package %q has no context", pkgPath))
|
|
}
|
|
|
|
err := p.scope.AddImport(importPkg.shortName, importPkg.scope)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// 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 (p *packageContext) ImportAs(as, pkgPath string) {
|
|
checkCalledFromInit()
|
|
importPkg, ok := packageContexts[pkgPath]
|
|
if !ok {
|
|
panic(fmt.Errorf("package %q has no context", pkgPath))
|
|
}
|
|
|
|
err := validateNinjaName(as)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = p.scope.AddImport(as, importPkg.scope)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
type staticVariable struct {
|
|
pctx *packageContext
|
|
name_ string
|
|
value_ string
|
|
fullName_ string
|
|
}
|
|
|
|
// 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 (p *packageContext) StaticVariable(name, value string) Variable {
|
|
checkCalledFromInit()
|
|
err := validateNinjaName(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
v := &staticVariable{
|
|
pctx: p,
|
|
name_: name,
|
|
value_: value,
|
|
}
|
|
err = p.scope.AddVariable(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func (v *staticVariable) packageContext() *packageContext {
|
|
return v.pctx
|
|
}
|
|
|
|
func (v *staticVariable) name() string {
|
|
return v.name_
|
|
}
|
|
|
|
func (v *staticVariable) fullName(pkgNames map[*packageContext]string) string {
|
|
if v.fullName_ != "" {
|
|
return v.fullName_
|
|
}
|
|
return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
|
|
}
|
|
|
|
func (v *staticVariable) memoizeFullName(pkgNames map[*packageContext]string) {
|
|
v.fullName_ = v.fullName(pkgNames)
|
|
}
|
|
|
|
func (v *staticVariable) value(interface{}) (ninjaString, error) {
|
|
ninjaStr, err := parseNinjaString(v.pctx.scope, v.value_)
|
|
if err != nil {
|
|
err = fmt.Errorf("error parsing variable %s value: %s", v, err)
|
|
panic(err)
|
|
}
|
|
return ninjaStr, nil
|
|
}
|
|
|
|
func (v *staticVariable) String() string {
|
|
return v.pctx.pkgPath + "." + v.name_
|
|
}
|
|
|
|
type variableFunc struct {
|
|
pctx *packageContext
|
|
name_ string
|
|
value_ func(interface{}) (string, error)
|
|
fullName_ string
|
|
}
|
|
|
|
// VariableFunc returns a Variable whose value is determined by a function that
|
|
// 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 (p *packageContext) VariableFunc(name string,
|
|
f func(config interface{}) (string, error)) Variable {
|
|
|
|
checkCalledFromInit()
|
|
|
|
err := validateNinjaName(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
v := &variableFunc{
|
|
pctx: p,
|
|
name_: name,
|
|
value_: f,
|
|
}
|
|
err = p.scope.AddVariable(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
// VariableConfigMethod returns a Variable whose value is determined by calling
|
|
// 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 (p *packageContext) VariableConfigMethod(name string,
|
|
method interface{}) Variable {
|
|
|
|
checkCalledFromInit()
|
|
|
|
err := validateNinjaName(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
methodValue := reflect.ValueOf(method)
|
|
validateVariableMethod(name, methodValue)
|
|
|
|
fun := func(config interface{}) (string, error) {
|
|
result := methodValue.Call([]reflect.Value{reflect.ValueOf(config)})
|
|
resultStr := result[0].Interface().(string)
|
|
return resultStr, nil
|
|
}
|
|
|
|
v := &variableFunc{
|
|
pctx: p,
|
|
name_: name,
|
|
value_: fun,
|
|
}
|
|
err = p.scope.AddVariable(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func (v *variableFunc) packageContext() *packageContext {
|
|
return v.pctx
|
|
}
|
|
|
|
func (v *variableFunc) name() string {
|
|
return v.name_
|
|
}
|
|
|
|
func (v *variableFunc) fullName(pkgNames map[*packageContext]string) string {
|
|
if v.fullName_ != "" {
|
|
return v.fullName_
|
|
}
|
|
return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
|
|
}
|
|
|
|
func (v *variableFunc) memoizeFullName(pkgNames map[*packageContext]string) {
|
|
v.fullName_ = v.fullName(pkgNames)
|
|
}
|
|
|
|
func (v *variableFunc) value(config interface{}) (ninjaString, error) {
|
|
value, err := v.value_(config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ninjaStr, err := parseNinjaString(v.pctx.scope, value)
|
|
if err != nil {
|
|
err = fmt.Errorf("error parsing variable %s value: %s", v, err)
|
|
panic(err)
|
|
}
|
|
|
|
return ninjaStr, nil
|
|
}
|
|
|
|
func (v *variableFunc) String() string {
|
|
return v.pctx.pkgPath + "." + v.name_
|
|
}
|
|
|
|
func validateVariableMethod(name string, methodValue reflect.Value) {
|
|
methodType := methodValue.Type()
|
|
if methodType.Kind() != reflect.Func {
|
|
panic(fmt.Errorf("method given for variable %s is not a function",
|
|
name))
|
|
}
|
|
if n := methodType.NumIn(); n != 1 {
|
|
panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
|
|
name, n))
|
|
}
|
|
if n := methodType.NumOut(); n != 1 {
|
|
panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
|
|
name, n))
|
|
}
|
|
if kind := methodType.Out(0).Kind(); kind != reflect.String {
|
|
panic(fmt.Errorf("method for variable %s does not return a string",
|
|
name))
|
|
}
|
|
}
|
|
|
|
// An argVariable is a Variable that exists only when it is set by a build
|
|
// statement to pass a value to the rule being invoked. It has no value, so it
|
|
// can never be used to create a Ninja assignment statement. It is inserted
|
|
// into the rule's scope, which is used for name lookups within the rule and
|
|
// when assigning argument values as part of a build statement.
|
|
type argVariable struct {
|
|
name_ string
|
|
}
|
|
|
|
func (v *argVariable) packageContext() *packageContext {
|
|
panic("this should not be called")
|
|
}
|
|
|
|
func (v *argVariable) name() string {
|
|
return v.name_
|
|
}
|
|
|
|
func (v *argVariable) fullName(pkgNames map[*packageContext]string) string {
|
|
return v.name_
|
|
}
|
|
|
|
func (v *argVariable) memoizeFullName(pkgNames map[*packageContext]string) {
|
|
// Nothing to do, full name is known at initialization.
|
|
}
|
|
|
|
func (v *argVariable) value(config interface{}) (ninjaString, error) {
|
|
return nil, errVariableIsArg
|
|
}
|
|
|
|
func (v *argVariable) String() string {
|
|
return "<arg>:" + v.name_
|
|
}
|
|
|
|
type staticPool struct {
|
|
pctx *packageContext
|
|
name_ string
|
|
params PoolParams
|
|
fullName_ string
|
|
}
|
|
|
|
// 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 (p *packageContext) StaticPool(name string, params PoolParams) Pool {
|
|
checkCalledFromInit()
|
|
|
|
err := validateNinjaName(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
pool := &staticPool{
|
|
pctx: p,
|
|
name_: name,
|
|
params: params,
|
|
}
|
|
err = p.scope.AddPool(pool)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return pool
|
|
}
|
|
|
|
func (p *staticPool) packageContext() *packageContext {
|
|
return p.pctx
|
|
}
|
|
|
|
func (p *staticPool) name() string {
|
|
return p.name_
|
|
}
|
|
|
|
func (p *staticPool) fullName(pkgNames map[*packageContext]string) string {
|
|
if p.fullName_ != "" {
|
|
return p.fullName_
|
|
}
|
|
return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_
|
|
}
|
|
|
|
func (p *staticPool) memoizeFullName(pkgNames map[*packageContext]string) {
|
|
p.fullName_ = p.fullName(pkgNames)
|
|
}
|
|
|
|
func (p *staticPool) def(config interface{}) (*poolDef, error) {
|
|
def, err := parsePoolParams(p.pctx.scope, &p.params)
|
|
if err != nil {
|
|
panic(fmt.Errorf("error parsing PoolParams for %s: %s", p, err))
|
|
}
|
|
return def, nil
|
|
}
|
|
|
|
func (p *staticPool) String() string {
|
|
return p.pctx.pkgPath + "." + p.name_
|
|
}
|
|
|
|
type poolFunc struct {
|
|
pctx *packageContext
|
|
name_ string
|
|
paramsFunc func(interface{}) (PoolParams, error)
|
|
fullName_ string
|
|
}
|
|
|
|
// 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 (p *packageContext) PoolFunc(name string, f func(interface{}) (PoolParams,
|
|
error)) Pool {
|
|
|
|
checkCalledFromInit()
|
|
|
|
err := validateNinjaName(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
pool := &poolFunc{
|
|
pctx: p,
|
|
name_: name,
|
|
paramsFunc: f,
|
|
}
|
|
err = p.scope.AddPool(pool)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return pool
|
|
}
|
|
|
|
func (p *poolFunc) packageContext() *packageContext {
|
|
return p.pctx
|
|
}
|
|
|
|
func (p *poolFunc) name() string {
|
|
return p.name_
|
|
}
|
|
|
|
func (p *poolFunc) fullName(pkgNames map[*packageContext]string) string {
|
|
if p.fullName_ != "" {
|
|
return p.fullName_
|
|
}
|
|
return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_
|
|
}
|
|
|
|
func (p *poolFunc) memoizeFullName(pkgNames map[*packageContext]string) {
|
|
p.fullName_ = p.fullName(pkgNames)
|
|
}
|
|
|
|
func (p *poolFunc) def(config interface{}) (*poolDef, error) {
|
|
params, err := p.paramsFunc(config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
def, err := parsePoolParams(p.pctx.scope, ¶ms)
|
|
if err != nil {
|
|
panic(fmt.Errorf("error parsing PoolParams for %s: %s", p, err))
|
|
}
|
|
return def, nil
|
|
}
|
|
|
|
func (p *poolFunc) String() string {
|
|
return p.pctx.pkgPath + "." + p.name_
|
|
}
|
|
|
|
type builtinPool struct {
|
|
name_ string
|
|
}
|
|
|
|
func (p *builtinPool) packageContext() *packageContext {
|
|
return nil
|
|
}
|
|
|
|
func (p *builtinPool) name() string {
|
|
return p.name_
|
|
}
|
|
|
|
func (p *builtinPool) fullName(pkgNames map[*packageContext]string) string {
|
|
return p.name_
|
|
}
|
|
|
|
func (p *builtinPool) memoizeFullName(pkgNames map[*packageContext]string) {
|
|
// Nothing to do, full name is known at initialization.
|
|
}
|
|
|
|
func (p *builtinPool) def(config interface{}) (*poolDef, error) {
|
|
return nil, errPoolIsBuiltin
|
|
}
|
|
|
|
// NewBuiltinPool returns a Pool object that refers to a pool name created outside of Blueprint
|
|
func NewBuiltinPool(name string) Pool {
|
|
return &builtinPool{
|
|
name_: name,
|
|
}
|
|
}
|
|
|
|
func (p *builtinPool) String() string {
|
|
return "<builtin>:" + p.name_
|
|
}
|
|
|
|
type staticRule struct {
|
|
pctx *packageContext
|
|
name_ string
|
|
params RuleParams
|
|
argNames map[string]bool
|
|
scope_ *basicScope
|
|
fullName_ string
|
|
sync.Mutex // protects scope_ during lazy creation
|
|
}
|
|
|
|
// 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 (p *packageContext) StaticRule(name string, params RuleParams,
|
|
argNames ...string) Rule {
|
|
|
|
checkCalledFromInit()
|
|
|
|
err := validateNinjaName(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = validateArgNames(argNames)
|
|
if err != nil {
|
|
panic(fmt.Errorf("invalid argument name: %s", err))
|
|
}
|
|
|
|
argNamesSet := make(map[string]bool)
|
|
for _, argName := range argNames {
|
|
argNamesSet[argName] = true
|
|
}
|
|
|
|
ruleScope := (*basicScope)(nil) // This will get created lazily
|
|
|
|
r := &staticRule{
|
|
pctx: p,
|
|
name_: name,
|
|
params: params,
|
|
argNames: argNamesSet,
|
|
scope_: ruleScope,
|
|
}
|
|
err = p.scope.AddRule(r)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func (r *staticRule) packageContext() *packageContext {
|
|
return r.pctx
|
|
}
|
|
|
|
func (r *staticRule) name() string {
|
|
return r.name_
|
|
}
|
|
|
|
func (r *staticRule) fullName(pkgNames map[*packageContext]string) string {
|
|
if r.fullName_ != "" {
|
|
return r.fullName_
|
|
}
|
|
return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_
|
|
}
|
|
|
|
func (r *staticRule) memoizeFullName(pkgNames map[*packageContext]string) {
|
|
r.fullName_ = r.fullName(pkgNames)
|
|
}
|
|
|
|
func (r *staticRule) def(interface{}) (*ruleDef, error) {
|
|
def, err := parseRuleParams(r.scope(), &r.params)
|
|
if err != nil {
|
|
panic(fmt.Errorf("error parsing RuleParams for %s: %s", r, err))
|
|
}
|
|
return def, nil
|
|
}
|
|
|
|
func (r *staticRule) scope() *basicScope {
|
|
// We lazily create the scope so that all the package-scoped variables get
|
|
// declared before the args are created. Otherwise we could incorrectly
|
|
// shadow a package-scoped variable with an arg variable.
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
if r.scope_ == nil {
|
|
r.scope_ = makeRuleScope(r.pctx.scope, r.argNames)
|
|
}
|
|
return r.scope_
|
|
}
|
|
|
|
func (r *staticRule) isArg(argName string) bool {
|
|
return r.argNames[argName]
|
|
}
|
|
|
|
func (r *staticRule) String() string {
|
|
return r.pctx.pkgPath + "." + r.name_
|
|
}
|
|
|
|
type ruleFunc struct {
|
|
pctx *packageContext
|
|
name_ string
|
|
paramsFunc func(interface{}) (RuleParams, error)
|
|
argNames map[string]bool
|
|
scope_ *basicScope
|
|
fullName_ string
|
|
sync.Mutex // protects scope_ during lazy creation
|
|
}
|
|
|
|
// 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 (p *packageContext) RuleFunc(name string, f func(interface{}) (RuleParams,
|
|
error), argNames ...string) Rule {
|
|
|
|
checkCalledFromInit()
|
|
|
|
err := validateNinjaName(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = validateArgNames(argNames)
|
|
if err != nil {
|
|
panic(fmt.Errorf("invalid argument name: %s", err))
|
|
}
|
|
|
|
argNamesSet := make(map[string]bool)
|
|
for _, argName := range argNames {
|
|
argNamesSet[argName] = true
|
|
}
|
|
|
|
ruleScope := (*basicScope)(nil) // This will get created lazily
|
|
|
|
rule := &ruleFunc{
|
|
pctx: p,
|
|
name_: name,
|
|
paramsFunc: f,
|
|
argNames: argNamesSet,
|
|
scope_: ruleScope,
|
|
}
|
|
err = p.scope.AddRule(rule)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return rule
|
|
}
|
|
|
|
func (r *ruleFunc) packageContext() *packageContext {
|
|
return r.pctx
|
|
}
|
|
|
|
func (r *ruleFunc) name() string {
|
|
return r.name_
|
|
}
|
|
|
|
func (r *ruleFunc) fullName(pkgNames map[*packageContext]string) string {
|
|
if r.fullName_ != "" {
|
|
return r.fullName_
|
|
}
|
|
return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_
|
|
}
|
|
|
|
func (r *ruleFunc) memoizeFullName(pkgNames map[*packageContext]string) {
|
|
r.fullName_ = r.fullName(pkgNames)
|
|
}
|
|
|
|
func (r *ruleFunc) def(config interface{}) (*ruleDef, error) {
|
|
params, err := r.paramsFunc(config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
def, err := parseRuleParams(r.scope(), ¶ms)
|
|
if err != nil {
|
|
panic(fmt.Errorf("error parsing RuleParams for %s: %s", r, err))
|
|
}
|
|
return def, nil
|
|
}
|
|
|
|
func (r *ruleFunc) scope() *basicScope {
|
|
// We lazily create the scope so that all the global variables get declared
|
|
// before the args are created. Otherwise we could incorrectly shadow a
|
|
// global variable with an arg variable.
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
if r.scope_ == nil {
|
|
r.scope_ = makeRuleScope(r.pctx.scope, r.argNames)
|
|
}
|
|
return r.scope_
|
|
}
|
|
|
|
func (r *ruleFunc) isArg(argName string) bool {
|
|
return r.argNames[argName]
|
|
}
|
|
|
|
func (r *ruleFunc) String() string {
|
|
return r.pctx.pkgPath + "." + r.name_
|
|
}
|
|
|
|
type builtinRule struct {
|
|
name_ string
|
|
scope_ *basicScope
|
|
sync.Mutex // protects scope_ during lazy creation
|
|
}
|
|
|
|
func (r *builtinRule) packageContext() *packageContext {
|
|
return nil
|
|
}
|
|
|
|
func (r *builtinRule) name() string {
|
|
return r.name_
|
|
}
|
|
|
|
func (r *builtinRule) fullName(pkgNames map[*packageContext]string) string {
|
|
return r.name_
|
|
}
|
|
|
|
func (r *builtinRule) memoizeFullName(pkgNames map[*packageContext]string) {
|
|
// Nothing to do, full name is known at initialization.
|
|
}
|
|
|
|
func (r *builtinRule) def(config interface{}) (*ruleDef, error) {
|
|
return nil, errRuleIsBuiltin
|
|
}
|
|
|
|
func (r *builtinRule) scope() *basicScope {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
if r.scope_ == nil {
|
|
r.scope_ = makeRuleScope(nil, nil)
|
|
}
|
|
return r.scope_
|
|
}
|
|
|
|
func (r *builtinRule) isArg(argName string) bool {
|
|
return false
|
|
}
|
|
|
|
func (r *builtinRule) String() string {
|
|
return "<builtin>:" + r.name_
|
|
}
|
|
|
|
// NewBuiltinRule returns a Rule object that refers to a rule that was created outside of Blueprint
|
|
func NewBuiltinRule(name string) Rule {
|
|
return &builtinRule{
|
|
name_: name,
|
|
}
|
|
}
|
|
|
|
func (p *packageContext) AddNinjaFileDeps(deps ...string) {
|
|
p.ninjaFileDeps = append(p.ninjaFileDeps, deps...)
|
|
}
|