Portable install packages with find package on frameworks?

I’m seeing conflicting information and wondering what the best practice is for handling frameworks with installed packages. Since find_package is setting a hard coded path on some of the macOS system frameworks I’m seeing the path in the installed package from using the install export commands.

I was originally going to suggest that someone skip find_package for system frameworks and use something such as -framework CoreFoundation as a variable instead. However I’m seeing conflicting information on why not to do that after looking to see if there was a bug on this.

https://gitlab.kitware.com/cmake/cmake/-/issues/20871
https://gitlab.kitware.com/cmake/cmake/-/issues/18753

What is the proper way to handle making sure the install package is portable with target names?

I think one should always use IMPORTED targets and re-find them once installed so that things are fully relocatable (basically, you find the package again so that if it moved, it doesn’t need to be tracked explicitly).

I don’t think I’m following. Since find_package on system frameworks such as CoreFoundation is putting hard coded paths in INTERFACE_LINK_LIBRARIES doesn’t that kind of negate any downstream call on find_package since it’s not a target?

No, because your targets will refer to it as Apple::CoreFoundation (or whatever). When that target gets remade, it can re-find the framework and provide a new path.

That sounds like an ideal state and what I was expecting to see, however it’s not what I’m observing. I’m seeing the hard coded paths in INTERFACE_LINK_LIBRARIES instead of a target.

Interesting. Can you share a minimal example that shows this?

Here’s an example, I did the following with these three files.

  1. mkdir build && cd build
  2. cmake ..
  3. cmake --build .
  4. cmake --install .
  5. open install/lib/cmake/libtestLibTargets.cmake and observe the INTERFACE_LINK_LIBRARIES for target testLib::testLib

CMakeLists.txt

cmake_minimum_required(VERSION 3.23)
project("Example Framework")

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install)
set(CMAKE_INSTALL_INCLUDEDIR ${CMAKE_BINARY_DIR}/install/include)

find_library(APPKIT_LIBRARY AppKit)

set(targetName "testLib")

add_library(${targetName} SHARED dummy.cpp)

target_link_libraries(${targetName} PUBLIC ${APPKIT_LIBRARY})

install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dummy.h DESTINATION include)
install(TARGETS ${targetName} EXPORT ${targetName}Targets
    RUNTIME DESTINATION bin
    ARCHIVE DESTINATION lib
    LIBRARY DESTINATION lib
    FRAMEWORK DESTINATION lib
)
install(EXPORT ${targetName}Targets
    FILE lib${targetName}Targets.cmake
    NAMESPACE ${targetName}::
    DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/lib${targetName}
    COMPONENT ${targetName}
)

dummy.h

#include <string>

class dummy {
public:
    dummy(std::string inputString);

private:
    std::string _myString;
};

dummy.cpp

#include "dummy.h"

dummy::dummy(std::string inputString) : _myString{ inputString } {}

I don’t know if it is related to your core issue, but you’ve used absolute paths in a couple of places that should use relative paths:

  • The CMAKE_INSTALL_INCLUDEDIR variable should be relative to the install prefix, so you should only be setting it to include, not ${CMAKE_BINARY_DIR}/install/include.
  • Similar for installing the export file. The install(EXPORT ...) command should use a relative DESTINATION. It should be just lib/cmake/lib${targetName}, not ${CMAKE_INSTALL_PREFIX}/lib/cmake/lib${targetName}.

As for linking AppKit, don’t use find_library() for that. In a multi-architecture world like Xcode on Apple platforms, find_library() isn’t appropriate because it typically will only find a library for one architecture. In this case, AppKit is expected to be provided by the SDK, so it should be linked by name only. There’s a couple of ways of doing that. With CMake 3.24 or later, I’d recommend using the generator expression that specifies to treat its argument as a framework:

target_link_libraries(${targeTName} PUBLIC $<LINK_LIBRARY:FRAMEWORK,AppKit>)

If you need to support older CMake versions, you can list the framework name with the -framework flag as a single quoted argument:

target_link_libraries(${targeTName} PUBLIC "-framework AppKit")

Thanks that clears things up.