github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/blueprint/scope.go (about)

     1  // Copyright 2014 Google Inc. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package blueprint
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"unicode"
    21  	"unicode/utf8"
    22  )
    23  
    24  // A Variable represents a global Ninja variable definition that will be written
    25  // to the output .ninja file.  A variable may contain references to other global
    26  // Ninja variables, but circular variable references are not allowed.
    27  type Variable interface {
    28  	packageContext() *packageContext
    29  	name() string                                        // "foo"
    30  	fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
    31  	value(config interface{}) (*ninjaString, error)
    32  	String() string
    33  }
    34  
    35  // A Pool represents a Ninja pool that will be written to the output .ninja
    36  // file.
    37  type Pool interface {
    38  	packageContext() *packageContext
    39  	name() string                                        // "foo"
    40  	fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
    41  	def(config interface{}) (*poolDef, error)
    42  	String() string
    43  }
    44  
    45  // A Rule represents a Ninja build rule that will be written to the output
    46  // .ninja file.
    47  type Rule interface {
    48  	packageContext() *packageContext
    49  	name() string                                        // "foo"
    50  	fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
    51  	def(config interface{}) (*ruleDef, error)
    52  	scope() *basicScope
    53  	isArg(argName string) bool
    54  	String() string
    55  }
    56  
    57  type basicScope struct {
    58  	parent    *basicScope
    59  	variables map[string]Variable
    60  	pools     map[string]Pool
    61  	rules     map[string]Rule
    62  	imports   map[string]*basicScope
    63  }
    64  
    65  func newScope(parent *basicScope) *basicScope {
    66  	return &basicScope{
    67  		parent:    parent,
    68  		variables: make(map[string]Variable),
    69  		pools:     make(map[string]Pool),
    70  		rules:     make(map[string]Rule),
    71  		imports:   make(map[string]*basicScope),
    72  	}
    73  }
    74  
    75  func makeRuleScope(parent *basicScope, argNames map[string]bool) *basicScope {
    76  	scope := newScope(parent)
    77  	for argName := range argNames {
    78  		_, err := scope.LookupVariable(argName)
    79  		if err != nil {
    80  			arg := &argVariable{argName}
    81  			err = scope.AddVariable(arg)
    82  			if err != nil {
    83  				// This should not happen.  We should have already checked that
    84  				// the name is valid and that the scope doesn't have a variable
    85  				// with this name.
    86  				panic(err)
    87  			}
    88  		}
    89  	}
    90  
    91  	// We treat built-in variables like arguments for the purpose of this scope.
    92  	for _, builtin := range builtinRuleArgs {
    93  		arg := &argVariable{builtin}
    94  		err := scope.AddVariable(arg)
    95  		if err != nil {
    96  			panic(err)
    97  		}
    98  	}
    99  
   100  	return scope
   101  }
   102  
   103  func (s *basicScope) LookupVariable(name string) (Variable, error) {
   104  	dotIndex := strings.IndexRune(name, '.')
   105  	if dotIndex >= 0 {
   106  		// The variable name looks like "pkg.var"
   107  		if dotIndex+1 == len(name) {
   108  			return nil, fmt.Errorf("variable name %q ends with a '.'", name)
   109  		}
   110  		if strings.ContainsRune(name[dotIndex+1:], '.') {
   111  			return nil, fmt.Errorf("variable name %q contains multiple '.' "+
   112  				"characters", name)
   113  		}
   114  
   115  		pkgName := name[:dotIndex]
   116  		varName := name[dotIndex+1:]
   117  
   118  		first, _ := utf8.DecodeRuneInString(varName)
   119  		if !unicode.IsUpper(first) {
   120  			return nil, fmt.Errorf("cannot refer to unexported name %q", name)
   121  		}
   122  
   123  		importedScope, err := s.lookupImportedScope(pkgName)
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  
   128  		v, ok := importedScope.variables[varName]
   129  		if !ok {
   130  			return nil, fmt.Errorf("package %q does not contain variable %q",
   131  				pkgName, varName)
   132  		}
   133  
   134  		return v, nil
   135  	} else {
   136  		// The variable name has no package part; just "var"
   137  		for ; s != nil; s = s.parent {
   138  			v, ok := s.variables[name]
   139  			if ok {
   140  				return v, nil
   141  			}
   142  		}
   143  		return nil, fmt.Errorf("undefined variable %q", name)
   144  	}
   145  }
   146  
   147  func (s *basicScope) IsRuleVisible(rule Rule) bool {
   148  	_, isBuiltin := rule.(*builtinRule)
   149  	if isBuiltin {
   150  		return true
   151  	}
   152  
   153  	name := rule.name()
   154  
   155  	for s != nil {
   156  		if s.rules[name] == rule {
   157  			return true
   158  		}
   159  
   160  		for _, import_ := range s.imports {
   161  			if import_.rules[name] == rule {
   162  				return true
   163  			}
   164  		}
   165  
   166  		s = s.parent
   167  	}
   168  
   169  	return false
   170  }
   171  
   172  func (s *basicScope) IsPoolVisible(pool Pool) bool {
   173  	_, isBuiltin := pool.(*builtinPool)
   174  	if isBuiltin {
   175  		return true
   176  	}
   177  
   178  	name := pool.name()
   179  
   180  	for s != nil {
   181  		if s.pools[name] == pool {
   182  			return true
   183  		}
   184  
   185  		for _, import_ := range s.imports {
   186  			if import_.pools[name] == pool {
   187  				return true
   188  			}
   189  		}
   190  
   191  		s = s.parent
   192  	}
   193  
   194  	return false
   195  }
   196  
   197  func (s *basicScope) lookupImportedScope(pkgName string) (*basicScope, error) {
   198  	for ; s != nil; s = s.parent {
   199  		importedScope, ok := s.imports[pkgName]
   200  		if ok {
   201  			return importedScope, nil
   202  		}
   203  	}
   204  	return nil, fmt.Errorf("unknown imported package %q (missing call to "+
   205  		"blueprint.Import()?)", pkgName)
   206  }
   207  
   208  func (s *basicScope) AddImport(name string, importedScope *basicScope) error {
   209  	_, present := s.imports[name]
   210  	if present {
   211  		return fmt.Errorf("import %q is already defined in this scope", name)
   212  	}
   213  	s.imports[name] = importedScope
   214  	return nil
   215  }
   216  
   217  func (s *basicScope) AddVariable(v Variable) error {
   218  	name := v.name()
   219  	_, present := s.variables[name]
   220  	if present {
   221  		return fmt.Errorf("variable %q is already defined in this scope", name)
   222  	}
   223  	s.variables[name] = v
   224  	return nil
   225  }
   226  
   227  func (s *basicScope) AddPool(p Pool) error {
   228  	name := p.name()
   229  	_, present := s.pools[name]
   230  	if present {
   231  		return fmt.Errorf("pool %q is already defined in this scope", name)
   232  	}
   233  	s.pools[name] = p
   234  	return nil
   235  }
   236  
   237  func (s *basicScope) AddRule(r Rule) error {
   238  	name := r.name()
   239  	_, present := s.rules[name]
   240  	if present {
   241  		return fmt.Errorf("rule %q is already defined in this scope", name)
   242  	}
   243  	s.rules[name] = r
   244  	return nil
   245  }
   246  
   247  type localScope struct {
   248  	namePrefix string
   249  	scope      *basicScope
   250  }
   251  
   252  func newLocalScope(parent *basicScope, namePrefix string) *localScope {
   253  	return &localScope{
   254  		namePrefix: namePrefix,
   255  		scope:      newScope(parent),
   256  	}
   257  }
   258  
   259  // ReparentTo sets the localScope's parent scope to the scope of the given
   260  // package context.  This allows a ModuleContext and SingletonContext to call
   261  // a function defined in a different Go package and have that function retain
   262  // access to all of the package-scoped variables of its own package.
   263  func (s *localScope) ReparentTo(pctx PackageContext) {
   264  	s.scope.parent = pctx.getScope()
   265  }
   266  
   267  func (s *localScope) LookupVariable(name string) (Variable, error) {
   268  	return s.scope.LookupVariable(name)
   269  }
   270  
   271  func (s *localScope) IsRuleVisible(rule Rule) bool {
   272  	return s.scope.IsRuleVisible(rule)
   273  }
   274  
   275  func (s *localScope) IsPoolVisible(pool Pool) bool {
   276  	return s.scope.IsPoolVisible(pool)
   277  }
   278  
   279  func (s *localScope) AddLocalVariable(name, value string) (*localVariable,
   280  	error) {
   281  
   282  	err := validateNinjaName(name)
   283  	if err != nil {
   284  		return nil, err
   285  	}
   286  
   287  	if strings.ContainsRune(name, '.') {
   288  		return nil, fmt.Errorf("local variable name %q contains '.'", name)
   289  	}
   290  
   291  	ninjaValue, err := parseNinjaString(s.scope, value)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	v := &localVariable{
   297  		namePrefix: s.namePrefix,
   298  		name_:      name,
   299  		value_:     ninjaValue,
   300  	}
   301  
   302  	err = s.scope.AddVariable(v)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	return v, nil
   308  }
   309  
   310  func (s *localScope) AddLocalRule(name string, params *RuleParams,
   311  	argNames ...string) (*localRule, error) {
   312  
   313  	err := validateNinjaName(name)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  
   318  	err = validateArgNames(argNames)
   319  	if err != nil {
   320  		return nil, fmt.Errorf("invalid argument name: %s", err)
   321  	}
   322  
   323  	argNamesSet := make(map[string]bool)
   324  	for _, argName := range argNames {
   325  		argNamesSet[argName] = true
   326  	}
   327  
   328  	ruleScope := makeRuleScope(s.scope, argNamesSet)
   329  
   330  	def, err := parseRuleParams(ruleScope, params)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	r := &localRule{
   336  		namePrefix: s.namePrefix,
   337  		name_:      name,
   338  		def_:       def,
   339  		argNames:   argNamesSet,
   340  		scope_:     ruleScope,
   341  	}
   342  
   343  	err = s.scope.AddRule(r)
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  
   348  	return r, nil
   349  }
   350  
   351  type localVariable struct {
   352  	namePrefix string
   353  	name_      string
   354  	value_     *ninjaString
   355  }
   356  
   357  func (l *localVariable) packageContext() *packageContext {
   358  	return nil
   359  }
   360  
   361  func (l *localVariable) name() string {
   362  	return l.name_
   363  }
   364  
   365  func (l *localVariable) fullName(pkgNames map[*packageContext]string) string {
   366  	return l.namePrefix + l.name_
   367  }
   368  
   369  func (l *localVariable) value(interface{}) (*ninjaString, error) {
   370  	return l.value_, nil
   371  }
   372  
   373  func (l *localVariable) String() string {
   374  	return "<local var>:" + l.namePrefix + l.name_
   375  }
   376  
   377  type localRule struct {
   378  	namePrefix string
   379  	name_      string
   380  	def_       *ruleDef
   381  	argNames   map[string]bool
   382  	scope_     *basicScope
   383  }
   384  
   385  func (l *localRule) packageContext() *packageContext {
   386  	return nil
   387  }
   388  
   389  func (l *localRule) name() string {
   390  	return l.name_
   391  }
   392  
   393  func (l *localRule) fullName(pkgNames map[*packageContext]string) string {
   394  	return l.namePrefix + l.name_
   395  }
   396  
   397  func (l *localRule) def(interface{}) (*ruleDef, error) {
   398  	return l.def_, nil
   399  }
   400  
   401  func (r *localRule) scope() *basicScope {
   402  	return r.scope_
   403  }
   404  
   405  func (r *localRule) isArg(argName string) bool {
   406  	return r.argNames[argName]
   407  }
   408  
   409  func (r *localRule) String() string {
   410  	return "<local rule>:" + r.namePrefix + r.name_
   411  }