Repository: conan-io/conan

Issue:
cmake_multi basic setup
The macro `macro(conan_basic_setup)` is not managing the same arguments than the regular cmake generator, even not calling the std adjustements (`cmake_common.py`)




Discussion:
This is also happening for us. When building with Xcode and a `cmake_multi` setup, the RPATH variables are belong lost, because the `KEEP_RPATHS` argument isn't handled.

Is there any reason that the `conan_basic_setup` macro has to be different for `cmake_multi`?

We're doing this in our `CMakeLists.txt`:

```
# Executables are built with relative RPATH support, so the dynamic libs are
# found either together with the executable, or in the adjacent lib directory.
# This aligns with the Filesystem Hierarchy Standard.
# Necessary so that if building with dynamic libraries, the tests work.
# See: https://docs.conan.io/en/latest/howtos/manage_shared_libraries/rpaths.html#different-approaches
# See: https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling
# See: http://www.pathname.com/fhs/pub/fhs-2.3.html
conan_basic_setup(TARGETS KEEP_RPATHS)
if (APPLE)
  set(CMAKE_INSTALL_RPATH "@executable_path;@executable_path/../lib")
else()
  set(CMAKE_INSTALL_RPATH "$ORIGIN;$ORIGIN/../lib")
endif()
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
```

Resolving patch:
diff --git a/conans/client/generators/cmake_common.py b/conans/client/generators/cmake_common.py
--- a/conans/client/generators/cmake_common.py
+++ b/conans/client/generators/cmake_common.py
@@ -20,7 +20,6 @@
 set(CONAN_CXX_FLAGS_{dep}{build_type}_LIST "{deps.cxxflags_list}")
 set(CONAN_SHARED_LINKER_FLAGS_{dep}{build_type}_LIST "{deps.sharedlinkflags_list}")
 set(CONAN_EXE_LINKER_FLAGS_{dep}{build_type}_LIST "{deps.exelinkflags_list}")
-
 """
 
 
@@ -141,7 +140,7 @@ def cmake_global_vars(deps, build_type=""):
                                                                   $<$<CONFIG:RelWithDebInfo>:${{CONAN_C_FLAGS_{uname}_RELEASE_LIST}} ${{CONAN_CXX_FLAGS_{uname}_RELEASE_LIST}}>
                                                                   $<$<CONFIG:MinSizeRel>:${{CONAN_C_FLAGS_{uname}_RELEASE_LIST}} ${{CONAN_CXX_FLAGS_{uname}_RELEASE_LIST}}>
                                                                   $<$<CONFIG:Debug>:${{CONAN_C_FLAGS_{uname}_DEBUG_LIST}}  ${{CONAN_CXX_FLAGS_{uname}_DEBUG_LIST}}>)
- """
+"""
 
 
 def generate_targets_section(dependencies):
@@ -168,7 +167,6 @@ def generate_targets_section(dependencies):
 
 
 _cmake_common_macros = """
-
 function(conan_message MESSAGE_OUTPUT)
     if(NOT CONAN_CMAKE_SILENT_OUTPUT)
         message(${ARGV${0}})
@@ -519,21 +517,33 @@ def generate_targets_section(dependencies):
 endmacro()
 """
 
-cmake_macros = """
+
+def _conan_basic_setup_common(addtional_macros, cmake_multi=False):
+    output_dirs_section = """
+    if(NOT ARGUMENTS_NO_OUTPUT_DIRS)
+        conan_message(STATUS "Conan: Adjusting output directories")
+        conan_output_dirs_setup()
+    endif()"""
+
+    output_dirs_multi_section = """
+    if(ARGUMENTS_NO_OUTPUT_DIRS)
+        conan_message(WARNING "Conan: NO_OUTPUT_DIRS has no effect with cmake_multi generator")
+    endif()"""
+
+    main_section = """
 macro(conan_basic_setup)
     set(options TARGETS NO_OUTPUT_DIRS SKIP_RPATH KEEP_RPATHS SKIP_STD SKIP_FPIC)
     cmake_parse_arguments(ARGUMENTS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
+
     if(CONAN_EXPORTED)
         conan_message(STATUS "Conan: called by CMake conan helper")
     endif()
+
     if(CONAN_IN_LOCAL_CACHE)
         conan_message(STATUS "Conan: called inside local cache")
     endif()
-    conan_check_compiler()
-    if(NOT ARGUMENTS_NO_OUTPUT_DIRS)
-        conan_output_dirs_setup()
-    endif()
-    conan_set_find_library_paths()
+%%OUTPUT_DIRS_SECTION%%
+
     if(NOT ARGUMENTS_TARGETS)
         conan_message(STATUS "Conan: Using cmake global configuration")
         conan_global_flags()
@@ -541,27 +551,41 @@ def generate_targets_section(dependencies):
         conan_message(STATUS "Conan: Using cmake targets configuration")
         conan_define_targets()
     endif()
+
     if(ARGUMENTS_SKIP_RPATH)
         # Change by "DEPRECATION" or "SEND_ERROR" when we are ready
         conan_message(WARNING "Conan: SKIP_RPATH is deprecated, it has been renamed to KEEP_RPATHS")
     endif()
+
     if(NOT ARGUMENTS_SKIP_RPATH AND NOT ARGUMENTS_KEEP_RPATHS)
         # Parameter has renamed, but we keep the compatibility with old SKIP_RPATH
         conan_message(STATUS "Conan: Adjusting default RPATHs Conan policies")
         conan_set_rpath()
     endif()
+
     if(NOT ARGUMENTS_SKIP_STD)
         conan_message(STATUS "Conan: Adjusting language standard")
         conan_set_std()
     endif()
+
     if(NOT ARGUMENTS_SKIP_FPIC)
         conan_set_fpic()
     endif()
-    conan_set_vs_runtime()
+
+    conan_check_compiler()
     conan_set_libcxx()
+    conan_set_vs_runtime()
     conan_set_find_paths()
+    %%INVOKE_MACROS%%
 endmacro()
+"""
+    result = main_section.replace("%%OUTPUT_DIRS_SECTION%%",
+                                  output_dirs_multi_section if cmake_multi else output_dirs_section)
+    result = result.replace("%%INVOKE_MACROS%%", "\n    ".join(addtional_macros))
+    return result
+
 
+cmake_macros = _conan_basic_setup_common(["conan_set_find_library_paths()"]) + """
 macro(conan_set_find_paths)
     # CMAKE_MODULE_PATH does not have Debug/Release config, but there are variables
     # CONAN_CMAKE_MODULE_PATH_DEBUG to be used by the consumer
@@ -617,47 +641,21 @@ def generate_targets_section(dependencies):
     conan_set_vs_runtime()
     conan_set_libcxx()
 endmacro()
-
 """ + _cmake_common_macros
 
-
 cmake_macros_multi = """
 if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/conanbuildinfo_release.cmake)
     include(${CMAKE_CURRENT_LIST_DIR}/conanbuildinfo_release.cmake)
 else()
     message(FATAL_ERROR "No conanbuildinfo_release.cmake, please install the Release conf first")
 endif()
+
 if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/conanbuildinfo_debug.cmake)
     include(${CMAKE_CURRENT_LIST_DIR}/conanbuildinfo_debug.cmake)
 else()
     message(FATAL_ERROR "No conanbuildinfo_debug.cmake, please install the Debug conf first")
 endif()
 
-macro(conan_basic_setup)
-    set(options TARGETS)
-    cmake_parse_arguments(ARGUMENTS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
-    if(CONAN_EXPORTED)
-        conan_message(STATUS "Conan: called by CMake conan helper")
-    endif()
-    if(CONAN_IN_LOCAL_CACHE)
-        conan_message(STATUS "Conan: called inside local cache")
-    endif()
-    conan_check_compiler()
-    # conan_output_dirs_setup()
-    if(NOT ARGUMENTS_TARGETS)
-        conan_message(STATUS "Conan: Using cmake global configuration")
-        conan_global_flags()
-    else()
-        conan_message(STATUS "Conan: Using cmake targets configuration")
-        conan_define_targets()
-    endif()
-    conan_set_rpath()
-    conan_set_vs_runtime()
-    conan_set_libcxx()
-    conan_set_find_paths()
-    conan_set_fpic()
-endmacro()
-
 macro(conan_set_vs_runtime)
     # This conan_set_vs_runtime is MORE opinionated than the regular one. It will
     # Leave the defaults MD (MDd) or replace them with MT (MTd) but taking into account the
@@ -690,4 +688,4 @@ def generate_targets_section(dependencies):
         endif()
     endif()
 endmacro()
-""" + _cmake_common_macros
+""" + _conan_basic_setup_common([], cmake_multi=True) + _cmake_common_macros


Test patch:
diff --git a/conans/test/functional/generators/cmake_multi_test.py b/conans/test/functional/generators/cmake_multi_test.py
--- a/conans/test/functional/generators/cmake_multi_test.py
+++ b/conans/test/functional/generators/cmake_multi_test.py
@@ -1,10 +1,11 @@
 import os
 import platform
+import textwrap
 import unittest
 
 from nose.plugins.attrib import attr
 
-from conans.client import tools
+from conans.client.tools import chdir, load
 from conans.test.utils.multi_config import multi_config_files
 from conans.test.utils.tools import TestClient
 
@@ -96,6 +97,7 @@ def package_files(name, deps=None):
 set(CMAKE_FIND_ROOT_PATH "/some/path")
 set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
 include(${CMAKE_BINARY_DIR}/conanbuildinfo_multi.cmake)
+
 conan_basic_setup()
 
 add_executable(say_hello main.cpp)
@@ -184,9 +186,8 @@ class HelloConan(ConanFile):
             self.assertIn("FIND HELLO RELEASE!", client.user_io.out)
             self.assertNotIn("FIND HELLO DEBUG!", client.user_io.out)
 
+    @unittest.skipUnless(platform.system() in ["Windows", "Darwin"], "Exclude Linux")
     def cmake_multi_test(self):
-        if platform.system() not in ["Windows", "Darwin"]:
-            return
         client = TestClient()
 
         client.save(multi_config_files("Hello0", test=False), clean_first=True)
@@ -235,3 +236,39 @@ def cmake_multi_test(self):
                 self.assertIn("Conan: Using cmake targets configuration", client.user_io.out)
             else:
                 self.assertIn("Conan: Using cmake global configuration", client.user_io.out)
+
+
+class CMakeMultiSyntaxTest(unittest.TestCase):
+
+    def setUp(self):
+        self.client = TestClient()
+        cmakelists = textwrap.dedent("""
+            cmake_minimum_required(VERSION 3.12)
+            include(${CMAKE_BINARY_DIR}/conanbuildinfo_multi.cmake)
+            conan_basic_setup(NO_OUTPUT_DIRS)
+            """)
+        self.client.save({"conanfile.txt": "[generators]\ncmake_multi\ncmake",
+                          "CMakeLists.txt": cmakelists})
+        self.client.run("install .")
+        self.client.run("install . -s build_type=Debug")
+
+    def conan_basic_setup_interface_test(self):
+        """
+        Check conan_basic_setup() interface is the same one for cmake and cmake_multi generators
+        """
+        conanbuildinfo = load(os.path.join(self.client.current_folder, "conanbuildinfo.cmake"))
+        conanbuildinfo_multi = load(os.path.join(self.client.current_folder,
+                                                 "conanbuildinfo_multi.cmake"))
+        expected = "set(options TARGETS NO_OUTPUT_DIRS SKIP_RPATH KEEP_RPATHS SKIP_STD SKIP_FPIC)"
+        self.assertIn(expected, conanbuildinfo)
+        self.assertIn(expected, conanbuildinfo_multi)
+
+    def conan_basic_setup_output_dirs_warning_test(self):
+        """
+        Check warning when suing NO_OUTPUT_DIRS
+        """
+        with chdir(self.client.current_folder):
+            self.client.runner("cmake .")
+        self.assertTrue("CMake Warning at conanbuildinfo_multi.cmake", self.client.out)
+        self.assertTrue("Conan: NO_OUTPUT_DIRS has no effect with cmake_multi generator",
+                        self.client.out)
diff --git a/conans/test/unittests/client/generators/cmake_test.py b/conans/test/unittests/client/generators/cmake_test.py
--- a/conans/test/unittests/client/generators/cmake_test.py
+++ b/conans/test/unittests/client/generators/cmake_test.py
@@ -169,17 +169,20 @@ def aux_cmake_test_setup_test(self):
         self.assertEqual("""macro(conan_basic_setup)
     set(options TARGETS NO_OUTPUT_DIRS SKIP_RPATH KEEP_RPATHS SKIP_STD SKIP_FPIC)
     cmake_parse_arguments(ARGUMENTS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
+
     if(CONAN_EXPORTED)
         conan_message(STATUS "Conan: called by CMake conan helper")
     endif()
+
     if(CONAN_IN_LOCAL_CACHE)
         conan_message(STATUS "Conan: called inside local cache")
     endif()
-    conan_check_compiler()
+
     if(NOT ARGUMENTS_NO_OUTPUT_DIRS)
+        conan_message(STATUS "Conan: Adjusting output directories")
         conan_output_dirs_setup()
     endif()
-    conan_set_find_library_paths()
+
     if(NOT ARGUMENTS_TARGETS)
         conan_message(STATUS "Conan: Using cmake global configuration")
         conan_global_flags()
@@ -187,25 +190,32 @@ def aux_cmake_test_setup_test(self):
         conan_message(STATUS "Conan: Using cmake targets configuration")
         conan_define_targets()
     endif()
+
     if(ARGUMENTS_SKIP_RPATH)
         # Change by "DEPRECATION" or "SEND_ERROR" when we are ready
         conan_message(WARNING "Conan: SKIP_RPATH is deprecated, it has been renamed to KEEP_RPATHS")
     endif()
+
     if(NOT ARGUMENTS_SKIP_RPATH AND NOT ARGUMENTS_KEEP_RPATHS)
         # Parameter has renamed, but we keep the compatibility with old SKIP_RPATH
         conan_message(STATUS "Conan: Adjusting default RPATHs Conan policies")
         conan_set_rpath()
     endif()
+
     if(NOT ARGUMENTS_SKIP_STD)
         conan_message(STATUS "Conan: Adjusting language standard")
         conan_set_std()
     endif()
+
     if(NOT ARGUMENTS_SKIP_FPIC)
         conan_set_fpic()
     endif()
-    conan_set_vs_runtime()
+
+    conan_check_compiler()
     conan_set_libcxx()
+    conan_set_vs_runtime()
     conan_set_find_paths()
+    conan_set_find_library_paths()
 endmacro()""", macro)
 
         # extract the conan_set_find_paths macro