From 7d93ccc5c77b9c5829162c11b52bdbaaf3e9bcb6 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 14 Feb 2024 12:23:59 +0100 Subject: [PATCH] Feature/cmaketoolchain multi flags (#15654) * cmaketoolchain multi flags * wip * CMakeToolchain multi-config tools.build:xxxx --- conan/tools/cmake/toolchain/blocks.py | 74 ++++++++++++++-- .../toolchains/cmake/test_cmake_toolchain.py | 84 +++++++++++++++++++ .../cmake/test_cmake_toolchain_win_clang.py | 2 +- 3 files changed, 153 insertions(+), 7 deletions(-) diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 455cc5743c9..0a1fcabeb74 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -12,6 +12,7 @@ from conan.tools.build.flags import architecture_flag, libcxx_flags from conan.tools.build.cross_building import cross_building from conan.tools.cmake.toolchain import CONAN_TOOLCHAIN_FILENAME +from conan.tools.cmake.utils import is_multi_configuration from conan.tools.intel import IntelCC from conan.tools.microsoft.visual import msvc_version_to_toolset_version from conans.client.subsystems import deduce_subsystem, WINDOWS @@ -544,25 +545,62 @@ def context(self): class ExtraFlagsBlock(Block): """This block is adding flags directly from user [conf] section""" - template = textwrap.dedent(""" - # Extra c, cxx, linkflags and defines + _template = textwrap.dedent(""" + # Conan conf flags start: {{config}} {% if cxxflags %} - string(APPEND CONAN_CXX_FLAGS "{% for cxxflag in cxxflags %} {{ cxxflag }}{% endfor %}") + string(APPEND CONAN_CXX_FLAGS{{suffix}} "{% for cxxflag in cxxflags %} {{ cxxflag }}{% endfor %}") {% endif %} {% if cflags %} - string(APPEND CONAN_C_FLAGS "{% for cflag in cflags %} {{ cflag }}{% endfor %}") + string(APPEND CONAN_C_FLAGS{{suffix}} "{% for cflag in cflags %} {{ cflag }}{% endfor %}") {% endif %} {% if sharedlinkflags %} - string(APPEND CONAN_SHARED_LINKER_FLAGS "{% for sharedlinkflag in sharedlinkflags %} {{ sharedlinkflag }}{% endfor %}") + string(APPEND CONAN_SHARED_LINKER_FLAGS{{suffix}} "{% for sharedlinkflag in sharedlinkflags %} {{ sharedlinkflag }}{% endfor %}") {% endif %} {% if exelinkflags %} - string(APPEND CONAN_EXE_LINKER_FLAGS "{% for exelinkflag in exelinkflags %} {{ exelinkflag }}{% endfor %}") + string(APPEND CONAN_EXE_LINKER_FLAGS{{suffix}} "{% for exelinkflag in exelinkflags %} {{ exelinkflag }}{% endfor %}") {% endif %} {% if defines %} + {% if config %} + add_compile_definitions($<$:{% for define in defines %}" {{ define }}"{% endfor %}>) + {% else %} add_compile_definitions({% for define in defines %} "{{ define }}"{% endfor %}) {% endif %} + {% endif %} + # Conan conf flags end """) + @property + def template(self): + if not is_multi_configuration(self._toolchain.generator): + return self._template + + sections = {} + if os.path.exists(CONAN_TOOLCHAIN_FILENAME): + existing_toolchain = load(CONAN_TOOLCHAIN_FILENAME) + lines = existing_toolchain.splitlines() + current_section = None + for line in lines: + if line.startswith("# Conan conf flags start: "): + section_name = line.split(":", 1)[1].strip() + current_section = [line] + sections[section_name] = current_section + elif line == "# Conan conf flags end": + current_section.append(line) + current_section = None + elif current_section is not None: + current_section.append(line) + sections.pop("", None) # Just in case it had a single config before + + config = self._conanfile.settings.get_safe("build_type") + for k, v in sections.items(): + if k != config: + v.insert(0, "{% raw %}") + v.append("{% endraw %}") + sections[config] = [self._template] + sections = ["\n".join(lines) for lines in sections.values()] + sections = "\n".join(sections) + return sections + def context(self): # Now, it's time to get all the flags defined by the user cxxflags = self._toolchain.extra_cxxflags + self._conanfile.conf.get("tools.build:cxxflags", default=[], check_type=list) @@ -579,7 +617,14 @@ def context(self): self._conanfile.output.warning("tools.build:cxxflags or cflags are defined, but Android NDK toolchain may be overriding " "the values. Consider setting tools.android:cmake_legacy_toolchain to False.") + config = "" + suffix = "" + if is_multi_configuration(self._toolchain.generator): + config = self._conanfile.settings.get_safe("build_type") + suffix = f"_{config.upper()}" if config else "" return { + "config": config, + "suffix": suffix, "cxxflags": cxxflags, "cflags": cflags, "sharedlinkflags": sharedlinkflags, @@ -590,6 +635,22 @@ def context(self): class CMakeFlagsInitBlock(Block): template = textwrap.dedent(""" + foreach(config ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${config} config) + if(DEFINED CONAN_CXX_FLAGS_${config}) + string(APPEND CMAKE_CXX_FLAGS_${config}_INIT " ${CONAN_CXX_FLAGS_${config}}") + endif() + if(DEFINED CONAN_C_FLAGS_${config}) + string(APPEND CMAKE_C_FLAGS_${config}_INIT " ${CONAN_C_FLAGS_${config}}") + endif() + if(DEFINED CONAN_SHARED_LINKER_FLAGS_${config}) + string(APPEND CMAKE_SHARED_LINKER_FLAGS_${config}_INIT " ${CONAN_SHARED_LINKER_FLAGS_${config}}") + endif() + if(DEFINED CONAN_EXE_LINKER_FLAGS_${config}) + string(APPEND CMAKE_EXE_LINKER_FLAGS_${config}_INIT " ${CONAN_EXE_LINKER_FLAGS_${config}}") + endif() + endforeach() + if(DEFINED CONAN_CXX_FLAGS) string(APPEND CMAKE_CXX_FLAGS_INIT " ${CONAN_CXX_FLAGS}") endif() @@ -602,6 +663,7 @@ class CMakeFlagsInitBlock(Block): if(DEFINED CONAN_EXE_LINKER_FLAGS) string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT " ${CONAN_EXE_LINKER_FLAGS}") endif() + """) diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index 7a3d7054f07..e92fea6acdc 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -1686,3 +1686,87 @@ def generate(self): c.run_command(f"ctest --preset conan-release") assert "tests passed" in c.out + + +@pytest.mark.tool("cmake") +@pytest.mark.skipif(platform.system() != "Windows", reason="neeed multi-config") +def test_cmake_toolchain_cxxflags_multi_config(): + c = TestClient() + profile_release = textwrap.dedent(r""" + include(default) + [conf] + tools.build:defines=["answer=42"] + tools.build:cxxflags=["/Zc:__cplusplus"] + """) + profile_debug = textwrap.dedent(r""" + include(default) + [settings] + build_type=Debug + [conf] + tools.build:defines=["answer=123"] + tools.build:cxxflags=["/W4"] + """) + + conanfile = textwrap.dedent(r''' + from conan import ConanFile + from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout + + class Test(ConanFile): + exports_sources = "CMakeLists.txt", "src/*" + settings = "os", "compiler", "arch", "build_type" + generators = "CMakeToolchain" + + def layout(self): + cmake_layout(self) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + ''') + + main = textwrap.dedent(r""" + #include + #include + + #define STR(x) #x + #define SHOW_DEFINE(x) printf("%s=%s\n", #x, STR(x)) + + int main() { + SHOW_DEFINE(answer); + char a = 123L; // to trigger warnings + + #if __cplusplus + std::cout << "CPLUSPLUS: __cplusplus" << __cplusplus<< "\n"; + #endif + } + """) + + cmakelists = textwrap.dedent(""" + cmake_minimum_required(VERSION 3.15) + project(Test CXX) + add_executable(example src/main.cpp) + """) + + c.save({"conanfile.py": conanfile, + "profile_release": profile_release, + "profile_debug": profile_debug, + "src/main.cpp": main, + "CMakeLists.txt": cmakelists}, clean_first=True) + c.run("install . -pr=./profile_release") + c.run("install . -pr=./profile_debug") + + with c.chdir("build"): + c.run_command("cmake .. -DCMAKE_TOOLCHAIN_FILE=generators/conan_toolchain.cmake") + c.run_command("cmake --build . --config Release") + assert "warning C4189" not in c.out + c.run_command("cmake --build . --config Debug") + assert "warning C4189" in c.out + + c.run_command(r"build\Release\example.exe") + assert 'answer=42' in c.out + assert "CPLUSPLUS: __cplusplus20" in c.out + + c.run_command(r"build\Debug\example.exe") + assert 'answer=123' in c.out + assert "CPLUSPLUS: __cplusplus19" in c.out diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain_win_clang.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain_win_clang.py index b09dccd59a6..c2fcd265e39 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain_win_clang.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain_win_clang.py @@ -155,7 +155,7 @@ def test_clang_visual_studio_generator(self, client): '-c tools.cmake.cmaketoolchain:generator="{}"'.format(generator)) assert 'cmake -G "{}"'.format(generator) in client.out assert "MSVC-like command-line" in client.out - assert "main __clang_major__14" in client.out + assert "main __clang_major__16" in client.out # Check this! Clang compiler in Windows is reporting MSC_VER and MSVC_LANG! assert "main _MSC_VER193" in client.out assert "main _MSVC_LANG2017" in client.out