cmake2meson.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. #!/usr/bin/env python3
  2. # Copyright 2014 Jussi Pakkanen
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS,
  9. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. # See the License for the specific language governing permissions and
  11. # limitations under the License.
  12. import sys, os
  13. import re
  14. class Token:
  15. def __init__(self, tid, value):
  16. self.tid = tid
  17. self.value = value
  18. self.lineno = 0
  19. self.colno = 0
  20. class Statement:
  21. def __init__(self, name, args):
  22. self.name = name
  23. self.args = args
  24. class Lexer:
  25. def __init__(self):
  26. self.token_specification = [
  27. # Need to be sorted longest to shortest.
  28. ('ignore', re.compile(r'[ \t]')),
  29. ('string', re.compile(r'"([^\\]|(\\.))*?"', re.M)),
  30. ('varexp', re.compile(r'\${[-_0-9a-z/A-Z.]+}')),
  31. ('id', re.compile('''[,-><${}=+_0-9a-z/A-Z|@.*]+''')),
  32. ('eol', re.compile(r'\n')),
  33. ('comment', re.compile(r'#.*')),
  34. ('lparen', re.compile(r'\(')),
  35. ('rparen', re.compile(r'\)')),
  36. ]
  37. def lex(self, code):
  38. lineno = 1
  39. line_start = 0
  40. loc = 0
  41. col = 0
  42. while loc < len(code):
  43. matched = False
  44. for (tid, reg) in self.token_specification:
  45. mo = reg.match(code, loc)
  46. if mo:
  47. col = mo.start() - line_start
  48. matched = True
  49. loc = mo.end()
  50. match_text = mo.group()
  51. if tid == 'ignore':
  52. continue
  53. if tid == 'comment':
  54. yield(Token('comment', match_text))
  55. elif tid == 'lparen':
  56. yield(Token('lparen', '('))
  57. elif tid == 'rparen':
  58. yield(Token('rparen', ')'))
  59. elif tid == 'string':
  60. yield(Token('string', match_text[1:-1]))
  61. elif tid == 'id':
  62. yield(Token('id', match_text))
  63. elif tid == 'eol':
  64. # yield('eol')
  65. lineno += 1
  66. col = 1
  67. line_start = mo.end()
  68. pass
  69. elif tid == 'varexp':
  70. yield(Token('varexp', match_text[2:-1]))
  71. else:
  72. raise RuntimeError('Wharrgarbl')
  73. break
  74. if not matched:
  75. raise RuntimeError('Lexer got confused line %d column %d' % (lineno, col))
  76. class Parser:
  77. def __init__(self, code):
  78. self.stream = Lexer().lex(code)
  79. self.getsym()
  80. def getsym(self):
  81. try:
  82. self.current = next(self.stream)
  83. except StopIteration:
  84. self.current = Token('eof', '')
  85. def accept(self, s):
  86. if self.current.tid == s:
  87. self.getsym()
  88. return True
  89. return False
  90. def expect(self, s):
  91. if self.accept(s):
  92. return True
  93. raise RuntimeError('Expecting %s got %s.' % (s, self.current.tid), self.current.lineno, self.current.colno)
  94. def statement(self):
  95. cur = self.current
  96. if self.accept('comment'):
  97. return Statement('_', [cur.value])
  98. self.accept('id')
  99. self.expect('lparen')
  100. args = self.arguments()
  101. self.expect('rparen')
  102. return Statement(cur.value, args)
  103. def arguments(self):
  104. args = []
  105. if self.accept('lparen'):
  106. args.append(self.arguments())
  107. self.expect('rparen')
  108. arg = self.current
  109. if self.accept('string') \
  110. or self.accept('varexp') \
  111. or self.accept('id'):
  112. args.append(arg)
  113. rest = self.arguments()
  114. args += rest
  115. return args
  116. def parse(self):
  117. while not self.accept('eof'):
  118. yield(self.statement())
  119. class Converter:
  120. ignored_funcs = {'cmake_minimum_required': True,
  121. 'enable_testing': True,
  122. 'include': True}
  123. def __init__(self, cmake_root):
  124. self.cmake_root = cmake_root
  125. self.indent_unit = ' '
  126. self.indent_level = 0
  127. self.options = []
  128. def convert_args(self, args, as_array=True):
  129. res = []
  130. if as_array:
  131. start = '['
  132. end = ']'
  133. else:
  134. start = ''
  135. end = ''
  136. for i in args:
  137. if i.tid == 'id':
  138. res.append("'%s'" % i.value)
  139. elif i.tid == 'varexp':
  140. res.append('%s' % i.value)
  141. elif i.tid == 'string':
  142. res.append("'%s'" % i.value)
  143. else:
  144. print(i)
  145. raise RuntimeError('Unknown arg type.')
  146. if len(res) > 1:
  147. return start + ', '.join(res) + end
  148. if len(res) == 1:
  149. return res[0]
  150. return ''
  151. def write_entry(self, outfile, t):
  152. if t.name in Converter.ignored_funcs:
  153. return
  154. preincrement = 0
  155. postincrement = 0
  156. if t.name == '_':
  157. line = t.args[0]
  158. elif t.name == 'add_subdirectory':
  159. line = "subdir('" + t.args[0].value + "')"
  160. elif t.name == 'pkg_search_module' or t.name == 'pkg_search_modules':
  161. varname = t.args[0].value.lower()
  162. mods = ["dependency('%s')" % i.value for i in t.args[1:]]
  163. if len(mods) == 1:
  164. line = '%s = %s' % (varname, mods[0])
  165. else:
  166. line = '%s = [%s]' % (varname, ', '.join(["'%s'" % i for i in mods]))
  167. elif t.name == 'find_package':
  168. line = "%s_dep = dependency('%s')" % (t.args[0].value, t.args[0].value)
  169. elif t.name == 'find_library':
  170. line = "%s = find_library('%s')" % (t.args[0].value.lower(), t.args[0].value)
  171. elif t.name == 'add_executable':
  172. line = '%s_exe = executable(%s)' % (t.args[0].value, self.convert_args(t.args, False))
  173. elif t.name == 'add_library':
  174. if t.args[1].value == 'SHARED':
  175. libcmd = 'shared_library'
  176. args = [t.args[0]] + t.args[2:]
  177. elif t.args[1].value == 'STATIC':
  178. libcmd = 'static_library'
  179. args = [t.args[0]] + t.args[2:]
  180. else:
  181. libcmd = 'static_library'
  182. args = t.args
  183. line = '%s_lib = %s(%s)' % (t.args[0].value, libcmd, self.convert_args(args, False))
  184. elif t.name == 'add_test':
  185. line = 'test(%s)' % self.convert_args(t.args, False)
  186. elif t.name == 'option':
  187. optname = t.args[0].value
  188. description = t.args[1].value
  189. if len(t.args) > 2:
  190. default = t.args[2].value
  191. else:
  192. default = None
  193. self.options.append((optname, description, default))
  194. return
  195. elif t.name == 'project':
  196. pname = t.args[0].value
  197. args = [pname]
  198. for l in t.args[1:]:
  199. l = l.value.lower()
  200. if l == 'cxx':
  201. l = 'cpp'
  202. args.append(l)
  203. args = ["'%s'" % i for i in args]
  204. line = 'project(' + ', '.join(args) + ')'
  205. elif t.name == 'set':
  206. varname = t.args[0].value.lower()
  207. line = '%s = %s\n' % (varname, self.convert_args(t.args[1:]))
  208. elif t.name == 'if':
  209. postincrement = 1
  210. line = 'if %s' % self.convert_args(t.args, False)
  211. elif t.name == 'elseif':
  212. preincrement = -1
  213. postincrement = 1
  214. line = 'elif %s' % self.convert_args(t.args, False)
  215. elif t.name == 'else':
  216. preincrement = -1
  217. postincrement = 1
  218. line = 'else'
  219. elif t.name == 'endif':
  220. preincrement = -1
  221. line = 'endif'
  222. else:
  223. line = '''# %s(%s)''' % (t.name, self.convert_args(t.args))
  224. self.indent_level += preincrement
  225. indent = self.indent_level * self.indent_unit
  226. outfile.write(indent)
  227. outfile.write(line)
  228. if not(line.endswith('\n')):
  229. outfile.write('\n')
  230. self.indent_level += postincrement
  231. def convert(self, subdir=''):
  232. if subdir == '':
  233. subdir = self.cmake_root
  234. cfile = os.path.join(subdir, 'CMakeLists.txt')
  235. try:
  236. with open(cfile) as f:
  237. cmakecode = f.read()
  238. except FileNotFoundError:
  239. print('\nWarning: No CMakeLists.txt in', subdir, '\n')
  240. return
  241. p = Parser(cmakecode)
  242. with open(os.path.join(subdir, 'meson.build'), 'w') as outfile:
  243. for t in p.parse():
  244. if t.name == 'add_subdirectory':
  245. # print('\nRecursing to subdir',
  246. # os.path.join(self.cmake_root, t.args[0].value),
  247. # '\n')
  248. self.convert(os.path.join(subdir, t.args[0].value))
  249. # print('\nReturning to', self.cmake_root, '\n')
  250. self.write_entry(outfile, t)
  251. if subdir == self.cmake_root and len(self.options) > 0:
  252. self.write_options()
  253. def write_options(self):
  254. filename = os.path.join(self.cmake_root, 'meson_options.txt')
  255. with open(filename, 'w') as optfile:
  256. for o in self.options:
  257. (optname, description, default) = o
  258. if default is None:
  259. typestr = ''
  260. defaultstr = ''
  261. else:
  262. if default == 'OFF':
  263. typestr = ' type : \'boolean\','
  264. default = 'false'
  265. elif default == 'ON':
  266. default = 'true'
  267. typestr = ' type : \'boolean\','
  268. else:
  269. typestr = ' type : \'string\','
  270. defaultstr = ' value : %s,' % default
  271. line = "option(%r,%s%s description : '%s')\n" % (optname,
  272. typestr,
  273. defaultstr,
  274. description)
  275. optfile.write(line)
  276. if __name__ == '__main__':
  277. if len(sys.argv) != 2:
  278. print(sys.argv[0], '<CMake project root>')
  279. sys.exit(1)
  280. c = Converter(sys.argv[1])
  281. c.convert()