🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

312 lines
10 KiB

  1. #!/usr/bin/env python
  2. #
  3. # Kuroga, single python file meta-build system for ninja
  4. # https://github.com/lighttransport/kuroga
  5. #
  6. # Requirements: python 2.6 or 2.7
  7. #
  8. # Usage: $ python kuroga.py input.py
  9. #
  10. import imp
  11. import re
  12. import textwrap
  13. import glob
  14. import os
  15. import sys
  16. # gcc preset
  17. def add_gnu_rule(ninja):
  18. ninja.rule('gnucxx', description='CXX $out',
  19. command='$gnucxx -MMD -MF $out.d $gnudefines $gnuincludes $gnucxxflags -c $in -o $out',
  20. depfile='$out.d', deps='gcc')
  21. ninja.rule('gnucc', description='CC $out',
  22. command='$gnucc -MMD -MF $out.d $gnudefines $gnuincludes $gnucflags -c $in -o $out',
  23. depfile='$out.d', deps='gcc')
  24. ninja.rule('gnulink', description='LINK $out', pool='link_pool',
  25. command='$gnuld -o $out $in $libs $gnuldflags')
  26. ninja.rule('gnuar', description='AR $out', pool='link_pool',
  27. command='$gnuar rsc $out $in')
  28. ninja.rule('gnustamp', description='STAMP $out', command='touch $out')
  29. ninja.newline()
  30. ninja.variable('gnucxx', 'g++')
  31. ninja.variable('gnucc', 'gcc')
  32. ninja.variable('gnuld', '$gnucxx')
  33. ninja.variable('gnuar', 'ar')
  34. ninja.newline()
  35. # clang preset
  36. def add_clang_rule(ninja):
  37. ninja.rule('clangcxx', description='CXX $out',
  38. command='$clangcxx -MMD -MF $out.d $clangdefines $clangincludes $clangcxxflags -c $in -o $out',
  39. depfile='$out.d', deps='gcc')
  40. ninja.rule('clangcc', description='CC $out',
  41. command='$clangcc -MMD -MF $out.d $clangdefines $clangincludes $clangcflags -c $in -o $out',
  42. depfile='$out.d', deps='gcc')
  43. ninja.rule('clanglink', description='LINK $out', pool='link_pool',
  44. command='$clangld -o $out $in $libs $gnuldflags')
  45. ninja.rule('clangar', description='AR $out', pool='link_pool',
  46. command='$clangar rsc $out $in')
  47. ninja.rule('clangstamp', description='STAMP $out', command='touch $out')
  48. ninja.newline()
  49. ninja.variable('clangcxx', 'clang++')
  50. ninja.variable('clangcc', 'clang')
  51. ninja.variable('clangld', '$clangcxx')
  52. ninja.variable('clangar', 'ar')
  53. ninja.newline()
  54. # msvc preset
  55. def add_msvc_rule(ninja):
  56. ninja.rule('msvccxx', description='CXX $out',
  57. command='$msvccxx /TP /showIncludes $msvcdefines $msvcincludes $msvccxxflags -c $in /Fo$out',
  58. depfile='$out.d', deps='msvc')
  59. ninja.rule('msvccc', description='CC $out',
  60. command='$msvccc /TC /showIncludes $msvcdefines $msvcincludes $msvccflags -c $in /Fo$out',
  61. depfile='$out.d', deps='msvc')
  62. ninja.rule('msvclink', description='LINK $out', pool='link_pool',
  63. command='$msvcld $msvcldflags $in $libs /OUT:$out')
  64. ninja.rule('msvcar', description='AR $out', pool='link_pool',
  65. command='$msvcar $in /OUT:$out')
  66. #ninja.rule('msvcstamp', description='STAMP $out', command='touch $out')
  67. ninja.newline()
  68. ninja.variable('msvccxx', 'cl.exe')
  69. ninja.variable('msvccc', 'cl.exe')
  70. ninja.variable('msvcld', 'link.exe')
  71. ninja.variable('msvcar', 'lib.exe')
  72. ninja.newline()
  73. # -- from ninja_syntax.py --
  74. def escape_path(word):
  75. return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
  76. class Writer(object):
  77. def __init__(self, output, width=78):
  78. self.output = output
  79. self.width = width
  80. def newline(self):
  81. self.output.write('\n')
  82. def comment(self, text, has_path=False):
  83. for line in textwrap.wrap(text, self.width - 2, break_long_words=False,
  84. break_on_hyphens=False):
  85. self.output.write('# ' + line + '\n')
  86. def variable(self, key, value, indent=0):
  87. if value is None:
  88. return
  89. if isinstance(value, list):
  90. value = ' '.join(filter(None, value)) # Filter out empty strings.
  91. self._line('%s = %s' % (key, value), indent)
  92. def pool(self, name, depth):
  93. self._line('pool %s' % name)
  94. self.variable('depth', depth, indent=1)
  95. def rule(self, name, command, description=None, depfile=None,
  96. generator=False, pool=None, restat=False, rspfile=None,
  97. rspfile_content=None, deps=None):
  98. self._line('rule %s' % name)
  99. self.variable('command', command, indent=1)
  100. if description:
  101. self.variable('description', description, indent=1)
  102. if depfile:
  103. self.variable('depfile', depfile, indent=1)
  104. if generator:
  105. self.variable('generator', '1', indent=1)
  106. if pool:
  107. self.variable('pool', pool, indent=1)
  108. if restat:
  109. self.variable('restat', '1', indent=1)
  110. if rspfile:
  111. self.variable('rspfile', rspfile, indent=1)
  112. if rspfile_content:
  113. self.variable('rspfile_content', rspfile_content, indent=1)
  114. if deps:
  115. self.variable('deps', deps, indent=1)
  116. def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
  117. variables=None):
  118. outputs = as_list(outputs)
  119. out_outputs = [escape_path(x) for x in outputs]
  120. all_inputs = [escape_path(x) for x in as_list(inputs)]
  121. if implicit:
  122. implicit = [escape_path(x) for x in as_list(implicit)]
  123. all_inputs.append('|')
  124. all_inputs.extend(implicit)
  125. if order_only:
  126. order_only = [escape_path(x) for x in as_list(order_only)]
  127. all_inputs.append('||')
  128. all_inputs.extend(order_only)
  129. self._line('build %s: %s' % (' '.join(out_outputs),
  130. ' '.join([rule] + all_inputs)))
  131. if variables:
  132. if isinstance(variables, dict):
  133. iterator = iter(variables.items())
  134. else:
  135. iterator = iter(variables)
  136. for key, val in iterator:
  137. self.variable(key, val, indent=1)
  138. return outputs
  139. def include(self, path):
  140. self._line('include %s' % path)
  141. def subninja(self, path):
  142. self._line('subninja %s' % path)
  143. def default(self, paths):
  144. self._line('default %s' % ' '.join(as_list(paths)))
  145. def _count_dollars_before_index(self, s, i):
  146. """Returns the number of '$' characters right in front of s[i]."""
  147. dollar_count = 0
  148. dollar_index = i - 1
  149. while dollar_index > 0 and s[dollar_index] == '$':
  150. dollar_count += 1
  151. dollar_index -= 1
  152. return dollar_count
  153. def _line(self, text, indent=0):
  154. """Write 'text' word-wrapped at self.width characters."""
  155. leading_space = ' ' * indent
  156. while len(leading_space) + len(text) > self.width:
  157. # The text is too wide; wrap if possible.
  158. # Find the rightmost space that would obey our width constraint and
  159. # that's not an escaped space.
  160. available_space = self.width - len(leading_space) - len(' $')
  161. space = available_space
  162. while True:
  163. space = text.rfind(' ', 0, space)
  164. if (space < 0 or
  165. self._count_dollars_before_index(text, space) % 2 == 0):
  166. break
  167. if space < 0:
  168. # No such space; just use the first unescaped space we can find.
  169. space = available_space - 1
  170. while True:
  171. space = text.find(' ', space + 1)
  172. if (space < 0 or
  173. self._count_dollars_before_index(text, space) % 2 == 0):
  174. break
  175. if space < 0:
  176. # Give up on breaking.
  177. break
  178. self.output.write(leading_space + text[0:space] + ' $\n')
  179. text = text[space+1:]
  180. # Subsequent lines are continuations, so indent them.
  181. leading_space = ' ' * (indent+2)
  182. self.output.write(leading_space + text + '\n')
  183. def close(self):
  184. self.output.close()
  185. def as_list(input):
  186. if input is None:
  187. return []
  188. if isinstance(input, list):
  189. return input
  190. return [input]
  191. # -- end from ninja_syntax.py --
  192. def gen(ninja, toolchain, config):
  193. ninja.variable('ninja_required_version', '1.4')
  194. ninja.newline()
  195. if hasattr(config, "builddir"):
  196. builddir = config.builddir[toolchain]
  197. ninja.variable(toolchain + 'builddir', builddir)
  198. else:
  199. builddir = ''
  200. ninja.variable(toolchain + 'defines', config.defines[toolchain] or [])
  201. ninja.variable(toolchain + 'includes', config.includes[toolchain] or [])
  202. ninja.variable(toolchain + 'cflags', config.cflags[toolchain] or [])
  203. ninja.variable(toolchain + 'cxxflags', config.cxxflags[toolchain] or [])
  204. ninja.variable(toolchain + 'ldflags', config.ldflags[toolchain] or [])
  205. ninja.newline()
  206. if hasattr(config, "link_pool_depth"):
  207. ninja.pool('link_pool', depth=config.link_pool_depth)
  208. else:
  209. ninja.pool('link_pool', depth=4)
  210. ninja.newline()
  211. # Add default toolchain(gnu, clang and msvc)
  212. add_gnu_rule(ninja)
  213. add_clang_rule(ninja)
  214. add_msvc_rule(ninja)
  215. obj_files = []
  216. cc = toolchain + 'cc'
  217. cxx = toolchain + 'cxx'
  218. link = toolchain + 'link'
  219. ar = toolchain + 'ar'
  220. if hasattr(config, "cxx_files"):
  221. for src in config.cxx_files:
  222. srcfile = src
  223. obj = os.path.splitext(srcfile)[0] + '.o'
  224. obj = os.path.join(builddir, obj);
  225. obj_files.append(obj)
  226. ninja.build(obj, cxx, srcfile)
  227. ninja.newline()
  228. if hasattr(config, "c_files"):
  229. for src in config.c_files:
  230. srcfile = src
  231. obj = os.path.splitext(srcfile)[0] + '.o'
  232. obj = os.path.join(builddir, obj);
  233. obj_files.append(obj)
  234. ninja.build(obj, cc, srcfile)
  235. ninja.newline()
  236. targetlist = []
  237. if hasattr(config, "exe"):
  238. ninja.build(config.exe, link, obj_files)
  239. targetlist.append(config.exe)
  240. if hasattr(config, "staticlib"):
  241. ninja.build(config.staticlib, ar, obj_files)
  242. targetlist.append(config.staticlib)
  243. ninja.build('all', 'phony', targetlist)
  244. ninja.newline()
  245. ninja.default('all')
  246. def main():
  247. if len(sys.argv) < 2:
  248. print("Usage: python kuroga.py config.py")
  249. sys.exit(1)
  250. config = imp.load_source("config", sys.argv[1])
  251. f = open('build.ninja', 'w')
  252. ninja = Writer(f)
  253. if hasattr(config, "register_toolchain"):
  254. config.register_toolchain(ninja)
  255. gen(ninja, config.toolchain, config)
  256. f.close()
  257. main()