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