cmake: First working version

This commit is contained in:
Daniel Mensinger 2019-02-23 11:28:04 +01:00
parent e55236bde4
commit be6a9191e1
No known key found for this signature in database
GPG Key ID: 54DD94C131E277D4
2 changed files with 135 additions and 25 deletions

View File

@ -20,7 +20,7 @@ from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCo
from .. import mlog from .. import mlog
from ..build import Build from ..build import Build
from ..environment import Environment from ..environment import Environment
from ..mparser import Token, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, StringNode, IdNode from ..mparser import Token, BaseNode, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, AssignmentNode, BooleanNode, StringNode, IdNode
from ..backend.backends import Backend from ..backend.backends import Backend
from ..dependencies.base import CMakeDependency, ExternalProgram from ..dependencies.base import CMakeDependency, ExternalProgram
from subprocess import Popen, PIPE, STDOUT from subprocess import Popen, PIPE, STDOUT
@ -45,6 +45,13 @@ CMAKE_LANGUAGE_MAP = {
'swift': 'Swift', 'swift': 'Swift',
} }
CMAKE_TGT_TYPE_MAP = {
'STATIC_LIBRARY': 'static_library',
'MODULE_LIBRARY': 'shared_module',
'SHARED_LIBRARY': 'shared_library',
'EXECUTABLE': 'executable',
}
class ConverterTarget: class ConverterTarget:
lang_cmake_to_meson = {val.lower(): key for key, val in CMAKE_LANGUAGE_MAP.items()} lang_cmake_to_meson = {val.lower(): key for key, val in CMAKE_LANGUAGE_MAP.items()}
@ -68,6 +75,7 @@ class ConverterTarget:
self.includes = [] self.includes = []
self.link_with = [] self.link_with = []
self.compile_opts = {} self.compile_opts = {}
self.pie = False
# Project default override options (c_std, cpp_std, etc.) # Project default override options (c_std, cpp_std, etc.)
self.override_options = [] self.override_options = []
@ -99,7 +107,7 @@ class ConverterTarget:
std_regex = re.compile(r'([-]{1,2}std=|/std:v?)(.*)') std_regex = re.compile(r'([-]{1,2}std=|/std:v?)(.*)')
def postprocess(self, output_target_map) -> None: def postprocess(self, output_target_map: dict, root_src_dir: str) -> None:
# Detect setting the C and C++ standard # Detect setting the C and C++ standard
for i in ['c', 'cpp']: for i in ['c', 'cpp']:
if not i in self.compile_opts: if not i in self.compile_opts:
@ -110,6 +118,8 @@ class ConverterTarget:
m = ConverterTarget.std_regex.match(j) m = ConverterTarget.std_regex.match(j)
if m: if m:
self.override_options += ['{}_std={}'.format(i, m.group(2))] self.override_options += ['{}_std={}'.format(i, m.group(2))]
elif j in ['-fPIC', '-fpic', '-fPIE', '-fpie']:
self.pie = True
else: else:
temp += [j] temp += [j]
@ -130,14 +140,23 @@ class ConverterTarget:
# Make paths relative # Make paths relative
def rel_path(x: str) -> str: def rel_path(x: str) -> str:
if os.path.isabs(x) and os.path.commonpath([x, self.src_dir]) == self.src_dir: if not os.path.isabs(x):
return os.path.relpath(x, self.src_dir) x = os.path.normpath(os.path.join(self.src_dir, x))
if os.path.isabs(x) and os.path.commonpath([x, root_src_dir]) == root_src_dir:
return os.path.relpath(x, root_src_dir)
return x return x
self.includes = [rel_path(x) for x in self.includes] self.includes = [rel_path(x) for x in self.includes]
self.sources = [rel_path(x) for x in self.sources] self.sources = [rel_path(x) for x in self.sources]
self.generated = [rel_path(x) for x in self.generated] self.generated = [rel_path(x) for x in self.generated]
# Make sure '.' is always in the include directories
if '.' not in self.includes:
self.includes += ['.']
def meson_func(self) -> str:
return CMAKE_TGT_TYPE_MAP.get(self.type.upper())
def log(self) -> None: def log(self) -> None:
mlog.log('Target', mlog.bold(self.name)) mlog.log('Target', mlog.bold(self.name))
mlog.log(' -- full_name: ', mlog.bold(self.full_name)) mlog.log(' -- full_name: ', mlog.bold(self.full_name))
@ -151,6 +170,7 @@ class ConverterTarget:
mlog.log(' -- includes: ', mlog.bold(str(self.includes))) mlog.log(' -- includes: ', mlog.bold(str(self.includes)))
mlog.log(' -- sources: ', mlog.bold(str(self.sources))) mlog.log(' -- sources: ', mlog.bold(str(self.sources)))
mlog.log(' -- generated: ', mlog.bold(str(self.generated))) mlog.log(' -- generated: ', mlog.bold(str(self.generated)))
mlog.log(' -- pie: ', mlog.bold('true' if self.pie else 'false'))
mlog.log(' -- override_opts: ', mlog.bold(str(self.override_options))) mlog.log(' -- override_opts: ', mlog.bold(str(self.override_options)))
mlog.log(' -- options:') mlog.log(' -- options:')
for key, val in self.compile_opts.items(): for key, val in self.compile_opts.items():
@ -174,6 +194,7 @@ class CMakeInterpreter:
# Analysed data # Analysed data
self.project_name = '' self.project_name = ''
self.languages = [] self.languages = []
self.targets = []
def configure(self) -> None: def configure(self) -> None:
# Find CMake # Find CMake
@ -258,23 +279,23 @@ class CMakeInterpreter:
# Clear analyser data # Clear analyser data
self.project_name = '' self.project_name = ''
self.languages = [] self.languages = []
self.targets = []
# Find all targets # Find all targets
targets = []
for i in self.codemodel.configs: for i in self.codemodel.configs:
for j in i.projects: for j in i.projects:
if not self.project_name: if not self.project_name:
self.project_name = j.name self.project_name = j.name
for k in j.targets: for k in j.targets:
targets += [ConverterTarget(k)] self.targets += [ConverterTarget(k)]
output_target_map = {x.full_name: x for x in targets} output_target_map = {x.full_name: x for x in self.targets}
for i in targets: for i in self.targets:
i.postprocess(output_target_map) i.postprocess(output_target_map, self.src_dir)
self.languages += [x for x in i.languages if x not in self.languages] self.languages += [x for x in i.languages if x not in self.languages]
mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(targets))), 'build targets.') mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets))), 'build targets.')
def pretend_to_be_meson(self) -> CodeBlockNode: def pretend_to_be_meson(self) -> CodeBlockNode:
if not self.project_name: if not self.project_name:
@ -289,24 +310,100 @@ class CMakeInterpreter:
def id(value: str) -> IdNode: def id(value: str) -> IdNode:
return IdNode(token(val=value)) return IdNode(token(val=value))
def array(elements: list) -> ArrayNode: def nodeify(value):
if isinstance(value, str):
return string(value)
elif isinstance(value, bool):
return BooleanNode(token(), value)
elif isinstance(value, list):
return array(value)
return value
def array(elements) -> ArrayNode:
args = ArgumentNode(token()) args = ArgumentNode(token())
for i in elements: if not isinstance(elements, list):
if isinstance(i, str): elements = [args]
i = string(i) args.arguments += [nodeify(x) for x in elements]
args.arguments += [i]
return ArrayNode(args, 0, 0) return ArrayNode(args, 0, 0)
def function(name: str, args=[]) -> FunctionNode: def function(name: str, args=[], kwargs={}) -> FunctionNode:
args_n = ArgumentNode(token()) args_n = ArgumentNode(token())
for i in args: if not isinstance(args, list):
if isinstance(i, str): args = [args]
i = string(i) args_n.arguments = [nodeify(x) for x in args]
args_n.arguments += [i] args_n.kwargs = {k: nodeify(v) for k, v in kwargs.items()}
func_n = FunctionNode(self.subdir, 0, 0, name, args_n) func_n = FunctionNode(self.subdir, 0, 0, name, args_n)
return func_n return func_n
def assign(var_name: str, value: BaseNode) -> AssignmentNode:
return AssignmentNode(self.subdir, 0, 0, var_name, value)
# Generate the root code block and the project function call
root_cb = CodeBlockNode(token()) root_cb = CodeBlockNode(token())
root_cb.lines += [function('project', [self.project_name, array(self.languages)])] root_cb.lines += [function('project', [self.project_name] + self.languages)]
processed = {}
def process_target(tgt: ConverterTarget):
# First handle inter dependencies
link_with = []
for i in tgt.link_with:
assert(isinstance(i, ConverterTarget))
if i.name not in processed:
process_target(i)
link_with += [id(processed[i.name]['tgt'])]
# Determine the meson function to use for the build target
tgt_func = tgt.meson_func()
if not tgt_func:
raise CMakeException('Unknown target type "{}"'.format(tgt.type))
# Determine the variable names
base_name = tgt.name
inc_var = '{}_inc'.format(base_name)
src_var = '{}_src'.format(base_name)
dep_var = '{}_dep'.format(base_name)
tgt_var = base_name
# Generate target kwargs
tgt_kwargs = {
'link_args': tgt.link_flags + tgt.link_libraries,
'link_with': link_with,
'include_directories': id(inc_var),
'install': tgt.install,
'install_dir': tgt.install_dir,
'override_options': tgt.override_options,
}
# Handle compiler args
for key, val in tgt.compile_opts.items():
tgt_kwargs['{}_args'.format(key)] = val
# Handle -fPCI, etc
if tgt_func == 'executable':
tgt_kwargs['pie'] = tgt.pie
elif tgt_func == 'static_library':
tgt_kwargs['pic'] = tgt.pie
# declare_dependency kwargs
dep_kwargs = {
'link_args': tgt.link_flags + tgt.link_libraries,
'link_with': id(tgt_var),
'include_directories': id(inc_var),
}
# Generate the function nodes
inc_node = assign(inc_var, function('include_directories', tgt.includes))
src_node = assign(src_var, function('files', tgt.sources + tgt.generated))
tgt_node = assign(tgt_var, function(tgt_func, [base_name, id(src_var)], tgt_kwargs))
dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs))
# Add the nodes to the ast
root_cb.lines += [inc_node, src_node, tgt_node, dep_node]
processed[tgt.name] = {'inc': inc_var, 'src': src_var, 'dep': dep_var, 'tgt': tgt_var}
# Now generate the target function calls
for i in self.targets:
if i.name not in processed:
process_target(i)
return root_cb return root_cb

View File

@ -2513,7 +2513,7 @@ external dependencies (including libraries) must go to "dependencies".''')
return self.disabled_subproject(dirname) return self.disabled_subproject(dirname)
raise e raise e
def do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None): def do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None, build_def_files=None):
with mlog.nested(): with mlog.nested():
new_build = self.build.copy() new_build = self.build.copy()
subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir,
@ -2536,7 +2536,10 @@ external dependencies (including libraries) must go to "dependencies".''')
self.subprojects.update(subi.subprojects) self.subprojects.update(subi.subprojects)
self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname) self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname)
# Duplicates are possible when subproject uses files from project root # Duplicates are possible when subproject uses files from project root
self.build_def_files = list(set(self.build_def_files + subi.build_def_files)) if build_def_files:
self.build_def_files += list(set(self.build_def_files + build_def_files))
else:
self.build_def_files += list(set(self.build_def_files + subi.build_def_files))
self.build.merge(subi.build) self.build.merge(subi.build)
self.build.subprojects[dirname] = subi.project_version self.build.subprojects[dirname] = subi.project_version
return self.subprojects[dirname] return self.subprojects[dirname]
@ -2550,12 +2553,22 @@ external dependencies (including libraries) must go to "dependencies".''')
cm_int.analyse() cm_int.analyse()
# Generate a meson ast and execute it with the normal do_subproject_meson # Generate a meson ast and execute it with the normal do_subproject_meson
ast = cm_int.pretend_to_be_meson()
mlog.log() mlog.log()
with mlog.nested(): with mlog.nested():
mlog.log('Processing generated meson AST') mlog.log('Processing generated meson AST')
mlog.log() mlog.log()
ast = cm_int.pretend_to_be_meson() mlog.log('=== BEGIN meson.build ===')
result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast) from .ast import AstIndentationGenerator, AstPrinter
printer = AstPrinter()
ast.accept(AstIndentationGenerator())
ast.accept(printer)
printer.post_process()
mlog.log(printer.result)
mlog.log('=== END meson.build ===')
mlog.log()
result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files)
mlog.log() mlog.log()
return result return result