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