Repository: conan-io/conan

Issue:
CMake generator creates invalid CMake files if cflags/linker flags have backslashes
To help us debug your issue please explain:

- [X] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [X] I've specified the Conan version, operating system version and any tool that can be relevant.
- [X] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.

Conan 1.2.0-dev

Generated CMake files are not properly escaped if c(pp)flags/linkflags contain backslashes. This results in a CMake error when a flag requires this inside of it. The package cannot perform the escape itself because it cannot assume the consumer will use CMake (a non-CMake consumer would need to un-escape the flags, in that case).

This is similar to the problem already fixed in #1640 but in a different place.

I already have a fix for this issue; I will create an appropriate PR. However, this change is possibly breaking because packages running into this issue may have assumed their consumers use CMake and worked around it by manually escaping inside of cpp_info. However, I have made a cursory check on conan-center and my sample did not encounter any packages running into this problem.



Discussion:

Resolving patch:
diff --git a/conans/client/generators/cmake.py b/conans/client/generators/cmake.py
--- a/conans/client/generators/cmake.py
+++ b/conans/client/generators/cmake.py
@@ -7,8 +7,17 @@
 
 class DepsCppCmake(object):
     def __init__(self, cpp_info):
+        def escape(value, path=False):
+            """Escapes the specified string for use in a CMake command surrounded with double quotes
+                :param value the string to escape
+                :param path True if this should be treated as a path (backslash will be transformed to forward slash)
+                                 or False otherwise (backslash will be transformed to double backslash)"""
+            return '{0}'.format(value.replace('\\', '/' if path else '\\\\')
+                                     .replace('$', '\\$')
+                                     .replace('"', '\\"'))
+
         def multiline(field):
-            return "\n\t\t\t".join('"%s"' % p.replace("\\", "/") for p in field)
+            return "\n\t\t\t".join('"%s"' % escape(p, True) for p in field)
 
         self.include_paths = multiline(cpp_info.include_paths)
         self.lib_paths = multiline(cpp_info.lib_paths)
@@ -16,24 +25,24 @@ def multiline(field):
         self.bin_paths = multiline(cpp_info.bin_paths)
         self.build_paths = multiline(cpp_info.build_paths)
 
-        self.libs = " ".join(cpp_info.libs)
+        self.libs = escape(" ".join(cpp_info.libs))
         self.defines = "\n\t\t\t".join("-D%s" % d for d in cpp_info.defines)
         self.compile_definitions = "\n\t\t\t".join(cpp_info.defines)
 
-        self.cppflags = " ".join(cpp_info.cppflags)
-        self.cflags = " ".join(cpp_info.cflags)
-        self.sharedlinkflags = " ".join(cpp_info.sharedlinkflags)
-        self.exelinkflags = " ".join(cpp_info.exelinkflags)
+        self.cppflags = escape(" ".join(cpp_info.cppflags))
+        self.cflags = escape(" ".join(cpp_info.cflags))
+        self.sharedlinkflags = escape(" ".join(cpp_info.sharedlinkflags))
+        self.exelinkflags = escape(" ".join(cpp_info.exelinkflags))
 
         # For modern CMake targets we need to prepare a list to not
         # loose the elements in the list by replacing " " with ";". Example "-framework Foundation"
         # Issue: #1251
-        self.cppflags_list = ";".join(cpp_info.cppflags)
-        self.cflags_list = ";".join(cpp_info.cflags)
-        self.sharedlinkflags_list = ";".join(cpp_info.sharedlinkflags)
-        self.exelinkflags_list = ";".join(cpp_info.exelinkflags)
+        self.cppflags_list = escape(";".join(cpp_info.cppflags))
+        self.cflags_list = escape(";".join(cpp_info.cflags))
+        self.sharedlinkflags_list = escape(";".join(cpp_info.sharedlinkflags))
+        self.exelinkflags_list = escape(";".join(cpp_info.exelinkflags))
 
-        self.rootpath = '"%s"' % cpp_info.rootpath.replace("\\", "/")
+        self.rootpath = '"%s"' % escape(cpp_info.rootpath, True)
 
 
 class CMakeGenerator(Generator):


Test patch:
diff --git a/conans/test/generators/cmake_test.py b/conans/test/generators/cmake_test.py
--- a/conans/test/generators/cmake_test.py
+++ b/conans/test/generators/cmake_test.py
@@ -89,6 +89,20 @@ def multi_flag_test(self):
         self.assertIn('set(CONAN_CXX_FLAGS "-DGTEST_USE_OWN_TR1_TUPLE=1'
                       ' -DGTEST_LINKED_AS_SHARED_LIBRARY=1 ${CONAN_CXX_FLAGS}")', cmake_lines)
 
+    def escaped_flags_test(self):
+        conanfile = ConanFile(None, None, Settings({}), None)
+        ref = ConanFileReference.loads("MyPkg/0.1@lasote/stables")
+        cpp_info = CppInfo("dummy_root_folder1")
+        cpp_info.includedirs.append("other_include_dir")
+        cpp_info.cppflags = ["-load", r"C:\foo\bar.dll"]
+        cpp_info.cflags = ["-load", r"C:\foo\bar2.dll"]
+        conanfile.deps_cpp_info.update(cpp_info, ref.name)
+        generator = CMakeGenerator(conanfile)
+        content = generator.content
+        cmake_lines = content.splitlines()
+        self.assertIn(r'set(CONAN_C_FLAGS_MYPKG "-load C:\\foo\\bar2.dll")', cmake_lines)
+        self.assertIn(r'set(CONAN_CXX_FLAGS_MYPKG "-load C:\\foo\\bar.dll")', cmake_lines)
+
     def aux_cmake_test_setup_test(self):
         conanfile = ConanFile(None, None, Settings({}), None)
         generator = CMakeGenerator(conanfile)