blob: 7d9f592dfadfbc882866d8d7b7f2707edc1b646e [file] [log] [blame]
Peter Collingbourned5395fb2012-01-08 22:09:58 +00001#!/usr/bin/python
2
3"""Python module for generating .ninja files.
4
5Note that this is emphatically not a required piece of Ninja; it's
6just a helpful utility for build-file-generation systems that already
7use Python.
8"""
9
10import textwrap
Tom Stellard91d51db2014-01-29 20:03:26 +000011import re
Peter Collingbourned5395fb2012-01-08 22:09:58 +000012
13class Writer(object):
14 def __init__(self, output, width=78):
15 self.output = output
16 self.width = width
17
18 def newline(self):
19 self.output.write('\n')
20
21 def comment(self, text):
22 for line in textwrap.wrap(text, self.width - 2):
23 self.output.write('# ' + line + '\n')
24
25 def variable(self, key, value, indent=0):
26 if value is None:
27 return
28 if isinstance(value, list):
29 value = ' '.join(value)
30 self._line('%s = %s' % (key, value), indent)
31
32 def rule(self, name, command, description=None, depfile=None,
33 generator=False):
34 self._line('rule %s' % name)
Tom Stellard91d51db2014-01-29 20:03:26 +000035 self.variable('command', escape(command), indent=1)
Peter Collingbourned5395fb2012-01-08 22:09:58 +000036 if description:
37 self.variable('description', description, indent=1)
38 if depfile:
39 self.variable('depfile', depfile, indent=1)
40 if generator:
41 self.variable('generator', '1', indent=1)
42
43 def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
44 variables=None):
45 outputs = self._as_list(outputs)
46 all_inputs = self._as_list(inputs)[:]
47
48 if implicit:
49 all_inputs.append('|')
50 all_inputs.extend(self._as_list(implicit))
51 if order_only:
52 all_inputs.append('||')
53 all_inputs.extend(self._as_list(order_only))
54
55 self._line('build %s: %s %s' % (' '.join(outputs),
56 rule,
57 ' '.join(all_inputs)))
58
59 if variables:
60 for key, val in variables:
61 self.variable(key, val, indent=1)
62
63 return outputs
64
65 def include(self, path):
66 self._line('include %s' % path)
67
68 def subninja(self, path):
69 self._line('subninja %s' % path)
70
71 def default(self, paths):
72 self._line('default %s' % ' '.join(self._as_list(paths)))
73
74 def _line(self, text, indent=0):
75 """Write 'text' word-wrapped at self.width characters."""
76 leading_space = ' ' * indent
77 while len(text) > self.width:
78 # The text is too wide; wrap if possible.
79
80 # Find the rightmost space that would obey our width constraint.
81 available_space = self.width - len(leading_space) - len(' $')
82 space = text.rfind(' ', 0, available_space)
83 if space < 0:
84 # No such space; just use the first space we can find.
85 space = text.find(' ', available_space)
86 if space < 0:
87 # Give up on breaking.
88 break
89
90 self.output.write(leading_space + text[0:space] + ' $\n')
91 text = text[space+1:]
92
93 # Subsequent lines are continuations, so indent them.
94 leading_space = ' ' * (indent+2)
95
96 self.output.write(leading_space + text + '\n')
97
98 def _as_list(self, input):
99 if input is None:
100 return []
101 if isinstance(input, list):
102 return input
103 return [input]
104
105
106def escape(string):
Tom Stellard91d51db2014-01-29 20:03:26 +0000107 """Escape a string such that Makefile and shell variables are
108 correctly escaped for use in a Ninja file.
109 """
Peter Collingbourned5395fb2012-01-08 22:09:58 +0000110 assert '\n' not in string, 'Ninja syntax does not allow newlines'
111 # We only have one special metacharacter: '$'.
Tom Stellard91d51db2014-01-29 20:03:26 +0000112
113 # We should leave $in and $out untouched.
114 # Just look for makefile/shell style substitutions
115 return re.sub(r'(\$[{(][a-z_]+[})])',
116 r'$\1',
117 string,
118 flags=re.IGNORECASE)