Repository: conan-io/conan

Issue:
[feature] cmake_find_package[_multi] generators: provide a way to create non namespaced imported targets
Currently, `cmake_find_package` and `cmake_find_package_multi` generators always create namespaced imported targets in generated module/config files.
While not considered as best practice, it's not uncommon that official CMake config file of a library create non-namespaced imported targets (and quite uncommon for module files because official ones are usually provided by Kitware).

This is a non-exhaustive list of CCI recipes whose official config file creates non-namespaced targets, to show you that it's not just a corner case:

```
ade
amqp-cpp
antlr4-cppruntime
arcus
arduinojson
argtable3
asyncplusplus
celero
cereal
charls
cjson
cppzmq
cryptopp
cwalk
czmq
dcmtk
debug_assert
docopt.cpp
easy_profiler
effolkronium-random
embree3
evix2
ezc3d
fast-cdr
fast-dds
fcl
foonathan-memory
freetype
g3log
gcem
gdcm
gflags
glfw
glslang
gtsam
hana
jinja2cpp
json-schema-validator
jsoncpp
kangaru
leptonica
libccd
libde265
libe57format
libgeotiff
libkml
libmediainfo
libpng
libsigcpp
libtins
libwebsockets
libyaml
libzen
litehtml
log4cxx
mbits-args
mfast
mpark-variant
msgpack
octomap
onnx
opencascade
opencolorio
opencv
opengv
openjpeg
openmesh
parallel-hashmap
pro-mdnsd
rapidcheck
rapidjson
raylib
read-excel
rxcpp
sfml
spirv-cross
spirv-tools
sqlitecpp
symengine
szip
tensorpipe
tesseract
tinyobjloader
tlx
tmx
type_safe
utfcpp
wslay
xproperty
xsimd
xtensor
xtl
yaml-cpp
zeromq
```

It's worth noting that a library may have an official module and config file, and one might create namespaced targets but not the other.
libpng and freetype are good examples.

| library | cmake target in module file | cmake target in config file |
|--------|--------| --------| 
| freetype | Freetype::Freetype | freetype |
| libpng | PNG::PNG | png |

- [x] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).



Discussion:
Hi @SpaceIm 

First, it is sad to discover this CMake lack of consistency (though not very surprising).
Second, I agree, this is not a corner case, I thought it was going to be just a very few recipes, but it is clear I was wrong.
I think Conan needs to provide a native and idiomatic way to define these targets too. Not sure they can be introduced without breaking, we might need to wait until Conan 2.0 (which will start very soon), but lets have a look and think about possible approaches.

Regarding the multiple module/config definitions, I am not so sure. I would say that this seems too much, and Conan should provide only one of them (I guess the modern module one).
Lets try to think about the possible approaches in Conan 1.30.
@SpaceIm do you have an idea how the syntax to describe non-namespaced targets may look like? e.g. assigning an empty string, or `None`, or something else? example would be helpful.
@SSE4 I don't know how it could be implemented with existing cpp_info properties without breaking current behaviour. For example, setting `self.cpp_info.names["cmake_find_package"] = ""` would prevent defining global target `name::name`?
Having multiple namespaces would also be useful.
e.g. thrift generates `thrift::thrift`, `thriftz::thriftz`, `thriftnb::thriftnb`, `thriftqt5::thriftqt5`.

In general, the cmake_find_package generators should assume that everything can be modified.
(e.g. the name of a components is not always the same as the name of the imported target)
Difference between component name and underlying imported target can be emulated, but not multiple namespace (openexr also has multiple namespaces).
Agreeing with these points, confusingly CMake target names can be ANYTHING (and with my relatively brief CMake experience I've seen almost every type of target name in active use). Some packages like GTest even define things like GTest::GTest and then GTest::Main which as far as I know cannot be modeled with components right now.
unfortunately, I don't see how it could be implemented with the current `CppInfo` model.
I suppose `CppInfo` should be extended, for instance:
```
# generates "libzmq-static" (no namespace)
cpp_info.names["cmake_find_package_multi"] = "libzmq-static"
cpp_info.namespaces["cmake_find_package_multi"] = ""
# alternatively: cpp_info.namespaces["cmake_find_package_multi"] = None

# generates "thriftz::thriftz"
cpp_info.components["thriftz"].names["cmake_find_package_multi"] = "thriftz"
cpp_info.components["thriftz"].namespaces["cmake_find_package_multi"] = "thriftz"

# generates "thriftnb::thriftnb"
cpp_info.components["thriftnb"].names["cmake_find_package_multi"] = "thriftnb"
cpp_info.components["thriftnb"].namespaces["cmake_find_package_multi"] = "thriftnb"

# generates "OpenEXR::IlmThread"
cpp_info.components["IlmThread"].names["cmake_find_package_multi"] = "IlmThread"
cpp_info.components["IlmThread"].namespaces["cmake_find_package_multi"] = "OpenEXR"

# generates "Imath::ImathConfig"
cpp_info.components["ImathConfig"].names["cmake_find_package_multi"] = "ImathConfig"
cpp_info.components["ImathConfig"].namespaces["cmake_find_package_multi"] = "Imath"
```

Resolving patch:
diff --git a/conans/client/generators/cmake_find_package.py b/conans/client/generators/cmake_find_package.py
--- a/conans/client/generators/cmake_find_package.py
+++ b/conans/client/generators/cmake_find_package.py
@@ -46,6 +46,10 @@ class CMakeFindPackageGenerator(GeneratorComponentsMixin, Generator):
                 {find_dependencies_block}
             endif()
         endif()
+
+        foreach(_BUILD_MODULE_PATH ${{{name}_BUILD_MODULES_PATHS}})
+            include(${{_BUILD_MODULE_PATH}})
+        endforeach()
         """)
 
     find_components_tpl = Template(textwrap.dedent("""\
@@ -156,10 +160,6 @@ class CMakeFindPackageGenerator(GeneratorComponentsMixin, Generator):
         set(CMAKE_MODULE_PATH {{ comp.build_paths }} ${CMAKE_MODULE_PATH})
         set(CMAKE_PREFIX_PATH {{ comp.build_paths }} ${CMAKE_PREFIX_PATH})
 
-        foreach(_BUILD_MODULE_PATH {{ '${'+pkg_name+'_'+comp_name+'_BUILD_MODULES_PATHS}' }})
-            include(${_BUILD_MODULE_PATH})
-        endforeach()
-
         {%- endfor %}
 
 
@@ -199,6 +199,18 @@ class CMakeFindPackageGenerator(GeneratorComponentsMixin, Generator):
                          INTERFACE_LINK_LIBRARIES "{{ '${'+pkg_name+'_COMPONENTS}' }}")
         endif()
 
+        ########## BUILD MODULES ####################################################################
+        #############################################################################################
+
+        {%- for comp_name, comp in components %}
+        ########## COMPONENT {{ comp_name }} BUILD MODULES ##########################################
+
+        foreach(_BUILD_MODULE_PATH {{ '${'+pkg_name+'_'+comp_name+'_BUILD_MODULES_PATHS}' }})
+            include(${_BUILD_MODULE_PATH})
+        endforeach()
+
+        {%- endfor %}
+
     """))
 
     @property
diff --git a/conans/client/generators/cmake_find_package_common.py b/conans/client/generators/cmake_find_package_common.py
--- a/conans/client/generators/cmake_find_package_common.py
+++ b/conans/client/generators/cmake_find_package_common.py
@@ -72,10 +72,6 @@
 
 set(CMAKE_MODULE_PATH {deps.build_paths} ${{CMAKE_MODULE_PATH}})
 set(CMAKE_PREFIX_PATH {deps.build_paths} ${{CMAKE_PREFIX_PATH}})
-
-foreach(_BUILD_MODULE_PATH ${{{name}_BUILD_MODULES_PATHS{build_type_suffix}}})
-    include(${{_BUILD_MODULE_PATH}})
-endforeach()
 """
 
 
diff --git a/conans/client/generators/cmake_find_package_multi.py b/conans/client/generators/cmake_find_package_multi.py
--- a/conans/client/generators/cmake_find_package_multi.py
+++ b/conans/client/generators/cmake_find_package_multi.py
@@ -26,6 +26,7 @@ class CMakeFindPackageMultiGenerator(CMakeFindPackageGenerator):
         include(${{CMAKE_CURRENT_LIST_DIR}}/{filename}Targets.cmake)
 
         {target_props_block}
+        {build_modules_block}
         {find_dependencies_block}
         """)
 
@@ -69,6 +70,15 @@ class CMakeFindPackageMultiGenerator(CMakeFindPackageGenerator):
              {%- endfor %})
     """)
 
+    build_modules = Template("""
+# Build modules
+{%- for config in configs %}
+foreach(_BUILD_MODULE_PATH {{ '${'+name+'_BUILD_MODULES_PATHS_'+config.upper()+'}' }})
+    include(${_BUILD_MODULE_PATH})
+endforeach()
+{%- endfor %}
+    """)
+
     # https://gitlab.kitware.com/cmake/cmake/blob/master/Modules/BasicConfigVersion-SameMajorVersion.cmake.in
     config_version_template = textwrap.dedent("""
         set(PACKAGE_VERSION "{version}")
@@ -222,7 +232,9 @@ class CMakeFindPackageMultiGenerator(CMakeFindPackageGenerator):
         {%- macro tvalue(pkg_name, comp_name, var, config) -%}
         {{'${'+pkg_name+'_'+comp_name+'_'+var+'_'+config.upper()+'}'}}
         {%- endmacro -%}
+
         {%- for comp_name, comp in components %}
+
         ########## COMPONENT {{ comp_name }} TARGET PROPERTIES ######################################
 
         set_property(TARGET {{ pkg_name }}::{{ comp_name }} PROPERTY INTERFACE_LINK_LIBRARIES
@@ -256,6 +268,22 @@ class CMakeFindPackageMultiGenerator(CMakeFindPackageGenerator):
                          $<$<CONFIG:{{config}}>:{{ '${'+pkg_name+'_COMPONENTS_'+config.upper()+'}'}}>
                          {%- endfor %})
         endif()
+
+        ########## BUILD MODULES ####################################################################
+        #############################################################################################
+
+        {%- for comp_name, comp in components %}
+
+        ########## COMPONENT {{ comp_name }} BUILD MODULES ##########################################
+
+        {%- for config in configs %}
+
+        foreach(_BUILD_MODULE_PATH {{ '${'+pkg_name+'_'+comp_name+'_BUILD_MODULES_PATHS_'+config.upper()+'}' }})
+            include(${_BUILD_MODULE_PATH})
+        endforeach()
+        {%- endfor %}
+
+        {%- endfor %}
         """))
 
     def __init__(self, conanfile):
@@ -376,6 +404,8 @@ def _config(self, filename, name, version, public_deps_names):
 
         # Define the targets properties
         targets_props = self.target_properties.render(name=name, configs=self.configurations)
+        # Add build modules
+        build_modules_block = self.build_modules.render(name=name, configs=self.configurations)
         # The find_dependencies_block
         find_dependencies_block = ""
         if public_deps_names:
@@ -386,6 +416,7 @@ def _config(self, filename, name, version, public_deps_names):
         tmp = self.config_template.format(name=name, version=version,
                                           filename=filename,
                                           target_props_block=targets_props,
+                                          build_modules_block=build_modules_block,
                                           find_dependencies_block=find_dependencies_block,
                                           macros_and_functions=macros_and_functions)
         return tmp


Test patch:
diff --git a/conans/test/functional/generators/cmake_find_package_multi_test.py b/conans/test/functional/generators/cmake_find_package_multi_test.py
--- a/conans/test/functional/generators/cmake_find_package_multi_test.py
+++ b/conans/test/functional/generators/cmake_find_package_multi_test.py
@@ -12,6 +12,80 @@
 from conans.util.files import load
 
 
+@pytest.mark.tool_cmake
+class TestCMakeFindPackageMultiGenerator:
+
+    @pytest.mark.parametrize("use_components", [False, True])
+    def test_build_modules_alias_target(self, use_components):
+        client = TestClient()
+        conanfile = textwrap.dedent("""
+            import os
+            from conans import ConanFile, CMake
+
+            class Conan(ConanFile):
+                name = "hello"
+                version = "1.0"
+                settings = "os", "arch", "compiler", "build_type"
+                exports_sources = ["target-alias.cmake"]
+                generators = "cmake"
+
+                def package(self):
+                    self.copy("target-alias.cmake", dst="share/cmake")
+
+                def package_info(self):
+                    module = os.path.join("share", "cmake", "target-alias.cmake")
+            %s
+            """)
+        if use_components:
+            info = textwrap.dedent("""\
+                self.cpp_info.name = "namespace"
+                self.cpp_info.filenames["cmake_find_package_multi"] = "hello"
+                self.cpp_info.components["comp"].libs = ["hello"]
+                self.cpp_info.components["comp"].build_modules["cmake_find_package_multi"].append(module)
+                """)
+        else:
+            info = textwrap.dedent("""\
+                self.cpp_info.libs = ["hello"]
+                self.cpp_info.build_modules["cmake_find_package_multi"].append(module)
+                """)
+        target_alias = textwrap.dedent("""
+            add_library(otherhello INTERFACE IMPORTED)
+            target_link_libraries(otherhello INTERFACE {target_name})
+            """).format(target_name="namespace::comp" if use_components else "hello::hello")
+        conanfile = conanfile % "\n".join(["        %s" % line for line in info.splitlines()])
+        client.save({"conanfile.py": conanfile, "target-alias.cmake": target_alias})
+        client.run("create .")
+
+        consumer = textwrap.dedent("""
+            from conans import ConanFile, CMake
+
+            class Conan(ConanFile):
+                name = "consumer"
+                version = "1.0"
+                settings = "os", "compiler", "build_type", "arch"
+                exports_sources = ["CMakeLists.txt"]
+                generators = "cmake_find_package_multi"
+                requires = "hello/1.0"
+
+                def build(self):
+                    cmake = CMake(self)
+                    cmake.configure()
+            """)
+        cmakelists = textwrap.dedent("""
+            cmake_minimum_required(VERSION 3.0)
+            project(test)
+            find_package(hello)
+            get_target_property(tmp otherhello INTERFACE_LINK_LIBRARIES)
+            message("otherhello link libraries: ${tmp}")
+            """)
+        client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+        client.run("create .")
+        if use_components:
+            assert "otherhello link libraries: namespace::comp" in client.out
+        else:
+            assert "otherhello link libraries: hello::hello" in client.out
+
+
 @pytest.mark.slow
 @pytest.mark.tool_cmake
 class CMakeFindPathMultiGeneratorTest(unittest.TestCase):
diff --git a/conans/test/functional/generators/cmake_find_package_test.py b/conans/test/functional/generators/cmake_find_package_test.py
--- a/conans/test/functional/generators/cmake_find_package_test.py
+++ b/conans/test/functional/generators/cmake_find_package_test.py
@@ -13,6 +13,80 @@
 from conans.test.utils.tools import TestClient, NO_SETTINGS_PACKAGE_ID
 
 
+@pytest.mark.tool_cmake
+class TestCMakeFindPackageGenerator:
+
+    @pytest.mark.parametrize("use_components", [False, True])
+    def test_build_modules_alias_target(self, use_components):
+        client = TestClient()
+        conanfile = textwrap.dedent("""
+            import os
+            from conans import ConanFile, CMake
+
+            class Conan(ConanFile):
+                name = "hello"
+                version = "1.0"
+                settings = "os", "arch", "compiler", "build_type"
+                exports_sources = ["target-alias.cmake"]
+                generators = "cmake"
+
+                def package(self):
+                    self.copy("target-alias.cmake", dst="share/cmake")
+
+                def package_info(self):
+                    module = os.path.join("share", "cmake", "target-alias.cmake")
+            %s
+            """)
+        if use_components:
+            info = textwrap.dedent("""\
+                self.cpp_info.name = "namespace"
+                self.cpp_info.filenames["cmake_find_package"] = "hello"
+                self.cpp_info.components["comp"].libs = ["hello"]
+                self.cpp_info.components["comp"].build_modules["cmake_find_package"].append(module)
+                """)
+        else:
+            info = textwrap.dedent("""\
+                self.cpp_info.libs = ["hello"]
+                self.cpp_info.build_modules["cmake_find_package"].append(module)
+                """)
+        target_alias = textwrap.dedent("""
+            add_library(otherhello INTERFACE IMPORTED)
+            target_link_libraries(otherhello INTERFACE {target_name})
+            """).format(target_name="namespace::comp" if use_components else "hello::hello")
+        conanfile = conanfile % "\n".join(["        %s" % line for line in info.splitlines()])
+        client.save({"conanfile.py": conanfile, "target-alias.cmake": target_alias})
+        client.run("create .")
+
+        consumer = textwrap.dedent("""
+            from conans import ConanFile, CMake
+
+            class Conan(ConanFile):
+                name = "consumer"
+                version = "1.0"
+                settings = "os", "compiler", "build_type", "arch"
+                exports_sources = ["CMakeLists.txt"]
+                generators = "cmake_find_package"
+                requires = "hello/1.0"
+
+                def build(self):
+                    cmake = CMake(self)
+                    cmake.configure()
+            """)
+        cmakelists = textwrap.dedent("""
+            cmake_minimum_required(VERSION 3.0)
+            project(test)
+            find_package(hello)
+            get_target_property(tmp otherhello INTERFACE_LINK_LIBRARIES)
+            message("otherhello link libraries: ${tmp}")
+            """)
+        client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+        client.run("create .")
+        if use_components:
+            assert "otherhello link libraries: namespace::comp" in client.out
+        else:
+            assert "otherhello link libraries: hello::hello" in client.out
+
+
 @pytest.mark.slow
 @pytest.mark.tool_cmake
 class CMakeFindPathGeneratorTest(unittest.TestCase):
diff --git a/conans/test/functional/generators/cmake_test.py b/conans/test/functional/generators/cmake_test.py
--- a/conans/test/functional/generators/cmake_test.py
+++ b/conans/test/functional/generators/cmake_test.py
@@ -504,3 +504,57 @@ def build(self):
 
         # Test cmake_find_package_multi generator without components
         run_test("cmake_find_package_multi", cmakelists, with_components=False)
+
+    def test_build_modules_alias_target(self, use_components=False):
+        client = TestClient()
+        conanfile = textwrap.dedent("""
+            import os
+            from conans import ConanFile, CMake
+
+            class Conan(ConanFile):
+                name = "hello"
+                version = "1.0"
+                settings = "os", "arch", "compiler", "build_type"
+                exports_sources = ["target-alias.cmake"]
+
+                def package(self):
+                    self.copy("target-alias.cmake", dst="share/cmake")
+
+                def package_info(self):
+                    module = os.path.join("share", "cmake", "target-alias.cmake")
+                    self.cpp_info.libs = ["hello"]
+                    self.cpp_info.build_modules["cmake"].append(module)
+            """)
+        target_alias = textwrap.dedent("""
+            add_library(otherhello INTERFACE IMPORTED)
+            target_link_libraries(otherhello INTERFACE hello::hello)
+            """)
+        client.save({"conanfile.py": conanfile, "target-alias.cmake": target_alias})
+        client.run("create .")
+
+        consumer = textwrap.dedent("""
+            from conans import ConanFile, CMake
+
+            class Conan(ConanFile):
+                name = "consumer"
+                version = "1.0"
+                settings = "os", "compiler", "build_type", "arch"
+                exports_sources = ["CMakeLists.txt"]
+                generators = "cmake"
+                requires = "hello/1.0"
+
+                def build(self):
+                    cmake = CMake(self)
+                    cmake.configure()
+            """)
+        cmakelists = textwrap.dedent("""
+            cmake_minimum_required(VERSION 3.0)
+            project(test)
+            include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+            conan_basic_setup(TARGETS)
+            get_target_property(tmp otherhello INTERFACE_LINK_LIBRARIES)
+            message("otherhello link libraries: ${tmp}")
+            """)
+        client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
+        client.run("create .")
+        assert "otherhello link libraries: hello::hello" in client.out