ninja_syntax.py 5.4 KB

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