Clone of Bael'Zharon's Respite @ https://github.com/boardwalk/bzr

ninja_syntax.py 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. #!/usr/bin/python
  2. """Python module for generating .ninja files.
  3. Note that this is emphatically not a required piece of Ninja; it's
  4. just a helpful utility for build-file-generation systems that already
  5. use Python.
  6. """
  7. import re
  8. import textwrap
  9. def escape_path(word):
  10. return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
  11. class Writer(object):
  12. def __init__(self, output, width=78):
  13. self.output = output
  14. self.width = width
  15. def newline(self):
  16. self.output.write('\n')
  17. def comment(self, text):
  18. for line in textwrap.wrap(text, self.width - 2):
  19. self.output.write('# ' + line + '\n')
  20. def variable(self, key, value, indent=0):
  21. if value is None:
  22. return
  23. if isinstance(value, list):
  24. value = ' '.join(filter(None, value)) # Filter out empty strings.
  25. self._line('%s = %s' % (key, value), indent)
  26. def pool(self, name, depth):
  27. self._line('pool %s' % name)
  28. self.variable('depth', depth, indent=1)
  29. def rule(self, name, command, description=None, depfile=None,
  30. generator=False, pool=None, restat=False, rspfile=None,
  31. rspfile_content=None, deps=None):
  32. self._line('rule %s' % name)
  33. self.variable('command', command, indent=1)
  34. if description:
  35. self.variable('description', description, indent=1)
  36. if depfile:
  37. self.variable('depfile', depfile, indent=1)
  38. if generator:
  39. self.variable('generator', '1', indent=1)
  40. if pool:
  41. self.variable('pool', pool, indent=1)
  42. if restat:
  43. self.variable('restat', '1', indent=1)
  44. if rspfile:
  45. self.variable('rspfile', rspfile, indent=1)
  46. if rspfile_content:
  47. self.variable('rspfile_content', rspfile_content, indent=1)
  48. if deps:
  49. self.variable('deps', deps, indent=1)
  50. def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
  51. variables=None):
  52. outputs = as_list(outputs)
  53. out_outputs = [escape_path(x) for x in outputs]
  54. all_inputs = [escape_path(x) for x in as_list(inputs)]
  55. if implicit:
  56. implicit = [escape_path(x) for x in as_list(implicit)]
  57. all_inputs.append('|')
  58. all_inputs.extend(implicit)
  59. if order_only:
  60. order_only = [escape_path(x) for x in as_list(order_only)]
  61. all_inputs.append('||')
  62. all_inputs.extend(order_only)
  63. self._line('build %s: %s' % (' '.join(out_outputs),
  64. ' '.join([rule] + all_inputs)))
  65. if variables:
  66. if isinstance(variables, dict):
  67. iterator = iter(variables.items())
  68. else:
  69. iterator = iter(variables)
  70. for key, val in iterator:
  71. self.variable(key, val, indent=1)
  72. return outputs
  73. def include(self, path):
  74. self._line('include %s' % path)
  75. def subninja(self, path):
  76. self._line('subninja %s' % path)
  77. def default(self, paths):
  78. self._line('default %s' % ' '.join(as_list(paths)))
  79. def _count_dollars_before_index(self, s, i):
  80. """Returns the number of '$' characters right in front of s[i]."""
  81. dollar_count = 0
  82. dollar_index = i - 1
  83. while dollar_index > 0 and s[dollar_index] == '$':
  84. dollar_count += 1
  85. dollar_index -= 1
  86. return dollar_count
  87. def _line(self, text, indent=0):
  88. """Write 'text' word-wrapped at self.width characters."""
  89. leading_space = ' ' * indent
  90. while len(leading_space) + len(text) > self.width:
  91. # The text is too wide; wrap if possible.
  92. # Find the rightmost space that would obey our width constraint and
  93. # that's not an escaped space.
  94. available_space = self.width - len(leading_space) - len(' $')
  95. space = available_space
  96. while True:
  97. space = text.rfind(' ', 0, space)
  98. if (space < 0 or
  99. self._count_dollars_before_index(text, space) % 2 == 0):
  100. break
  101. if space < 0:
  102. # No such space; just use the first unescaped space we can find.
  103. space = available_space - 1
  104. while True:
  105. space = text.find(' ', space + 1)
  106. if (space < 0 or
  107. self._count_dollars_before_index(text, space) % 2 == 0):
  108. break
  109. if space < 0:
  110. # Give up on breaking.
  111. break
  112. self.output.write(leading_space + text[0:space] + ' $\n')
  113. text = text[space+1:]
  114. # Subsequent lines are continuations, so indent them.
  115. leading_space = ' ' * (indent+2)
  116. self.output.write(leading_space + text + '\n')
  117. def close(self):
  118. self.output.close()
  119. def as_list(input):
  120. if input is None:
  121. return []
  122. if isinstance(input, list):
  123. return input
  124. return [input]
  125. def escape(string):
  126. """Escape a string such that it can be embedded into a Ninja file without
  127. further interpretation."""
  128. assert '\n' not in string, 'Ninja syntax does not allow newlines'
  129. # We only have one special metacharacter: '$'.
  130. return string.replace('$', '$$')
  131. def expand(string, vars, local_vars={}):
  132. """Expand a string containing $vars as Ninja would.
  133. Note: doesn't handle the full Ninja variable syntax, but it's enough
  134. to make configure.py's use of it work.
  135. """
  136. def exp(m):
  137. var = m.group(1)
  138. if var == '$':
  139. return '$'
  140. return local_vars.get(var, vars.get(var, ''))
  141. return re.sub(r'\$(\$|\w*)', exp, string)