Mercurial > hgrepos > Python > libs > ConfigMix
comparison windows-dev/ninja_syntax.py @ 571:aa240af27589
Move Configure.py and ninja_syntax.py to windows-dev
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 08 Jan 2022 18:35:01 +0100 |
| parents | ninja_syntax.py@e15e86c47a27 |
| children |
comparison
equal
deleted
inserted
replaced
| 570:e15e86c47a27 | 571:aa240af27589 |
|---|---|
| 1 # -*- coding: utf-8 -*- | |
| 2 # | |
| 3 # Copyright 2011 Google Inc. All Rights Reserved. | |
| 4 # Copyright 2020 Franz Glasner. All Rights Reserved. | |
| 5 # | |
| 6 # Licensed under the Apache License, Version 2.0 (the "License"); | |
| 7 # you may not use this file except in compliance with the License. | |
| 8 # You may obtain a copy of the License at | |
| 9 # | |
| 10 # http://www.apache.org/licenses/LICENSE-2.0 | |
| 11 # | |
| 12 # Unless required by applicable law or agreed to in writing, software | |
| 13 # distributed under the License is distributed on an "AS IS" BASIS, | |
| 14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 15 # See the License for the specific language governing permissions and | |
| 16 # limitations under the License. | |
| 17 # | |
| 18 r"""Python module for generating .ninja files. | |
| 19 | |
| 20 Note that this is emphatically not a required piece of Ninja; it's | |
| 21 just a helpful utility for build-file-generation systems that already | |
| 22 use Python. | |
| 23 | |
| 24 :Copyright: \(c) 2001 Google Inc. All Rights Reserved. | |
| 25 :Copyright: \(c) 2020 Franz Glasner. All Rights Reserved. | |
| 26 :License: Apache License 2.0. See :ref:`LICENSE.txt <license>` for details. | |
| 27 | |
| 28 """ | |
| 29 | |
| 30 from __future__ import absolute_import | |
| 31 | |
| 32 import re | |
| 33 import textwrap | |
| 34 | |
| 35 | |
| 36 def escape_path(word): | |
| 37 """Escape `word` according to Ninja's path interpretation rules. | |
| 38 | |
| 39 Escapes not only ``$`` but also spaces and colons. | |
| 40 """ | |
| 41 return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') | |
| 42 | |
| 43 | |
| 44 class Writer(object): | |
| 45 | |
| 46 """An object-oriented writer of a Ninja build file. | |
| 47 | |
| 48 Can act as its own context manager. | |
| 49 """ | |
| 50 | |
| 51 def __init__(self, output, width=78): | |
| 52 """ | |
| 53 :param output: an opened file-alike where to write into | |
| 54 :param int width: the width where line-wrapping should happen | |
| 55 | |
| 56 `output` just needs a `write()` and -- if :meth:`~.close` is called | |
| 57 -- a `close()` method. | |
| 58 """ | |
| 59 self.output = output | |
| 60 self.width = width | |
| 61 | |
| 62 def newline(self): | |
| 63 self.output.write('\n') | |
| 64 | |
| 65 def comment(self, text): | |
| 66 for line in textwrap.wrap(text, self.width - 2, break_long_words=False, | |
| 67 break_on_hyphens=False): | |
| 68 self.output.write('# ' + line + '\n') | |
| 69 | |
| 70 def variable(self, key, value, indent=0): | |
| 71 if value is None: | |
| 72 return | |
| 73 if isinstance(value, list): | |
| 74 value = ' '.join(filter(None, value)) # Filter out empty strings. | |
| 75 self._line('%s = %s' % (key, value), indent) | |
| 76 | |
| 77 def pool(self, name, depth): | |
| 78 self._line('pool %s' % name) | |
| 79 self.variable('depth', depth, indent=1) | |
| 80 | |
| 81 def rule(self, name, command, description=None, depfile=None, | |
| 82 generator=False, pool=None, restat=False, rspfile=None, | |
| 83 rspfile_content=None, deps=None): | |
| 84 self._line('rule %s' % name) | |
| 85 self.variable('command', command, indent=1) | |
| 86 if description: | |
| 87 self.variable('description', description, indent=1) | |
| 88 if depfile: | |
| 89 self.variable('depfile', depfile, indent=1) | |
| 90 if generator: | |
| 91 self.variable('generator', '1', indent=1) | |
| 92 if pool: | |
| 93 self.variable('pool', pool, indent=1) | |
| 94 if restat: | |
| 95 self.variable('restat', '1', indent=1) | |
| 96 if rspfile: | |
| 97 self.variable('rspfile', rspfile, indent=1) | |
| 98 if rspfile_content: | |
| 99 self.variable('rspfile_content', rspfile_content, indent=1) | |
| 100 if deps: | |
| 101 self.variable('deps', deps, indent=1) | |
| 102 | |
| 103 def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, | |
| 104 variables=None, implicit_outputs=None, pool=None): | |
| 105 outputs = as_list(outputs) | |
| 106 out_outputs = [escape_path(x) for x in outputs] | |
| 107 all_inputs = [escape_path(x) for x in as_list(inputs)] | |
| 108 | |
| 109 if implicit: | |
| 110 implicit = [escape_path(x) for x in as_list(implicit)] | |
| 111 all_inputs.append('|') | |
| 112 all_inputs.extend(implicit) | |
| 113 if order_only: | |
| 114 order_only = [escape_path(x) for x in as_list(order_only)] | |
| 115 all_inputs.append('||') | |
| 116 all_inputs.extend(order_only) | |
| 117 if implicit_outputs: | |
| 118 implicit_outputs = [escape_path(x) | |
| 119 for x in as_list(implicit_outputs)] | |
| 120 out_outputs.append('|') | |
| 121 out_outputs.extend(implicit_outputs) | |
| 122 | |
| 123 self._line('build %s: %s' % (' '.join(out_outputs), | |
| 124 ' '.join([rule] + all_inputs))) | |
| 125 if pool is not None: | |
| 126 self._line(' pool = %s' % pool) | |
| 127 | |
| 128 if variables: | |
| 129 if isinstance(variables, dict): | |
| 130 iterator = iter(variables.items()) | |
| 131 else: | |
| 132 iterator = iter(variables) | |
| 133 | |
| 134 for key, val in iterator: | |
| 135 self.variable(key, val, indent=1) | |
| 136 | |
| 137 return outputs | |
| 138 | |
| 139 def include(self, path): | |
| 140 self._line('include %s' % path) | |
| 141 | |
| 142 def subninja(self, path): | |
| 143 self._line('subninja %s' % path) | |
| 144 | |
| 145 def default(self, paths): | |
| 146 self._line('default %s' % ' '.join(as_list(paths))) | |
| 147 | |
| 148 def _count_dollars_before_index(self, s, i): | |
| 149 """Returns the number of '$' characters right in front of s[i].""" | |
| 150 dollar_count = 0 | |
| 151 dollar_index = i - 1 | |
| 152 while dollar_index > 0 and s[dollar_index] == '$': | |
| 153 dollar_count += 1 | |
| 154 dollar_index -= 1 | |
| 155 return dollar_count | |
| 156 | |
| 157 def _line(self, text, indent=0): | |
| 158 """Write 'text' word-wrapped at self.width characters.""" | |
| 159 leading_space = ' ' * indent | |
| 160 while len(leading_space) + len(text) > self.width: | |
| 161 # The text is too wide; wrap if possible. | |
| 162 | |
| 163 # Find the rightmost space that would obey our width constraint and | |
| 164 # that's not an escaped space. | |
| 165 available_space = self.width - len(leading_space) - len(' $') | |
| 166 space = available_space | |
| 167 while True: | |
| 168 space = text.rfind(' ', 0, space) | |
| 169 if (space < 0 | |
| 170 or self._count_dollars_before_index(text, space) % 2 == 0): # noqa: E501 | |
| 171 break | |
| 172 | |
| 173 if space < 0: | |
| 174 # | |
| 175 # No such space; | |
| 176 # just use the first unescaped space we can find. | |
| 177 # | |
| 178 space = available_space - 1 | |
| 179 while True: | |
| 180 space = text.find(' ', space + 1) | |
| 181 if (space < 0 | |
| 182 or self._count_dollars_before_index(text, space) % 2 == 0): # noqa: E501 | |
| 183 break | |
| 184 if space < 0: | |
| 185 # Give up on breaking. | |
| 186 break | |
| 187 | |
| 188 self.output.write(leading_space + text[0:space] + ' $\n') | |
| 189 text = text[space+1:] | |
| 190 | |
| 191 # Subsequent lines are continuations, so indent them. | |
| 192 leading_space = ' ' * (indent+2) | |
| 193 | |
| 194 self.output.write(leading_space + text + '\n') | |
| 195 | |
| 196 def close(self): | |
| 197 """Close the associated output""" | |
| 198 self.output.close() | |
| 199 | |
| 200 def __enter__(self): | |
| 201 """Return `self`""" | |
| 202 return self | |
| 203 | |
| 204 def __exit__(self, *args, **kwds): | |
| 205 """Close the associated output""" | |
| 206 self.close() | |
| 207 | |
| 208 | |
| 209 def as_list(input): | |
| 210 """Ensure that `input` is always a :class:`list` -- wrap if needed | |
| 211 | |
| 212 :rtype: list | |
| 213 | |
| 214 """ | |
| 215 | |
| 216 if input is None: | |
| 217 return [] | |
| 218 if isinstance(input, list): | |
| 219 return input | |
| 220 return [input] | |
| 221 | |
| 222 | |
| 223 def escape(string): | |
| 224 """Escape a string such that it can be embedded into a Ninja file without | |
| 225 further interpretation.""" | |
| 226 assert '\n' not in string, 'Ninja syntax does not allow newlines' | |
| 227 # We only have one special metacharacter: '$'. | |
| 228 return string.replace('$', '$$') | |
| 229 | |
| 230 | |
| 231 def expand(string, vars, local_vars={}): | |
| 232 """Expand a string containing $vars as Ninja would. | |
| 233 | |
| 234 Note: doesn't handle the full Ninja variable syntax, but it's enough | |
| 235 to make configure.py's use of it work. | |
| 236 """ | |
| 237 def exp(m): | |
| 238 var = m.group(1) | |
| 239 if var == '$': | |
| 240 return '$' | |
| 241 return local_vars.get(var, vars.get(var, '')) | |
| 242 return re.sub(r'\$(\$|\w*)', exp, string) |
