diff options
author | Daniel Mensinger <daniel@mensinger-ka.de> | 2019-12-18 15:32:13 +0100 |
---|---|---|
committer | Jussi Pakkanen <jpakkane@gmail.com> | 2019-12-22 22:38:20 +0200 |
commit | 77e0008a1fede82851fdf13438619cb62e61c297 (patch) | |
tree | c17db0ec3c718b5d664534344056b5139ae68fa4 | |
parent | 7981308e6e600535efeba6b50cdda62d43e7c584 (diff) | |
download | meson-77e0008a1fede82851fdf13438619cb62e61c297.zip meson-77e0008a1fede82851fdf13438619cb62e61c297.tar.gz meson-77e0008a1fede82851fdf13438619cb62e61c297.tar.bz2 |
cmake: Fix obeject libraries
This fixes an issue with generated sources and object libraries, as
well as an issue on windows with the `link` linker and the vs backend.
The last issue is resolved by building the source files multiple times
to avoid extracting object files in meson.
16 files changed, 236 insertions, 23 deletions
diff --git a/docs/markdown/snippets/cmake_improvements.md b/docs/markdown/snippets/cmake_improvements.md new file mode 100644 index 0000000..95f30cc --- /dev/null +++ b/docs/markdown/snippets/cmake_improvements.md @@ -0,0 +1,11 @@ +## Improved CMake subprojects support + +With this release even more CMake projects are supported via +[CMake subprojects](CMake-module.md#cmake-subprojects) due to these internal +improvements: + +- Use the CMake file API for CMake >=3.14 +- Handle the explicit dependencies via `add_dependency` +- Basic support for `add_custom_target` +- Improved `add_custom_command` support +- Object library support on Windows diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index d9f1e18..66d0994 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -25,9 +25,10 @@ from ..environment import Environment from ..mesonlib import MachineChoice, version_compare from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header from subprocess import Popen, PIPE -from typing import Any, List, Dict, Optional, Union, TYPE_CHECKING +from typing import Any, List, Dict, Optional, Set, Union, TYPE_CHECKING from threading import Thread from enum import Enum +from functools import lru_cache import os, re from ..mparser import ( @@ -391,23 +392,60 @@ class ConverterTarget: if tgt: self.depends.append(tgt) - def process_object_libs(self, obj_target_list: List['ConverterTarget']): + def process_object_libs(self, obj_target_list: List['ConverterTarget'], linker_workaround: bool): # Try to detect the object library(s) from the generated input sources temp = [x for x in self.generated if isinstance(x, str)] temp = [os.path.basename(x) for x in temp] temp = [x for x in temp if any([x.endswith('.' + y) for y in obj_suffixes])] temp = [os.path.splitext(x)[0] for x in temp] + exts = self._all_source_suffixes() # Temp now stores the source filenames of the object files for i in obj_target_list: - source_files = [os.path.basename(x) for x in i.sources + i.generated] - for j in source_files: - if j in temp: - self.object_libs += [i] + source_files = [x for x in i.sources + i.generated if isinstance(x, str)] + source_files = [os.path.basename(x) for x in source_files] + for j in temp: + # On some platforms (specifically looking at you Windows with vs20xy backend) CMake does + # not produce object files with the format `foo.cpp.obj`, instead it skipps the language + # suffix and just produces object files like `foo.obj`. Thus we have to do our best to + # undo this step and guess the correct language suffix of the object file. This is done + # by trying all language suffixes meson knows and checking if one of them fits. + candidates = [j] # type: List[str] + if not any([j.endswith('.' + x) for x in exts]): + mlog.warning('Object files do not contain source file extensions, thus falling back to guessing them.', once=True) + candidates += ['{}.{}'.format(j, x) for x in exts] + if any([x in source_files for x in candidates]): + if linker_workaround: + self._append_objlib_sources(i) + else: + self.includes += i.includes + self.includes = list(set(self.includes)) + self.object_libs += [i] break # Filter out object files from the sources self.generated = [x for x in self.generated if not isinstance(x, str) or not any([x.endswith('.' + y) for y in obj_suffixes])] + def _append_objlib_sources(self, tgt: 'ConverterTarget') -> None: + self.includes += tgt.includes + self.sources += tgt.sources + self.generated += tgt.generated + self.sources = list(set(self.sources)) + self.generated = list(set(self.generated)) + self.includes = list(set(self.includes)) + + # Inherit compiler arguments since they may be required for building + for lang, opts in tgt.compile_opts.items(): + if lang not in self.compile_opts: + self.compile_opts[lang] = [] + self.compile_opts[lang] += [x for x in opts if x not in self.compile_opts[lang]] + + @lru_cache(maxsize=None) + def _all_source_suffixes(self) -> List[str]: + suffixes = [] # type: List[str] + for exts in lang_suffixes.values(): + suffixes += [x for x in exts] + return suffixes + def process_inter_target_dependencies(self): # Move the dependencies from all transfer_dependencies_from to the target to_process = list(self.depends) @@ -620,6 +658,7 @@ class CMakeInterpreter: self.install_prefix = install_prefix self.env = env self.backend_name = backend.name + self.linkers = set() # type: Set[str] self.cmake_api = CMakeAPI.SERVER self.client = CMakeClient(self.env) self.fileapi = CMakeFileAPI(self.build_dir) @@ -661,6 +700,7 @@ class CMakeInterpreter: for lang, comp in self.env.coredata.compilers[for_machine].items(): if lang not in language_map: continue + self.linkers.add(comp.get_linker_id()) cmake_lang = language_map[lang] exelist = comp.get_exelist() if len(exelist) == 1: @@ -787,12 +827,16 @@ class CMakeInterpreter: self.trace.parse(self.raw_trace) # Find all targets + added_target_names = [] # type: List[str] for i in self.codemodel_configs: for j in i.projects: if not self.project_name: self.project_name = j.name for k in j.targets: - if k.type not in skip_targets: + # Avoid duplicate targets from different configurations and known + # dummy CMake internal target types + if k.type not in skip_targets and k.name not in added_target_names: + added_target_names += [k.name] self.targets += [ConverterTarget(k, self.env)] # Add interface targets from trace, if not already present. @@ -830,7 +874,7 @@ class CMakeInterpreter: # Second pass: Detect object library dependencies for i in self.targets: - i.process_object_libs(object_libs) + i.process_object_libs(object_libs, self._object_lib_workaround()) # Third pass: Reassign dependencies to avoid some loops for i in self.targets: @@ -1010,6 +1054,7 @@ class CMakeInterpreter: # Generate target kwargs tgt_kwargs = { + 'build_by_default': False, 'link_args': tgt.link_flags + tgt.link_libraries, 'link_with': link_with, 'include_directories': id_node(inc_var), @@ -1144,3 +1189,6 @@ class CMakeInterpreter: res = [x for x in self.generated_targets.keys()] res = [x[prx_len:] if x.startswith(prx_str) else x for x in res] return res + + def _object_lib_workaround(self) -> bool: + return 'link' in self.linkers and self.backend_name.startswith('vs') diff --git a/test cases/cmake/15 object library advanced/main.cpp b/test cases/cmake/15 object library advanced/main.cpp new file mode 100644 index 0000000..4cc01a8 --- /dev/null +++ b/test cases/cmake/15 object library advanced/main.cpp @@ -0,0 +1,11 @@ +#include <iostream> +#include "libA.hpp" +#include "libB.hpp" + +using namespace std; + +int main(void) { + cout << getLibStr() << endl; + cout << getZlibVers() << endl; + return EXIT_SUCCESS; +} diff --git a/test cases/cmake/15 object library advanced/meson.build b/test cases/cmake/15 object library advanced/meson.build new file mode 100644 index 0000000..6a4448b --- /dev/null +++ b/test cases/cmake/15 object library advanced/meson.build @@ -0,0 +1,13 @@ +project('cmake_object_lib_test', 'cpp', default_options: ['cpp_std=c++11']) + +cm = import('cmake') + +sub_pro = cm.subproject('cmObjLib') +sub_sha = sub_pro.dependency('lib_sha') +sub_sta = sub_pro.dependency('lib_sta') + +exe_sha = executable('shared', ['main.cpp'], dependencies: [sub_sha]) +exe_sta = executable('static', ['main.cpp'], dependencies: [sub_sta]) + +test('test1', exe_sha) +test('test1', exe_sta) diff --git a/test cases/cmake/15 object library advanced/subprojects/cmObjLib/CMakeLists.txt b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/CMakeLists.txt new file mode 100644 index 0000000..47f1ad3 --- /dev/null +++ b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.7) +project(cmObject CXX) + +add_executable(genC genC.cpp) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libC.cpp" "${CMAKE_CURRENT_BINARY_DIR}/libC.hpp" + COMMAND genC + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" +) + +include_directories("${CMAKE_CURRENT_BINARY_DIR}") + +add_library(lib_obj OBJECT libA.cpp libB.cpp "${CMAKE_CURRENT_BINARY_DIR}/libC.cpp" "${CMAKE_CURRENT_BINARY_DIR}/libC.hpp") +add_library(lib_sha SHARED $<TARGET_OBJECTS:lib_obj>) +add_library(lib_sta STATIC $<TARGET_OBJECTS:lib_obj>) + +target_compile_definitions(lib_obj PRIVATE "-DBUILD_AS_OBJ=1") diff --git a/test cases/cmake/15 object library advanced/subprojects/cmObjLib/genC.cpp b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/genC.cpp new file mode 100644 index 0000000..a9e4b5e --- /dev/null +++ b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/genC.cpp @@ -0,0 +1,31 @@ +#include <iostream> +#include <fstream> + +using namespace std; + +int main() { + ofstream hpp("libC.hpp"); + ofstream cpp("libC.cpp"); + if (!hpp.is_open() || !cpp.is_open()) { + cerr << "Failed to open 'libC.hpp' or 'libC.cpp' for writing" << endl; + return 1; + } + + hpp << R"cpp( +#pragma once + +#include <string> + +std::string getGenStr(); +)cpp"; + + cpp << R"cpp( +#include "libC.hpp" + +std::string getGenStr(void) { + return "GEN STR"; +} +)cpp"; + + return 0; +}
\ No newline at end of file diff --git a/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libA.cpp b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libA.cpp new file mode 100644 index 0000000..fd5aa48 --- /dev/null +++ b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libA.cpp @@ -0,0 +1,9 @@ +#include "libA.hpp" + +#if not BUILD_AS_OBJ +#error "BUILD_AS_OBJ was not defined" +#endif + +std::string getLibStr(void) { + return "Hello World"; +} diff --git a/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libA.hpp b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libA.hpp new file mode 100644 index 0000000..84b7bc7 --- /dev/null +++ b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libA.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include <string> + +#if defined _WIN32 || defined __CYGWIN__ + #define DLL_PUBLIC __declspec(dllexport) +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +std::string DLL_PUBLIC getLibStr(); diff --git a/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libB.cpp b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libB.cpp new file mode 100644 index 0000000..4b832ec --- /dev/null +++ b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libB.cpp @@ -0,0 +1,6 @@ +#include "libB.hpp" +#include "libC.hpp" + +std::string getZlibVers(void) { + return getGenStr(); +} diff --git a/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libB.hpp b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libB.hpp new file mode 100644 index 0000000..52ccc16 --- /dev/null +++ b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libB.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include <string> + +#if defined _WIN32 || defined __CYGWIN__ + #define DLL_PUBLIC __declspec(dllexport) +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +std::string DLL_PUBLIC getZlibVers(); diff --git a/test cases/cmake/5 object library/meson.build b/test cases/cmake/5 object library/meson.build index c338136..f38a2dd 100644 --- a/test cases/cmake/5 object library/meson.build +++ b/test cases/cmake/5 object library/meson.build @@ -1,15 +1,10 @@ -project('cmake_object_lib_test', 'cpp') +project('cmake_object_lib_test', ['c', 'cpp']) dep_test = dependency('ZLIB', method: 'cmake', required: false) if not dep_test.found() error('MESON_SKIP_TEST: zlib is not installed') endif -cpp = meson.get_compiler('cpp') -if build_machine.system() == 'windows' and cpp.get_id() != 'gcc' - error('MESON_SKIP_TEST: Windows link.exe is not supported because of symbol export problems') -endif - cm = import('cmake') sub_pro = cm.subproject('cmObjLib') diff --git a/test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp b/test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp index 58c9413..84b7bc7 100644 --- a/test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp +++ b/test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp @@ -2,4 +2,15 @@ #include <string> -std::string getLibStr(); +#if defined _WIN32 || defined __CYGWIN__ + #define DLL_PUBLIC __declspec(dllexport) +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +std::string DLL_PUBLIC getLibStr(); diff --git a/test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp b/test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp index 71db6b7..52ccc16 100644 --- a/test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp +++ b/test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp @@ -2,4 +2,15 @@ #include <string> -std::string getZlibVers(); +#if defined _WIN32 || defined __CYGWIN__ + #define DLL_PUBLIC __declspec(dllexport) +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +std::string DLL_PUBLIC getZlibVers(); diff --git a/test cases/cmake/6 object library no dep/meson.build b/test cases/cmake/6 object library no dep/meson.build index b2c66ed..65b8700 100644 --- a/test cases/cmake/6 object library no dep/meson.build +++ b/test cases/cmake/6 object library no dep/meson.build @@ -1,10 +1,5 @@ project('cmake_object_lib_test', 'cpp') -cpp = meson.get_compiler('cpp') -if build_machine.system() == 'windows' and cpp.get_id() != 'gcc' - error('MESON_SKIP_TEST: Windows link.exe is not supported because of symbol export problems') -endif - cm = import('cmake') sub_pro = cm.subproject('cmObjLib') diff --git a/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp index 58c9413..84b7bc7 100644 --- a/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp +++ b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp @@ -2,4 +2,15 @@ #include <string> -std::string getLibStr(); +#if defined _WIN32 || defined __CYGWIN__ + #define DLL_PUBLIC __declspec(dllexport) +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +std::string DLL_PUBLIC getLibStr(); diff --git a/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp index 71db6b7..52ccc16 100644 --- a/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp +++ b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp @@ -2,4 +2,15 @@ #include <string> -std::string getZlibVers(); +#if defined _WIN32 || defined __CYGWIN__ + #define DLL_PUBLIC __declspec(dllexport) +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +std::string DLL_PUBLIC getZlibVers(); |