| // 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)(nil) |
| |
| 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...) |
| } |