From a77b97817a73fbdd2fbeb5f7038ad400aafd1803 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 29 Sep 2020 00:25:48 +0200 Subject: [PATCH] Feature/toolchain cmake simplify (#7738) * moving logic from cmake to python for cmake toolchain * fix test * working * removed print * fix some tests * fixing linux tests * fixing apple tests * fix MacOS test * reuse existing function * variables and preprocessor_definitions * fixing test in Linux * fixing test * remove add_compile_definitions --- conans/client/toolchain/cmake.py | 233 +++++++++++------- .../test/functional/toolchain/test_basic.py | 4 +- .../test/functional/toolchain/test_cmake.py | 35 ++- 3 files changed, 172 insertions(+), 100 deletions(-) diff --git a/conans/client/toolchain/cmake.py b/conans/client/toolchain/cmake.py index 594409aaee4..5c34d5897e0 100644 --- a/conans/client/toolchain/cmake.py +++ b/conans/client/toolchain/cmake.py @@ -1,13 +1,14 @@ -# coding=utf-8 import os import textwrap from collections import OrderedDict, defaultdict from jinja2 import Template -from conans.client.build.cmake_flags import get_generator, get_generator_platform, \ - CMakeDefinitionsBuilder, get_toolset, is_multi_configuration -from conans.client.generators.cmake_common import CMakeCommonMacros +from conans.client.build.cmake_flags import get_generator, get_generator_platform, get_toolset, \ + is_multi_configuration +from conans.client.build.compiler_flags import architecture_flag +from conans.client.tools import cpu_count +from conans.errors import ConanException from conans.util.files import save @@ -16,16 +17,16 @@ # https://github.com/microsoft/vcpkg/tree/master/scripts/buildsystems -class Definitions(OrderedDict): +class Variables(OrderedDict): _configuration_types = None # Needed for py27 to avoid infinite recursion def __init__(self): - super(Definitions, self).__init__() + super(Variables, self).__init__() self._configuration_types = {} def __getattribute__(self, config): try: - return super(Definitions, self).__getattribute__(config) + return super(Variables, self).__getattribute__(config) except AttributeError: return self._configuration_types.setdefault(config, dict()) @@ -43,8 +44,8 @@ class CMakeToolchain(object): filename = "conan_toolchain.cmake" _template_toolchain = textwrap.dedent(""" - # Conan generated toolchain file - cmake_minimum_required(VERSION 3.0) # Needed for targets + # Conan automatically generated toolchain file + # DO NOT EDIT MANUALLY, it will be overwritten # Avoid including toolchain file several times (bad if appending to variables like # CMAKE_CXX_FLAGS. See https://github.com/android/ndk/issues/323 @@ -53,31 +54,18 @@ class CMakeToolchain(object): endif() set(CONAN_TOOLCHAIN_INCLUDED TRUE) - ########### Utility macros and functions ########### - {{ cmake_macros_and_functions }} - ########### End of Utility macros and functions ########### - # Configure - {% if generator_platform %} + {%- if generator_platform %} set(CMAKE_GENERATOR_PLATFORM "{{ generator_platform }}" CACHE STRING "" FORCE) - {% endif %} - {% if toolset %} + {%- endif %} + {%- if toolset %} set(CMAKE_GENERATOR_TOOLSET "{{ toolset }}" CACHE STRING "" FORCE) - {% endif%} + {%- endif %} # build_type (Release, Debug, etc) is only defined for single-config generators - {% if build_type %} + {%- if build_type %} set(CMAKE_BUILD_TYPE "{{ build_type }}" CACHE STRING "Choose the type of build." FORCE) - {% endif %} - - # -- - CMake.flags --> CMakeDefinitionsBuilder::get_definitions - {%- for it, value in definitions.items() %} - {%- if it.startswith('CONAN_') %} - set({{ it }} "{{ value }}") - {%- else %} - set({{ it }} "{{ value }}" CACHE STRING "Value assigned from the Conan toolchain" FORCE) {%- endif %} - {%- endfor %} get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE ) if(_CMAKE_IN_TRY_COMPILE) @@ -106,46 +94,106 @@ class CMakeToolchain(object): # We are going to adjust automagically many things as requested by Conan # these are the things done by 'conan_basic_setup()' - # To support the cmake_find_package generators: - {% if cmake_module_path %} + set(CMAKE_EXPORT_NO_PACKAGE_REGISTRY ON) + + # To support the cmake_find_package generators + {% if cmake_module_path -%} set(CMAKE_MODULE_PATH {{ cmake_module_path }} ${CMAKE_MODULE_PATH}) - {% endif%} - {% if cmake_prefix_path %} + {%- endif %} + {% if cmake_prefix_path -%} set(CMAKE_PREFIX_PATH {{ cmake_prefix_path }} ${CMAKE_PREFIX_PATH}) - {% endif%} + {%- endif %} + + # shared libs + {% if shared_libs -%} + message(STATUS "Conan toolchain: Setting BUILD_SHARED_LIBS= {{ shared_libs }}") + set(BUILD_SHARED_LIBS {{ shared_libs }}) + {%- endif %} - {% if fpic %} + # fPIC + {% if fpic -%} message(STATUS "Conan toolchain: Setting CMAKE_POSITION_INDEPENDENT_CODE=ON (options.fPIC)") set(CMAKE_POSITION_INDEPENDENT_CODE ON) - {% endif %} + {%- endif %} - {% if set_rpath %}conan_set_rpath(){% endif %} - {% if set_std %}conan_set_std(){% endif %} + # SKIP_RPATH + {% if skip_rpath -%} + set(CMAKE_SKIP_RPATH 1 CACHE BOOL "rpaths" FORCE) + # Policy CMP0068 + # We want the old behavior, in CMake >= 3.9 CMAKE_SKIP_RPATH won't affect install_name in OSX + set(CMAKE_INSTALL_NAME_DIR "") + {% endif -%} + + # Parallel builds + {% if parallel -%} + set(CONAN_CXX_FLAGS "${CONAN_CXX_FLAGS} {{ parallel }}") + set(CONAN_C_FLAGS "${CONAN_C_FLAGS} {{ parallel }}") + {%- endif %} + + # Architecture + {% if architecture -%} + set(CONAN_CXX_FLAGS "${CONAN_CXX_FLAGS} {{ architecture }}") + set(CONAN_C_FLAGS "${CONAN_C_FLAGS} {{ architecture }}") + set(CONAN_SHARED_LINKER_FLAGS "${CONAN_SHARED_LINKER_FLAGS} {{ architecture }}") + set(CONAN_EXE_LINKER_FLAGS "${CONAN_EXE_LINKER_FLAGS} {{ architecture }}") + {%- endif %} # C++ Standard Library - {%- if set_libcxx %} + {% if set_libcxx -%} set(CONAN_CXX_FLAGS "${CONAN_CXX_FLAGS} {{ set_libcxx }}") {%- endif %} - {%- if glibcxx %} + {% if glibcxx -%} add_definitions(-D_GLIBCXX_USE_CXX11_ABI={{ glibcxx }}) {%- endif %} - {% if install_prefix %} + # C++ Standard + {% if cppstd -%} + message(STATUS "Conan C++ Standard {{ cppstd }} with extensions {{ cppstd_extensions }}}") + set(CMAKE_CXX_STANDARD {{ cppstd }}) + set(CMAKE_CXX_EXTENSIONS {{ cppstd_extensions }}) + {%- endif %} + + # Install prefix + {% if install_prefix -%} set(CMAKE_INSTALL_PREFIX {{install_prefix}} CACHE STRING "" FORCE) - {% endif %} + {%- endif %} - # Variables scoped to a configuration - {%- for it, values in configuration_types_definitions.items() -%} + # Variables + {% for it, value in variables.items() -%} + set({{ it }} "{{ value }}") + {%- endfor %} + # Variables per configuration + {% for it, values in variables_config.items() -%} {%- set genexpr = namespace(str='') %} {%- for conf, value in values -%} {%- set genexpr.str = genexpr.str + '$,"' + value|string + '",' %} - {%- if loop.last %}{% set genexpr.str = genexpr.str + '""' %}{% endif %} + {%- if loop.last %}{% set genexpr.str = genexpr.str + '""' -%}{%- endif -%} + {%- endfor -%} + {% for i in range(values|count) %}{%- set genexpr.str = genexpr.str + '>' %} {%- endfor -%} - {% for i in range(values|count) %}{%- set genexpr.str = genexpr.str + '>' %}{% endfor %} set({{ it }} {{ genexpr.str }}) {%- endfor %} + # Preprocessor definitions + {% for it, value in preprocessor_definitions.items() -%} + # add_compile_definitions only works in cmake >= 3.12 + add_definitions(-D{{ it }}="{{ value }}") + {%- endfor %} + # Preprocessor definitions per configuration + {% for it, values in preprocessor_definitions_config.items() -%} + {%- set genexpr = namespace(str='') %} + {%- for conf, value in values -%} + {%- set genexpr.str = genexpr.str + + '$,"' + value|string + '",' %} + {%- if loop.last %}{% set genexpr.str = genexpr.str + '""' -%}{%- endif -%} + {%- endfor -%} + {% for i in range(values|count) %}{%- set genexpr.str = genexpr.str + '>' %} + {%- endfor -%} + add_definitions(-D{{ it }}={{ genexpr.str }}) + {%- endfor %} + + set(CMAKE_CXX_FLAGS_INIT "${CONAN_CXX_FLAGS}" CACHE STRING "" FORCE) set(CMAKE_C_FLAGS_INIT "${CONAN_C_FLAGS}" CACHE STRING "" FORCE) set(CMAKE_SHARED_LINKER_FLAGS_INIT "${CONAN_SHARED_LINKER_FLAGS}" CACHE STRING "" FORCE) @@ -161,11 +209,16 @@ class CMakeToolchain(object): endif() ########### Utility macros and functions ########### - {{ cmake_macros_and_functions }} + function(conan_get_policy policy_id policy) + if(POLICY "${policy_id}") + cmake_policy(GET "${policy_id}" _policy) + set(${policy} "${_policy}" PARENT_SCOPE) + else() + set(${policy} "" PARENT_SCOPE) + endif() + endfunction() ########### End of Utility macros and functions ########### - - # Adjustments that depends on the build_type {% if vs_static_runtime %} conan_get_policy(CMP0091 policy_0091) @@ -185,16 +238,12 @@ class CMakeToolchain(object): """) def __init__(self, conanfile, generator=None, generator_platform=None, build_type=None, - cmake_system_name=True, toolset=None, parallel=True, make_program=None, - # cmake_program=None, # TODO: cmake program should be considered in the environment - ): + toolset=None, parallel=True): self._conanfile = conanfile self._fpic = self._deduce_fpic() self._vs_static_runtime = self._deduce_vs_static_runtime() - - self._set_rpath = True - self._set_std = True + self._parallel = parallel # To find the generated cmake_find_package finders self._cmake_prefix_path = "${CMAKE_BINARY_DIR}" @@ -207,24 +256,12 @@ def __init__(self, conanfile, generator=None, generator_platform=None, build_typ self._toolset = toolset or get_toolset(self._conanfile.settings, self._generator) self._build_type = build_type or self._conanfile.settings.get_safe("build_type") - builder = CMakeDefinitionsBuilder(self._conanfile, - cmake_system_name=cmake_system_name, - make_program=make_program, parallel=parallel, - generator=self._generator, - set_cmake_flags=False, - output=self._conanfile.output) - self.definitions = Definitions() - self.definitions.update(builder.get_definitions()) - # FIXME: Removing too many things. We want to bring the new logic for the toolchain here - # so we don't mess with the common code. - self.definitions.pop("CMAKE_BUILD_TYPE", None) - self.definitions.pop("CONAN_IN_LOCAL_CACHE", None) - self.definitions.pop("CMAKE_PREFIX_PATH", None) - self.definitions.pop("CMAKE_MODULE_PATH", None) - self.definitions.pop("CONAN_LINK_RUNTIME", None) - for install in ("PREFIX", "BINDIR", "SBINDIR", "LIBEXECDIR", "LIBDIR", "INCLUDEDIR", - "OLDINCLUDEDIR", "DATAROOTDIR"): - self.definitions.pop("CMAKE_INSTALL_%s" % install, None) + self.variables = Variables() + self.preprocessor_definitions = Variables() + try: + self._build_shared_libs = "ON" if self._conanfile.options.shared else "OFF" + except ConanException: + self._build_shared_libs = None def _deduce_fpic(self): fpic = self._conanfile.options.get_safe("fPIC") @@ -241,6 +278,10 @@ def _deduce_fpic(self): return None return fpic + def _get_architecture(self): + # This should be factorized and make it toolchain-private + return architecture_flag(self._conanfile.settings) + def _deduce_vs_static_runtime(self): settings = self._conanfile.settings if (settings.get_safe("compiler") == "Visual Studio" and @@ -278,13 +319,24 @@ def _get_libcxx(self): glib = "0" return lib, glib + def _cppstd(self): + cppstd = cppstd_extensions = None + compiler_cppstd = self._conanfile.settings.get_safe("compiler.cppstd") + if compiler_cppstd: + if compiler_cppstd.startswith("gnu"): + cppstd = compiler_cppstd[3:] + cppstd_extensions = "ON" + else: + cppstd = compiler_cppstd + cppstd_extensions = "OFF" + return cppstd, cppstd_extensions + def write_toolchain_files(self): # Make it absolute, wrt to current folder, set by the caller conan_project_include_cmake = os.path.abspath("conan_project_include.cmake") conan_project_include_cmake = conan_project_include_cmake.replace("\\", "/") t = Template(self._template_project_include) - content = t.render(configuration_types_definitions=self.definitions.configuration_types, - vs_static_runtime=self._vs_static_runtime) + content = t.render(vs_static_runtime=self._vs_static_runtime) save(conan_project_include_cmake, content) # TODO: I need the profile_host and profile_build here! @@ -301,28 +353,37 @@ def write_toolchain_files(self): set_libcxx, glibcxx = self._get_libcxx() + parallel = None + if self._parallel: + if self._generator and "Visual Studio" in self._generator: + parallel = "/MP%s" % cpu_count(output=self._conanfile.output) + + cppstd, cppstd_extensions = self._cppstd() + + skip_rpath = True if self._conanfile.settings.get_safe("os") == "Macos" else False + architecture = self._get_architecture() + context = { - "configuration_types_definitions": self.definitions.configuration_types, + "variables": self.variables, + "variables_config": self.variables.configuration_types, + "preprocessor_definitions": self.preprocessor_definitions, + "preprocessor_definitions_config": self.preprocessor_definitions.configuration_types, "build_type": build_type, "generator_platform": self._generator_platform, "toolset": self._toolset, - "definitions": self.definitions, "cmake_prefix_path": self._cmake_prefix_path, "cmake_module_path": self._cmake_module_path, "fpic": self._fpic, - "set_rpath": self._set_rpath, - "set_std": self._set_std, + "skip_rpath": skip_rpath, "set_libcxx": set_libcxx, "glibcxx": glibcxx, - "install_prefix": install_prefix + "install_prefix": install_prefix, + "parallel": parallel, + "cppstd": cppstd, + "cppstd_extensions": cppstd_extensions, + "shared_libs": self._build_shared_libs, + "architecture": architecture } t = Template(self._template_toolchain) - content = t.render(conan_project_include_cmake=conan_project_include_cmake, - cmake_macros_and_functions="\n".join([ - CMakeCommonMacros.conan_message, - CMakeCommonMacros.conan_get_policy, - CMakeCommonMacros.conan_set_rpath, - CMakeCommonMacros.conan_set_std, - ]), - **context) + content = t.render(conan_project_include_cmake=conan_project_include_cmake, **context) save(self.filename, content) diff --git a/conans/test/functional/toolchain/test_basic.py b/conans/test/functional/toolchain/test_basic.py index adb05146213..73c59823674 100644 --- a/conans/test/functional/toolchain/test_basic.py +++ b/conans/test/functional/toolchain/test_basic.py @@ -24,7 +24,7 @@ def toolchain(self): self.assertIn("conanfile.py: Generating toolchain files", client.out) toolchain = client.load("conan_toolchain.cmake") - self.assertIn("cmake_minimum_required", toolchain) + self.assertIn("Conan automatically generated toolchain file", toolchain) def test_declarative(self): conanfile = textwrap.dedent(""" @@ -38,7 +38,7 @@ class Pkg(ConanFile): self.assertIn("conanfile.py: Generating toolchain files", client.out) toolchain = client.load("conan_toolchain.cmake") - self.assertIn("cmake_minimum_required", toolchain) + self.assertIn("Conan automatically generated toolchain file", toolchain) def test_declarative_new_helper(self): conanfile = textwrap.dedent(""" diff --git a/conans/test/functional/toolchain/test_cmake.py b/conans/test/functional/toolchain/test_cmake.py index b7d263e333e..869ab7de66b 100644 --- a/conans/test/functional/toolchain/test_cmake.py +++ b/conans/test/functional/toolchain/test_cmake.py @@ -25,9 +25,12 @@ class App(ConanFile): def toolchain(self): tc = CMakeToolchain(self) - tc.definitions["DEFINITIONS_BOTH"] = True - tc.definitions.debug["DEFINITIONS_CONFIG"] = "Debug" - tc.definitions.release["DEFINITIONS_CONFIG"] = "Release" + tc.variables["MYVAR"] = "MYVAR_VALUE" + tc.variables.debug["MYVAR_CONFIG"] = "MYVAR_DEBUG" + tc.variables.release["MYVAR_CONFIG"] = "MYVAR_RELEASE" + tc.preprocessor_definitions["MYDEFINE"] = "MYDEF_VALUE" + tc.preprocessor_definitions.debug["MYDEFINE_CONFIG"] = "MYDEF_DEBUG" + tc.preprocessor_definitions.release["MYDEFINE_CONFIG"] = "MYDEF_RELEASE" tc.write_toolchain_files() def build(self): @@ -57,8 +60,10 @@ def build(self): #else std::cout << "App: Debug!" <> BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") get_directory_property(_COMPILE_DEFS DIRECTORY ${CMAKE_SOURCE_DIR} COMPILE_DEFINITIONS) message(">> COMPILE_DEFINITIONS: ${_COMPILE_DEFS}") + find_package(hello REQUIRED) add_library(app_lib app_lib.cpp) target_link_libraries(app_lib PRIVATE hello::hello) - target_compile_definitions(app_lib PRIVATE DEFINITIONS_BOTH="${DEFINITIONS_BOTH}") - target_compile_definitions(app_lib PRIVATE DEFINITIONS_CONFIG=${DEFINITIONS_CONFIG}) + target_compile_definitions(app_lib PRIVATE MYVAR="${MYVAR}") + target_compile_definitions(app_lib PRIVATE MYVAR_CONFIG=${MYVAR_CONFIG}) add_executable(app app.cpp) target_link_libraries(app PRIVATE app_lib) """) @@ -168,8 +174,10 @@ def _run_app(self, build_type, bin_folder=False, msg="App", dyld_path=None): self.client.run_command(command_str) self.assertIn("Hello: %s" % build_type, self.client.out) self.assertIn("%s: %s!" % (msg, build_type), self.client.out) - self.assertIn("DEFINITIONS_BOTH: True", self.client.out) - self.assertIn("DEFINITIONS_CONFIG: %s" % build_type, self.client.out) + self.assertIn("MYVAR: MYVAR_VALUE", self.client.out) + self.assertIn("MYVAR_CONFIG: MYVAR_%s" % build_type.upper(), self.client.out) + self.assertIn("MYDEFINE: MYDEF_VALUE", self.client.out) + self.assertIn("MYDEFINE_CONFIG: MYDEF_%s" % build_type.upper(), self.client.out) @unittest.skipUnless(platform.system() == "Windows", "Only for windows") @@ -267,6 +275,9 @@ def test_toolchain_linux(self, build_type, cppstd, arch, libcxx, shared): pic_str = "" if shared else "ON" arch_str = "-m32" if arch == "x86" else "-m64" cxx11_abi_str = "1" if libcxx == "libstdc++11" else "0" + defines = '_GLIBCXX_USE_CXX11_ABI=%s;MYDEFINE="MYDEF_VALUE";'\ + 'MYDEFINE_CONFIG=$,"MYDEF_DEBUG",'\ + '$,"MYDEF_RELEASE","">>' % cxx11_abi_str vals = {"CMAKE_CXX_STANDARD": "14", "CMAKE_CXX_EXTENSIONS": extensions_str, "CMAKE_BUILD_TYPE": build_type, @@ -277,8 +288,8 @@ def test_toolchain_linux(self, build_type, cppstd, arch, libcxx, shared): "CMAKE_C_FLAGS_DEBUG": "-g", "CMAKE_C_FLAGS_RELEASE": "-O3 -DNDEBUG", "CMAKE_SHARED_LINKER_FLAGS": arch_str, - "CMAKE_EXE_LINKER_FLAGS": "", - "COMPILE_DEFINITIONS": "_GLIBCXX_USE_CXX11_ABI=%s" % cxx11_abi_str, + "CMAKE_EXE_LINKER_FLAGS": arch_str, + "COMPILE_DEFINITIONS": defines, "CMAKE_POSITION_INDEPENDENT_CODE": pic_str } @@ -326,7 +337,7 @@ def test_toolchain_apple(self, build_type, cppstd, shared): "CMAKE_C_FLAGS_DEBUG": "-g", "CMAKE_C_FLAGS_RELEASE": "-O3 -DNDEBUG", "CMAKE_SHARED_LINKER_FLAGS": "-m64", - "CMAKE_EXE_LINKER_FLAGS": "", + "CMAKE_EXE_LINKER_FLAGS": "-m64", "CMAKE_SKIP_RPATH": "1", "CMAKE_INSTALL_NAME_DIR": "" }