blob: 247a060888cb7f4408912a08dfa5bb14f3c947db [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 Ukaib816ffb2015-07-28 17:46:04 +090031type nodeState int
32
33const (
34 nodeInit nodeState = iota // not visited
35 nodeVisit // visited
36 nodeFile // visited & file exists
37 nodeAlias // visited & alias for other target
38 nodeBuild // visited & build emitted
39)
40
41func (s nodeState) String() string {
42 switch s {
43 case nodeInit:
44 return "node-init"
45 case nodeVisit:
46 return "node-visit"
47 case nodeFile:
48 return "node-file"
49 case nodeAlias:
50 return "node-alias"
51 case nodeBuild:
52 return "node-build"
53 default:
54 return fmt.Sprintf("node-unknown[%d]", int(s))
55 }
56}
57
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +090058// NinjaGenerator generates ninja build files from DepGraph.
59type NinjaGenerator struct {
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +090060 // Args is original arguments to generate the ninja file.
61 Args []string
62 // Suffix is suffix for generated files.
63 Suffix string
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +090064 // GomaDir is goma directory. If empty, goma will not be used.
65 GomaDir string
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +090066 // DetectAndroidEcho detects echo as description.
67 DetectAndroidEcho bool
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +090068 // ErrorOnEnvChange cause error when env change is detected when run ninja.
69 ErrorOnEnvChange bool
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +090070
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +090071 f *os.File
72 nodes []*DepNode
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +090073 exports map[string]bool
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +090074
75 ctx *execContext
76
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +090077 ruleID int
78 done map[string]nodeState
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +090079 shortNames map[string][]string
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +090080}
81
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +090082func (n *NinjaGenerator) init(g *DepGraph) {
83 n.nodes = g.nodes
84 n.exports = g.exports
85 n.ctx = newExecContext(g.vars, g.vpaths, true)
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +090086 n.done = make(map[string]nodeState)
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +090087 n.shortNames = make(map[string][]string)
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +090088}
89
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +090090func getDepfileImpl(ss string) (string, error) {
Shinichiro Hamaji6dca2202015-05-27 18:00:34 +090091 tss := ss + " "
Fumitoshi Ukaib8b80502015-07-21 14:28:26 +090092 if (!strings.Contains(tss, " -MD ") && !strings.Contains(tss, " -MMD ")) || !strings.Contains(tss, " -c ") {
Shinichiro Hamaji6dca2202015-05-27 18:00:34 +090093 return "", nil
94 }
95
96 mfIndex := strings.Index(ss, " -MF ")
97 if mfIndex >= 0 {
98 mf := trimLeftSpace(ss[mfIndex+4:])
99 if strings.Index(mf, " -MF ") >= 0 {
100 return "", fmt.Errorf("Multiple output file candidates in %s", ss)
101 }
102 mfEndIndex := strings.IndexAny(mf, " \t\n")
103 if mfEndIndex >= 0 {
104 mf = mf[:mfEndIndex]
105 }
106
Shinichiro Hamaji6dca2202015-05-27 18:00:34 +0900107 return mf, nil
108 }
109
110 outIndex := strings.Index(ss, " -o ")
111 if outIndex < 0 {
112 return "", fmt.Errorf("Cannot find the depfile in %s", ss)
113 }
114 out := trimLeftSpace(ss[outIndex+4:])
115 if strings.Index(out, " -o ") >= 0 {
116 return "", fmt.Errorf("Multiple output file candidates in %s", ss)
117 }
118 outEndIndex := strings.IndexAny(out, " \t\n")
119 if outEndIndex >= 0 {
120 out = out[:outEndIndex]
121 }
122 return stripExt(out) + ".d", nil
123}
124
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900125// getDepfile gets depfile from cmdline, and returns cmdline and depfile.
126func getDepfile(cmdline string) (string, string, error) {
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900127 // A hack for Android - llvm-rs-cc seems not to emit a dep file.
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900128 if strings.Contains(cmdline, "bin/llvm-rs-cc ") {
129 return cmdline, "", nil
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900130 }
131
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900132 depfile, err := getDepfileImpl(cmdline)
133 if depfile == "" || err != nil {
134 return cmdline, depfile, err
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900135 }
136
Shinichiro Hamaji3438ae32015-06-05 11:33:20 +0900137 // A hack for Makefiles generated by automake.
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900138 mvCmd := "(mv -f " + depfile + " "
139 if i := strings.LastIndex(cmdline, mvCmd); i >= 0 {
140 rest := cmdline[i+len(mvCmd):]
Shinichiro Hamaji3438ae32015-06-05 11:33:20 +0900141 ei := strings.IndexByte(rest, ')')
142 if ei < 0 {
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900143 return cmdline, "", fmt.Errorf("unbalanced parenthes? %s", cmdline)
Shinichiro Hamaji3438ae32015-06-05 11:33:20 +0900144 }
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900145 cmdline = cmdline[:i] + "(cp -f " + depfile + " " + rest
146 return cmdline, depfile, nil
Shinichiro Hamaji3438ae32015-06-05 11:33:20 +0900147 }
148
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900149 // A hack for Android to get .P files instead of .d.
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900150 p := stripExt(depfile) + ".P"
151 if strings.Contains(cmdline, p) {
152 rmfCmd := "; rm -f " + depfile
153 ncmdline := strings.Replace(cmdline, rmfCmd, "", 1)
154 if ncmdline == cmdline {
155 return cmdline, "", fmt.Errorf("cannot find removal of .d file: %s", cmdline)
156 }
157 return ncmdline, p, nil
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900158 }
159
160 // A hack for Android. For .s files, GCC does not use
161 // C preprocessor, so it ignores -MF flag.
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900162 as := "/" + stripExt(filepath.Base(depfile)) + ".s"
163 if strings.Contains(cmdline, as) {
164 return cmdline, "", nil
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900165 }
166
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900167 cmdline += fmt.Sprintf(" && cp %s %s.tmp", depfile, depfile)
168 depfile += ".tmp"
169 return cmdline, depfile, nil
Shinichiro Hamaji74ab4b62015-05-28 22:56:24 +0900170}
171
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900172func stripShellComment(s string) string {
173 if strings.IndexByte(s, '#') < 0 {
174 // Fast path.
175 return s
176 }
Fumitoshi Ukai43b56662015-07-15 10:41:47 +0900177 // set space as an initial value so the leading comment will be
178 // stripped out.
179 lastch := rune(' ')
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900180 var escape bool
181 var quote rune
182 for i, c := range s {
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900183 if quote != 0 {
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900184 if quote == c && (quote == '\'' || !escape) {
185 quote = 0
186 }
187 } else if !escape {
Fumitoshi Ukai43b56662015-07-15 10:41:47 +0900188 if c == '#' && isWhitespace(lastch) {
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900189 return s[:i]
190 } else if c == '\'' || c == '"' || c == '`' {
191 quote = c
192 }
193 }
194 if escape {
195 escape = false
196 } else if c == '\\' {
197 escape = true
198 } else {
199 escape = false
200 }
Fumitoshi Ukai43b56662015-07-15 10:41:47 +0900201 lastch = c
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900202 }
203 return s
204}
205
Fumitoshi Ukai1713c6f2015-07-14 13:08:24 +0900206var ccRE = regexp.MustCompile(`^prebuilts/(gcc|clang)/.*(gcc|g\+\+|clang|clang\+\+) .* ?-c `)
207
208func gomaCmdForAndroidCompileCmd(cmd string) (string, bool) {
209 i := strings.Index(cmd, " ")
210 if i < 0 {
211 return cmd, false
212 }
213 driver := cmd[:i]
214 if strings.HasSuffix(driver, "ccache") {
215 return gomaCmdForAndroidCompileCmd(cmd[i+1:])
216 }
217 return cmd, ccRE.MatchString(cmd)
218}
219
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900220func descriptionFromCmd(cmd string) (string, bool) {
221 if !strings.HasPrefix(cmd, "echo") || !isWhitespace(rune(cmd[4])) {
222 return "", false
223 }
224 echoarg := cmd[5:]
225
226 // strip outer quotes, and fail if it is not a single echo command.
227 var buf bytes.Buffer
228 var escape bool
229 var quote rune
230 for _, c := range echoarg {
231 if escape {
232 escape = false
233 buf.WriteRune(c)
234 continue
235 }
236 if c == '\\' {
237 escape = true
238 buf.WriteRune(c)
239 continue
240 }
241 if quote != 0 {
242 if c == quote {
243 quote = 0
244 continue
245 }
246 buf.WriteRune(c)
247 continue
248 }
249 switch c {
250 case '\'', '"', '`':
251 quote = c
252 case '<', '>', '&', '|', ';':
253 return "", false
254 default:
255 buf.WriteRune(c)
256 }
257 }
258 return buf.String(), true
259}
260
261func (n *NinjaGenerator) genShellScript(runners []runner) (cmd string, desc string, useLocalPool bool) {
262 const defaultDesc = "build $out"
263 var useGomacc bool
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900264 var buf bytes.Buffer
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900265 for i, r := range runners {
266 if i > 0 {
267 if runners[i-1].ignoreError {
268 buf.WriteString(" ; ")
269 } else {
270 buf.WriteString(" && ")
271 }
272 }
Shinichiro Hamaji246cd232015-05-27 17:08:23 +0900273 cmd := stripShellComment(r.cmd)
274 cmd = trimLeftSpace(cmd)
Fumitoshi Ukai5af212f2015-07-28 17:57:17 +0900275 cmd = strings.Replace(cmd, "\\\n\t", "", -1)
Fumitoshi Ukai74663592015-07-17 17:53:11 +0900276 cmd = strings.Replace(cmd, "\\\n", "", -1)
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900277 cmd = strings.TrimRight(cmd, " \t\n;")
Fumitoshi Ukai701b0e02015-07-28 14:06:06 +0900278 cmd = escapeNinja(cmd)
Shinichiro Hamaji501f68e2015-05-27 17:33:41 +0900279 if cmd == "" {
280 cmd = "true"
281 }
Fumitoshi Ukai5af212f2015-07-28 17:57:17 +0900282 glog.V(2).Infof("cmd %q=>%q", r.cmd, cmd)
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900283 if n.GomaDir != "" {
Fumitoshi Ukai1713c6f2015-07-14 13:08:24 +0900284 rcmd, ok := gomaCmdForAndroidCompileCmd(cmd)
285 if ok {
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900286 cmd = fmt.Sprintf("%s/gomacc %s", n.GomaDir, rcmd)
Fumitoshi Ukai1713c6f2015-07-14 13:08:24 +0900287 useGomacc = true
288 }
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900289 }
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900290 if n.DetectAndroidEcho && desc == "" {
291 d, ok := descriptionFromCmd(cmd)
292 if ok {
293 desc = d
294 cmd = "true"
295 }
296 }
Shinichiro Hamaji13653b22015-05-28 22:45:12 +0900297 needsSubShell := i > 0 || len(runners) > 1
298 if cmd[0] == '(' {
299 needsSubShell = false
300 }
301
302 if needsSubShell {
303 buf.WriteByte('(')
304 }
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900305 buf.WriteString(cmd)
306 if i == len(runners)-1 && r.ignoreError {
307 buf.WriteString(" ; true")
308 }
Shinichiro Hamaji13653b22015-05-28 22:45:12 +0900309 if needsSubShell {
310 buf.WriteByte(')')
311 }
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900312 }
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900313 if desc == "" {
314 desc = defaultDesc
315 }
316 return buf.String(), desc, n.GomaDir != "" && !useGomacc
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900317}
318
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900319func (n *NinjaGenerator) genRuleName() string {
Fumitoshi Ukai936de102015-06-08 11:21:16 +0900320 ruleName := fmt.Sprintf("rule%d", n.ruleID)
321 n.ruleID++
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900322 return ruleName
323}
324
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900325func (n *NinjaGenerator) emitBuild(output, rule, inputs, orderOnlys string) {
326 fmt.Fprintf(n.f, "build %s: %s", escapeBuildTarget(output), rule)
327 if inputs != "" {
328 fmt.Fprintf(n.f, " %s", inputs)
329 }
330 if orderOnlys != "" {
331 fmt.Fprintf(n.f, " || %s", orderOnlys)
332 }
Fumitoshi Ukai26e83812015-07-21 14:38:33 +0900333}
334
335func escapeBuildTarget(s string) string {
336 i := strings.IndexAny(s, "$: ")
337 if i < 0 {
338 return s
339 }
340 var buf bytes.Buffer
341 for _, c := range s {
342 switch c {
343 case '$', ':', ' ':
344 buf.WriteByte('$')
345 }
346 buf.WriteRune(c)
347 }
348 return buf.String()
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900349}
350
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900351func getDepString(node *DepNode) (string, string) {
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900352 var deps []string
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900353 seen := make(map[string]bool)
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900354 for _, d := range node.Deps {
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900355 t := escapeBuildTarget(d.Output)
356 if seen[t] {
357 continue
358 }
359 deps = append(deps, t)
360 seen[t] = true
Fumitoshi Ukaic916ea22015-06-30 09:52:13 +0900361 }
362 var orderOnlys []string
363 for _, d := range node.OrderOnlys {
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900364 t := escapeBuildTarget(d.Output)
365 if seen[t] {
366 continue
367 }
368 orderOnlys = append(orderOnlys, t)
369 seen[t] = true
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900370 }
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900371 return strings.Join(deps, " "), strings.Join(orderOnlys, " ")
Shinichiro Hamajifd872162015-05-26 18:06:48 +0900372}
373
Fumitoshi Ukai701b0e02015-07-28 14:06:06 +0900374func escapeNinja(s string) string {
375 return strings.Replace(s, "$", "$$", -1)
376}
377
Fumitoshi Ukaicf6c6c22015-07-21 15:24:35 +0900378func escapeShell(s string) string {
379 i := strings.IndexAny(s, "$`!\\\"")
380 if i < 0 {
381 return s
382 }
383 var buf bytes.Buffer
384 var lastDollar bool
385 for _, c := range s {
386 switch c {
387 case '$':
388 if lastDollar {
389 buf.WriteRune(c)
390 lastDollar = false
391 continue
392 }
393 buf.WriteString(`\$`)
394 lastDollar = true
395 continue
396 case '`', '"', '!', '\\':
397 buf.WriteByte('\\')
398 }
399 buf.WriteRune(c)
400 lastDollar = false
401 }
402 return buf.String()
403}
404
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900405func (n *NinjaGenerator) ninjaVars(s string, nv [][]string, esc func(string) string) string {
406 for _, v := range nv {
407 k, v := v[0], v[1]
408 if v == "" {
409 continue
410 }
Fumitoshi Ukai53c880d2015-07-28 17:10:15 +0900411 if strings.Contains(v, "/./") || strings.Contains(v, "/../") || strings.Contains(v, "$") {
412 // ninja will normalize paths (/./, /../), so keep it as is
413 // ninja will emit quoted string for $
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900414 continue
415 }
416 if esc != nil {
417 v = esc(v)
418 }
419 s = strings.Replace(s, v, k, -1)
420 }
421 return s
422}
423
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900424func (n *NinjaGenerator) emitNode(node *DepNode) error {
Fumitoshi Ukai73b0b242015-07-28 16:55:26 +0900425 if _, found := n.done[node.Output]; found {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900426 return nil
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900427 }
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900428 n.done[node.Output] = nodeVisit
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900429
Fumitoshi Ukaic916ea22015-06-30 09:52:13 +0900430 if len(node.Cmds) == 0 && len(node.Deps) == 0 && len(node.OrderOnlys) == 0 && !node.IsPhony {
Fumitoshi Ukai3857a242015-07-17 17:15:12 +0900431 if _, ok := n.ctx.vpaths.exists(node.Output); ok {
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900432 n.done[node.Output] = nodeFile
Fumitoshi Ukai3857a242015-07-17 17:15:12 +0900433 return nil
434 }
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900435 o := filepath.Clean(node.Output)
436 if o != node.Output {
437 // if normalized target has been done, it marks as alias.
438 if s, found := n.done[o]; found {
439 glog.V(1).Infof("node %s=%s => %s=alias", o, s, node.Output)
440 n.done[node.Output] = nodeAlias
441 return nil
442 }
443 }
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900444 return nil
Shinichiro Hamajifd4aaa02015-05-26 15:40:53 +0900445 }
446
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +0900447 base := filepath.Base(node.Output)
448 if base != node.Output {
449 n.shortNames[base] = append(n.shortNames[base], node.Output)
450 }
451
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900452 runners, _, err := createRunners(n.ctx, node)
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900453 if err != nil {
454 return err
455 }
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900456 ruleName := "phony"
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900457 useLocalPool := false
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900458 inputs, orderOnlys := getDepString(node)
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900459 if len(runners) > 0 {
460 ruleName = n.genRuleName()
Fumitoshi Ukai701b0e02015-07-28 14:06:06 +0900461 fmt.Fprintf(n.f, "\n# rule for %q\n", node.Output)
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900462 fmt.Fprintf(n.f, "rule %s\n", ruleName)
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900463
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900464 ss, desc, ulp := n.genShellScript(runners)
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900465 if ulp {
466 useLocalPool = true
467 }
Fumitoshi Ukaib92f0282015-07-21 14:05:51 +0900468 fmt.Fprintf(n.f, " description = %s\n", desc)
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900469 cmdline, depfile, err := getDepfile(ss)
Shinichiro Hamaji0ab9a2f2015-05-27 18:32:46 +0900470 if err != nil {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900471 return err
Shinichiro Hamaji0ab9a2f2015-05-27 18:32:46 +0900472 }
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900473 if depfile != "" {
474 fmt.Fprintf(n.f, " depfile = %s\n", depfile)
Fumitoshi Ukai923b1282015-07-13 12:53:30 +0900475 fmt.Fprintf(n.f, " deps = gcc\n")
Shinichiro Hamajibd59ebb2015-05-28 17:07:17 +0900476 }
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900477 nv := [][]string{
478 []string{"${in}", inputs},
479 []string{"${out}", escapeNinja(node.Output)},
480 }
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900481 // It seems Linux is OK with ~130kB.
482 // TODO: Find this number automatically.
483 ArgLenLimit := 100 * 1000
Fumitoshi Ukaicf6c6c22015-07-21 15:24:35 +0900484 if len(cmdline) > ArgLenLimit {
Shinichiro Hamajia5467792015-05-27 16:28:40 +0900485 fmt.Fprintf(n.f, " rspfile = $out.rsp\n")
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900486 cmdline = n.ninjaVars(cmdline, nv, nil)
Fumitoshi Ukaicf6c6c22015-07-21 15:24:35 +0900487 fmt.Fprintf(n.f, " rspfile_content = %s\n", cmdline)
488 fmt.Fprintf(n.f, " command = %s $out.rsp\n", n.ctx.shell)
489 } else {
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900490 cmdline = escapeShell(cmdline)
Fumitoshi Ukaib6adb392015-07-28 16:46:11 +0900491 cmdline = n.ninjaVars(cmdline, nv, escapeShell)
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900492 fmt.Fprintf(n.f, " command = %s -c \"%s\"\n", n.ctx.shell, cmdline)
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900493 }
494 }
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900495 n.emitBuild(node.Output, ruleName, inputs, orderOnlys)
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900496 if useLocalPool {
497 fmt.Fprintf(n.f, " pool = local_pool\n")
498 }
Fumitoshi Ukaic916ea22015-06-30 09:52:13 +0900499 fmt.Fprintf(n.f, "\n")
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900500 n.done[node.Output] = nodeBuild
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900501
502 for _, d := range node.Deps {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900503 err := n.emitNode(d)
504 if err != nil {
505 return err
506 }
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900507 glog.V(1).Infof("node %s dep node %q %s", node.Output, d.Output, n.done[d.Output])
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900508 }
Fumitoshi Ukaic916ea22015-06-30 09:52:13 +0900509 for _, d := range node.OrderOnlys {
510 err := n.emitNode(d)
511 if err != nil {
512 return err
513 }
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900514 glog.V(1).Infof("node %s order node %q %s", node.Output, d.Output, n.done[d.Output])
Fumitoshi Ukaic916ea22015-06-30 09:52:13 +0900515 }
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900516 return nil
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900517}
518
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900519func (n *NinjaGenerator) emitRegenRules() error {
520 if len(n.Args) == 0 {
521 return nil
522 }
523 mkfiles, err := n.ctx.ev.EvaluateVar("MAKEFILE_LIST")
524 if err != nil {
525 return err
526 }
527 fmt.Fprintf(n.f, `
528rule regen_ninja
529 description = Regenerate ninja files due to dependency
530 generator=1
531 command=%s
532`, strings.Join(n.Args, " "))
533 fmt.Fprintf(n.f, "build %s: regen_ninja %s", n.ninjaName(), mkfiles)
534 // TODO: Add dependencies to directories read by $(wildcard) or
535 // $(shell find).
536 if len(usedEnvs) > 0 {
537 fmt.Fprintf(n.f, " %s", n.envlistName())
538 }
539 fmt.Fprintf(n.f, "\n\n")
540 if len(usedEnvs) == 0 {
541 return nil
542 }
543 fmt.Fprint(n.f, `
544build .always_build: phony
545rule regen_envlist
546 description = Check $out
547 generator = 1
548 restat = 1
549 command = rm -f $out.tmp`)
550 for env := range usedEnvs {
551 fmt.Fprintf(n.f, " && echo %s=$$%s >> $out.tmp", env, env)
552 }
553 if n.ErrorOnEnvChange {
554 fmt.Fprintln(n.f, " && (cmp -s $out.tmp $out || (echo Environment variable changes are detected && diff -u $out $out.tmp))")
555 } else {
556 fmt.Fprintln(n.f, " && (cmp -s $out.tmp $out || mv $out.tmp $out)")
557 }
558
559 fmt.Fprintf(n.f, "build %s: regen_envlist .always_build\n\n", n.envlistName())
560 return nil
Fumitoshi Ukai861c9f22015-07-22 10:54:01 +0900561}
562
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900563func (n *NinjaGenerator) shName() string {
564 return fmt.Sprintf("ninja%s.sh", n.Suffix)
Fumitoshi Ukai861c9f22015-07-22 10:54:01 +0900565}
566
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900567func (n *NinjaGenerator) ninjaName() string {
568 return fmt.Sprintf("build%s.ninja", n.Suffix)
569}
570
571func (n *NinjaGenerator) envlistName() string {
572 return fmt.Sprintf(".kati_env%s", n.Suffix)
573}
574
575func (n *NinjaGenerator) generateEnvlist() (err error) {
576 f, err := os.Create(n.envlistName())
577 if err != nil {
578 return err
579 }
580 defer func() {
581 cerr := f.Close()
582 if err == nil {
583 err = cerr
584 }
585 }()
586 for k := range usedEnvs {
587 v, err := n.ctx.ev.EvaluateVar(k)
588 if err != nil {
589 return err
590 }
Fumitoshi Ukaid5570262015-07-28 12:44:31 +0900591 fmt.Fprintf(f, "%q=%q\n", k, v)
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900592 }
593 return nil
594}
595
596func (n *NinjaGenerator) generateShell() (err error) {
597 f, err := os.Create(n.shName())
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900598 if err != nil {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900599 return err
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900600 }
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900601 defer func() {
602 cerr := f.Close()
603 if err == nil {
604 err = cerr
605 }
606 }()
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900607
Fumitoshi Ukaibe9104d2015-07-28 14:20:43 +0900608 fmt.Fprintf(f, "#!/bin/bash\n")
Fumitoshi Ukai08db53a2015-07-17 14:45:57 +0900609 fmt.Fprintf(f, "# Generated by kati %s\n", gitVersion)
610 fmt.Fprintln(f)
611 fmt.Fprintln(f, `cd $(dirname "$0")`)
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900612 if n.Suffix != "" {
613 fmt.Fprintf(f, "if [ -f %s ]; then\n export $(cat %s)\nfi\n", n.envlistName(), n.envlistName())
614 }
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900615 for name, export := range n.exports {
Fumitoshi Ukaif90f73c2015-07-28 14:37:02 +0900616 // export "a b"=c will error on bash
617 // bash: export `a b=c': not a valid identifier
618 if strings.ContainsAny(name, " \t\n\r") {
619 glog.V(1).Infof("ignore export %q (export:%t)", name, export)
620 continue
621 }
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900622 if export {
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900623 v, err := n.ctx.ev.EvaluateVar(name)
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900624 if err != nil {
625 return err
626 }
Fumitoshi Ukaid5570262015-07-28 12:44:31 +0900627 fmt.Fprintf(f, "export %q=%q\n", name, v)
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900628 } else {
Fumitoshi Ukaid5570262015-07-28 12:44:31 +0900629 fmt.Fprintf(f, "unset %q\n", name)
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900630 }
631 }
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900632 if n.GomaDir == "" {
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900633 fmt.Fprintf(f, `exec ninja -f %s "$@"`+"\n", n.ninjaName())
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900634 } else {
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900635 fmt.Fprintf(f, `exec ninja -f %s -j500 "$@"`+"\n", n.ninjaName())
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900636 }
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900637
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900638 return f.Chmod(0755)
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900639}
640
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900641func (n *NinjaGenerator) generateNinja(defaultTarget string) (err error) {
642 f, err := os.Create(n.ninjaName())
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900643 if err != nil {
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900644 return err
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900645 }
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900646 defer func() {
647 cerr := f.Close()
648 if err == nil {
649 err = cerr
650 }
651 }()
Shinichiro Hamaji30a02ad2015-05-29 14:56:43 +0900652
653 n.f = f
Fumitoshi Ukai9dd48a92015-07-14 13:39:03 +0900654 fmt.Fprintf(n.f, "# Generated by kati %s\n", gitVersion)
Shinichiro Hamajiea2e0de2015-05-28 15:22:16 +0900655 fmt.Fprintf(n.f, "\n")
656
Fumitoshi Ukaib5612a82015-07-14 13:22:17 +0900657 if len(usedEnvs) > 0 {
658 fmt.Fprintln(n.f, "# Environment variables used:")
659 var names []string
660 for name := range usedEnvs {
661 names = append(names, name)
662 }
663 sort.Strings(names)
664 for _, name := range names {
665 v, err := n.ctx.ev.EvaluateVar(name)
666 if err != nil {
667 return err
668 }
Fumitoshi Ukaid5570262015-07-28 12:44:31 +0900669 fmt.Fprintf(n.f, "# %q=%q\n", name, v)
Fumitoshi Ukaib5612a82015-07-14 13:22:17 +0900670 }
671 fmt.Fprintf(n.f, "\n")
672 }
673
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900674 if n.GomaDir != "" {
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900675 fmt.Fprintf(n.f, "pool local_pool\n")
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900676 fmt.Fprintf(n.f, " depth = %d\n\n", runtime.NumCPU())
677 }
678
679 err = n.emitRegenRules()
680 if err != nil {
681 return err
Shinichiro Hamajibcecb502015-06-04 16:20:25 +0900682 }
683
Fumitoshi Ukai3dae8042015-07-22 13:49:23 +0900684 // defining $out for $@ and $in for $^ here doesn't work well,
685 // because these texts will be processed in escapeShell...
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900686 for _, node := range n.nodes {
687 err := n.emitNode(node)
688 if err != nil {
689 return err
690 }
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900691 glog.V(1).Infof("node %q %s", node.Output, n.done[node.Output])
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900692 }
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +0900693
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900694 // emit phony targets for visited nodes that are
695 // - not existing file
696 // - not alias for other targets.
697 var nodes []string
698 for node, state := range n.done {
699 if state != nodeVisit {
700 continue
701 }
702 nodes = append(nodes, node)
703 }
704 if len(nodes) > 0 {
705 fmt.Fprintln(n.f)
706 sort.Strings(nodes)
707 for _, node := range nodes {
708 n.emitBuild(node, "phony", "", "")
709 fmt.Fprintln(n.f)
710 n.done[node] = nodeBuild
711 }
712 }
713
714 // emit default if the target was emitted.
715 if defaultTarget != "" && n.done[defaultTarget] == nodeBuild {
Fumitoshi Ukai701b0e02015-07-28 14:06:06 +0900716 fmt.Fprintf(n.f, "\ndefault %s\n", escapeNinja(defaultTarget))
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900717 }
718
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +0900719 var names []string
720 for name := range n.shortNames {
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900721 if n.done[name] != nodeInit {
Fumitoshi Ukai2dc2c172015-07-17 17:39:26 +0900722 continue
723 }
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +0900724 if len(n.shortNames[name]) != 1 {
725 // we generate shortcuts only for targets whose basename are unique.
726 continue
727 }
Fumitoshi Ukaib816ffb2015-07-28 17:46:04 +0900728 names = append(names, name)
729 }
730 if len(names) > 0 {
731 fmt.Fprintf(n.f, "\n# shortcuts:\n")
732 sort.Strings(names)
733 for _, name := range names {
734 fmt.Fprintf(n.f, "build %s: phony %s\n", name, n.shortNames[name][0])
735 }
Fumitoshi Ukaie08e9ee2015-07-13 13:14:41 +0900736 }
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900737 return nil
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900738}
739
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900740// Save generates build.ninja from DepGraph.
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900741func (n *NinjaGenerator) Save(g *DepGraph, name string, targets []string) error {
Fumitoshi Ukai49599e52015-06-26 10:10:24 +0900742 startTime := time.Now()
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900743 n.init(g)
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900744 err := n.generateEnvlist()
745 if err != nil {
746 return err
747 }
748 err = n.generateShell()
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900749 if err != nil {
750 return err
751 }
Fumitoshi Ukaib79b2902015-07-16 16:40:09 +0900752 var defaultTarget string
753 if len(targets) == 0 && len(g.nodes) > 0 {
754 defaultTarget = g.nodes[0].Output
755 }
Fumitoshi Ukaid4a016e2015-07-27 16:50:00 +0900756 err = n.generateNinja(defaultTarget)
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900757 if err != nil {
758 return err
759 }
Fumitoshi Ukai49599e52015-06-26 10:10:24 +0900760 logStats("generate ninja time: %q", time.Since(startTime))
Fumitoshi Ukai65c72332015-06-26 21:32:50 +0900761 return nil
Shinichiro Hamaji6aa86ac2015-05-26 15:21:11 +0900762}