blob: eded6cdfeafeb2f67ee49fd676a41eac68e3040e [file] [log] [blame]
Shinichiro Hamajib69bf8a2015-06-10 14:52:06 +09001// Copyright 2015 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
Fumitoshi Ukai744bb2b2015-06-25 00:10:52 +090015package kati
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +090016
17import (
18 "bytes"
19 "fmt"
20 "os"
Shinichiro Hamaji11c45842015-05-27 19:02:07 +090021 "path/filepath"
Shinichiro Hamajibcecb502015-06-04 16:20:25 +090022 "regexp"
23 "runtime"
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +090024 "sort"
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +090025 "strings"
Fumitoshi Ukai49599e52015-06-26 10:10:24 +090026 "time"
Fumitoshi Ukaif90f73c2015-07-28 14:37:02 +090027
28 "github.com/golang/glog"
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +090029)
30
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +090031// NinjaGenerator generates ninja build files from DepGraph.
32type NinjaGenerator struct {
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +090033 // Args is original arguments to generate the ninja file.
34 Args []string
35 // Suffix is suffix for generated files.
36 Suffix string
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +090037 // GomaDir is goma directory. If empty, goma will not be used.
38 GomaDir string
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +090039 // DetectAndroidEcho detects echo as description.
40 DetectAndroidEcho bool
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +090041 // ErrorOnEnvChange cause error when env change is detected when run ninja.
42 ErrorOnEnvChange bool
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +090043
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +090044 f *os.File
45 nodes []*DepNode
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +090046 exports map[string]bool
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +090047
48 ctx *execContext
49
Fumitoshi Ukai73b0b242015-07-28 16:55:26 +090050 ruleID int
51 // true: rule emitted
52 // false: visited, but no rule emitted
53 // undef: not visited yet
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +090054 done map[string]bool
55 shortNames map[string][]string
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +090056}
57
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +090058func (n *NinjaGenerator) init(g *DepGraph) {
59 n.nodes = g.nodes
60 n.exports = g.exports
61 n.ctx = newExecContext(g.vars, g.vpaths, true)
62 n.done = make(map[string]bool)
63 n.shortNames = make(map[string][]string)
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +090064}
65
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +090066func getDepfileImpl(ss string) (string, error) {
Shinichiro Hamaji6dca2202015-05-27 18:00:34 +090067 tss := ss + " "
Fumitoshi Ukaib8b80502015-07-21 14:28:26 +090068 if (!strings.Contains(tss, " -MD ") && !strings.Contains(tss, " -MMD ")) || !strings.Contains(tss, " -c ") {
Shinichiro Hamaji6dca2202015-05-27 18:00:34 +090069 return "", nil
70 }
71
72 mfIndex := strings.Index(ss, " -MF ")
73 if mfIndex >= 0 {
74 mf := trimLeftSpace(ss[mfIndex+4:])
75 if strings.Index(mf, " -MF ") >= 0 {
76 return "", fmt.Errorf("Multiple output file candidates in %s", ss)
77 }
78 mfEndIndex := strings.IndexAny(mf, " \t\n")
79 if mfEndIndex >= 0 {
80 mf = mf[:mfEndIndex]
81 }
82
Shinichiro Hamaji6dca2202015-05-27 18:00:34 +090083 return mf, nil
84 }
85
86 outIndex := strings.Index(ss, " -o ")
87 if outIndex < 0 {
88 return "", fmt.Errorf("Cannot find the depfile in %s", ss)
89 }
90 out := trimLeftSpace(ss[outIndex+4:])
91 if strings.Index(out, " -o ") >= 0 {
92 return "", fmt.Errorf("Multiple output file candidates in %s", ss)
93 }
94 outEndIndex := strings.IndexAny(out, " \t\n")
95 if outEndIndex >= 0 {
96 out = out[:outEndIndex]
97 }
98 return stripExt(out) + ".d", nil
99}
100
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900101// getDepfile gets depfile from cmdline, and returns cmdline and depfile.
102func getDepfile(cmdline string) (string, string, error) {
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900103 // A hack for Android - llvm-rs-cc seems not to emit a dep file.
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900104 if strings.Contains(cmdline, "bin/llvm-rs-cc ") {
105 return cmdline, "", nil
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900106 }
107
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900108 depfile, err := getDepfileImpl(cmdline)
109 if depfile == "" || err != nil {
110 return cmdline, depfile, err
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900111 }
112
Shinichiro Hamaji3438ae32015-06-05 11:33:20 +0900113 // A hack for Makefiles generated by automake.
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900114 mvCmd := "(mv -f " + depfile + " "
115 if i := strings.LastIndex(cmdline, mvCmd); i >= 0 {
116 rest := cmdline[i+len(mvCmd):]
Shinichiro Hamaji3438ae32015-06-05 11:33:20 +0900117 ei := strings.IndexByte(rest, ')')
118 if ei < 0 {
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900119 return cmdline, "", fmt.Errorf("unbalanced parenthes? %s", cmdline)
Shinichiro Hamaji3438ae32015-06-05 11:33:20 +0900120 }
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900121 cmdline = cmdline[:i] + "(cp -f " + depfile + " " + rest
122 return cmdline, depfile, nil
Shinichiro Hamaji3438ae32015-06-05 11:33:20 +0900123 }
124
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900125 // A hack for Android to get .P files instead of .d.
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900126 p := stripExt(depfile) + ".P"
127 if strings.Contains(cmdline, p) {
128 rmfCmd := "; rm -f " + depfile
129 ncmdline := strings.Replace(cmdline, rmfCmd, "", 1)
130 if ncmdline == cmdline {
131 return cmdline, "", fmt.Errorf("cannot find removal of .d file: %s", cmdline)
132 }
133 return ncmdline, p, nil
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900134 }
135
136 // A hack for Android. For .s files, GCC does not use
137 // C preprocessor, so it ignores -MF flag.
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900138 as := "/" + stripExt(filepath.Base(depfile)) + ".s"
139 if strings.Contains(cmdline, as) {
140 return cmdline, "", nil
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900141 }
142
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900143 cmdline += fmt.Sprintf(" && cp %s %s.tmp", depfile, depfile)
144 depfile += ".tmp"
145 return cmdline, depfile, nil
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900146}
147
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900148func stripShellComment(s string) string {
149 if strings.IndexByte(s, '#') < 0 {
150 // Fast path.
151 return s
152 }
Fumitoshi Ukai43b56662015-07-15 10:41:47 +0900153 // set space as an initial value so the leading comment will be
154 // stripped out.
155 lastch := rune(' ')
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900156 var escape bool
157 var quote rune
158 for i, c := range s {
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900159 if quote != 0 {
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900160 if quote == c && (quote == '\'' || !escape) {
161 quote = 0
162 }
163 } else if !escape {
Fumitoshi Ukai43b56662015-07-15 10:41:47 +0900164 if c == '#' && isWhitespace(lastch) {
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900165 return s[:i]
166 } else if c == '\'' || c == '"' || c == '`' {
167 quote = c
168 }
169 }
170 if escape {
171 escape = false
172 } else if c == '\\' {
173 escape = true
174 } else {
175 escape = false
176 }
Fumitoshi Ukai43b56662015-07-15 10:41:47 +0900177 lastch = c
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900178 }
179 return s
180}
181
Fumitoshi Ukai1713c6f2015-07-14 13:08:24 +0900182var ccRE = regexp.MustCompile(`^prebuilts/(gcc|clang)/.*(gcc|g\+\+|clang|clang\+\+) .* ?-c `)
183
184func gomaCmdForAndroidCompileCmd(cmd string) (string, bool) {
185 i := strings.Index(cmd, " ")
186 if i < 0 {
187 return cmd, false
188 }
189 driver := cmd[:i]
190 if strings.HasSuffix(driver, "ccache") {
191 return gomaCmdForAndroidCompileCmd(cmd[i+1:])
192 }
193 return cmd, ccRE.MatchString(cmd)
194}
195
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900196func descriptionFromCmd(cmd string) (string, bool) {
197 if !strings.HasPrefix(cmd, "echo") || !isWhitespace(rune(cmd[4])) {
198 return "", false
199 }
200 echoarg := cmd[5:]
201
202 // strip outer quotes, and fail if it is not a single echo command.
203 var buf bytes.Buffer
204 var escape bool
205 var quote rune
206 for _, c := range echoarg {
207 if escape {
208 escape = false
209 buf.WriteRune(c)
210 continue
211 }
212 if c == '\\' {
213 escape = true
214 buf.WriteRune(c)
215 continue
216 }
217 if quote != 0 {
218 if c == quote {
219 quote = 0
220 continue
221 }
222 buf.WriteRune(c)
223 continue
224 }
225 switch c {
226 case '\'', '"', '`':
227 quote = c
228 case '<', '>', '&', '|', ';':
229 return "", false
230 default:
231 buf.WriteRune(c)
232 }
233 }
234 return buf.String(), true
235}
236
237func (n *NinjaGenerator) genShellScript(runners []runner) (cmd string, desc string, useLocalPool bool) {
238 const defaultDesc = "build $out"
239 var useGomacc bool
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900240 var buf bytes.Buffer
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900241 for i, r := range runners {
242 if i > 0 {
243 if runners[i-1].ignoreError {
244 buf.WriteString(" ; ")
245 } else {
246 buf.WriteString(" && ")
247 }
248 }
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900249 cmd := stripShellComment(r.cmd)
250 cmd = trimLeftSpace(cmd)
Fumitoshi Ukai74663592015-07-17 17:53:11 +0900251 cmd = strings.Replace(cmd, "\\\n", "", -1)
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900252 cmd = strings.TrimRight(cmd, " \t\n;")
Fumitoshi Ukai701b0e02015-07-28 14:06:06 +0900253 cmd = escapeNinja(cmd)
Shinichiro Hamaji501f68e2015-05-27 17:33:41 +0900254 if cmd == "" {
255 cmd = "true"
256 }
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900257 if n.GomaDir != "" {
Fumitoshi Ukai1713c6f2015-07-14 13:08:24 +0900258 rcmd, ok := gomaCmdForAndroidCompileCmd(cmd)
259 if ok {
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900260 cmd = fmt.Sprintf("%s/gomacc %s", n.GomaDir, rcmd)
Fumitoshi Ukai1713c6f2015-07-14 13:08:24 +0900261 useGomacc = true
262 }
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900263 }
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900264 if n.DetectAndroidEcho && desc == "" {
265 d, ok := descriptionFromCmd(cmd)
266 if ok {
267 desc = d
268 cmd = "true"
269 }
270 }
Shinichiro Hamaji13653b22015-05-28 22:45:12 +0900271 needsSubShell := i > 0 || len(runners) > 1
272 if cmd[0] == '(' {
273 needsSubShell = false
274 }
275
276 if needsSubShell {
277 buf.WriteByte('(')
278 }
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900279 buf.WriteString(cmd)
280 if i == len(runners)-1 && r.ignoreError {
281 buf.WriteString(" ; true")
282 }
Shinichiro Hamaji13653b22015-05-28 22:45:12 +0900283 if needsSubShell {
284 buf.WriteByte(')')
285 }
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900286 }
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900287 if desc == "" {
288 desc = defaultDesc
289 }
290 return buf.String(), desc, n.GomaDir != "" && !useGomacc
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900291}
292
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900293func (n *NinjaGenerator) genRuleName() string {
Fumitoshi Ukai936de102015-06-08 11:21:16 +0900294 ruleName := fmt.Sprintf("rule%d", n.ruleID)
295 n.ruleID++
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900296 return ruleName
297}
298
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900299func (n *NinjaGenerator) emitBuild(output, rule, inputs, orderOnlys string) {
300 fmt.Fprintf(n.f, "build %s: %s", escapeBuildTarget(output), rule)
301 if inputs != "" {
302 fmt.Fprintf(n.f, " %s", inputs)
303 }
304 if orderOnlys != "" {
305 fmt.Fprintf(n.f, " || %s", orderOnlys)
306 }
Fumitoshi Ukai26e83812015-07-21 14:38:33 +0900307}
308
309func escapeBuildTarget(s string) string {
310 i := strings.IndexAny(s, "$: ")
311 if i < 0 {
312 return s
313 }
314 var buf bytes.Buffer
315 for _, c := range s {
316 switch c {
317 case '$', ':', ' ':
318 buf.WriteByte('$')
319 }
320 buf.WriteRune(c)
321 }
322 return buf.String()
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900323}
324
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900325func getDepString(node *DepNode) (string, string) {
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900326 var deps []string
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900327 seen := make(map[string]bool)
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900328 for _, d := range node.Deps {
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900329 t := escapeBuildTarget(d.Output)
330 if seen[t] {
331 continue
332 }
333 deps = append(deps, t)
334 seen[t] = true
Fumitoshi Ukaic916ea22015-06-30 09:52:13 +0900335 }
336 var orderOnlys []string
337 for _, d := range node.OrderOnlys {
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900338 t := escapeBuildTarget(d.Output)
339 if seen[t] {
340 continue
341 }
342 orderOnlys = append(orderOnlys, t)
343 seen[t] = true
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900344 }
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900345 return strings.Join(deps, " "), strings.Join(orderOnlys, " ")
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900346}
347
Fumitoshi Ukai701b0e02015-07-28 14:06:06 +0900348func escapeNinja(s string) string {
349 return strings.Replace(s, "$", "$$", -1)
350}
351
Fumitoshi Ukaicf6c6c22015-07-21 15:24:35 +0900352func escapeShell(s string) string {
353 i := strings.IndexAny(s, "$`!\\\"")
354 if i < 0 {
355 return s
356 }
357 var buf bytes.Buffer
358 var lastDollar bool
359 for _, c := range s {
360 switch c {
361 case '$':
362 if lastDollar {
363 buf.WriteRune(c)
364 lastDollar = false
365 continue
366 }
367 buf.WriteString(`\$`)
368 lastDollar = true
369 continue
370 case '`', '"', '!', '\\':
371 buf.WriteByte('\\')
372 }
373 buf.WriteRune(c)
374 lastDollar = false
375 }
376 return buf.String()
377}
378
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900379func (n *NinjaGenerator) ninjaVars(s string, nv [][]string, esc func(string) string) string {
380 for _, v := range nv {
381 k, v := v[0], v[1]
382 if v == "" {
383 continue
384 }
Fumitoshi Ukai53c880d2015-07-28 17:10:15 +0900385 if strings.Contains(v, "/./") || strings.Contains(v, "/../") || strings.Contains(v, "$") {
386 // ninja will normalize paths (/./, /../), so keep it as is
387 // ninja will emit quoted string for $
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900388 continue
389 }
390 if esc != nil {
391 v = esc(v)
392 }
393 s = strings.Replace(s, v, k, -1)
394 }
395 return s
396}
397
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900398func (n *NinjaGenerator) emitNode(node *DepNode) error {
Fumitoshi Ukai73b0b242015-07-28 16:55:26 +0900399 if _, found := n.done[node.Output]; found {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900400 return nil
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900401 }
Fumitoshi Ukai73b0b242015-07-28 16:55:26 +0900402 n.done[node.Output] = false
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900403
Fumitoshi Ukaic916ea22015-06-30 09:52:13 +0900404 if len(node.Cmds) == 0 && len(node.Deps) == 0 && len(node.OrderOnlys) == 0 && !node.IsPhony {
Fumitoshi Ukai3857a242015-07-17 17:15:12 +0900405 if _, ok := n.ctx.vpaths.exists(node.Output); ok {
406 return nil
407 }
Fumitoshi Ukai73b0b242015-07-28 16:55:26 +0900408 n.done[node.Output] = true
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900409 n.emitBuild(node.Output, "phony", "", "")
Fumitoshi Ukai3857a242015-07-17 17:15:12 +0900410 fmt.Fprintln(n.f)
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900411 return nil
Shinichiro Hamajifd4aaa02015-05-26 15:40:53 +0900412 }
413
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +0900414 base := filepath.Base(node.Output)
415 if base != node.Output {
416 n.shortNames[base] = append(n.shortNames[base], node.Output)
417 }
418
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900419 runners, _, err := createRunners(n.ctx, node)
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900420 if err != nil {
421 return err
422 }
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900423 ruleName := "phony"
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900424 useLocalPool := false
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900425 inputs, orderOnlys := getDepString(node)
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900426 if len(runners) > 0 {
427 ruleName = n.genRuleName()
Fumitoshi Ukai701b0e02015-07-28 14:06:06 +0900428 fmt.Fprintf(n.f, "\n# rule for %q\n", node.Output)
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900429 fmt.Fprintf(n.f, "rule %s\n", ruleName)
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900430
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900431 ss, desc, ulp := n.genShellScript(runners)
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900432 if ulp {
433 useLocalPool = true
434 }
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900435 fmt.Fprintf(n.f, " description = %s\n", desc)
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900436 cmdline, depfile, err := getDepfile(ss)
Shinichiro Hamaji0ab9a2f2015-05-27 18:32:46 +0900437 if err != nil {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900438 return err
Shinichiro Hamaji0ab9a2f2015-05-27 18:32:46 +0900439 }
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900440 if depfile != "" {
441 fmt.Fprintf(n.f, " depfile = %s\n", depfile)
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900442 fmt.Fprintf(n.f, " deps = gcc\n")
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900443 }
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900444 nv := [][]string{
445 []string{"${in}", inputs},
446 []string{"${out}", escapeNinja(node.Output)},
447 }
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900448 // It seems Linux is OK with ~130kB.
449 // TODO: Find this number automatically.
450 ArgLenLimit := 100 * 1000
Fumitoshi Ukaicf6c6c22015-07-21 15:24:35 +0900451 if len(cmdline) > ArgLenLimit {
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900452 fmt.Fprintf(n.f, " rspfile = $out.rsp\n")
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900453 cmdline = n.ninjaVars(cmdline, nv, nil)
Fumitoshi Ukaicf6c6c22015-07-21 15:24:35 +0900454 fmt.Fprintf(n.f, " rspfile_content = %s\n", cmdline)
455 fmt.Fprintf(n.f, " command = %s $out.rsp\n", n.ctx.shell)
456 } else {
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900457 cmdline = escapeShell(cmdline)
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900458 cmdline = n.ninjaVars(cmdline, nv, escapeShell)
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900459 fmt.Fprintf(n.f, " command = %s -c \"%s\"\n", n.ctx.shell, cmdline)
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900460 }
461 }
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900462 n.emitBuild(node.Output, ruleName, inputs, orderOnlys)
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900463 if useLocalPool {
464 fmt.Fprintf(n.f, " pool = local_pool\n")
465 }
Fumitoshi Ukaic916ea22015-06-30 09:52:13 +0900466 fmt.Fprintf(n.f, "\n")
Fumitoshi Ukai73b0b242015-07-28 16:55:26 +0900467 n.done[node.Output] = true
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900468
469 for _, d := range node.Deps {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900470 err := n.emitNode(d)
471 if err != nil {
472 return err
473 }
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900474 }
Fumitoshi Ukaic916ea22015-06-30 09:52:13 +0900475 for _, d := range node.OrderOnlys {
476 err := n.emitNode(d)
477 if err != nil {
478 return err
479 }
480 }
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900481 return nil
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900482}
483
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900484func (n *NinjaGenerator) emitRegenRules() error {
485 if len(n.Args) == 0 {
486 return nil
487 }
488 mkfiles, err := n.ctx.ev.EvaluateVar("MAKEFILE_LIST")
489 if err != nil {
490 return err
491 }
492 fmt.Fprintf(n.f, `
493rule regen_ninja
494 description = Regenerate ninja files due to dependency
495 generator=1
496 command=%s
497`, strings.Join(n.Args, " "))
498 fmt.Fprintf(n.f, "build %s: regen_ninja %s", n.ninjaName(), mkfiles)
499 // TODO: Add dependencies to directories read by $(wildcard) or
500 // $(shell find).
501 if len(usedEnvs) > 0 {
502 fmt.Fprintf(n.f, " %s", n.envlistName())
503 }
504 fmt.Fprintf(n.f, "\n\n")
505 if len(usedEnvs) == 0 {
506 return nil
507 }
508 fmt.Fprint(n.f, `
509build .always_build: phony
510rule regen_envlist
511 description = Check $out
512 generator = 1
513 restat = 1
514 command = rm -f $out.tmp`)
515 for env := range usedEnvs {
516 fmt.Fprintf(n.f, " && echo %s=$$%s >> $out.tmp", env, env)
517 }
518 if n.ErrorOnEnvChange {
519 fmt.Fprintln(n.f, " && (cmp -s $out.tmp $out || (echo Environment variable changes are detected && diff -u $out $out.tmp))")
520 } else {
521 fmt.Fprintln(n.f, " && (cmp -s $out.tmp $out || mv $out.tmp $out)")
522 }
523
524 fmt.Fprintf(n.f, "build %s: regen_envlist .always_build\n\n", n.envlistName())
525 return nil
Fumitoshi Ukai861c9f22015-07-22 10:54:01 +0900526}
527
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900528func (n *NinjaGenerator) shName() string {
529 return fmt.Sprintf("ninja%s.sh", n.Suffix)
Fumitoshi Ukai861c9f22015-07-22 10:54:01 +0900530}
531
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900532func (n *NinjaGenerator) ninjaName() string {
533 return fmt.Sprintf("build%s.ninja", n.Suffix)
534}
535
536func (n *NinjaGenerator) envlistName() string {
537 return fmt.Sprintf(".kati_env%s", n.Suffix)
538}
539
540func (n *NinjaGenerator) generateEnvlist() (err error) {
541 f, err := os.Create(n.envlistName())
542 if err != nil {
543 return err
544 }
545 defer func() {
546 cerr := f.Close()
547 if err == nil {
548 err = cerr
549 }
550 }()
551 for k := range usedEnvs {
552 v, err := n.ctx.ev.EvaluateVar(k)
553 if err != nil {
554 return err
555 }
Fumitoshi Ukaid5570262015-07-28 12:44:31 +0900556 fmt.Fprintf(f, "%q=%q\n", k, v)
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900557 }
558 return nil
559}
560
561func (n *NinjaGenerator) generateShell() (err error) {
562 f, err := os.Create(n.shName())
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900563 if err != nil {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900564 return err
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900565 }
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900566 defer func() {
567 cerr := f.Close()
568 if err == nil {
569 err = cerr
570 }
571 }()
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900572
Fumitoshi Ukaibe9104d2015-07-28 14:20:43 +0900573 fmt.Fprintf(f, "#!/bin/bash\n")
Fumitoshi Ukai08db53a2015-07-17 14:45:57 +0900574 fmt.Fprintf(f, "# Generated by kati %s\n", gitVersion)
575 fmt.Fprintln(f)
576 fmt.Fprintln(f, `cd $(dirname "$0")`)
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900577 if n.Suffix != "" {
578 fmt.Fprintf(f, "if [ -f %s ]; then\n export $(cat %s)\nfi\n", n.envlistName(), n.envlistName())
579 }
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900580 for name, export := range n.exports {
Fumitoshi Ukaif90f73c2015-07-28 14:37:02 +0900581 // export "a b"=c will error on bash
582 // bash: export `a b=c': not a valid identifier
583 if strings.ContainsAny(name, " \t\n\r") {
584 glog.V(1).Infof("ignore export %q (export:%t)", name, export)
585 continue
586 }
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900587 if export {
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900588 v, err := n.ctx.ev.EvaluateVar(name)
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900589 if err != nil {
590 return err
591 }
Fumitoshi Ukaid5570262015-07-28 12:44:31 +0900592 fmt.Fprintf(f, "export %q=%q\n", name, v)
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900593 } else {
Fumitoshi Ukaid5570262015-07-28 12:44:31 +0900594 fmt.Fprintf(f, "unset %q\n", name)
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900595 }
596 }
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900597 if n.GomaDir == "" {
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900598 fmt.Fprintf(f, `exec ninja -f %s "$@"`+"\n", n.ninjaName())
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900599 } else {
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900600 fmt.Fprintf(f, `exec ninja -f %s -j500 "$@"`+"\n", n.ninjaName())
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900601 }
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900602
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900603 return f.Chmod(0755)
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900604}
605
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900606func (n *NinjaGenerator) generateNinja(defaultTarget string) (err error) {
607 f, err := os.Create(n.ninjaName())
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900608 if err != nil {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900609 return err
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900610 }
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900611 defer func() {
612 cerr := f.Close()
613 if err == nil {
614 err = cerr
615 }
616 }()
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900617
618 n.f = f
Fumitoshi Ukai9dd48a92015-07-14 13:39:03 +0900619 fmt.Fprintf(n.f, "# Generated by kati %s\n", gitVersion)
Shinichiro Hamajiea2e0de2015-05-28 15:22:16 +0900620 fmt.Fprintf(n.f, "\n")
621
Fumitoshi Ukaib5612a82015-07-14 13:22:17 +0900622 if len(usedEnvs) > 0 {
623 fmt.Fprintln(n.f, "# Environment variables used:")
624 var names []string
625 for name := range usedEnvs {
626 names = append(names, name)
627 }
628 sort.Strings(names)
629 for _, name := range names {
630 v, err := n.ctx.ev.EvaluateVar(name)
631 if err != nil {
632 return err
633 }
Fumitoshi Ukaid5570262015-07-28 12:44:31 +0900634 fmt.Fprintf(n.f, "# %q=%q\n", name, v)
Fumitoshi Ukaib5612a82015-07-14 13:22:17 +0900635 }
636 fmt.Fprintf(n.f, "\n")
637 }
638
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900639 if n.GomaDir != "" {
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900640 fmt.Fprintf(n.f, "pool local_pool\n")
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900641 fmt.Fprintf(n.f, " depth = %d\n\n", runtime.NumCPU())
642 }
643
644 err = n.emitRegenRules()
645 if err != nil {
646 return err
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900647 }
648
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900649 // defining $out for $@ and $in for $^ here doesn't work well,
650 // because these texts will be processed in escapeShell...
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900651 for _, node := range n.nodes {
652 err := n.emitNode(node)
653 if err != nil {
654 return err
655 }
656 }
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +0900657
Fumitoshi Ukai73b0b242015-07-28 16:55:26 +0900658 if defaultTarget != "" && n.done[defaultTarget] {
Fumitoshi Ukai701b0e02015-07-28 14:06:06 +0900659 fmt.Fprintf(n.f, "\ndefault %s\n", escapeNinja(defaultTarget))
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900660 }
661
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +0900662 fmt.Fprintf(n.f, "\n# shortcuts:\n")
663 var names []string
664 for name := range n.shortNames {
Fumitoshi Ukai2dc2c172015-07-17 17:39:26 +0900665 if n.done[name] {
666 continue
667 }
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +0900668 names = append(names, name)
669 }
670 sort.Strings(names)
671 for _, name := range names {
672 if len(n.shortNames[name]) != 1 {
673 // we generate shortcuts only for targets whose basename are unique.
674 continue
675 }
676 fmt.Fprintf(n.f, "build %s: phony %s\n", name, n.shortNames[name][0])
677 }
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900678 return nil
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900679}
680
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900681// Save generates build.ninja from DepGraph.
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900682func (n *NinjaGenerator) Save(g *DepGraph, name string, targets []string) error {
Fumitoshi Ukai49599e52015-06-26 10:10:24 +0900683 startTime := time.Now()
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900684 n.init(g)
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900685 err := n.generateEnvlist()
686 if err != nil {
687 return err
688 }
689 err = n.generateShell()
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900690 if err != nil {
691 return err
692 }
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900693 var defaultTarget string
694 if len(targets) == 0 && len(g.nodes) > 0 {
695 defaultTarget = g.nodes[0].Output
696 }
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900697 err = n.generateNinja(defaultTarget)
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900698 if err != nil {
699 return err
700 }
Fumitoshi Ukai49599e52015-06-26 10:10:24 +0900701 logStats("generate ninja time: %q", time.Since(startTime))
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900702 return nil
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900703}