6bc984abca
memoizeFullName was added to variables, rules and pools as an optimization to prevent recomputing the full name repeatedly, but the storage of variables, rules and pools are generally global and not tied to the Context. When running multiple tests in parallel there will be multiple Context objects all trying to update the memoized names on the global variables, causing a data race. Package names were previously memoized via a pkgNames map stored on the Context. Expand pkgNames to a nameTracker object that contains maps for packages, variables, rules and pools, and replace calls to fullName with calls through nameTracker. Test: context_test.go Change-Id: I15040b85a6d1dab9ab3cff44f227b22985acee18
449 lines
10 KiB
Go
449 lines
10 KiB
Go
// 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 (
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// A Variable represents a global Ninja variable definition that will be written
|
|
// to the output .ninja file. A variable may contain references to other global
|
|
// Ninja variables, but circular variable references are not allowed.
|
|
type Variable interface {
|
|
packageContext() *packageContext
|
|
name() string // "foo"
|
|
fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
|
|
value(ctx VariableFuncContext, config interface{}) (*ninjaString, error)
|
|
String() string
|
|
}
|
|
|
|
// A Pool represents a Ninja pool that will be written to the output .ninja
|
|
// file.
|
|
type Pool interface {
|
|
packageContext() *packageContext
|
|
name() string // "foo"
|
|
fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
|
|
def(config interface{}) (*poolDef, error)
|
|
String() string
|
|
}
|
|
|
|
// A Rule represents a Ninja build rule that will be written to the output
|
|
// .ninja file.
|
|
type Rule interface {
|
|
packageContext() *packageContext
|
|
name() string // "foo"
|
|
fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
|
|
def(config interface{}) (*ruleDef, error)
|
|
scope() *basicScope
|
|
isArg(argName string) bool
|
|
String() string
|
|
}
|
|
|
|
type basicScope struct {
|
|
parent *basicScope
|
|
variables map[string]Variable
|
|
pools map[string]Pool
|
|
rules map[string]Rule
|
|
imports map[string]*basicScope
|
|
}
|
|
|
|
func newScope(parent *basicScope) *basicScope {
|
|
return &basicScope{
|
|
parent: parent,
|
|
variables: make(map[string]Variable),
|
|
pools: make(map[string]Pool),
|
|
rules: make(map[string]Rule),
|
|
imports: make(map[string]*basicScope),
|
|
}
|
|
}
|
|
|
|
func makeRuleScope(parent *basicScope, argNames map[string]bool) *basicScope {
|
|
scope := newScope(parent)
|
|
for argName := range argNames {
|
|
_, err := scope.LookupVariable(argName)
|
|
if err != nil {
|
|
arg := &argVariable{argName}
|
|
err = scope.AddVariable(arg)
|
|
if err != nil {
|
|
// This should not happen. We should have already checked that
|
|
// the name is valid and that the scope doesn't have a variable
|
|
// with this name.
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// We treat built-in variables like arguments for the purpose of this scope.
|
|
for _, builtin := range builtinRuleArgs {
|
|
arg := &argVariable{builtin}
|
|
err := scope.AddVariable(arg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
return scope
|
|
}
|
|
|
|
func (s *basicScope) LookupVariable(name string) (Variable, error) {
|
|
dotIndex := strings.IndexRune(name, '.')
|
|
if dotIndex >= 0 {
|
|
// The variable name looks like "pkg.var"
|
|
if dotIndex+1 == len(name) {
|
|
return nil, fmt.Errorf("variable name %q ends with a '.'", name)
|
|
}
|
|
if strings.ContainsRune(name[dotIndex+1:], '.') {
|
|
return nil, fmt.Errorf("variable name %q contains multiple '.' "+
|
|
"characters", name)
|
|
}
|
|
|
|
pkgName := name[:dotIndex]
|
|
varName := name[dotIndex+1:]
|
|
|
|
first, _ := utf8.DecodeRuneInString(varName)
|
|
if !unicode.IsUpper(first) {
|
|
return nil, fmt.Errorf("cannot refer to unexported name %q", name)
|
|
}
|
|
|
|
importedScope, err := s.lookupImportedScope(pkgName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v, ok := importedScope.variables[varName]
|
|
if !ok {
|
|
return nil, fmt.Errorf("package %q does not contain variable %q",
|
|
pkgName, varName)
|
|
}
|
|
|
|
return v, nil
|
|
} else {
|
|
// The variable name has no package part; just "var"
|
|
for ; s != nil; s = s.parent {
|
|
v, ok := s.variables[name]
|
|
if ok {
|
|
return v, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("undefined variable %q", name)
|
|
}
|
|
}
|
|
|
|
func (s *basicScope) IsRuleVisible(rule Rule) bool {
|
|
_, isBuiltin := rule.(*builtinRule)
|
|
if isBuiltin {
|
|
return true
|
|
}
|
|
|
|
name := rule.name()
|
|
|
|
for s != nil {
|
|
if s.rules[name] == rule {
|
|
return true
|
|
}
|
|
|
|
for _, import_ := range s.imports {
|
|
if import_.rules[name] == rule {
|
|
return true
|
|
}
|
|
}
|
|
|
|
s = s.parent
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (s *basicScope) IsPoolVisible(pool Pool) bool {
|
|
_, isBuiltin := pool.(*builtinPool)
|
|
if isBuiltin {
|
|
return true
|
|
}
|
|
|
|
name := pool.name()
|
|
|
|
for s != nil {
|
|
if s.pools[name] == pool {
|
|
return true
|
|
}
|
|
|
|
for _, import_ := range s.imports {
|
|
if import_.pools[name] == pool {
|
|
return true
|
|
}
|
|
}
|
|
|
|
s = s.parent
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (s *basicScope) lookupImportedScope(pkgName string) (*basicScope, error) {
|
|
for ; s != nil; s = s.parent {
|
|
importedScope, ok := s.imports[pkgName]
|
|
if ok {
|
|
return importedScope, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("unknown imported package %q (missing call to "+
|
|
"blueprint.Import()?)", pkgName)
|
|
}
|
|
|
|
func (s *basicScope) AddImport(name string, importedScope *basicScope) error {
|
|
_, present := s.imports[name]
|
|
if present {
|
|
return fmt.Errorf("import %q is already defined in this scope", name)
|
|
}
|
|
s.imports[name] = importedScope
|
|
return nil
|
|
}
|
|
|
|
func (s *basicScope) AddVariable(v Variable) error {
|
|
name := v.name()
|
|
_, present := s.variables[name]
|
|
if present {
|
|
return fmt.Errorf("variable %q is already defined in this scope", name)
|
|
}
|
|
s.variables[name] = v
|
|
return nil
|
|
}
|
|
|
|
func (s *basicScope) AddPool(p Pool) error {
|
|
name := p.name()
|
|
_, present := s.pools[name]
|
|
if present {
|
|
return fmt.Errorf("pool %q is already defined in this scope", name)
|
|
}
|
|
s.pools[name] = p
|
|
return nil
|
|
}
|
|
|
|
func (s *basicScope) AddRule(r Rule) error {
|
|
name := r.name()
|
|
_, present := s.rules[name]
|
|
if present {
|
|
return fmt.Errorf("rule %q is already defined in this scope", name)
|
|
}
|
|
s.rules[name] = r
|
|
return nil
|
|
}
|
|
|
|
type localScope struct {
|
|
namePrefix string
|
|
scope *basicScope
|
|
}
|
|
|
|
func newLocalScope(parent *basicScope, namePrefix string) *localScope {
|
|
return &localScope{
|
|
namePrefix: namePrefix,
|
|
scope: newScope(parent),
|
|
}
|
|
}
|
|
|
|
// ReparentTo sets the localScope's parent scope to the scope of the given
|
|
// package context. This allows a ModuleContext and SingletonContext to call
|
|
// a function defined in a different Go package and have that function retain
|
|
// access to all of the package-scoped variables of its own package.
|
|
func (s *localScope) ReparentTo(pctx PackageContext) {
|
|
s.scope.parent = pctx.getScope()
|
|
}
|
|
|
|
func (s *localScope) LookupVariable(name string) (Variable, error) {
|
|
return s.scope.LookupVariable(name)
|
|
}
|
|
|
|
func (s *localScope) IsRuleVisible(rule Rule) bool {
|
|
return s.scope.IsRuleVisible(rule)
|
|
}
|
|
|
|
func (s *localScope) IsPoolVisible(pool Pool) bool {
|
|
return s.scope.IsPoolVisible(pool)
|
|
}
|
|
|
|
func (s *localScope) AddLocalVariable(name, value string) (*localVariable,
|
|
error) {
|
|
|
|
err := validateNinjaName(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if strings.ContainsRune(name, '.') {
|
|
return nil, fmt.Errorf("local variable name %q contains '.'", name)
|
|
}
|
|
|
|
ninjaValue, err := parseNinjaString(s.scope, value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v := &localVariable{
|
|
fullName_: s.namePrefix + name,
|
|
name_: name,
|
|
value_: ninjaValue,
|
|
}
|
|
|
|
err = s.scope.AddVariable(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
func (s *localScope) AddLocalRule(name string, params *RuleParams,
|
|
argNames ...string) (*localRule, error) {
|
|
|
|
err := validateNinjaName(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = validateArgNames(argNames)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid argument name: %s", err)
|
|
}
|
|
|
|
argNamesSet := make(map[string]bool)
|
|
for _, argName := range argNames {
|
|
argNamesSet[argName] = true
|
|
}
|
|
|
|
ruleScope := makeRuleScope(s.scope, argNamesSet)
|
|
|
|
def, err := parseRuleParams(ruleScope, params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r := &localRule{
|
|
fullName_: s.namePrefix + name,
|
|
name_: name,
|
|
def_: def,
|
|
argNames: argNamesSet,
|
|
scope_: ruleScope,
|
|
}
|
|
|
|
err = s.scope.AddRule(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
type localVariable struct {
|
|
fullName_ string
|
|
name_ string
|
|
value_ *ninjaString
|
|
}
|
|
|
|
func (l *localVariable) packageContext() *packageContext {
|
|
return nil
|
|
}
|
|
|
|
func (l *localVariable) name() string {
|
|
return l.name_
|
|
}
|
|
|
|
func (l *localVariable) fullName(pkgNames map[*packageContext]string) string {
|
|
return l.fullName_
|
|
}
|
|
|
|
func (l *localVariable) value(VariableFuncContext, interface{}) (*ninjaString, error) {
|
|
return l.value_, nil
|
|
}
|
|
|
|
func (l *localVariable) String() string {
|
|
return "<local var>:" + l.fullName_
|
|
}
|
|
|
|
type localRule struct {
|
|
fullName_ string
|
|
name_ string
|
|
def_ *ruleDef
|
|
argNames map[string]bool
|
|
scope_ *basicScope
|
|
}
|
|
|
|
func (l *localRule) packageContext() *packageContext {
|
|
return nil
|
|
}
|
|
|
|
func (l *localRule) name() string {
|
|
return l.name_
|
|
}
|
|
|
|
func (l *localRule) fullName(pkgNames map[*packageContext]string) string {
|
|
return l.fullName_
|
|
}
|
|
|
|
func (l *localRule) def(interface{}) (*ruleDef, error) {
|
|
return l.def_, nil
|
|
}
|
|
|
|
func (r *localRule) scope() *basicScope {
|
|
return r.scope_
|
|
}
|
|
|
|
func (r *localRule) isArg(argName string) bool {
|
|
return r.argNames[argName]
|
|
}
|
|
|
|
func (r *localRule) String() string {
|
|
return "<local rule>:" + r.fullName_
|
|
}
|
|
|
|
type nameTracker struct {
|
|
variables map[Variable]string
|
|
rules map[Rule]string
|
|
pools map[Pool]string
|
|
|
|
pkgNames map[*packageContext]string
|
|
}
|
|
|
|
func (m *nameTracker) Variable(v Variable) string {
|
|
if m == nil {
|
|
return v.fullName(nil)
|
|
}
|
|
if name, ok := m.variables[v]; ok {
|
|
return name
|
|
}
|
|
return v.fullName(m.pkgNames)
|
|
}
|
|
|
|
func (m *nameTracker) Rule(r Rule) string {
|
|
if m == nil {
|
|
return r.fullName(nil)
|
|
}
|
|
if name, ok := m.rules[r]; ok {
|
|
return name
|
|
}
|
|
return r.fullName(m.pkgNames)
|
|
}
|
|
|
|
func (m *nameTracker) Pool(p Pool) string {
|
|
if m == nil {
|
|
return p.fullName(nil)
|
|
}
|
|
if name, ok := m.pools[p]; ok {
|
|
return name
|
|
}
|
|
return p.fullName(m.pkgNames)
|
|
}
|