Repository: conan-io/conan
Pull request: [Bugfix] `CMakeDeps` and options for linking
Changelog: Bugfix: CMakeDeps generated `*-data.cmake` was not including properly the set of link flags.
Changelog: Bugfix: CMakeDeps was not populating `INTERFACE_LINK_OPTIONS` to each target.
Closes: https://github.com/conan-io/conan/issues/9936
Docs: omit

- [x] Refer to the issue that supports this Pull Request.
- [x] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [x] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one. 

<sup>**Note:** By default this PR will skip the slower tests and will use a limited set of python versions. Check [here](https://github.com/conan-io/conan/blob/develop/.github/PR_INCREASE_TESTING.md) how to increase the testing level by writing some tags in the current PR body text.</sup>


Problem statement:
[bug] CMakeDeps generated .cmake config files doesn't put linker flags in quotation
<!--
  Please don't forget to update the issue title.
  Include all applicable information to help us reproduce your problem.

  To help us debug your issue please explain:
-->

### Environment Details (include every applicable attribute)
  * Operating System+version: Ubuntu 16.04
  * Compiler+version: GCC 9.3
  * Conan version: 1.40.1
  * Python version: 3.5.2

### Steps to reproduce (Include if Applicable)

Create a package that specifies linker flags, e.g: MyLib

```
def package_info(self):
    self.cpp_info.libs = ["mylib"]
    self.cpp_info.sharedlinkflags = ["-z now", "-z relro"]
    self.cpp_info.exelinkflags = ["-z now", "-z relro"]
```

Then for a package that depends on MyLib we rely on CMakeDeps generator

```
[requires]
mylib/1.0.0

[generators]
CMakeDeps
```

It then has a CMakeLists.txt that uses the library:

```
cmake_minimum_required(VERSION 3.15)
project(CMakeDepsTest C)

# Prefer -config.cmake files to be ConanDeps friendly
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}")

find_package(MYLIB)

add_executable(mytest main.c)
target_link_libraries(mytest mylib::mylib)
```

When building this test application the linker will fail with errors:
```
/usr/bin/ld: warning: -z --library=now ignored.
/usr/bin/ld: cannot find -lrelro
```

Inspecting the linker command line it's clear that the linker args got passed wrong, i.e. CMake thinks they are a list of libraries to be linked in. The reason for this is that the generated file `mylib-release-x86_64-data.cmake` specifies:
```
set(mylib_SHARED_LINK_FLAGS_RELEASE -z now;-z relro)
```
which is treated as a list "-z;now;-z;relro;". Now this further goes wrong because the linker flags are added as libraries in the file `mylib-Target-release.cmake`:
```
set_property(TARGET intel-mpi::intel-mpi
             PROPERTY INTERFACE_LINK_LIBRARIES
             $<$<CONFIG:Release>:${intel-mpi_LIBRARIES_TARGETS_RELEASE}
                                           ${intel-mpi_LINKER_FLAGS_RELEASE}> APPEND)
```
and not populating the `INTERFACE_LINK_OPTIONS` property.

The solution is clear:
1) Make sure `mylib_SHARED_LINK_FLAGS_RELEASE` is set as a list of strings or a single string
2) Set linker flags by populating the `INTERFACE_LINK_OPTIONS` property instead of appending them to the `INTERFACE_LINK_LIBRARIES`.

Hints from issue discussion:
This is true, the ``sharedlinkflags`` is not prepared to contain things with spaces, this has to be fixed.

Please @franramirez688 try the following:

- Lets build a full functional test, that uses ``exelinkflags``, but also ``cxxflags``
- Real test, building something. 
- Only linux
- The dependency defining cpp_info can be empty, only injecting flags
- Use several flags, some using spaces too
- Check the flags are correctly applied both compile and link
Then, changing the cmake to use INTERFACE_LINK_OPTIONS seems easy, and it is compatible CMake 3.15:

```diff
--- a/conan/tools/cmake/cmakedeps/templates/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_configuration.py
@@ -110,8 +110,10 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
         set_property(TARGET {{target_namespace}}::{{global_target_name}}
                      PROPERTY INTERFACE_LINK_LIBRARIES
                      $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LIBRARIES_TARGETS{{config_suffix}}}
-                                                   ${{'{'}}{{pkg_name}}_LINKER_FLAGS{{config_suffix}}}
                                                    ${{'{'}}{{pkg_name}}_OBJECTS{{config_suffix}}}> APPEND)
+        set_property(TARGET {{target_namespace}}::{{global_target_name}}
+                     PROPERTY INTERFACE_LINK_OPTIONS
+                     $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LINKER_FLAGS{{config_suffix}}}> APPEND)
         set_property(TARGET {{target_namespace}}::{{global_target_name}}
                      PROPERTY INTERFACE_INCLUDE_DIRECTORIES
                      $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_INCLUDE_DIRS{{config_suffix}}}> APPEND)
@@ -129,8 +131,9 @@ class TargetConfigurationTemplate(CMakeDepsFileTemplate):
         ########## COMPONENT {{ comp_name }} TARGET PROPERTIES ######################################
         set_property(TARGET {{ target_namespace }}::{{ comp_name }} PROPERTY INTERFACE_LINK_LIBRARIES
                      $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_name, 'LINK_LIBS', config_suffix)}}
-                     {{tvalue(pkg_name, comp_name, 'LINKER_FLAGS', config_suffix)}}
                      {{tvalue(pkg_name, comp_name, 'OBJECTS', config_suffix)}}> APPEND)
+        set_property(TARGET {{ target_namespace }}::{{ comp_name }} PROPERTY INTERFACE_LINK_OPTIONS
+                     $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_name, 'LINKER_FLAGS', config_suffix)}}> APPEND)
         set_property(TARGET {{ target_namespace }}::{{ comp_name }} PROPERTY INTERFACE_INCLUDE_DIRECTORIES
                      $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_name, 'INCLUDE_DIRS', config_suffix)}}> APPEND)
         set_property(TARGET {{ target_namespace }}::{{ comp_name }} PROPERTY INTERFACE_COMPILE_DEFINITIONS
```
Hi @memsharded , thanks for reacting so quickly. Let me know if I can help with anything.

Commit message: Bad indentation

Patch to review:
diff --git a/conan/tools/cmake/cmakedeps/templates/target_configuration.py b/conan/tools/cmake/cmakedeps/templates/target_configuration.py
index 1d094692b1d..60cf4e307e3 100644
--- a/conan/tools/cmake/cmakedeps/templates/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_configuration.py
@@ -110,8 +110,10 @@ def template(self):
         set_property(TARGET {{target_namespace}}::{{global_target_name}}
                      PROPERTY INTERFACE_LINK_LIBRARIES
                      $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LIBRARIES_TARGETS{{config_suffix}}}
-                                                   ${{'{'}}{{pkg_name}}_LINKER_FLAGS{{config_suffix}}}
                                                    ${{'{'}}{{pkg_name}}_OBJECTS{{config_suffix}}}> APPEND)
+        set_property(TARGET {{target_namespace}}::{{global_target_name}}
+                     PROPERTY INTERFACE_LINK_OPTIONS
+                     $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LINKER_FLAGS{{config_suffix}}}> APPEND)
         set_property(TARGET {{target_namespace}}::{{global_target_name}}
                      PROPERTY INTERFACE_INCLUDE_DIRECTORIES
                      $<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_INCLUDE_DIRS{{config_suffix}}}> APPEND)
@@ -129,8 +131,9 @@ def template(self):
         ########## COMPONENT {{ comp_name }} TARGET PROPERTIES ######################################
         set_property(TARGET {{ target_namespace }}::{{ comp_name }} PROPERTY INTERFACE_LINK_LIBRARIES
                      $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_name, 'LINK_LIBS', config_suffix)}}
-                     {{tvalue(pkg_name, comp_name, 'LINKER_FLAGS', config_suffix)}}
                      {{tvalue(pkg_name, comp_name, 'OBJECTS', config_suffix)}}> APPEND)
+        set_property(TARGET {{ target_namespace }}::{{ comp_name }} PROPERTY INTERFACE_LINK_OPTIONS
+                     $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_name, 'LINKER_FLAGS', config_suffix)}}> APPEND)
         set_property(TARGET {{ target_namespace }}::{{ comp_name }} PROPERTY INTERFACE_INCLUDE_DIRECTORIES
                      $<$<CONFIG:{{ configuration }}>:{{tvalue(pkg_name, comp_name, 'INCLUDE_DIRS', config_suffix)}}> APPEND)
         set_property(TARGET {{ target_namespace }}::{{ comp_name }} PROPERTY INTERFACE_COMPILE_DEFINITIONS
diff --git a/conan/tools/cmake/cmakedeps/templates/target_data.py b/conan/tools/cmake/cmakedeps/templates/target_data.py
index 7c2bca54eaa..7bb65943bfb 100644
--- a/conan/tools/cmake/cmakedeps/templates/target_data.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_data.py
@@ -174,10 +174,13 @@ def join_paths(paths):
                     ret.append('"${%s}/%s"' % (pfolder_var_name, norm_path))
             return "\n\t\t\t".join(ret)
 
-        def join_flags(separator, values):
+        def join_flags(separator, values, as_string=False):
             # Flags have to be escaped
-            return separator.join(v.replace('\\', '\\\\').replace('$', '\\$').replace('"', '\\"')
-                                  for v in values)
+            ret = separator.join(v.replace('\\', '\\\\').replace('$', '\\$').replace('"', '\\"')
+                                 for v in values)
+            if ret and as_string is True:
+                ret = '"%s"' % ret
+            return ret
 
         def join_defines(values, prefix=""):
             # Defines have to be escaped, included spaces
@@ -215,8 +218,8 @@ def join_paths_single_var(values):
         # linker flags without magic: trying to mess with - and / =>
         # https://github.com/conan-io/conan/issues/8811
         # frameworks should be declared with cppinfo.frameworks not "-framework Foundation"
-        self.sharedlinkflags_list = join_flags(";", cpp_info.sharedlinkflags)
-        self.exelinkflags_list = join_flags(";", cpp_info.exelinkflags)
+        self.sharedlinkflags_list = join_flags(";", cpp_info.sharedlinkflags, as_string=True)
+        self.exelinkflags_list = join_flags(";", cpp_info.exelinkflags, as_string=True)
 
         self.objects_list = join_paths(cpp_info.objects)
 
diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py
index 2b337fb3496..6b34380cbf0 100644
--- a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py
+++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py
@@ -151,17 +151,12 @@ def package_info(self):
         if build_type == "Release":
             assert "System libs release: %s" % library_name in client.out
             assert "Libraries to Link release: lib1" in client.out
-            target_libs = ("$<$<CONFIG:Release>:CONAN_LIB::Test_lib1_RELEASE;sys1;"
-                           "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:>;"
-                           "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>:>;"
-                           "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:>;>")
+            target_libs = "$<$<CONFIG:Release>:CONAN_LIB::Test_lib1_RELEASE;sys1;"
         else:
             assert "System libs debug: %s" % library_name in client.out
             assert "Libraries to Link debug: lib1" in client.out
-            target_libs = ("$<$<CONFIG:Debug>:CONAN_LIB::Test_lib1_DEBUG;sys1d;"
-                           "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:>;"
-                           "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>:>;"
-                           "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:>;>")
+            target_libs = "$<$<CONFIG:Debug>:CONAN_LIB::Test_lib1_DEBUG;sys1d;"
+
         assert "Target libs: %s" % target_libs in client.out
 
 
diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_and_linker_flags.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_and_linker_flags.py
new file mode 100644
index 00000000000..073f0058659
--- /dev/null
+++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_and_linker_flags.py
@@ -0,0 +1,59 @@
+import os
+import platform
+import textwrap
+
+import pytest
+
+from conans.test.utils.tools import TestClient
+
+
+@pytest.mark.skipif(platform.system() != "Linux", reason="Only Linux")
+@pytest.mark.tool_cmake
+def test_shared_link_flags():
+    """
+    Testing CMakeDeps and linker flags injection
+
+    Issue: https://github.com/conan-io/conan/issues/9936
+    """
+    conanfile = textwrap.dedent("""
+    from conans import ConanFile
+    from conan.tools.cmake import CMake
+    from conan.tools.layout import cmake_layout
+
+
+    class HelloConan(ConanFile):
+        name = "hello"
+        version = "1.0"
+        settings = "os", "compiler", "build_type", "arch"
+        options = {"shared": [True, False]}
+        default_options = {"shared": False}
+        exports_sources = "CMakeLists.txt", "src/*"
+        generators = "CMakeDeps", "CMakeToolchain"
+
+        def layout(self):
+            cmake_layout(self)
+
+        def build(self):
+            cmake = CMake(self)
+            cmake.configure()
+            cmake.build()
+
+        def package(self):
+            cmake = CMake(self)
+            cmake.install()
+
+        def package_info(self):
+            self.cpp_info.libs = ["hello"]
+            self.cpp_info.sharedlinkflags = ["-z now", "-z relro"]
+            self.cpp_info.exelinkflags = ["-z now", "-z relro"]
+    """)
+
+    client = TestClient()
+    client.run("new hello/1.0 -m cmake_lib")
+    client.save({"conanfile.py": conanfile})
+    client.run("create .")
+    t = os.path.join("test_package", "cmake-build-release", "conan", "hello-release-x86_64-data.cmake")
+    target_data_cmake_content = client.load(t)
+    assert 'set(hello_SHARED_LINK_FLAGS_RELEASE "-z now;-z relro")' in target_data_cmake_content
+    assert 'set(hello_EXE_LINK_FLAGS_RELEASE "-z now;-z relro")' in target_data_cmake_content
+    assert "hello/1.0: Hello World Release!" in client.out
diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py
index 2cc2fc761b2..c99348f1d27 100644
--- a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py
+++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py
@@ -199,17 +199,19 @@ def build(self):
         project(consumer)
         cmake_minimum_required(VERSION 3.1)
         find_package(requirement)
-        get_target_property(tmp requirement::component INTERFACE_LINK_LIBRARIES)
-        message("component libs: ${tmp}")
+        get_target_property(tmp_libs requirement::component INTERFACE_LINK_LIBRARIES)
+        get_target_property(tmp_options requirement::component INTERFACE_LINK_OPTIONS)
+        message("component libs: ${tmp_libs}")
+        message("component options: ${tmp_options}")
     """)
 
     t.save({"conanfile.py": conanfile, "CMakeLists.txt": cmakelists})
     t.run("create . --build missing -s build_type=Release")
-
-    assert ("component libs: "
-            "$<$<CONFIG:Release>:system_lib_component;"
-            "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:>;"
-            "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>:>;"
-            "$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:>;>") in t.out
+    assert 'component libs: $<$<CONFIG:Release>:system_lib_component;>' in t.out
+    assert ('component options: '
+            '$<$<CONFIG:Release>:'
+            '$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:>;'
+            '$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>:>;'
+            '$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:>>') in t.out
     # NOTE: If there is no "conan install -s build_type=Debug", the properties won't contain the
     #       <CONFIG:Debug>
diff --git a/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
index b51b53482db..ef90bb2f5ed 100644
--- a/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
+++ b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
@@ -104,12 +104,10 @@ def package_info(self):
         content = f.read()
         assert """set_property(TARGET hello::say PROPERTY INTERFACE_LINK_LIBRARIES
              $<$<CONFIG:Release>:${hello_say_LINK_LIBS_RELEASE}
-             ${hello_say_LINKER_FLAGS_RELEASE}
              ${hello_say_OBJECTS_RELEASE}> APPEND)""" in content
         assert """set_property(TARGET hello::hello
              PROPERTY INTERFACE_LINK_LIBRARIES
              $<$<CONFIG:Release>:${hello_LIBRARIES_TARGETS_RELEASE}
-                                           ${hello_LINKER_FLAGS_RELEASE}
                                            ${hello_OBJECTS_RELEASE}> APPEND)""" in content
 
     with open(os.path.join(client.current_folder, "hello-release-x86_64-data.cmake")) as f:
diff --git a/conans/test/unittests/tools/cmake/test_cmakedeps.py b/conans/test/unittests/tools/cmake/test_cmakedeps.py
index 9995eee7517..1284b655af4 100644
--- a/conans/test/unittests/tools/cmake/test_cmakedeps.py
+++ b/conans/test/unittests/tools/cmake/test_cmakedeps.py
@@ -116,8 +116,8 @@ def test_cmake_deps_links_flags():
         cmakedeps = CMakeDeps(conanfile)
         files = cmakedeps.content
         data_cmake = files["mypkg-release-x86-data.cmake"]
-        assert "set(mypkg_SHARED_LINK_FLAGS_RELEASE -NODEFAULTLIB;-OTHERFLAG)" in data_cmake
-        assert "set(mypkg_EXE_LINK_FLAGS_RELEASE -OPT:NOICF)" in data_cmake
+        assert 'set(mypkg_SHARED_LINK_FLAGS_RELEASE "-NODEFAULTLIB;-OTHERFLAG")' in data_cmake
+        assert 'set(mypkg_EXE_LINK_FLAGS_RELEASE "-OPT:NOICF")' in data_cmake
         assert 'set(mypkg_OBJECTS_RELEASE "${mypkg_PACKAGE_FOLDER_RELEASE}/myobject.o")' in data_cmake
 
 


Code review comments:
diff_hunk: @@ -215,8 +218,8 @@ def join_paths_single_var(values):
         # linker flags without magic: trying to mess with - and / =>
         # https://github.com/conan-io/conan/issues/8811
         # frameworks should be declared with cppinfo.frameworks not "-framework Foundation"
-        self.sharedlinkflags_list = join_flags(";", cpp_info.sharedlinkflags)
-        self.exelinkflags_list = join_flags(";", cpp_info.exelinkflags)
+        self.sharedlinkflags_list = join_flags(";", cpp_info.sharedlinkflags, as_string=True)
+        self.exelinkflags_list = join_flags(";", cpp_info.exelinkflags, as_string=True)
path: conan/tools/cmake/cmakedeps/templates/target_data.py
text: @user1:
```suggestion
        self.exelinkflags_list = '"{}"'.format(join_flags(";", cpp_info.exelinkflags))
```

diff_hunk: @@ -215,8 +218,8 @@ def join_paths_single_var(values):
         # linker flags without magic: trying to mess with - and / =>
         # https://github.com/conan-io/conan/issues/8811
         # frameworks should be declared with cppinfo.frameworks not "-framework Foundation"
-        self.sharedlinkflags_list = join_flags(";", cpp_info.sharedlinkflags)
-        self.exelinkflags_list = join_flags(";", cpp_info.exelinkflags)
+        self.sharedlinkflags_list = join_flags(";", cpp_info.sharedlinkflags, as_string=True)
path: conan/tools/cmake/cmakedeps/templates/target_data.py
text: @user1:
I would remove the `as_string` and quote the final string in the assignation directly.

diff_hunk: @@ -215,8 +218,8 @@ def join_paths_single_var(values):
         # linker flags without magic: trying to mess with - and / =>
         # https://github.com/conan-io/conan/issues/8811
         # frameworks should be declared with cppinfo.frameworks not "-framework Foundation"
-        self.sharedlinkflags_list = join_flags(";", cpp_info.sharedlinkflags)
-        self.exelinkflags_list = join_flags(";", cpp_info.exelinkflags)
+        self.sharedlinkflags_list = join_flags(";", cpp_info.sharedlinkflags, as_string=True)
path: conan/tools/cmake/cmakedeps/templates/target_data.py
text: @user1:
```suggestion
        self.sharedlinkflags_list = '"{}"'.format(join_flags(";", cpp_info.sharedlinkflags))
```