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	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.
37type 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.
47type 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
57type 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
65func 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
75func 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
103func (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
147func (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
172func (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
197func (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
208func (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
217func (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
227func (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
237func (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
247type localScope struct {
248	namePrefix string
249	scope      *basicScope
250}
251
252func 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.
263func (s *localScope) ReparentTo(pctx PackageContext) {
264	s.scope.parent = pctx.getScope()
265}
266
267func (s *localScope) LookupVariable(name string) (Variable, error) {
268	return s.scope.LookupVariable(name)
269}
270
271func (s *localScope) IsRuleVisible(rule Rule) bool {
272	return s.scope.IsRuleVisible(rule)
273}
274
275func (s *localScope) IsPoolVisible(pool Pool) bool {
276	return s.scope.IsPoolVisible(pool)
277}
278
279func (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
310func (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
351type localVariable struct {
352	namePrefix string
353	name_      string
354	value_     *ninjaString
355}
356
357func (l *localVariable) packageContext() *packageContext {
358	return nil
359}
360
361func (l *localVariable) name() string {
362	return l.name_
363}
364
365func (l *localVariable) fullName(pkgNames map[*packageContext]string) string {
366	return l.namePrefix + l.name_
367}
368
369func (l *localVariable) value(interface{}) (*ninjaString, error) {
370	return l.value_, nil
371}
372
373func (l *localVariable) String() string {
374	return "<local var>:" + l.namePrefix + l.name_
375}
376
377type localRule struct {
378	namePrefix string
379	name_      string
380	def_       *ruleDef
381	argNames   map[string]bool
382	scope_     *basicScope
383}
384
385func (l *localRule) packageContext() *packageContext {
386	return nil
387}
388
389func (l *localRule) name() string {
390	return l.name_
391}
392
393func (l *localRule) fullName(pkgNames map[*packageContext]string) string {
394	return l.namePrefix + l.name_
395}
396
397func (l *localRule) def(interface{}) (*ruleDef, error) {
398	return l.def_, nil
399}
400
401func (r *localRule) scope() *basicScope {
402	return r.scope_
403}
404
405func (r *localRule) isArg(argName string) bool {
406	return r.argNames[argName]
407}
408
409func (r *localRule) String() string {
410	return "<local rule>:" + r.namePrefix + r.name_
411}
412