From 77e0008a1fede82851fdf13438619cb62e61c297 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Wed, 18 Dec 2019 15:32:13 +0100 Subject: [PATCH] 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. --- docs/markdown/snippets/cmake_improvements.md | 11 ++++ mesonbuild/cmake/interpreter.py | 64 ++++++++++++++++--- .../cmake/15 object library advanced/main.cpp | 11 ++++ .../15 object library advanced/meson.build | 13 ++++ .../subprojects/cmObjLib/CMakeLists.txt | 18 ++++++ .../subprojects/cmObjLib/genC.cpp | 31 +++++++++ .../subprojects/cmObjLib/libA.cpp | 9 +++ .../subprojects/cmObjLib/libA.hpp | 16 +++++ .../subprojects/cmObjLib/libB.cpp | 6 ++ .../subprojects/cmObjLib/libB.hpp | 16 +++++ test cases/cmake/5 object library/meson.build | 7 +- .../subprojects/cmObjLib/libA.hpp | 13 +++- .../subprojects/cmObjLib/libB.hpp | 13 +++- .../cmake/6 object library no dep/meson.build | 5 -- .../subprojects/cmObjLib/libA.hpp | 13 +++- .../subprojects/cmObjLib/libB.hpp | 13 +++- 16 files changed, 236 insertions(+), 23 deletions(-) create mode 100644 docs/markdown/snippets/cmake_improvements.md create mode 100644 test cases/cmake/15 object library advanced/main.cpp create mode 100644 test cases/cmake/15 object library advanced/meson.build create mode 100644 test cases/cmake/15 object library advanced/subprojects/cmObjLib/CMakeLists.txt create mode 100644 test cases/cmake/15 object library advanced/subprojects/cmObjLib/genC.cpp create mode 100644 test cases/cmake/15 object library advanced/subprojects/cmObjLib/libA.cpp create mode 100644 test cases/cmake/15 object library advanced/subprojects/cmObjLib/libA.hpp create mode 100644 test cases/cmake/15 object library advanced/subprojects/cmObjLib/libB.cpp create mode 100644 test cases/cmake/15 object library advanced/subprojects/cmObjLib/libB.hpp diff --git a/docs/markdown/snippets/cmake_improvements.md b/docs/markdown/snippets/cmake_improvements.md new file mode 100644 index 000000000..95f30cc6b --- /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 d9f1e18f6..66d099419 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 000000000..4cc01a83a --- /dev/null +++ b/test cases/cmake/15 object library advanced/main.cpp @@ -0,0 +1,11 @@ +#include +#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 000000000..6a4448ba7 --- /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 000000000..47f1ad31d --- /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 $) +add_library(lib_sta STATIC $) + +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 000000000..a9e4b5eff --- /dev/null +++ b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/genC.cpp @@ -0,0 +1,31 @@ +#include +#include + +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 + +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 000000000..fd5aa48f1 --- /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 000000000..84b7bc7d3 --- /dev/null +++ b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libA.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#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 000000000..4b832ec9f --- /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 000000000..52ccc1601 --- /dev/null +++ b/test cases/cmake/15 object library advanced/subprojects/cmObjLib/libB.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#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 c3381366b..f38a2ddae 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 58c941321..84b7bc7d3 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 -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 71db6b7a3..52ccc1601 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 -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 b2c66ed00..65b8700b4 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 58c941321..84b7bc7d3 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 -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 71db6b7a3..52ccc1601 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 -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();