diff options
-rw-r--r-- | mesonbuild/cmake/interpreter.py | 44 | ||||
-rw-r--r-- | mesonbuild/cmake/traceparser.py | 96 | ||||
-rw-r--r-- | test cases/cmake/9 header only/main.cpp | 10 | ||||
-rw-r--r-- | test cases/cmake/9 header only/meson.build | 12 | ||||
-rw-r--r-- | test cases/cmake/9 header only/subprojects/cmMod/CMakeLists.txt | 11 | ||||
-rw-r--r-- | test cases/cmake/9 header only/subprojects/cmMod/include/cmMod.hpp | 19 |
6 files changed, 173 insertions, 19 deletions
diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 28a8488..5687ad2 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -74,8 +74,11 @@ target_type_map = { 'SHARED_LIBRARY': 'shared_library', 'EXECUTABLE': 'executable', 'OBJECT_LIBRARY': 'static_library', + 'INTERFACE_LIBRARY': 'header_only' } +target_type_requires_trace = ['INTERFACE_LIBRARY'] + skip_targets = ['UTILITY'] blacklist_compiler_flags = [ @@ -138,6 +141,7 @@ class ConverterTarget: self.link_with = [] self.object_libs = [] self.compile_opts = {} + self.public_compile_opts = [] self.pie = False # Project default override options (c_std, cpp_std, etc.) @@ -170,7 +174,7 @@ class ConverterTarget: std_regex = re.compile(r'([-]{1,2}std=|/std:v?|[-]{1,2}std:)(.*)') - def postprocess(self, output_target_map: dict, root_src_dir: str, subdir: str, install_prefix: str) -> None: + def postprocess(self, output_target_map: dict, root_src_dir: str, subdir: str, install_prefix: str, trace: CMakeTraceParser) -> None: # Detect setting the C and C++ standard for i in ['c', 'cpp']: if i not in self.compile_opts: @@ -194,6 +198,18 @@ class ConverterTarget: if self.type.upper() == 'OBJECT_LIBRARY': self.pie = True + # Use the CMake trace, if required + if self.type.upper() in target_type_requires_trace: + if self.name in trace.targets: + props = trace.targets[self.name].properies + + self.includes += props.get('INTERFACE_INCLUDE_DIRECTORIES', []) + self.public_compile_opts += props.get('INTERFACE_COMPILE_DEFINITIONS', []) + self.public_compile_opts += props.get('INTERFACE_COMPILE_OPTIONS', []) + self.link_flags += props.get('INTERFACE_LINK_OPTIONS', []) + else: + mlog.warning('CMake: Target', mlog.bold(self.name), 'not found in CMake trace. This can lead to build errors') + # Fix link libraries temp = [] for i in self.link_libraries: @@ -584,7 +600,7 @@ class CMakeInterpreter: for i in self.custom_targets: i.postprocess(output_target_map, self.src_dir, self.subdir, self.build_dir) for i in self.targets: - i.postprocess(output_target_map, self.src_dir, self.subdir, self.install_prefix) + i.postprocess(output_target_map, self.src_dir, self.subdir, self.install_prefix, self.trace) if i.type == 'OBJECT_LIBRARY': object_libs += [i] self.languages += [x for x in i.languages if x not in self.languages] @@ -762,17 +778,31 @@ class CMakeInterpreter: dep_kwargs = { 'link_args': tgt.link_flags + tgt.link_libraries, 'link_with': id_node(tgt_var), + 'compile_args': tgt.public_compile_opts, 'include_directories': id_node(inc_var), } # Generate the function nodes - inc_node = assign(inc_var, function('include_directories', tgt.includes)) - src_node = assign(src_var, function('files', sources)) - tgt_node = assign(tgt_var, function(tgt_func, [base_name, [id_node(src_var)] + generated], tgt_kwargs)) - dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) + node_list = [] + if tgt_func == 'header_only': + del dep_kwargs['link_with'] + inc_node = assign(inc_var, function('include_directories', tgt.includes)) + dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) + + node_list = [inc_node, dep_node] + src_var = '' + tgt_var = '' + + else: + inc_node = assign(inc_var, function('include_directories', tgt.includes)) + src_node = assign(src_var, function('files', sources)) + tgt_node = assign(tgt_var, function(tgt_func, [base_name, [id_node(src_var)] + generated], tgt_kwargs)) + dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) + + node_list = [inc_node, src_node, tgt_node, dep_node] # Add the nodes to the ast - root_cb.lines += [inc_node, src_node, tgt_node, dep_node] + root_cb.lines += node_list processed[tgt.name] = {'inc': inc_var, 'src': src_var, 'dep': dep_var, 'tgt': tgt_var, 'func': tgt_func} def process_custom_target(tgt: ConverterCustomTarget) -> None: diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py index 4b87319..6106d16 100644 --- a/mesonbuild/cmake/traceparser.py +++ b/mesonbuild/cmake/traceparser.py @@ -81,7 +81,11 @@ class CMakeTraceParser: 'add_custom_command': self._cmake_add_custom_command, 'add_custom_target': self._cmake_add_custom_target, 'set_property': self._cmake_set_property, - 'set_target_properties': self._cmake_set_target_properties + 'set_target_properties': self._cmake_set_target_properties, + 'target_compile_definitions': self._cmake_target_compile_definitions, + 'target_compile_options': self._cmake_target_compile_options, + 'target_include_directories': self._cmake_target_include_directories, + 'target_link_options': self._cmake_target_link_options, } # Primary pass -- parse everything @@ -199,16 +203,23 @@ class CMakeTraceParser: args = list(tline.args) # Make a working copy # Make sure the lib is imported - if 'IMPORTED' not in args: - return self._gen_exception('add_library', 'non imported libraries are not supported', tline) + if 'INTERFACE' in args: + args.remove('INTERFACE') - args.remove('IMPORTED') + if len(args) < 1: + return self._gen_exception('add_library', 'interface library name not specified', tline) - # No only look at the first two arguments (target_name and target_type) and ignore the rest - if len(args) < 2: - return self._gen_exception('add_library', 'requires at least 2 arguments', tline) + self.targets[args[0]] = CMakeTarget(args[0], 'INTERFACE', {}) + elif 'IMPORTED' in args: + args.remove('IMPORTED') - self.targets[args[0]] = CMakeTarget(args[0], args[1], {}) + # No only look at the first two arguments (target_name and target_type) and ignore the rest + if len(args) < 2: + return self._gen_exception('add_library', 'requires at least 2 arguments', tline) + + self.targets[args[0]] = CMakeTarget(args[0], args[1], {}) + else: + return self._gen_exception('add_library', 'non imported / interface libraries are not supported', tline) def _cmake_add_custom_command(self, tline: CMakeTraceLine): # DOC: https://cmake.org/cmake/help/latest/command/add_custom_command.html @@ -343,8 +354,8 @@ class CMakeTraceParser: # set_property() this is not context free. There are two approaches I # can think of, both have drawbacks: # - # 1. Assume that the property will be capitalized, this is convention - # but cmake doesn't require it. + # 1. Assume that the property will be capitalized ([A-Z_]), this is + # convention but cmake doesn't require it. # 2. Maintain a copy of the list here: https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#target-properties # # Neither of these is awesome for obvious reasons. I'm going to try @@ -354,8 +365,9 @@ class CMakeTraceParser: arglist = [] # type: List[Tuple[str, List[str]]] name = args.pop(0) values = [] + prop_regex = re.compile(r'^[A-Z_]+$') for a in args: - if a.isupper(): + if prop_regex.match(a): if values: arglist.append((name, ' '.join(values).split(';'))) name = a @@ -372,6 +384,66 @@ class CMakeTraceParser: self.targets[i].properies[name] = value + def _cmake_target_compile_definitions(self, tline: CMakeTraceLine) -> None: + # DOC: https://cmake.org/cmake/help/latest/command/target_compile_definitions.html + self._parse_common_target_options('target_compile_definitions', 'COMPILE_DEFINITIONS', 'INTERFACE_COMPILE_DEFINITIONS', tline) + + def _cmake_target_compile_options(self, tline: CMakeTraceLine) -> None: + # DOC: https://cmake.org/cmake/help/latest/command/target_compile_options.html + self._parse_common_target_options('target_compile_options', 'COMPILE_OPTIONS', 'INTERFACE_COMPILE_OPTIONS', tline) + + def _cmake_target_include_directories(self, tline: CMakeTraceLine) -> None: + # DOC: https://cmake.org/cmake/help/latest/command/target_include_directories.html + self._parse_common_target_options('target_include_directories', 'INCLUDE_DIRECTORIES', 'INTERFACE_INCLUDE_DIRECTORIES', tline, ignore=['SYSTEM', 'BEFORE'], paths=True) + + def _cmake_target_link_options(self, tline: CMakeTraceLine) -> None: + # DOC: https://cmake.org/cmake/help/latest/command/target_link_options.html + self._parse_common_target_options('target_link_options', 'LINK_OPTIONS', 'INTERFACE_LINK_OPTIONS', tline) + + def _parse_common_target_options(self, func: str, private_prop: str, interface_prop: str, tline: CMakeTraceLine, ignore: Optional[List[str]] = None, paths: bool = False): + if ignore is None: + ignore = ['BEFORE'] + + args = list(tline.args) + + if len(args) < 1: + return self._gen_exception(func, 'requires at least one argument', tline) + + target = args[0] + if target not in self.targets: + return self._gen_exception(func, 'TARGET {} not found'.format(target), tline) + + interface = [] + private = [] + + mode = 'PUBLIC' + for i in args[1:]: + if i in ignore: + continue + + if i in ['INTERFACE', 'PUBLIC', 'PRIVATE']: + mode = i + continue + + if mode in ['INTERFACE', 'PUBLIC']: + interface += [i] + + if mode in ['PUBLIC', 'PRIVATE']: + private += [i] + + if paths: + interface = self._guess_files(interface) + private = self._guess_files(private) + + interface = [x for x in interface if x] + private = [x for x in private if x] + + for i in [(private_prop, private), (interface_prop, interface)]: + if not i[0] in self.targets[target].properies: + self.targets[target].properies[i[0]] = [] + + self.targets[target].properies[i[0]] += i[1] + def _lex_trace(self, trace): # The trace format is: '<file>(<line>): <func>(<args -- can contain \n> )\n' reg_tline = re.compile(r'\s*(.*\.(cmake|txt))\(([0-9]+)\):\s*(\w+)\(([\s\S]*?) ?\)\s*\n', re.MULTILINE) @@ -420,7 +492,7 @@ class CMakeTraceParser: # Abort concatination if curr_str no longer matches the regex fixed_list += [curr_str] curr_str = i - elif reg_end.match(i): + elif reg_end.match(i) or os.path.exists('{} {}'.format(curr_str, i)): # File detected curr_str = '{} {}'.format(curr_str, i) fixed_list += [curr_str] diff --git a/test cases/cmake/9 header only/main.cpp b/test cases/cmake/9 header only/main.cpp new file mode 100644 index 0000000..315c0f7 --- /dev/null +++ b/test cases/cmake/9 header only/main.cpp @@ -0,0 +1,10 @@ +#include <iostream> +#include <cmMod.hpp> + +using namespace std; + +int main() { + cmModClass obj("Hello"); + cout << obj.getStr() << endl; + return 0; +} diff --git a/test cases/cmake/9 header only/meson.build b/test cases/cmake/9 header only/meson.build new file mode 100644 index 0000000..ca08a3f --- /dev/null +++ b/test cases/cmake/9 header only/meson.build @@ -0,0 +1,12 @@ +project('cmakeSubTest', ['c', 'cpp']) + +cm = import('cmake') + +sub_pro = cm.subproject('cmMod') +sub_dep = sub_pro.dependency('cmModLib') + +assert(sub_pro.target_list() == ['cmModLib'], 'There should be exactly one target') +assert(sub_pro.target_type('cmModLib') == 'header_only', 'Target type should be header_only') + +exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep]) +test('test1', exe1) diff --git a/test cases/cmake/9 header only/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/9 header only/subprojects/cmMod/CMakeLists.txt new file mode 100644 index 0000000..f5d9a47 --- /dev/null +++ b/test cases/cmake/9 header only/subprojects/cmMod/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmMod) +set (CMAKE_CXX_STANDARD 14) + +add_definitions("-DDO_NOTHING_JUST_A_FLAG=1") + +add_library(cmModLib INTERFACE) +set_target_properties(cmModLib PROPERTIES INTERFACE_COMPILE_OPTIONS "-DCMAKE_FLAG_MUST_BE_PRESENT") +target_include_directories(cmModLib INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_compile_definitions(cmModLib INTERFACE -DCMAKE_COMPILER_DEFINE_STR="compDef") diff --git a/test cases/cmake/9 header only/subprojects/cmMod/include/cmMod.hpp b/test cases/cmake/9 header only/subprojects/cmMod/include/cmMod.hpp new file mode 100644 index 0000000..7ea72f7 --- /dev/null +++ b/test cases/cmake/9 header only/subprojects/cmMod/include/cmMod.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include <string> + +#ifndef CMAKE_FLAG_MUST_BE_PRESENT +#error "The flag CMAKE_FLAG_MUST_BE_PRESENT was not set" +#endif + +class cmModClass { + private: + std::string str; + public: + cmModClass(std::string foo) { + str = foo + " World "; + str += CMAKE_COMPILER_DEFINE_STR; + } + + inline std::string getStr() const { return str; } +}; |