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	"errors"
19	"fmt"
20	"sort"
21	"strconv"
22	"strings"
23)
24
25// A Deps value indicates the dependency file format that Ninja should expect to
26// be output by a compiler.
27type Deps int
28
29const (
30	DepsNone Deps = iota
31	DepsGCC
32	DepsMSVC
33)
34
35func (d Deps) String() string {
36	switch d {
37	case DepsNone:
38		return "none"
39	case DepsGCC:
40		return "gcc"
41	case DepsMSVC:
42		return "msvc"
43	default:
44		panic(fmt.Sprintf("unknown deps value: %d", d))
45	}
46}
47
48// A PoolParams object contains the set of parameters that make up a Ninja pool
49// definition.
50type PoolParams struct {
51	Comment string // The comment that will appear above the definition.
52	Depth   int    // The Ninja pool depth.
53}
54
55// A RuleParams object contains the set of parameters that make up a Ninja rule
56// definition.
57type RuleParams struct {
58	// These fields correspond to a Ninja variable of the same name.
59	Command        string // The command that Ninja will run for the rule.
60	Depfile        string // The dependency file name.
61	Deps           Deps   // The format of the dependency file.
62	Description    string // The description that Ninja will print for the rule.
63	Generator      bool   // Whether the rule generates the Ninja manifest file.
64	Pool           Pool   // The Ninja pool to which the rule belongs.
65	Restat         bool   // Whether Ninja should re-stat the rule's outputs.
66	Rspfile        string // The response file.
67	RspfileContent string // The response file content.
68
69	// These fields are used internally in Blueprint
70	CommandDeps      []string // Command-specific implicit dependencies to prepend to builds
71	CommandOrderOnly []string // Command-specific order-only dependencies to prepend to builds
72	Comment          string   // The comment that will appear above the definition.
73}
74
75// A BuildParams object contains the set of parameters that make up a Ninja
76// build statement.  Each field except for Args corresponds with a part of the
77// Ninja build statement.  The Args field contains variable names and values
78// that are set within the build statement's scope in the Ninja file.
79type BuildParams struct {
80	Comment         string            // The comment that will appear above the definition.
81	Depfile         string            // The dependency file name.
82	Deps            Deps              // The format of the dependency file.
83	Description     string            // The description that Ninja will print for the build.
84	Rule            Rule              // The rule to invoke.
85	Outputs         []string          // The list of explicit output targets.
86	ImplicitOutputs []string          // The list of implicit output targets.
87	Inputs          []string          // The list of explicit input dependencies.
88	Implicits       []string          // The list of implicit input dependencies.
89	OrderOnly       []string          // The list of order-only dependencies.
90	Validations     []string          // The list of validations to run when this rule runs.
91	Args            map[string]string // The variable/value pairs to set.
92	Optional        bool              // Skip outputting a default statement
93}
94
95// A poolDef describes a pool definition.  It does not include the name of the
96// pool.
97type poolDef struct {
98	Comment string
99	Depth   int
100}
101
102func parsePoolParams(scope scope, params *PoolParams) (*poolDef,
103	error) {
104
105	def := &poolDef{
106		Comment: params.Comment,
107		Depth:   params.Depth,
108	}
109
110	return def, nil
111}
112
113func (p *poolDef) WriteTo(nw *ninjaWriter, name string) error {
114	if p.Comment != "" {
115		err := nw.Comment(p.Comment)
116		if err != nil {
117			return err
118		}
119	}
120
121	err := nw.Pool(name)
122	if err != nil {
123		return err
124	}
125
126	return nw.ScopedAssign("depth", strconv.Itoa(p.Depth))
127}
128
129// A ruleDef describes a rule definition.  It does not include the name of the
130// rule.
131type ruleDef struct {
132	CommandDeps      []ninjaString
133	CommandOrderOnly []ninjaString
134	Comment          string
135	Pool             Pool
136	Variables        map[string]ninjaString
137}
138
139func parseRuleParams(scope scope, params *RuleParams) (*ruleDef,
140	error) {
141
142	r := &ruleDef{
143		Comment:   params.Comment,
144		Pool:      params.Pool,
145		Variables: make(map[string]ninjaString),
146	}
147
148	if params.Command == "" {
149		return nil, fmt.Errorf("encountered rule params with no command " +
150			"specified")
151	}
152
153	if r.Pool != nil && !scope.IsPoolVisible(r.Pool) {
154		return nil, fmt.Errorf("Pool %s is not visible in this scope", r.Pool)
155	}
156
157	value, err := parseNinjaString(scope, params.Command)
158	if err != nil {
159		return nil, fmt.Errorf("error parsing Command param: %s", err)
160	}
161	r.Variables["command"] = value
162
163	if params.Depfile != "" {
164		value, err = parseNinjaString(scope, params.Depfile)
165		if err != nil {
166			return nil, fmt.Errorf("error parsing Depfile param: %s", err)
167		}
168		r.Variables["depfile"] = value
169	}
170
171	if params.Deps != DepsNone {
172		r.Variables["deps"] = simpleNinjaString(params.Deps.String())
173	}
174
175	if params.Description != "" {
176		value, err = parseNinjaString(scope, params.Description)
177		if err != nil {
178			return nil, fmt.Errorf("error parsing Description param: %s", err)
179		}
180		r.Variables["description"] = value
181	}
182
183	if params.Generator {
184		r.Variables["generator"] = simpleNinjaString("true")
185	}
186
187	if params.Restat {
188		r.Variables["restat"] = simpleNinjaString("true")
189	}
190
191	if params.Rspfile != "" {
192		value, err = parseNinjaString(scope, params.Rspfile)
193		if err != nil {
194			return nil, fmt.Errorf("error parsing Rspfile param: %s", err)
195		}
196		r.Variables["rspfile"] = value
197	}
198
199	if params.RspfileContent != "" {
200		value, err = parseNinjaString(scope, params.RspfileContent)
201		if err != nil {
202			return nil, fmt.Errorf("error parsing RspfileContent param: %s",
203				err)
204		}
205		r.Variables["rspfile_content"] = value
206	}
207
208	r.CommandDeps, err = parseNinjaStrings(scope, params.CommandDeps)
209	if err != nil {
210		return nil, fmt.Errorf("error parsing CommandDeps param: %s", err)
211	}
212
213	r.CommandOrderOnly, err = parseNinjaStrings(scope, params.CommandOrderOnly)
214	if err != nil {
215		return nil, fmt.Errorf("error parsing CommandDeps param: %s", err)
216	}
217
218	return r, nil
219}
220
221func (r *ruleDef) WriteTo(nw *ninjaWriter, name string,
222	pkgNames map[*packageContext]string) error {
223
224	if r.Comment != "" {
225		err := nw.Comment(r.Comment)
226		if err != nil {
227			return err
228		}
229	}
230
231	err := nw.Rule(name)
232	if err != nil {
233		return err
234	}
235
236	if r.Pool != nil {
237		err = nw.ScopedAssign("pool", r.Pool.fullName(pkgNames))
238		if err != nil {
239			return err
240		}
241	}
242
243	err = writeVariables(nw, r.Variables, pkgNames)
244	if err != nil {
245		return err
246	}
247
248	return nil
249}
250
251// A buildDef describes a build target definition.
252type buildDef struct {
253	Comment         string
254	Rule            Rule
255	RuleDef         *ruleDef
256	Outputs         []ninjaString
257	ImplicitOutputs []ninjaString
258	Inputs          []ninjaString
259	Implicits       []ninjaString
260	OrderOnly       []ninjaString
261	Validations     []ninjaString
262	Args            map[Variable]ninjaString
263	Variables       map[string]ninjaString
264	Optional        bool
265}
266
267func parseBuildParams(scope scope, params *BuildParams) (*buildDef,
268	error) {
269
270	comment := params.Comment
271	rule := params.Rule
272
273	b := &buildDef{
274		Comment: comment,
275		Rule:    rule,
276	}
277
278	setVariable := func(name string, value ninjaString) {
279		if b.Variables == nil {
280			b.Variables = make(map[string]ninjaString)
281		}
282		b.Variables[name] = value
283	}
284
285	if !scope.IsRuleVisible(rule) {
286		return nil, fmt.Errorf("Rule %s is not visible in this scope", rule)
287	}
288
289	if len(params.Outputs) == 0 {
290		return nil, errors.New("Outputs param has no elements")
291	}
292
293	var err error
294	b.Outputs, err = parseNinjaStrings(scope, params.Outputs)
295	if err != nil {
296		return nil, fmt.Errorf("error parsing Outputs param: %s", err)
297	}
298
299	b.ImplicitOutputs, err = parseNinjaStrings(scope, params.ImplicitOutputs)
300	if err != nil {
301		return nil, fmt.Errorf("error parsing ImplicitOutputs param: %s", err)
302	}
303
304	b.Inputs, err = parseNinjaStrings(scope, params.Inputs)
305	if err != nil {
306		return nil, fmt.Errorf("error parsing Inputs param: %s", err)
307	}
308
309	b.Implicits, err = parseNinjaStrings(scope, params.Implicits)
310	if err != nil {
311		return nil, fmt.Errorf("error parsing Implicits param: %s", err)
312	}
313
314	b.OrderOnly, err = parseNinjaStrings(scope, params.OrderOnly)
315	if err != nil {
316		return nil, fmt.Errorf("error parsing OrderOnly param: %s", err)
317	}
318
319	b.Validations, err = parseNinjaStrings(scope, params.Validations)
320	if err != nil {
321		return nil, fmt.Errorf("error parsing Validations param: %s", err)
322	}
323
324	b.Optional = params.Optional
325
326	if params.Depfile != "" {
327		value, err := parseNinjaString(scope, params.Depfile)
328		if err != nil {
329			return nil, fmt.Errorf("error parsing Depfile param: %s", err)
330		}
331		setVariable("depfile", value)
332	}
333
334	if params.Deps != DepsNone {
335		setVariable("deps", simpleNinjaString(params.Deps.String()))
336	}
337
338	if params.Description != "" {
339		value, err := parseNinjaString(scope, params.Description)
340		if err != nil {
341			return nil, fmt.Errorf("error parsing Description param: %s", err)
342		}
343		setVariable("description", value)
344	}
345
346	argNameScope := rule.scope()
347
348	if len(params.Args) > 0 {
349		b.Args = make(map[Variable]ninjaString)
350		for name, value := range params.Args {
351			if !rule.isArg(name) {
352				return nil, fmt.Errorf("unknown argument %q", name)
353			}
354
355			argVar, err := argNameScope.LookupVariable(name)
356			if err != nil {
357				// This shouldn't happen.
358				return nil, fmt.Errorf("argument lookup error: %s", err)
359			}
360
361			ninjaValue, err := parseNinjaString(scope, value)
362			if err != nil {
363				return nil, fmt.Errorf("error parsing variable %q: %s", name,
364					err)
365			}
366
367			b.Args[argVar] = ninjaValue
368		}
369	}
370
371	return b, nil
372}
373
374func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*packageContext]string) error {
375	var (
376		comment       = b.Comment
377		rule          = b.Rule.fullName(pkgNames)
378		outputs       = valueList(b.Outputs, pkgNames, outputEscaper)
379		implicitOuts  = valueList(b.ImplicitOutputs, pkgNames, outputEscaper)
380		explicitDeps  = valueList(b.Inputs, pkgNames, inputEscaper)
381		implicitDeps  = valueList(b.Implicits, pkgNames, inputEscaper)
382		orderOnlyDeps = valueList(b.OrderOnly, pkgNames, inputEscaper)
383		validations   = valueList(b.Validations, pkgNames, inputEscaper)
384	)
385
386	if b.RuleDef != nil {
387		implicitDeps = append(valueList(b.RuleDef.CommandDeps, pkgNames, inputEscaper), implicitDeps...)
388		orderOnlyDeps = append(valueList(b.RuleDef.CommandOrderOnly, pkgNames, inputEscaper), orderOnlyDeps...)
389	}
390
391	err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps, validations)
392	if err != nil {
393		return err
394	}
395
396	args := make(map[string]string)
397
398	for argVar, value := range b.Args {
399		args[argVar.fullName(pkgNames)] = value.Value(pkgNames)
400	}
401
402	err = writeVariables(nw, b.Variables, pkgNames)
403	if err != nil {
404		return err
405	}
406
407	var keys []string
408	for k := range args {
409		keys = append(keys, k)
410	}
411	sort.Strings(keys)
412
413	for _, name := range keys {
414		err = nw.ScopedAssign(name, args[name])
415		if err != nil {
416			return err
417		}
418	}
419
420	if !b.Optional {
421		err = nw.Default(outputs...)
422		if err != nil {
423			return err
424		}
425	}
426
427	return nw.BlankLine()
428}
429
430func valueList(list []ninjaString, pkgNames map[*packageContext]string,
431	escaper *strings.Replacer) []string {
432
433	result := make([]string, len(list))
434	for i, ninjaStr := range list {
435		result[i] = ninjaStr.ValueWithEscaper(pkgNames, escaper)
436	}
437	return result
438}
439
440func writeVariables(nw *ninjaWriter, variables map[string]ninjaString,
441	pkgNames map[*packageContext]string) error {
442	var keys []string
443	for k := range variables {
444		keys = append(keys, k)
445	}
446	sort.Strings(keys)
447
448	for _, name := range keys {
449		err := nw.ScopedAssign(name, variables[name].Value(pkgNames))
450		if err != nil {
451			return err
452		}
453	}
454	return nil
455}
456