cmake2meson.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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. elif tid == 'varexp':
  69. yield(Token('varexp', match_text[2:-1]))
  70. else:
  71. raise RuntimeError('Wharrgarbl')
  72. break
  73. if not matched:
  74. raise RuntimeError('Lexer got confused line %d column %d' % (lineno, col))
  75. class Parser:
  76. def __init__(self, code):
  77. self.stream = Lexer().lex(code)
  78. self.getsym()
  79. def getsym(self):
  80. try:
  81. self.current = next(self.stream)
  82. except StopIteration:
  83. self.current = Token('eof', '')
  84. def accept(self, s):
  85. if self.current.tid == s:
  86. self.getsym()
  87. return True
  88. return False
  89. def expect(self, s):
  90. if self.accept(s):
  91. return True
  92. raise RuntimeError('Expecting %s got %s.' % (s, self.current.tid), self.current.lineno, self.current.colno)
  93. def statement(self):
  94. cur = self.current
  95. if self.accept('comment'):
  96. return Statement('_', [cur.value])
  97. self.accept('id')
  98. self.expect('lparen')
  99. args = self.arguments()
  100. self.expect('rparen')
  101. return Statement(cur.value, args)
  102. def arguments(self):
  103. args = []
  104. if self.accept('lparen'):
  105. args.append(self.arguments())
  106. self.expect('rparen')
  107. arg = self.current
  108. if self.accept('string') \
  109. or self.accept('varexp') \
  110. or self.accept('id'):
  111. args.append(arg)
  112. rest = self.arguments()
  113. args += rest
  114. return args
  115. def parse(self):
  116. while not self.accept('eof'):
  117. yield(self.statement())
  118. class Converter:
  119. ignored_funcs = {'cmake_minimum_required': True,
  120. 'enable_testing': True,
  121. 'include': True}
  122. def __init__(self, cmake_root):
  123. self.cmake_root = cmake_root
  124. self.indent_unit = ' '
  125. self.indent_level = 0
  126. self.options = []
  127. def convert_args(self, args, as_array=True):
  128. res = []
  129. if as_array:
  130. start = '['
  131. end = ']'
  132. else:
  133. start = ''
  134. end = ''
  135. for i in args:
  136. if i.tid == 'id':
  137. res.append("'%s'" % i.value)
  138. elif i.tid == 'varexp':
  139. res.append('%s' % i.value)
  140. elif i.tid == 'string':
  141. res.append("'%s'" % i.value)
  142. else:
  143. print(i)
  144. raise RuntimeError('Unknown arg type.')
  145. if len(res) > 1:
  146. return start + ', '.join(res) + end
  147. if len(res) == 1:
  148. return res[0]
  149. return ''
  150. def write_entry(self, outfile, t):
  151. if t.name in Converter.ignored_funcs:
  152. return
  153. preincrement = 0
  154. postincrement = 0
  155. if t.name == '_':
  156. line = t.args[0]
  157. elif t.name == 'add_subdirectory':
  158. line = "subdir('" + t.args[0].value + "')"
  159. elif t.name == 'pkg_search_module' or t.name == 'pkg_search_modules':
  160. varname = t.args[0].value.lower()
  161. mods = ["dependency('%s')" % i.value for i in t.args[1:]]
  162. if len(mods) == 1:
  163. line = '%s = %s' % (varname, mods[0])
  164. else:
  165. line = '%s = [%s]' % (varname, ', '.join(["'%s'" % i for i in mods]))
  166. elif t.name == 'find_package':
  167. line = "%s_dep = dependency('%s')" % (t.args[0].value, t.args[0].value)
  168. elif t.name == 'find_library':
  169. line = "%s = find_library('%s')" % (t.args[0].value.lower(), t.args[0].value)
  170. elif t.name == 'add_executable':
  171. line = '%s_exe = executable(%s)' % (t.args[0].value, self.convert_args(t.args, False))
  172. elif t.name == 'add_library':
  173. if t.args[1].value == 'SHARED':
  174. libcmd = 'shared_library'
  175. args = [t.args[0]] + t.args[2:]
  176. elif t.args[1].value == 'STATIC':
  177. libcmd = 'static_library'
  178. args = [t.args[0]] + t.args[2:]
  179. else:
  180. libcmd = 'static_library'
  181. args = t.args
  182. line = '%s_lib = %s(%s)' % (t.args[0].value, libcmd, self.convert_args(args, False))
  183. elif t.name == 'add_test':
  184. line = 'test(%s)' % self.convert_args(t.args, False)
  185. elif t.name == 'option':
  186. optname = t.args[0].value
  187. description = t.args[1].value
  188. if len(t.args) > 2:
  189. default = t.args[2].value
  190. else:
  191. default = None
  192. self.options.append((optname, description, default))
  193. return
  194. elif t.name == 'project':
  195. pname = t.args[0].value
  196. args = [pname]
  197. for l in t.args[1:]:
  198. l = l.value.lower()
  199. if l == 'cxx':
  200. l = 'cpp'
  201. args.append(l)
  202. args = ["'%s'" % i for i in args]
  203. line = 'project(' + ', '.join(args) + ')'
  204. elif t.name == 'set':
  205. varname = t.args[0].value.lower()
  206. line = '%s = %s\n' % (varname, self.convert_args(t.args[1:]))
  207. elif t.name == 'if':
  208. postincrement = 1
  209. line = 'if %s' % self.convert_args(t.args, False)
  210. elif t.name == 'elseif':
  211. preincrement = -1
  212. postincrement = 1
  213. line = 'elif %s' % self.convert_args(t.args, False)
  214. elif t.name == 'else':
  215. preincrement = -1
  216. postincrement = 1
  217. line = 'else'
  218. elif t.name == 'endif':
  219. preincrement = -1
  220. line = 'endif'
  221. else:
  222. line = '''# %s(%s)''' % (t.name, self.convert_args(t.args))
  223. self.indent_level += preincrement
  224. indent = self.indent_level * self.indent_unit
  225. outfile.write(indent)
  226. outfile.write(line)
  227. if not(line.endswith('\n')):
  228. outfile.write('\n')
  229. self.indent_level += postincrement
  230. def convert(self, subdir=''):
  231. if subdir == '':
  232. subdir = self.cmake_root
  233. cfile = os.path.join(subdir, 'CMakeLists.txt')
  234. try:
  235. with open(cfile) as f:
  236. cmakecode = f.read()
  237. except FileNotFoundError:
  238. print('\nWarning: No CMakeLists.txt in', subdir, '\n')
  239. return
  240. p = Parser(cmakecode)
  241. with open(os.path.join(subdir, 'meson.build'), 'w') as outfile:
  242. for t in p.parse():
  243. if t.name == 'add_subdirectory':
  244. # print('\nRecursing to subdir',
  245. # os.path.join(self.cmake_root, t.args[0].value),
  246. # '\n')
  247. self.convert(os.path.join(subdir, t.args[0].value))
  248. # print('\nReturning to', self.cmake_root, '\n')
  249. self.write_entry(outfile, t)
  250. if subdir == self.cmake_root and len(self.options) > 0:
  251. self.write_options()
  252. def write_options(self):
  253. filename = os.path.join(self.cmake_root, 'meson_options.txt')
  254. with open(filename, 'w') as optfile:
  255. for o in self.options:
  256. (optname, description, default) = o
  257. if default is None:
  258. typestr = ''
  259. defaultstr = ''
  260. else:
  261. if default == 'OFF':
  262. typestr = ' type : \'boolean\','
  263. default = 'false'
  264. elif default == 'ON':
  265. default = 'true'
  266. typestr = ' type : \'boolean\','
  267. else:
  268. typestr = ' type : \'string\','
  269. defaultstr = ' value : %s,' % default
  270. line = "option(%r,%s%s description : '%s')\n" % (optname,
  271. typestr,
  272. defaultstr,
  273. description)
  274. optfile.write(line)
  275. if __name__ == '__main__':
  276. if len(sys.argv) != 2:
  277. print(sys.argv[0], '<CMake project root>')
  278. sys.exit(1)
  279. c = Converter(sys.argv[1])
  280. c.convert()