Repository: conan-io/conan

Issue:
[bug] Conan returns incorrect path for Apple Framework when multiple components have the same framework name
### Environment Details (include every applicable attribute)
  * Operating System+version: macOS 12.0.1 (21A559)
  * Compiler+version: Apple clang version 13.0.0 (clang-1300.0.29.3) - Xcode 13.1 (latest)
  * Conan version: 1.42.2
  * Python version: Python 3.9.9

### Description

Since CMake [does not yet natively support XCFrameworks](https://gitlab.kitware.com/cmake/cmake/-/issues/21752) I'm looking for alternative solutions to integrating Apple's new format into our existing codebase.
After some research, I determined that [Conan Components](https://docs.conan.io/en/latest/creating_packages/package_information.html#using-components) together with the `cmake_find_package` generator are a perfect fit for the task at hand.
I will not go in detail what XCFrameworks are, but for anyone interested, the following blog post has a lot of useful info: [PSPDFKit - Supporting XCFrameworks](https://pspdfkit.com/blog/2020/supporting-xcframeworks/).
An XCFramework is basically a folder (+ some metadata) containing different architecture builds (slices) of a framework. It has the following structure:

```
ExampleFramework.xcframework
├── Info.plist
├── ios-arm64
│   └── ExampleFramework.framework
├── ios-x86_64-simulator
│   └── ExampleFramework.framework
├── tvos-arm64
│   └── ExampleFramework.framework
├── tvos-arm64_x86_64-simulator
│   └── ExampleFramework.framework
├── watchos-arm64_32_armv7k
│   └── ExampleFramework.framework
└── watchos-arm64_x86_64-simulator
    └── ExampleFramework.framework
```

The `conanfile.py` for Example Framework looks like this (simplified):

```python
def build(self):
    # Build all slices
    for platform in self._supported_platforms:
        archive_cmd = f"xcrun xcodebuild archive -quiet -project ExampleProject.xcodeproj -scheme 'ExampleFramework' -configuration {self.settings.build_type} -archivePath '{platform}.xcarchive'"
        self.run(archive_cmd)

    # Combine all slices
    create_cmd = f"xcrun xcodebuild -create-xcframework <args> -output '{self.name}.xcframework'"
    self.run(create_cmd)

# "Component" package
def package_info(self):
    self.cpp_info.name = self.name #"ExampleFramework"

    for platform in self._supported_platforms:
        arch_dir = self._arch_for_platform[platform]
        framework_path = os.path.join(self.package_folder, f"{self.name}.xcframework", arch_dir)

        self.cpp_info.components[platform].names["cmake_find_package"] = platform
        self.cpp_info.components[platform].frameworkdirs = [framework_path]
        self.cpp_info.components[platform].frameworks = [self.name]
```

For each supported architecture of the XCFramework, a component is defined with the name of the platform, e.g: iphoneos, iphonesimulator, macosx (names used by Xcode).
__NOTE:__ The framework name specified in `self.cpp_info.components[platform].frameworks` is identical for every component (`self.name`) and this __can't__ be trivially renamed (or symlinked) to e.g. `ExampleFramework_macosx.framework`.

The test_package is a bare minimum CMake project integrating this, with the following `CMakeLists.txt`:

```cmake
cmake_minimum_required(VERSION 3.1)
project(PackageTest)

include(CMakePrintHelpers)
set(CMAKE_C_FLAGS "-fmodules")
set(CMAKE_VERBOSE_MAKEFILE ON)

# cmake_find_package generator required
find_package(ExampleFramework REQUIRED COMPONENTS macosx)

add_executable(example example.m)
target_link_libraries(example ExampleFramework::macosx)
```

Contrary to expectations, running `conan create` yields the following link error:

```
/Applications/CMake-3.21.2.app/Contents/bin/cmake -E cmake_link_script CMakeFiles/example.dir/link.txt --verbose=1
/Applications/Xcode-13.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc -fmodules -O3 -DNDEBUG -arch x86_64 -isysroot /Applications/Xcode-13.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk -mmacosx-version-min=10.14 -Wl,-search_paths_first -Wl,-headerpad_max_install_names CMakeFiles/example.dir/example.m.o -o example -F/Users/user/.conan/data/ExampleFramework/0.1.0/user/testing/package/<package-id>/ExampleFramework.xcframework/ios-arm64  -Wl,-rpath,/Users/user/.conan/data/ExampleFramework/0.1.0/user/testing/package/<package-id>/ExampleFramework.xcframework/ios-arm64 -framework ExampleFramework
ld: warning: ignoring file /Users/user/.conan/data/ExampleFramework/0.1.0/user/testing/package/<package-id>/ExampleFramework.xcframework/ios-arm64/ExampleFramework.framework/ExampleFramework, building for macOS-x86_64 but attempting to link with file built for iOS-arm64
Undefined symbols for architecture x86_64:
  "_OBJC_CLASS_$_TestClass", referenced from:
      objc-class-ref in example.m.o
ld: symbol(s) not found for architecture x86_64
```

It is very interesting to note that __although__ we specified the __macosx__ component, what `FindExampleFramework.cmake` configured was actually the path leading to the __iphoneos__ framework in the subfolder `ios-arm64`.

After a bit of debugging, by inspecting the variables defined by the custom FindExampleFramework.cmake, we observe the following:
- ✅ `ExampleFramework_macosx_FRAMEWORK_DIRS: /Users/user/.conan/data/ExampleFramework/0.1.0/user/testing/package/<package-id>/ExampleFramework.xcframework/macos-arm64_x86_64`
- ❌ `ExampleFramework_macosx_LINK_LIBS: /Users/user/.conan/data/ExampleFramework/0.1.0/user/testing/package/<package-id>/ExampleFramework.xcframework/ios-arm64/ExampleFramework.framework`
- ❌ `ExampleFramework_macosx_LIBS_FRAMEWORKS_DEPS: /Users/user/.conan/data/ExampleFramework/0.1.0/user/testing/package/<package-id>/ExampleFramework.xcframework/ios-arm64/ExampleFramework.framework`

and from this, the target property is also incorrect:
❌ `ExampleFramework::macosx.INTERFACE_LINK_LIBRARIES = "/Users/user/.conan/data/ExampleFramework/0.1.0/user/testing/package/<package-id>/ExampleFramework.xcframework/ios-arm64/ExampleFramework.framework;$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:>;$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>:>;$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:>"`

### Solution

It seems that Conan's cmake_find_package generator cannot deal with different components declaring the same framework name. I have traced the behavior to the macro `conan_find_apple_frameworks` in `conans/client/generators/cmake_find_package_common.py`.
Specifically, it checks `if(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND)` but this global variable doesn't take the different `FRAMEWORKS_DIRS` given to the macro, which can be a disjoint-sets between different components.

The easiest solution to this is to add an `unset(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND CACHE)` immediately before the `find_library` call.
Personally, I think this is acceptable, as the macro throws a `FATAL_ERROR` if a framework isn't found.

From CMake's [find_library documentation](https://cmake.org/cmake/help/latest/command/find_library.html):
> If the library is found the result is stored in the variable and the search will not be repeated unless the variable is cleared.

I verified that this works as expected by monkey-patching the macro in `test_package/conanfile.py`:
```python
import textwrap
from conans.client.generators.cmake_find_package_common import CMakeFindPackageCommonMacros
CMakeFindPackageCommonMacros.apple_frameworks_macro = textwrap.dedent("""
    macro(conan_find_apple_frameworks FRAMEWORKS_FOUND FRAMEWORKS FRAMEWORKS_DIRS)
        if(APPLE)
            foreach(_FRAMEWORK ${FRAMEWORKS})
                # BUGFIX: Different components with the same framework name should still attempt to find the library
                unset(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND CACHE)

                # https://cmake.org/pipermail/cmake-developers/2017-August/030199.html
                find_library(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND NAME ${_FRAMEWORK} PATHS ${FRAMEWORKS_DIRS} CMAKE_FIND_ROOT_PATH_BOTH)
                if(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND)
                    list(APPEND ${FRAMEWORKS_FOUND} ${CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND})
                else()
                    message(FATAL_ERROR "Framework library ${_FRAMEWORK} not found in paths: ${FRAMEWORKS_DIRS}")
                endif()
            endforeach()
        endif()
    endmacro()
""")
```

A more elaborate solution would require defining separate `CONAN_FRAMEWORK_` variables for each component, and e.g. passing the component name to this function as a suffix.

In any case, I cannot see a reason why this use-case shouldn't be supported (same framework name in different components) and it would certainly make the lives of all Apple devs using Conan much easier in the future.

I'll be very happy to provide more information and code if necessary, as well as answer any questions that might arise.
[feature] Add new field `package_framework` to `cpp_info`
### What is your suggestion?

We should add a new field `package_frameworks ` to `self.cpp_info`. Reasoning:

* Keep `cpp_info.frameworks` as system frameworks (similar behavior as `system_libs`), so we do not have to change tons of recipes.
* `cpp_info.package_framework` and `cpp_info.location` will define the recipe as a custom framework.

For instance:

```python
class MyFramework(ConanFile):
    name = "frame"
    version = "1.0"
    package_type = "static-library"
    # ...

    def package_info(self):
        # Recipe working as a framework
        self.cpp_info.type = PackageType.STATIC
        self.cpp_info.package_framework = "MyFramework"
        self.cpp_info.location = os.path.join(self.package_folder, "MyFramework.framework", "MyFramework")
        # System framework
        self.cpp_info.frameworks = ["CoreFoundation"]
```

[bug] find frameworks path is cached in cmake and not invalidated when dependency updates
### Describe the bug

this code from cmakedeps_macros.cmake has nasty bug
```
macro(conan_find_apple_frameworks FRAMEWORKS_FOUND FRAMEWORKS FRAMEWORKS_DIRS)
    if(APPLE)
        foreach(_FRAMEWORK ${FRAMEWORKS})
            # https://cmake.org/pipermail/cmake-developers/2017-August/030199.html
            find_library(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND NAMES ${_FRAMEWORK} PATHS ${FRAMEWORKS_DIRS} CMAKE_FIND_ROOT_PATH_BOTH)
            if(CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND)
                list(APPEND ${FRAMEWORKS_FOUND} ${CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND})
                message(VERBOSE "Framework found! ${FRAMEWORKS_FOUND}")
            else()
                message(FATAL_ERROR "Framework library ${_FRAMEWORK} not found in paths: ${FRAMEWORKS_DIRS}")
            endif()
        endforeach()
    endif()
endmacro()
```
It caches CONAN_FRAMEWORK_${_FRAMEWORK}_FOUND variable and after conanfile is updated with the new version cmake still uses old cached framework.

This is CMakeDeps generator, conan 1.64. I suppose conan v2 has similar issue, but I didn't check





### How to reproduce it

_No response_


Discussion:
Hi @T1T4N 

I don't know if you have checked the new integrations in https://docs.conan.io/en/latest/reference/conanfile/tools/cmake.html. Those are the ones that will be valid in Conan 2.0. Our current efforts are on these. From Conan 1.43, they allow defining the property ``cpp_info.set_property("cmake_target_name", <global target name>)``.

Maybe it would be worth trying with the new integrations, as they are still experimental, much easier to be fixed if necessary, and in any case it will be necessary to migrate to them before 2.0 (we are already in 2.0.0-alpha1)?
Hello @memsharded,
Thanks for sharing the integrations documentation.
I can see [here](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmakedeps.html) that `cpp_info.components` is mentioned. Does that mean that the experimental Components feature is going to be present in Conan 2.0?
Additionally, is there any way to define components info using `set_property`?

Regarding my detailed bug description above, I wanted to give the full context to prevent unnecessary questions, but if it is too broad, please focus on the __Solution__ section detailing the issue in `conan_find_apple_frameworks`.

As you mentioned that you've focused all efforts on 2.0, I understand that you might be a bit time-constrained. Would you prefer if I open a PR with the bugfix I detailed above?
Hi @T1T4N 

We keep figuring out the issue. What I wonder is if it could solve the problem something like this:

```python
    def package_info(self):
        self.cpp_info.name = self.name  # "ExampleFramework"

        # Getting the platform name depending on the OS (and ARCH perhaps???)
        platform = self._supported_platforms[str(self.settings.os)]
        arch_dir = self._arch_for_platform[platform]
        framework_path = os.path.join(self.package_folder, f"{self.name}.xcframework", arch_dir)

       # Create one unique component
        self.cpp_info.components["ComponentPlatform"].names["cmake_find_package"] = platform
        self.cpp_info.components["ComponentPlatform"].frameworkdirs = [framework_path]
        self.cpp_info.components["ComponentPlatform"].frameworks = [self.name]
```
I'm saying this because if you have the same Framework name but it depends on the OS/ARCH, why don't you create one component depending directly on your settings? Then, when you consume it in your recipe you'll be sure you're using the correct Framework by pointing out its folder.
Hi @franramirez688,

Thanks for replying on the original issue!
Your suggestion would theoretically work, as it <ins>avoids</ins> the issue (not having 2 components with the same framework name).

It's a fact that Apple's design approach to XCFrameworks is *completely orthogonal* to Conan's design, and your suggestion is a good solution when different platforms are in question (e.g. not mixing macOS + iOS), but from a usability perspective it is not ideal in the (most common) case of iOS + iOS Simulator, as Xcode parses the metadata in the XCFramework for proper linking of the flavor, which _can be dynamically changed in the IDE_.

The ideal solution is to get `conan_find_apple_frameworks` fixed as IMO it is a purely logical bug.

My current "temporary solution" to the problem is implementing [#12088](https://github.com/conan-io/conan/pull/12088) directly in our `conanfile.py`.



Hi @technic 

Thanks for your report.

In some situations it is true that the CMake caching will interfere with dependencies changes. In general, when changing things outside of the current project, like Conan dependencies, we would recommend to invalidate the CMake cache when possible to avoid potential caching issue.

In any case, we are moving to a different model, based on the CPS standardization effort (that we are participating in), and we are implementing a new ``CMakeDeps`` generator in https://github.com/conan-io/conan/pull/16964. This will not have the `` cmakedeps_macros.cmake`` file or the ``find_frameworks()`` at all, so that will solve the issue already.
I think this can be fixed, if I look at how `conan_package_library_targets` is implemented there is `unset(CONAN_FOUND_LIBRARY CACHE)` call which removes the location from the cache. And there is no per library variable for it. Why do we have `${_FRAMEWORK}` part of the variable?

even better is to do such search in conan during generate() and provide full path to cmake instead of doing find_library calls. This will be faster when doing cmake reconfigure. 
Also this will probably make creating `CONAN_LIB::` targets not needed. I want to do install(IMPORTED_RUNTIME_ARTIFACTS) in cmake but I cant do it for normal target like zlib::zlib because it is a proxy target to CONANLIB::bla-bla target. This is inconvenient.
> I think this can be fixed, if I look at how conan_package_library_targets is implemented there is unset(CONAN_FOUND_LIBRARY CACHE) call which removes the location from the cache. And there is no per library variable for it. Why do we have ${_FRAMEWORK} part of the variable?

I see. If you think it is worth to try to ``unset()`` or something like that to avoid the issue, please try to contribute a Pull Request.

> even better is to do such search in conan during generate() and provide full path to cmake instead of doing find_library calls. This will be faster when doing cmake reconfigure.

This is basically what the new ``CMakeDeps`` is doing, together with an augmented new model in ``cpp_info`` that includes information like ``cpp_info.location``.
Also the full generation of targets is different in the new ``CMakeDeps``, no CONAN_LIB targets is generated. Please have a look to the PR https://github.com/conan-io/conan/pull/16964, it will also be made available in Conan 2.9 under a new ``conf`` to activate it (exclusively for testing, not for production).
I am re-opening this, this was not really solved yet by #16964 
Related to https://github.com/conan-io/conan/issues/10140



Patch:
diff --git a/conan/internal/model/cpp_info.py b/conan/internal/model/cpp_info.py
index 7967b75bf0c..1a702ba6e3a 100644
--- a/conan/internal/model/cpp_info.py
+++ b/conan/internal/model/cpp_info.py
@@ -12,8 +12,8 @@
 
 _DIRS_VAR_NAMES = ["_includedirs", "_srcdirs", "_libdirs", "_resdirs", "_bindirs", "_builddirs",
                    "_frameworkdirs", "_objects"]
-_FIELD_VAR_NAMES = ["_system_libs", "_frameworks", "_libs", "_defines", "_cflags", "_cxxflags",
-                    "_sharedlinkflags", "_exelinkflags"]
+_FIELD_VAR_NAMES = ["_system_libs", "_package_framework", "_frameworks", "_libs", "_defines",
+                    "_cflags", "_cxxflags", "_sharedlinkflags", "_exelinkflags"]
 _ALL_NAMES = _DIRS_VAR_NAMES + _FIELD_VAR_NAMES
 
 
@@ -72,7 +72,8 @@ def __init__(self, set_defaults=False):
 
         # ##### FIELDS
         self._system_libs = None  # Ordered list of system libraries
-        self._frameworks = None  # Macos .framework
+        self._frameworks = None  # system Apple OS frameworks
+        self._package_framework = None  # any other frameworks
         self._libs = None  # The libs to link against
         self._defines = None  # preprocessor definitions
         self._cflags = None  # pure C flags
@@ -204,16 +205,6 @@ def builddirs(self):
     def builddirs(self, value):
         self._builddirs = value
 
-    @property
-    def frameworkdirs(self):
-        if self._frameworkdirs is None:
-            self._frameworkdirs = []
-        return self._frameworkdirs
-
-    @frameworkdirs.setter
-    def frameworkdirs(self, value):
-        self._frameworkdirs = value
-
     @property
     def bindir(self):
         bindirs = self.bindirs
@@ -251,6 +242,14 @@ def system_libs(self):
     def system_libs(self, value):
         self._system_libs = value
 
+    @property
+    def package_framework(self):
+        return self._package_framework
+
+    @package_framework.setter
+    def package_framework(self, value):
+        self._package_framework = value
+
     @property
     def frameworks(self):
         if self._frameworks is None:
@@ -261,6 +260,16 @@ def frameworks(self):
     def frameworks(self, value):
         self._frameworks = value
 
+    @property
+    def frameworkdirs(self):
+        if self._frameworkdirs is None:
+            self._frameworkdirs = []
+        return self._frameworkdirs
+
+    @frameworkdirs.setter
+    def frameworkdirs(self, value):
+        self._frameworkdirs = value
+
     @property
     def libs(self):
         if self._libs is None:
@@ -612,7 +621,7 @@ def deduce_locations(self, conanfile, component_name=""):
                 raise ConanException(f"{name} has .exe and no .location")
             return
         if self._type is PackageType.APP:
-            # old school Conan application packages withoud defining an exe, not an error
+            # old school Conan application packages without defining an exe, not an error
             return
 
         # libraries
@@ -817,7 +826,6 @@ def deduce_full_cpp_info(self, conanfile):
             raise ConanException(f"{conanfile}: 'cpp_info' contains components and .exe or .libs")
 
         result = CppInfo()  # clone it
-
         if self.libs and len(self.libs) > 1:  # expand in multiple components
             ConanOutput().warning(f"{conanfile}: The 'cpp_info.libs' contain more than 1 library. "
                                   "Define 'cpp_info.components' instead.")
diff --git a/conan/tools/cmake/cmakedeps2/cmakedeps.py b/conan/tools/cmake/cmakedeps2/cmakedeps.py
index cae3326c01c..3030b982b53 100644
--- a/conan/tools/cmake/cmakedeps2/cmakedeps.py
+++ b/conan/tools/cmake/cmakedeps2/cmakedeps.py
@@ -5,15 +5,15 @@
 from jinja2 import Template
 
 from conan.api.output import Color
+from conan.errors import ConanException
 from conan.internal import check_duplicated_generator
 from conan.internal.api.install.generators import relativize_path
+from conan.internal.model.dependencies import get_transitive_requires
 from conan.tools.cmake.cmakedeps2.config import ConfigTemplate2
 from conan.tools.cmake.cmakedeps2.config_version import ConfigVersionTemplate2
 from conan.tools.cmake.cmakedeps2.target_configuration import TargetConfigurationTemplate2
 from conan.tools.cmake.cmakedeps2.targets import TargetsTemplate2
 from conan.tools.files import save
-from conan.errors import ConanException
-from conan.internal.model.dependencies import get_transitive_requires
 from conans.util.files import load
 
 FIND_MODE_MODULE = "module"
@@ -173,6 +173,7 @@ def _get_cmake_paths(self, requirements, dirs_name):
             "bindirs": "CMAKE_PROGRAM_PATH",
             "libdirs": "CMAKE_LIBRARY_PATH",
             "includedirs": "CMAKE_INCLUDE_PATH",
+            "frameworkdirs": "CMAKE_FRAMEWORK_PATH",
         }
         for req, dep in requirements:
             cppinfo = dep.cpp_info.aggregated_components()
@@ -206,11 +207,15 @@ def generate(self):
         {% if cmake_include_path %}
         list(PREPEND CMAKE_INCLUDE_PATH {{ cmake_include_path }})
         {% endif %}
+        {% if cmake_framework_path %}
+        list(PREPEND CMAKE_FRAMEWORK_PATH {{ cmake_framework_path }})
+        {% endif %}
         """)
         host_req = self._conanfile.dependencies.host
         build_req = self._conanfile.dependencies.direct_build
         test_req = self._conanfile.dependencies.test
-        all_reqs = list(host_req.items()) + list(test_req.items()) + list(build_req.items())
+        host_test_reqs = list(host_req.items()) + list(test_req.items())
+        all_reqs = host_test_reqs + list(build_req.items())
         # gen_folder = self._conanfile.generators_folder.replace("\\", "/")
         # if not, test_cmake_add_subdirectory test fails
         # content.append('set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)')
@@ -243,14 +248,15 @@ def generate(self):
 
         # CMAKE_PROGRAM_PATH | CMAKE_LIBRARY_PATH | CMAKE_INCLUDE_PATH
         cmake_program_path = self._get_cmake_paths([(req, dep) for req, dep in all_reqs if req.direct], "bindirs")
-        cmake_library_path = self._get_cmake_paths(list(host_req.items()) + list(test_req.items()), "libdirs")
-        cmake_include_path = self._get_cmake_paths(list(host_req.items()) + list(test_req.items()), "includedirs")
-
+        cmake_library_path = self._get_cmake_paths(host_test_reqs, "libdirs")
+        cmake_include_path = self._get_cmake_paths(host_test_reqs, "includedirs")
+        cmake_framework_path = self._get_cmake_paths(host_test_reqs, "frameworkdirs")
         context = {"host_runtime_dirs": self._get_host_runtime_dirs(),
                    "pkg_paths": pkg_paths,
                    "cmake_program_path": _join_paths(self._conanfile, cmake_program_path),
                    "cmake_library_path": _join_paths(self._conanfile, cmake_library_path),
                    "cmake_include_path": _join_paths(self._conanfile, cmake_include_path),
+                   "cmake_framework_path": _join_paths(self._conanfile, cmake_framework_path),
         }
         content = Template(template, trim_blocks=True, lstrip_blocks=True).render(context)
         save(self._conanfile, self._conan_cmakedeps_paths, content)
diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py
index e85df76fd3e..9f303c75f05 100644
--- a/conan/tools/cmake/cmakedeps2/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps2/target_configuration.py
@@ -4,10 +4,9 @@
 import jinja2
 from jinja2 import Template
 
-from conan.api.output import ConanOutput
 from conan.errors import ConanException
-from conans.client.graph.graph import CONTEXT_BUILD, CONTEXT_HOST
 from conan.internal.model.pkg_type import PackageType
+from conans.client.graph.graph import CONTEXT_BUILD, CONTEXT_HOST
 
 
 class TargetConfigurationTemplate2:
@@ -138,7 +137,7 @@ def _context(self):
                 "version": self._conanfile.ref.version,
                 "include_dirs": include_dirs,
                 "definitions": definitions,
-                "libraries": libraries
+                "libraries": libraries,
                 }
 
     def _get_libs(self, cpp_info, pkg_name, pkg_folder, pkg_folder_var) -> dict:
@@ -161,7 +160,7 @@ def _get_libs(self, cpp_info, pkg_name, pkg_folder, pkg_folder_var) -> dict:
         return libs
 
     def _get_cmake_lib(self, info, components, pkg_folder, pkg_folder_var):
-        if info.exe or not (info.includedirs or info.libs or info.system_libs):
+        if info.exe or not (info.package_framework or info.includedirs or info.libs or info.system_libs):
             return
 
         includedirs = ";".join(self._path(i, pkg_folder, pkg_folder_var)
@@ -182,12 +181,25 @@ def _get_cmake_lib(self, info, components, pkg_folder, pkg_folder_var):
                   "cflags": " ".join(info.cflags),
                   "sharedlinkflags": " ".join(info.sharedlinkflags),
                   "exelinkflags": " ".join(info.exelinkflags),
-                  "system_libs": system_libs}
-
+                  "system_libs": system_libs
+        }
+        # System frameworks (only Apple OS)
         if info.frameworks:
-            ConanOutput(scope=str(self._conanfile)).warning("frameworks not supported yet in new CMakeDeps generator")
-
-        if info.libs:  # TODO: to change to location
+            target['frameworks'] = " ".join([f"-framework {frw}" for frw in info.frameworks])
+        # FIXME: We're ignoring this value at this moment. It relies on cmake_target_name or lib name
+        #        Revisit when cpp.exe value is used too.
+        if info.package_framework:
+            target["package_framework"] = {}
+            lib_type = "SHARED" if info.type is PackageType.SHARED else \
+                "STATIC" if info.type is PackageType.STATIC else "STATIC"
+            assert lib_type, f"Unknown package type {info.type}"
+            assert info.location, f"cpp_info.location missing for framework {info.package_framework}"
+            target["type"] = lib_type
+            target["package_framework"]["location"] = self._path(info.location, pkg_folder, pkg_folder_var)
+            target["includedirs"] = []  # empty array as frameworks have their own way to inject headers
+            # FIXME: This is not needed for CMake < 3.24. Remove it when Conan requires CMake >= 3.24
+            target["package_framework"]["frameworkdir"] = self._path(pkg_folder, pkg_folder, pkg_folder_var)
+        if info.libs:
             if len(info.libs) != 1:
                 raise ConanException(f"New CMakeDeps only allows 1 lib per component:\n"
                                      f"{self._conanfile}: {info.libs}")
@@ -204,7 +216,6 @@ def _get_cmake_lib(self, info, components, pkg_folder, pkg_folder_var):
             link_languages = info.languages or self._conanfile.languages or []
             link_languages = ["CXX" if c == "C++" else c for c in link_languages]
             target["link_languages"] = link_languages
-
         return target
 
     def _add_root_lib_target(self, libs, pkg_name, cpp_info):
@@ -279,6 +290,7 @@ def _escape_cmake_string(values):
 
     @property
     def _template(self):
+        # TODO: CMake 3.24: Apple Frameworks: https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:LINK_LIBRARY
         # TODO: Check why not set_property instead of target_link_libraries
         return textwrap.dedent("""\
         {%- macro config_wrapper(config, value) -%}
@@ -380,7 +392,20 @@ def _template(self):
         {% if lib_info.get("system_libs") %}
         target_link_libraries({{lib}} INTERFACE {{lib_info["system_libs"]}})
         {% endif %}
-
+        {% if lib_info.get("frameworks") %}
+        set_property(TARGET {{lib}} PROPERTY INTERFACE_LINK_LIBRARIES "{{config_wrapper(config, lib_info["frameworks"])}}")
+        {% endif %}
+        {% if lib_info.get("package_framework") %}
+        set_target_properties({{lib}} PROPERTIES
+            IMPORTED_LOCATION_{{config}} "{{lib_info["package_framework"]["location"]}}"
+            FRAMEWORK TRUE)
+        if(CMAKE_VERSION VERSION_LESS "3.24")
+            set_property(TARGET {{lib}} APPEND PROPERTY INTERFACE_COMPILE_OPTIONS
+                         $<$<COMPILE_LANGUAGE:CXX>:-F{{lib_info["package_framework"]["frameworkdir"]}}>)
+            set_property(TARGET {{lib}} APPEND PROPERTY INTERFACE_COMPILE_OPTIONS
+                         $<$<COMPILE_LANGUAGE:C>:-F{{lib_info["package_framework"]["frameworkdir"]}}>)
+        endif()
+        {% endif %}
         {% endfor %}
 
         ################# Global variables for try compile and legacy ##############


Test patch:
diff --git a/test/functional/toolchains/cmake/cmakedeps2/__init__.py b/test/functional/toolchains/cmake/cmakedeps2/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_frameworks.py b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_frameworks.py
new file mode 100644
index 00000000000..9f98a402a1d
--- /dev/null
+++ b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_frameworks.py
@@ -0,0 +1,137 @@
+import platform
+import textwrap
+
+import pytest
+
+from conan.internal.model.pkg_type import PackageType
+from conan.test.utils.tools import TestClient
+
+new_value = "will_break_next"
+
+
+@pytest.mark.parametrize("shared", [True, False])
+@pytest.mark.tool("cmake")
+@pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX")
+def test_osx_frameworks(shared):
+    """
+    Testing custom package frameworks + system frameworks
+    """
+    cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(MyFramework CXX)
+
+    add_library(MyFramework frame.cpp frame.h)
+
+    set_target_properties(MyFramework PROPERTIES
+      FRAMEWORK TRUE
+      FRAMEWORK_VERSION C # Version "A" is macOS convention
+      MACOSX_FRAMEWORK_IDENTIFIER MyFramework
+      PUBLIC_HEADER frame.h
+    )
+
+    install(TARGETS MyFramework
+      FRAMEWORK DESTINATION .)
+    """)
+    frame_cpp = textwrap.dedent("""
+    #include "frame.h"
+    #include <iostream>
+    #include <CoreFoundation/CoreFoundation.h>
+
+    void greet() {
+        CFTypeRef keys[] = {CFSTR("key")};
+        CFTypeRef values[] = {CFSTR("value")};
+        CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, keys, values, sizeof(keys) / sizeof(keys[0]),
+                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+        if (dict)
+            CFRelease(dict);
+        std::cout << "Hello from MyFramework!" << std::endl;
+    }
+    """)
+    frame_h = textwrap.dedent("""
+    #pragma once
+    #include <vector>
+    #include <string>
+    void greet();
+    """)
+    cpp_info_type = PackageType.SHARED if shared else PackageType.STATIC
+    conanfile = textwrap.dedent(f"""
+    import os
+    from conan import ConanFile
+    from conan.tools.cmake import CMake
+
+    class MyFramework(ConanFile):
+        name = "frame"
+        version = "1.0"
+        settings = "os", "arch", "compiler", "build_type"
+        languages = ["C++"]
+        package_type = "{'static-library' if shared else 'shared-library'}"
+        exports_sources = ["frame.cpp", "frame.h", "CMakeLists.txt"]
+        generators = "CMakeToolchain"
+
+        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.type = "{cpp_info_type}"
+            self.cpp_info.package_framework = "MyFramework"
+            self.cpp_info.location = os.path.join(self.package_folder, "MyFramework.framework", "MyFramework")
+            # Using also a system framework
+            self.cpp_info.frameworks = ["CoreFoundation"]
+    """)
+    test_main_cpp = textwrap.dedent("""
+    #include <MyFramework/frame.h>
+    int main() {
+        greet();
+    }
+    """)
+    test_conanfile = textwrap.dedent("""
+    import os
+    from conan import ConanFile
+    from conan.tools.cmake import CMake, cmake_layout
+    from conan.tools.build import can_run
+
+    class LibTestConan(ConanFile):
+        settings = "os", "compiler", "build_type", "arch"
+        generators = "CMakeConfigDeps", "CMakeToolchain"
+
+        def requirements(self):
+            self.requires(self.tested_reference_str)
+
+        def build(self):
+            cmake = CMake(self)
+            cmake.configure()
+            cmake.build()
+
+        def layout(self):
+            cmake_layout(self)
+
+        def test(self):
+            if can_run(self):
+                cmd = os.path.join(self.cpp.build.bindir, "example")
+                self.run(cmd, env="conanrun")
+    """)
+    test_cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(PackageTest CXX)
+    find_package(frame CONFIG REQUIRED)
+    add_executable(example main.cpp)
+    target_link_libraries(example frame::frame)
+    """)
+    client = TestClient()
+    client.save({
+        'test_package/main.cpp': test_main_cpp,
+        'test_package/CMakeLists.txt': test_cmakelists,
+        'test_package/conanfile.py': test_conanfile,
+        'CMakeLists.txt': cmakelists,
+        'frame.cpp': frame_cpp,
+        'frame.h': frame_h,
+        'conanfile.py': conanfile
+    })
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value} -o '*:shared={shared}'")
+    assert "Hello from MyFramework!" in client.out
diff --git a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new.py b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py
similarity index 100%
rename from test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new.py
rename to test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py
diff --git a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_cpp_linkage.py b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new_cpp_linkage.py
similarity index 100%
rename from test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_cpp_linkage.py
rename to test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new_cpp_linkage.py
diff --git a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py b/test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new_paths.py
similarity index 100%
rename from test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py
rename to test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new_paths.py
diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_frameworks.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_frameworks.py
new file mode 100644
index 00000000000..ebbd2853314
--- /dev/null
+++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_frameworks.py
@@ -0,0 +1,44 @@
+import platform
+import textwrap
+
+import pytest
+
+from conan.test.utils.tools import TestClient
+
+new_value = "will_break_next"
+
+
+@pytest.mark.skipif(platform.system() != "Darwin", reason="Only OSX")
+def test_package_framework_needs_location():
+    conanfile = textwrap.dedent(f"""
+    import os
+    from conan import ConanFile
+
+    class MyFramework(ConanFile):
+        name = "frame"
+        version = "1.0"
+        settings = "os", "arch", "compiler", "build_type"
+        package_type = 'static-library'
+
+        def package_info(self):
+            self.cpp_info.type = 'static-library'
+            self.cpp_info.package_framework = "MyFramework"
+    """)
+    test_conanfile = textwrap.dedent("""
+    import os
+    from conan import ConanFile
+
+    class LibTestConan(ConanFile):
+        settings = "os", "compiler", "build_type", "arch"
+        generators = "CMakeConfigDeps", "CMakeToolchain"
+
+        def requirements(self):
+            self.requires(self.tested_reference_str)
+    """)
+    client = TestClient()
+    client.save({
+        'test_package/conanfile.py': test_conanfile,
+        'conanfile.py': conanfile
+    })
+    client.run(f"create . -c tools.cmake.cmakedeps:new={new_value}", assert_error=True)
+    assert "Error in generator 'CMakeConfigDeps': cpp_info.location missing for framework MyFramework" in client.out
diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
index e54bb2a613e..82b899cf64f 100644
--- a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
+++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
@@ -19,6 +19,7 @@ class TestConan(ConanFile):
             def package_info(self):
                 self.cpp_info.includedirs = ["myincludes"]
                 self.cpp_info.libdirs = ["mylib"]
+                self.cpp_info.frameworkdirs = ["myframework"]
     """)
     c.save({"conanfile.py": conanfile})
     c.run("create .")
@@ -39,6 +40,7 @@ def build(self):
     assert re.search(r"list\(PREPEND CMAKE_PROGRAM_PATH \".*/bin\"", cmake_paths)  # default
     assert re.search(r"list\(PREPEND CMAKE_LIBRARY_PATH \".*/mylib\"", cmake_paths)
     assert re.search(r"list\(PREPEND CMAKE_INCLUDE_PATH \".*/myincludes\"", cmake_paths)
+    assert re.search(r"list\(PREPEND CMAKE_FRAMEWORK_PATH \".*/myframework\"", cmake_paths)
 
 
 def test_cmakedeps_transitive_paths():