cmake2meson.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #!/usr/bin/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') or self.accept('varexp') or\
  110. 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. cmakecode = open(cfile).read()
  236. except FileNotFoundError:
  237. print('\nWarning: No CMakeLists.txt in', subdir, '\n')
  238. return
  239. p = Parser(cmakecode)
  240. outfile = open(os.path.join(subdir, 'meson.build'), 'w')
  241. for t in p.parse():
  242. if t.name == 'add_subdirectory':
  243. #print('\nRecursing to subdir', os.path.join(self.cmake_root, t.args[0].value), '\n')
  244. self.convert(os.path.join(subdir, t.args[0].value))
  245. #print('\nReturning to', self.cmake_root, '\n')
  246. self.write_entry(outfile, t)
  247. if subdir == self.cmake_root and len(self.options) > 0:
  248. self.write_options()
  249. def write_options(self):
  250. optfile = open(os.path.join(self.cmake_root, 'meson_options.txt'), 'w')
  251. for o in self.options:
  252. (optname, description, default) = o
  253. if default is None:
  254. defaultstr = ''
  255. else:
  256. if default == 'OFF':
  257. typestr = ' type : boolean,'
  258. default = 'false'
  259. elif default == 'ON':
  260. default = 'true'
  261. typestr = ' type : boolean,'
  262. else:
  263. typestr = ' type : string,'
  264. defaultstr = ' value : %s,' % default
  265. line = "option(%s,%s%s description : '%s')\n" % (optname, typestr, defaultstr, description)
  266. optfile.write(line)
  267. if __name__ == '__main__':
  268. if len(sys.argv) != 2:
  269. print(sys.argv[0], '<CMake project root>')
  270. sys.exit(1)
  271. c = Converter(sys.argv[1])
  272. c.convert()