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	Args            map[string]string // The variable/value pairs to set.
91	Optional        bool              // Skip outputting a default statement
92}
93
94// A poolDef describes a pool definition.  It does not include the name of the
95// pool.
96type poolDef struct {
97	Comment string
98	Depth   int
99}
100
101func parsePoolParams(scope scope, params *PoolParams) (*poolDef,
102	error) {
103
104	def := &poolDef{
105		Comment: params.Comment,
106		Depth:   params.Depth,
107	}
108
109	return def, nil
110}
111
112func (p *poolDef) WriteTo(nw *ninjaWriter, name string) error {
113	if p.Comment != "" {
114		err := nw.Comment(p.Comment)
115		if err != nil {
116			return err
117		}
118	}
119
120	err := nw.Pool(name)
121	if err != nil {
122		return err
123	}
124
125	return nw.ScopedAssign("depth", strconv.Itoa(p.Depth))
126}
127
128// A ruleDef describes a rule definition.  It does not include the name of the
129// rule.
130type ruleDef struct {
131	CommandDeps      []ninjaString
132	CommandOrderOnly []ninjaString
133	Comment          string
134	Pool             Pool
135	Variables        map[string]ninjaString
136}
137
138func parseRuleParams(scope scope, params *RuleParams) (*ruleDef,
139	error) {
140
141	r := &ruleDef{
142		Comment:   params.Comment,
143		Pool:      params.Pool,
144		Variables: make(map[string]ninjaString),
145	}
146
147	if params.Command == "" {
148		return nil, fmt.Errorf("encountered rule params with no command " +
149			"specified")
150	}
151
152	if r.Pool != nil && !scope.IsPoolVisible(r.Pool) {
153		return nil, fmt.Errorf("Pool %s is not visible in this scope", r.Pool)
154	}
155
156	value, err := parseNinjaString(scope, params.Command)
157	if err != nil {
158		return nil, fmt.Errorf("error parsing Command param: %s", err)
159	}
160	r.Variables["command"] = value
161
162	if params.Depfile != "" {
163		value, err = parseNinjaString(scope, params.Depfile)
164		if err != nil {
165			return nil, fmt.Errorf("error parsing Depfile param: %s", err)
166		}
167		r.Variables["depfile"] = value
168	}
169
170	if params.Deps != DepsNone {
171		r.Variables["deps"] = simpleNinjaString(params.Deps.String())
172	}
173
174	if params.Description != "" {
175		value, err = parseNinjaString(scope, params.Description)
176		if err != nil {
177			return nil, fmt.Errorf("error parsing Description param: %s", err)
178		}
179		r.Variables["description"] = value
180	}
181
182	if params.Generator {
183		r.Variables["generator"] = simpleNinjaString("true")
184	}
185
186	if params.Restat {
187		r.Variables["restat"] = simpleNinjaString("true")
188	}
189
190	if params.Rspfile != "" {
191		value, err = parseNinjaString(scope, params.Rspfile)
192		if err != nil {
193			return nil, fmt.Errorf("error parsing Rspfile param: %s", err)
194		}
195		r.Variables["rspfile"] = value
196	}
197
198	if params.RspfileContent != "" {
199		value, err = parseNinjaString(scope, params.RspfileContent)
200		if err != nil {
201			return nil, fmt.Errorf("error parsing RspfileContent param: %s",
202				err)
203		}
204		r.Variables["rspfile_content"] = value
205	}
206
207	r.CommandDeps, err = parseNinjaStrings(scope, params.CommandDeps)
208	if err != nil {
209		return nil, fmt.Errorf("error parsing CommandDeps param: %s", err)
210	}
211
212	r.CommandOrderOnly, err = parseNinjaStrings(scope, params.CommandOrderOnly)
213	if err != nil {
214		return nil, fmt.Errorf("error parsing CommandDeps param: %s", err)
215	}
216
217	return r, nil
218}
219
220func (r *ruleDef) WriteTo(nw *ninjaWriter, name string,
221	pkgNames map[*packageContext]string) error {
222
223	if r.Comment != "" {
224		err := nw.Comment(r.Comment)
225		if err != nil {
226			return err
227		}
228	}
229
230	err := nw.Rule(name)
231	if err != nil {
232		return err
233	}
234
235	if r.Pool != nil {
236		err = nw.ScopedAssign("pool", r.Pool.fullName(pkgNames))
237		if err != nil {
238			return err
239		}
240	}
241
242	err = writeVariables(nw, r.Variables, pkgNames)
243	if err != nil {
244		return err
245	}
246
247	return nil
248}
249
250// A buildDef describes a build target definition.
251type buildDef struct {
252	Comment         string
253	Rule            Rule
254	RuleDef         *ruleDef
255	Outputs         []ninjaString
256	ImplicitOutputs []ninjaString
257	Inputs          []ninjaString
258	Implicits       []ninjaString
259	OrderOnly       []ninjaString
260	Args            map[Variable]ninjaString
261	Variables       map[string]ninjaString
262	Optional        bool
263}
264
265func parseBuildParams(scope scope, params *BuildParams) (*buildDef,
266	error) {
267
268	comment := params.Comment
269	rule := params.Rule
270
271	b := &buildDef{
272		Comment: comment,
273		Rule:    rule,
274	}
275
276	setVariable := func(name string, value ninjaString) {
277		if b.Variables == nil {
278			b.Variables = make(map[string]ninjaString)
279		}
280		b.Variables[name] = value
281	}
282
283	if !scope.IsRuleVisible(rule) {
284		return nil, fmt.Errorf("Rule %s is not visible in this scope", rule)
285	}
286
287	if len(params.Outputs) == 0 {
288		return nil, errors.New("Outputs param has no elements")
289	}
290
291	var err error
292	b.Outputs, err = parseNinjaStrings(scope, params.Outputs)
293	if err != nil {
294		return nil, fmt.Errorf("error parsing Outputs param: %s", err)
295	}
296
297	b.ImplicitOutputs, err = parseNinjaStrings(scope, params.ImplicitOutputs)
298	if err != nil {
299		return nil, fmt.Errorf("error parsing ImplicitOutputs param: %s", err)
300	}
301
302	b.Inputs, err = parseNinjaStrings(scope, params.Inputs)
303	if err != nil {
304		return nil, fmt.Errorf("error parsing Inputs param: %s", err)
305	}
306
307	b.Implicits, err = parseNinjaStrings(scope, params.Implicits)
308	if err != nil {
309		return nil, fmt.Errorf("error parsing Implicits param: %s", err)
310	}
311
312	b.OrderOnly, err = parseNinjaStrings(scope, params.OrderOnly)
313	if err != nil {
314		return nil, fmt.Errorf("error parsing OrderOnly param: %s", err)
315	}
316
317	b.Optional = params.Optional
318
319	if params.Depfile != "" {
320		value, err := parseNinjaString(scope, params.Depfile)
321		if err != nil {
322			return nil, fmt.Errorf("error parsing Depfile param: %s", err)
323		}
324		setVariable("depfile", value)
325	}
326
327	if params.Deps != DepsNone {
328		setVariable("deps", simpleNinjaString(params.Deps.String()))
329	}
330
331	if params.Description != "" {
332		value, err := parseNinjaString(scope, params.Description)
333		if err != nil {
334			return nil, fmt.Errorf("error parsing Description param: %s", err)
335		}
336		setVariable("description", value)
337	}
338
339	argNameScope := rule.scope()
340
341	if len(params.Args) > 0 {
342		b.Args = make(map[Variable]ninjaString)
343		for name, value := range params.Args {
344			if !rule.isArg(name) {
345				return nil, fmt.Errorf("unknown argument %q", name)
346			}
347
348			argVar, err := argNameScope.LookupVariable(name)
349			if err != nil {
350				// This shouldn't happen.
351				return nil, fmt.Errorf("argument lookup error: %s", err)
352			}
353
354			ninjaValue, err := parseNinjaString(scope, value)
355			if err != nil {
356				return nil, fmt.Errorf("error parsing variable %q: %s", name,
357					err)
358			}
359
360			b.Args[argVar] = ninjaValue
361		}
362	}
363
364	return b, nil
365}
366
367func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*packageContext]string) error {
368	var (
369		comment       = b.Comment
370		rule          = b.Rule.fullName(pkgNames)
371		outputs       = valueList(b.Outputs, pkgNames, outputEscaper)
372		implicitOuts  = valueList(b.ImplicitOutputs, pkgNames, outputEscaper)
373		explicitDeps  = valueList(b.Inputs, pkgNames, inputEscaper)
374		implicitDeps  = valueList(b.Implicits, pkgNames, inputEscaper)
375		orderOnlyDeps = valueList(b.OrderOnly, pkgNames, inputEscaper)
376	)
377
378	if b.RuleDef != nil {
379		implicitDeps = append(valueList(b.RuleDef.CommandDeps, pkgNames, inputEscaper), implicitDeps...)
380		orderOnlyDeps = append(valueList(b.RuleDef.CommandOrderOnly, pkgNames, inputEscaper), orderOnlyDeps...)
381	}
382
383	err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps)
384	if err != nil {
385		return err
386	}
387
388	args := make(map[string]string)
389
390	for argVar, value := range b.Args {
391		args[argVar.fullName(pkgNames)] = value.Value(pkgNames)
392	}
393
394	err = writeVariables(nw, b.Variables, pkgNames)
395	if err != nil {
396		return err
397	}
398
399	var keys []string
400	for k := range args {
401		keys = append(keys, k)
402	}
403	sort.Strings(keys)
404
405	for _, name := range keys {
406		err = nw.ScopedAssign(name, args[name])
407		if err != nil {
408			return err
409		}
410	}
411
412	if !b.Optional {
413		err = nw.Default(outputs...)
414		if err != nil {
415			return err
416		}
417	}
418
419	return nw.BlankLine()
420}
421
422func valueList(list []ninjaString, pkgNames map[*packageContext]string,
423	escaper *strings.Replacer) []string {
424
425	result := make([]string, len(list))
426	for i, ninjaStr := range list {
427		result[i] = ninjaStr.ValueWithEscaper(pkgNames, escaper)
428	}
429	return result
430}
431
432func writeVariables(nw *ninjaWriter, variables map[string]ninjaString,
433	pkgNames map[*packageContext]string) error {
434	var keys []string
435	for k := range variables {
436		keys = append(keys, k)
437	}
438	sort.Strings(keys)
439
440	for _, name := range keys {
441		err := nw.ScopedAssign(name, variables[name].Value(pkgNames))
442		if err != nil {
443			return err
444		}
445	}
446	return nil
447}
448