123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- #!/usr/bin/env python3
- # Copyright 2014 Jussi Pakkanen
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- # http://www.apache.org/licenses/LICENSE-2.0
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- import typing as T
- from pathlib import Path
- import sys
- import re
- import argparse
- class Token:
- def __init__(self, tid: str, value: str):
- self.tid = tid
- self.value = value
- self.lineno = 0
- self.colno = 0
- class Statement:
- def __init__(self, name: str, args: list):
- self.name = name.lower()
- self.args = args
- class Lexer:
- def __init__(self) -> None:
- self.token_specification = [
- # Need to be sorted longest to shortest.
- ('ignore', re.compile(r'[ \t]')),
- ('string', re.compile(r'"([^\\]|(\\.))*?"', re.M)),
- ('varexp', re.compile(r'\${[-_0-9a-z/A-Z.]+}')),
- ('id', re.compile('''[,-><${}=+_0-9a-z/A-Z|@.*]+''')),
- ('eol', re.compile(r'\n')),
- ('comment', re.compile(r'#.*')),
- ('lparen', re.compile(r'\(')),
- ('rparen', re.compile(r'\)')),
- ]
- def lex(self, code: str) -> T.Iterator[Token]:
- lineno = 1
- line_start = 0
- loc = 0
- col = 0
- while loc < len(code):
- matched = False
- for (tid, reg) in self.token_specification:
- mo = reg.match(code, loc)
- if mo:
- col = mo.start() - line_start
- matched = True
- loc = mo.end()
- match_text = mo.group()
- if tid == 'ignore':
- continue
- if tid == 'comment':
- yield(Token('comment', match_text))
- elif tid == 'lparen':
- yield(Token('lparen', '('))
- elif tid == 'rparen':
- yield(Token('rparen', ')'))
- elif tid == 'string':
- yield(Token('string', match_text[1:-1]))
- elif tid == 'id':
- yield(Token('id', match_text))
- elif tid == 'eol':
- # yield('eol')
- lineno += 1
- col = 1
- line_start = mo.end()
- elif tid == 'varexp':
- yield(Token('varexp', match_text[2:-1]))
- else:
- raise ValueError(f'lex: unknown element {tid}')
- break
- if not matched:
- raise ValueError('Lexer got confused line %d column %d' % (lineno, col))
- class Parser:
- def __init__(self, code: str) -> None:
- self.stream = Lexer().lex(code)
- self.getsym()
- def getsym(self) -> None:
- try:
- self.current = next(self.stream)
- except StopIteration:
- self.current = Token('eof', '')
- def accept(self, s: str) -> bool:
- if self.current.tid == s:
- self.getsym()
- return True
- return False
- def expect(self, s: str) -> bool:
- if self.accept(s):
- return True
- raise ValueError(f'Expecting {s} got {self.current.tid}.', self.current.lineno, self.current.colno)
- def statement(self) -> Statement:
- cur = self.current
- if self.accept('comment'):
- return Statement('_', [cur.value])
- self.accept('id')
- self.expect('lparen')
- args = self.arguments()
- self.expect('rparen')
- return Statement(cur.value, args)
- def arguments(self) -> T.List[T.Union[Token, T.Any]]:
- args = [] # type: T.List[T.Union[Token, T.Any]]
- if self.accept('lparen'):
- args.append(self.arguments())
- self.expect('rparen')
- arg = self.current
- if self.accept('comment'):
- rest = self.arguments()
- args += rest
- elif self.accept('string') \
- or self.accept('varexp') \
- or self.accept('id'):
- args.append(arg)
- rest = self.arguments()
- args += rest
- return args
- def parse(self) -> T.Iterator[Statement]:
- while not self.accept('eof'):
- yield(self.statement())
- def token_or_group(arg: T.Union[Token, T.List[Token]]) -> str:
- if isinstance(arg, Token):
- return ' ' + arg.value
- elif isinstance(arg, list):
- line = ' ('
- for a in arg:
- line += ' ' + token_or_group(a)
- line += ' )'
- return line
- raise RuntimeError('Conversion error in token_or_group')
- class Converter:
- ignored_funcs = {'cmake_minimum_required': True,
- 'enable_testing': True,
- 'include': True}
- def __init__(self, cmake_root: str):
- self.cmake_root = Path(cmake_root).expanduser()
- self.indent_unit = ' '
- self.indent_level = 0
- self.options = [] # type: T.List[tuple]
- def convert_args(self, args: T.List[Token], as_array: bool = True) -> str:
- res = []
- if as_array:
- start = '['
- end = ']'
- else:
- start = ''
- end = ''
- for i in args:
- if i.tid == 'id':
- res.append("'%s'" % i.value)
- elif i.tid == 'varexp':
- res.append('%s' % i.value.lower())
- elif i.tid == 'string':
- res.append("'%s'" % i.value)
- else:
- raise ValueError(f'Unknown arg type {i.tid}')
- if len(res) > 1:
- return start + ', '.join(res) + end
- if len(res) == 1:
- return res[0]
- return ''
- def write_entry(self, outfile: T.TextIO, t: Statement) -> None:
- if t.name in Converter.ignored_funcs:
- return
- preincrement = 0
- postincrement = 0
- if t.name == '_':
- line = t.args[0]
- elif t.name == 'add_subdirectory':
- line = "subdir('" + t.args[0].value + "')"
- elif t.name == 'pkg_search_module' or t.name == 'pkg_search_modules':
- varname = t.args[0].value.lower()
- mods = ["dependency('%s')" % i.value for i in t.args[1:]]
- if len(mods) == 1:
- line = '{} = {}'.format(varname, mods[0])
- else:
- line = '{} = [{}]'.format(varname, ', '.join(["'%s'" % i for i in mods]))
- elif t.name == 'find_package':
- line = "{}_dep = dependency('{}')".format(t.args[0].value, t.args[0].value)
- elif t.name == 'find_library':
- line = "{} = find_library('{}')".format(t.args[0].value.lower(), t.args[0].value)
- elif t.name == 'add_executable':
- line = '{}_exe = executable({})'.format(t.args[0].value, self.convert_args(t.args, False))
- elif t.name == 'add_library':
- if t.args[1].value == 'SHARED':
- libcmd = 'shared_library'
- args = [t.args[0]] + t.args[2:]
- elif t.args[1].value == 'STATIC':
- libcmd = 'static_library'
- args = [t.args[0]] + t.args[2:]
- else:
- libcmd = 'library'
- args = t.args
- line = '{}_lib = {}({})'.format(t.args[0].value, libcmd, self.convert_args(args, False))
- elif t.name == 'add_test':
- line = 'test(%s)' % self.convert_args(t.args, False)
- elif t.name == 'option':
- optname = t.args[0].value
- description = t.args[1].value
- if len(t.args) > 2:
- default = t.args[2].value
- else:
- default = None
- self.options.append((optname, description, default))
- return
- elif t.name == 'project':
- pname = t.args[0].value
- args = [pname]
- for l in t.args[1:]:
- l = l.value.lower()
- if l == 'cxx':
- l = 'cpp'
- args.append(l)
- args = ["'%s'" % i for i in args]
- line = 'project(' + ', '.join(args) + ", default_options : ['default_library=static'])"
- elif t.name == 'set':
- varname = t.args[0].value.lower()
- line = '{} = {}\n'.format(varname, self.convert_args(t.args[1:]))
- elif t.name == 'if':
- postincrement = 1
- try:
- line = 'if %s' % self.convert_args(t.args, False)
- except AttributeError: # complex if statements
- line = t.name
- for arg in t.args:
- line += token_or_group(arg)
- elif t.name == 'elseif':
- preincrement = -1
- postincrement = 1
- try:
- line = 'elif %s' % self.convert_args(t.args, False)
- except AttributeError: # complex if statements
- line = t.name
- for arg in t.args:
- line += token_or_group(arg)
- elif t.name == 'else':
- preincrement = -1
- postincrement = 1
- line = 'else'
- elif t.name == 'endif':
- preincrement = -1
- line = 'endif'
- else:
- line = '''# {}({})'''.format(t.name, self.convert_args(t.args))
- self.indent_level += preincrement
- indent = self.indent_level * self.indent_unit
- outfile.write(indent)
- outfile.write(line)
- if not(line.endswith('\n')):
- outfile.write('\n')
- self.indent_level += postincrement
- def convert(self, subdir: Path = None) -> None:
- if not subdir:
- subdir = self.cmake_root
- cfile = Path(subdir).expanduser() / 'CMakeLists.txt'
- try:
- with cfile.open(encoding='utf-8') as f:
- cmakecode = f.read()
- except FileNotFoundError:
- print('\nWarning: No CMakeLists.txt in', subdir, '\n', file=sys.stderr)
- return
- p = Parser(cmakecode)
- with (subdir / 'meson.build').open('w', encoding='utf-8') as outfile:
- for t in p.parse():
- if t.name == 'add_subdirectory':
- # print('\nRecursing to subdir',
- # self.cmake_root / t.args[0].value,
- # '\n')
- self.convert(subdir / t.args[0].value)
- # print('\nReturning to', self.cmake_root, '\n')
- self.write_entry(outfile, t)
- if subdir == self.cmake_root and len(self.options) > 0:
- self.write_options()
- def write_options(self) -> None:
- filename = self.cmake_root / 'meson_options.txt'
- with filename.open('w', encoding='utf-8') as optfile:
- for o in self.options:
- (optname, description, default) = o
- if default is None:
- typestr = ''
- defaultstr = ''
- else:
- if default == 'OFF':
- typestr = ' type : \'boolean\','
- default = 'false'
- elif default == 'ON':
- default = 'true'
- typestr = ' type : \'boolean\','
- else:
- typestr = ' type : \'string\','
- defaultstr = ' value : %s,' % default
- line = "option({!r},{}{} description : '{}')\n".format(optname,
- typestr,
- defaultstr,
- description)
- optfile.write(line)
- if __name__ == '__main__':
- p = argparse.ArgumentParser(description='Convert CMakeLists.txt to meson.build and meson_options.txt')
- p.add_argument('cmake_root', help='CMake project root (where top-level CMakeLists.txt is)')
- P = p.parse_args()
- Converter(P.cmake_root).convert()
|