1f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org# This file comes from
2f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org#   https://github.com/martine/ninja/blob/master/misc/ninja_syntax.py
3f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org# Do not edit!  Edit the upstream one instead.
4f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
5f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org"""Python module for generating .ninja files.
6f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
7f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.orgNote that this is emphatically not a required piece of Ninja; it's
8f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.orgjust a helpful utility for build-file-generation systems that already
9f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.orguse Python.
10f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org"""
11f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
12f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.orgimport textwrap
135e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.orgimport re
145e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org
15a77d1c0e8a279cf297bc1a3cc4f4e1e449e6a8c6thakis@chromium.orgdef escape_path(word):
16a77d1c0e8a279cf297bc1a3cc4f4e1e449e6a8c6thakis@chromium.org    return word.replace('$ ','$$ ').replace(' ','$ ').replace(':', '$:')
17f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
18f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.orgclass Writer(object):
19f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    def __init__(self, output, width=78):
20f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        self.output = output
21f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        self.width = width
22f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
23f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    def newline(self):
24f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        self.output.write('\n')
25f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
26f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    def comment(self, text):
27f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        for line in textwrap.wrap(text, self.width - 2):
28f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            self.output.write('# ' + line + '\n')
29f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
30f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    def variable(self, key, value, indent=0):
315e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org        if value is None:
325e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org            return
335e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org        if isinstance(value, list):
34fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org            value = ' '.join(filter(None, value))  # Filter out empty strings.
35f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        self._line('%s = %s' % (key, value), indent)
36f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
37a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org    def pool(self, name, depth):
38a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org        self._line('pool %s' % name)
39a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org        self.variable('depth', depth, indent=1)
40a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org
415e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org    def rule(self, name, command, description=None, depfile=None,
42a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org             generator=False, pool=None, restat=False, rspfile=None,
43a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org             rspfile_content=None, deps=None):
44f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        self._line('rule %s' % name)
45f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        self.variable('command', command, indent=1)
46f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        if description:
47f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            self.variable('description', description, indent=1)
48f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        if depfile:
49f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            self.variable('depfile', depfile, indent=1)
505e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org        if generator:
515e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org            self.variable('generator', '1', indent=1)
52a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org        if pool:
53a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org            self.variable('pool', pool, indent=1)
54ecb67d9bce659fa56a54ae962661ded5b94d6901thakis@chromium.org        if restat:
55ecb67d9bce659fa56a54ae962661ded5b94d6901thakis@chromium.org            self.variable('restat', '1', indent=1)
567c97342c8eba9aa2418a6381e876a7d2bd03df12scottmg@chromium.org        if rspfile:
577c97342c8eba9aa2418a6381e876a7d2bd03df12scottmg@chromium.org            self.variable('rspfile', rspfile, indent=1)
587c97342c8eba9aa2418a6381e876a7d2bd03df12scottmg@chromium.org        if rspfile_content:
597c97342c8eba9aa2418a6381e876a7d2bd03df12scottmg@chromium.org            self.variable('rspfile_content', rspfile_content, indent=1)
60a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org        if deps:
61a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org            self.variable('deps', deps, indent=1)
62f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
63f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
64f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org              variables=None):
65f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        outputs = self._as_list(outputs)
66f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        all_inputs = self._as_list(inputs)[:]
67a77d1c0e8a279cf297bc1a3cc4f4e1e449e6a8c6thakis@chromium.org        out_outputs = list(map(escape_path, outputs))
68a77d1c0e8a279cf297bc1a3cc4f4e1e449e6a8c6thakis@chromium.org        all_inputs = list(map(escape_path, all_inputs))
69f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
70f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        if implicit:
71a77d1c0e8a279cf297bc1a3cc4f4e1e449e6a8c6thakis@chromium.org            implicit = map(escape_path, self._as_list(implicit))
72f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            all_inputs.append('|')
735e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org            all_inputs.extend(implicit)
74f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        if order_only:
75a77d1c0e8a279cf297bc1a3cc4f4e1e449e6a8c6thakis@chromium.org            order_only = map(escape_path, self._as_list(order_only))
76f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            all_inputs.append('||')
775e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org            all_inputs.extend(order_only)
78f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
79a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org        self._line('build %s: %s' % (' '.join(out_outputs),
80a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org                                        ' '.join([rule] + all_inputs)))
81f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
82f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        if variables:
8397bd1d8f114f5ca2e6f5dcb543fb338d4c2c4263thakis@chromium.org            if isinstance(variables, dict):
84a46c8832ac3d33d9d2ef8b743e968a7cd603c4d8thakis@chromium.org                iterator = iter(variables.items())
8597bd1d8f114f5ca2e6f5dcb543fb338d4c2c4263thakis@chromium.org            else:
8697bd1d8f114f5ca2e6f5dcb543fb338d4c2c4263thakis@chromium.org                iterator = iter(variables)
8797bd1d8f114f5ca2e6f5dcb543fb338d4c2c4263thakis@chromium.org
8897bd1d8f114f5ca2e6f5dcb543fb338d4c2c4263thakis@chromium.org            for key, val in iterator:
89f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org                self.variable(key, val, indent=1)
90f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
91f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        return outputs
92f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
933fbdb4103aef78dbf6935c48ffbc216240d46deeevan@chromium.org    def include(self, path):
943fbdb4103aef78dbf6935c48ffbc216240d46deeevan@chromium.org        self._line('include %s' % path)
953fbdb4103aef78dbf6935c48ffbc216240d46deeevan@chromium.org
963fbdb4103aef78dbf6935c48ffbc216240d46deeevan@chromium.org    def subninja(self, path):
973fbdb4103aef78dbf6935c48ffbc216240d46deeevan@chromium.org        self._line('subninja %s' % path)
983fbdb4103aef78dbf6935c48ffbc216240d46deeevan@chromium.org
995e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org    def default(self, paths):
1005e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org        self._line('default %s' % ' '.join(self._as_list(paths)))
1015e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org
102fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org    def _count_dollars_before_index(self, s, i):
103fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org      """Returns the number of '$' characters right in front of s[i]."""
104fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org      dollar_count = 0
105fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org      dollar_index = i - 1
106fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org      while dollar_index > 0 and s[dollar_index] == '$':
107fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org        dollar_count += 1
108fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org        dollar_index -= 1
109fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org      return dollar_count
110fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org
111f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    def _line(self, text, indent=0):
112f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        """Write 'text' word-wrapped at self.width characters."""
113f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        leading_space = '  ' * indent
11497bd1d8f114f5ca2e6f5dcb543fb338d4c2c4263thakis@chromium.org        while len(leading_space) + len(text) > self.width:
115f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            # The text is too wide; wrap if possible.
116f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
117fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org            # Find the rightmost space that would obey our width constraint and
118fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org            # that's not an escaped space.
119f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            available_space = self.width - len(leading_space) - len(' $')
120fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org            space = available_space
121fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org            while True:
122fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org              space = text.rfind(' ', 0, space)
123fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org              if space < 0 or \
124fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                 self._count_dollars_before_index(text, space) % 2 == 0:
125fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                break
126f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
127fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org            if space < 0:
128fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                # No such space; just use the first unescaped space we can find.
129fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                space = available_space - 1
130fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                while True:
131fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                  space = text.find(' ', space + 1)
132fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                  if space < 0 or \
133fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                     self._count_dollars_before_index(text, space) % 2 == 0:
1345e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org                    break
135fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org            if space < 0:
136fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                # Give up on breaking.
137fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org                break
1385e8f653193e6839055ca8fb77eafcc2ad968f206thakis@chromium.org
139fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org            self.output.write(leading_space + text[0:space] + ' $\n')
140fd06f3b34a9cd1b393c3f7ddf5d6d8e80dc2fa06thakis@chromium.org            text = text[space+1:]
141f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
142f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            # Subsequent lines are continuations, so indent them.
143f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            leading_space = '  ' * (indent+2)
144f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
145f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        self.output.write(leading_space + text + '\n')
146f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
147f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    def _as_list(self, input):
148f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        if input is None:
149f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            return []
150f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        if isinstance(input, list):
151f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org            return input
152f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org        return [input]
153f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
154f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org
155f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.orgdef escape(string):
156f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    """Escape a string such that it can be embedded into a Ninja file without
157f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    further interpretation."""
158f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    assert '\n' not in string, 'Ninja syntax does not allow newlines'
159f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    # We only have one special metacharacter: '$'.
160f040c6194657a5972871a96cb8c3572e9296ef37evan@chromium.org    return string.replace('$', '$$')
161