Repository: conan-io/conan

Issue:
cmake_paths generator broken for transitive dependencies
The **cmake_paths** generator currently doesn't handle transitive dependencies for Conan packages. I am not talking about the _requires_ option, but for transitive dependencies in a package itself.

Consider the following simple conanfile.txt configuration:
```
[requires]
opencv/4.0.1@conan/stable

[generators]
cmake_paths
```

Since the cmake_paths generator only declares the `CMAKE_MODULE_PATH` and `CMAKE_PREFIX_PATH` variables, a client is currently forced to call conan as follows:
```
$ mkdir build && cd build
$ conan install --build=missing ..
$ cmake \
    -DCMAKE_TOOLCHAIN_FILE=conan_paths.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    -DCONAN_LIBJPEG_ROOT:PATH=/Users/NewProggie/.conan/data/libjpeg/9c/bincrafters/stable/package/560b128b7f18cdc8f77f23c6834ffad4a/ \
    -DCONAN_ZLIB_ROOT:PATH=/Users/NewProggie/.conan/data/zlib/1.2.11/conan/stable/package/560b128b7f18cdc8ff080cad77f34ffad4a/ \
    -DCONAN_LIBPNG_ROOT:PATH=/Users/NewProggie/.conan/data/libpng/1.6.34/bincrafters/stable/package/2fcedcd518d74d31f30df6a210eeb54d/ \
    -DCONAN_LIBTIFF_ROOT:PATH=/Users/NewProggie/.conan/data/libtiff/4.0.9/bincrafters/stable/package/2fcedcd518d74d31f90df6a210eeb54d/ \
    ..
```
If a client doesn't add the `CONAN_*_ROOT` variables, this setup leads to linker problems, since the OpenCVConfig CMake module is cluttered with those variables and they will simple be empty otherwise (resulting in linker errors such as: `cannot find /lib/libjpeg.a.` Note the missing leading path here).

A rather simple and appropriate fix to this would be to extend the cmake_paths generator with all those custom and necessary `CONAN_*_ROOT` variables as follows:
```cmake
 # as before
set(CMAKE_MODULE_PATH ...)
set(CMAKE_PREFIX_PATH ...)

# new and mandatory for transitive package dependencies
set(CONAN_LIBJPEG_ROOT ...) 
set(CONAN_ZLIB_ROOT ...)
set(CONAN_LIBPNG_ROOT ...)
set(CONAN_LIBTIFF_ROOT) ...
```

Since the paths to those transitive dependencies already get appended to the `CMAKE_MODULE_PATH` and the `CMAKE_PREFIX_PATH` respectively, I guess this is simply a matter of extracting those out of `DepsCppCMake`:
https://github.com/conan-io/conan/blob/ca12f25dc6d5a11e640bbcb76e93d42205d3e0e7/conans/client/generators/cmake_paths.py#L13-L20

Please let me know, if I am doing something wrong here, or if the proposed solution is feasible.


Discussion:
I'm not sure I understand. 
The `cmake_paths` generator is intended to be used only when a `CMakeLists.txt` file is using `find_package` to locate ALL the dependencies. I don't know the details of the OpenCV recipe implementation but the ``CONAN_*_ROOT`` variables are generated by the `cmake` generator, not the `cmake_paths` generator. So probably you need to change the generator and include the `conanbuildinfo.cmake` file.
>  So probably you need to change the generator and include the `conanbuildinfo.cmake` file.

No, this is exactly what I want to avoid, since I don't want my package manager to interfere with my build system configuration. I've set up a working example on this issue [here](https://github.com/NewProggie/Non-Intrusive-Conan-CMake) [1].

In a nutshell:
In my CMakeLists.txt I declare `find_package(OpenCV REQUIRED)` and inside my `conanfile.txt` I write
```
[requires]
opencv/4.0.1@conan/stable

[generators]
cmake_paths
```
Since, the OpenCV package itself depends on other dependencies such as libjpeg, zlib etc.  it needs to be pointed to the dependencies Conan has already downloaded and cached for exactly this package. 

Typically, this is done via CMake calls to `find_dependency(...)`. However, it seems that the OpenCV Conan package is suboptimal here as it makes use of the previously mentioned `CONAN` variables. I'd like to stress that this information can be stored in the `conan_paths.cmake` file, since the information is already included.

I think, I've demonstrated a very simple use case of Conan where I have a small CMake-based project with a single dependency and want to use Conan in a non-intrusive way. Currently this is not possible. CMake's `find_package()` behaviour is well defined here, as it should resolve _all_ dependencies of a given package without forcing a client to list all transitive dependencies him/herself [2].

[1] https://github.com/NewProggie/Non-Intrusive-Conan-CMake
[2] https://cmake.org/cmake/help/v3.13/module/CMakeFindDependencyMacro.html
Thanks for the info, now I understand what you mean. I forgot we have the `cmake.patch_config_paths()` helper to replace the absolute paths with "ROOT" folders.
I think it makes a lot of sense what you are requesting. 

Resolving patch:
diff --git a/conans/client/generators/cmake_paths.py b/conans/client/generators/cmake_paths.py
--- a/conans/client/generators/cmake_paths.py
+++ b/conans/client/generators/cmake_paths.py
@@ -10,13 +10,25 @@ def filename(self):
 
     @property
     def content(self):
+        lines = []
         deps = DepsCppCmake(self.deps_build_info)
+        # The CONAN_XXX_ROOT variables are needed because the FindXXX.cmake or XXXConfig.cmake
+        # in a package could have been "patched" with the `cmake.patch_config_paths()`
+        # replacing absolute paths with CONAN_XXX_ROOT variables.
+        for dep_name, dep_cpp_info in self.deps_build_info.dependencies:
+            var_name = "CONAN_{}_ROOT".format(dep_name.upper())
+            deps = DepsCppCmake(dep_cpp_info)
+            lines.append('set({} {})'.format(var_name, deps.rootpath))
+
         # We want to prioritize the FindXXX.cmake files:
         # 1. First the files found in the packages
         # 2. The previously set (by default CMAKE_MODULE_PATH is empty)
         # 3. The "install_folder" ones, in case there is no FindXXX.cmake, try with the install dir
         #    if the user used the "cmake_find_package" will find the auto-generated
         # 4. The CMake installation dir/Modules ones.
-        return """set(CMAKE_MODULE_PATH {deps.build_paths} ${{CMAKE_MODULE_PATH}} ${{CMAKE_CURRENT_LIST_DIR}})
-set(CMAKE_PREFIX_PATH {deps.build_paths} ${{CMAKE_PREFIX_PATH}} ${{CMAKE_CURRENT_LIST_DIR}})
-""".format(deps=deps)
+        lines.append("set(CMAKE_MODULE_PATH {deps.build_paths} ${{CMAKE_MODULE_PATH}} "
+                     "${{CMAKE_CURRENT_LIST_DIR}})".format(deps=deps))
+        lines.append("set(CMAKE_PREFIX_PATH {deps.build_paths} ${{CMAKE_PREFIX_PATH}} "
+                     "${{CMAKE_CURRENT_LIST_DIR}})".format(deps=deps))
+
+        return "\n".join(lines)


Test patch:
diff --git a/conans/test/functional/generators/cmake_paths_test.py b/conans/test/functional/generators/cmake_paths_test.py
--- a/conans/test/functional/generators/cmake_paths_test.py
+++ b/conans/test/functional/generators/cmake_paths_test.py
@@ -2,7 +2,8 @@
 import shutil
 import unittest
 
-from conans.test.utils.tools import TestClient
+from conans.model.ref import ConanFileReference, PackageReference
+from conans.test.utils.tools import TestClient, NO_SETTINGS_PACKAGE_ID
 
 
 class CMakePathsGeneratorTest(unittest.TestCase):
@@ -24,6 +25,7 @@ def package(self):
                  "FindHello0.cmake": """
 SET(Hello0_FOUND 1)
 MESSAGE("HELLO FROM THE Hello0 FIND PACKAGE!")
+MESSAGE("ROOT PATH: ${CONAN_HELLO0_ROOT}")
 """}
         client.save(files)
         client.run("create . user/channel")
@@ -55,6 +57,11 @@ def package(self):
                             cwd=build_dir)
         self.assertEqual(ret, 0)
         self.assertIn("HELLO FROM THE Hello0 FIND PACKAGE!", client.out)
+        ref = ConanFileReference.loads("Hello0/0.1@user/channel")
+        pref = PackageReference(ref, NO_SETTINGS_PACKAGE_ID)
+        package_folder = client.cache.package_layout(ref).package(pref)
+        # Check that the CONAN_HELLO0_ROOT has been replaced with the real abs path
+        self.assertIn("ROOT PATH: %s" % package_folder.replace("\\", "/"), client.out)
 
         # Now try without toolchain but including the file
         files = {"CMakeLists.txt": """
diff --git a/conans/test/unittests/client/generators/cmake_paths_test.py b/conans/test/unittests/client/generators/cmake_paths_test.py
--- a/conans/test/unittests/client/generators/cmake_paths_test.py
+++ b/conans/test/unittests/client/generators/cmake_paths_test.py
@@ -25,15 +25,11 @@ def cmake_vars_unit_test(self):
         conanfile.deps_cpp_info.update(cpp_info, "MyLib")
 
         generator = CMakePathsGenerator(conanfile)
-        cmake_lines = [s.replace("\t\t\t", "").replace('\\', '/')
-                       for s in generator.content.splitlines()]
-        self.assertEqual('set(CMAKE_MODULE_PATH '
-                          '"%s/"' % tmp.replace('\\', '/'), cmake_lines[0])
-        self.assertEqual('"%s" ${CMAKE_MODULE_PATH} '
-                          '${CMAKE_CURRENT_LIST_DIR})' % custom_dir.replace('\\', '/'),
-                          cmake_lines[1])
-        self.assertEqual('set(CMAKE_PREFIX_PATH '
-                          '"%s/"' % tmp.replace('\\', '/'), cmake_lines[2])
-        self.assertEqual('"%s" ${CMAKE_PREFIX_PATH} '
-                          '${CMAKE_CURRENT_LIST_DIR})' % custom_dir.replace('\\', '/'),
-                          cmake_lines[3])
+        path = tmp.replace('\\', '/')
+        custom_dir = custom_dir.replace('\\', '/')
+        cmake_lines = generator.content.replace('\\', '/').replace("\n\t\t\t", " ").splitlines()
+        self.assertEquals('set(CONAN_MYLIB_ROOT "%s")' % path, cmake_lines[0])
+        self.assertEquals('set(CMAKE_MODULE_PATH "%s/" "%s" ${CMAKE_MODULE_PATH} '
+                          '${CMAKE_CURRENT_LIST_DIR})' % (path, custom_dir), cmake_lines[1])
+        self.assertEquals('set(CMAKE_PREFIX_PATH "%s/" "%s" ${CMAKE_PREFIX_PATH} '
+                          '${CMAKE_CURRENT_LIST_DIR})' % (path, custom_dir), cmake_lines[2])