1

I have a project which depends on FreeType, and uses CMake as build system. CMake has a FindFreeType built-in module which is supposed to be used like this, see for example this other SO question:

find_package(Freetype REQUIRED)
target_link_libraries(mylib ${FREETYPE_LIBRARIES})
target_include_directories(mylib PRIVATE ${FREETYPE_INCLUDE_DIRS})

Since CMake 3.10, there is also the Freetype::Freetype imported target so we can avoid the target_include_directories:

find_package(Freetype REQUIRED)
target_link_libraries(mylib Freetype::Freetype)

This worked great on Ubuntu 18.04 with FreeType installed via apt install libfreetype6-dev. I assume it also works on macOS when the package is installed via homebrew (I haven't tested yet).

However, on Windows, I wish to allow developers to depend on a vcpkg-installed FreeType:

git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg integrate install
.\vcpkg install freetype:x64-windows

Which they would target by running the following CMake command:

cmake .. -G "Visual Studio 15 2017" -A x64 -DCMAKE_TOOLCHAIN_FILE=C:/Users/Boris/vcpkg/scripts/buildsystems/vcpkg.cmake

Unfortunately, the above CMake command won't work with any of the two CMakeLists.txt at the beginning of this question, because the proper way to find and link to FreeType when it is installed via vcpkg is the following:

find_package(freetype CONFIG REQUIRED) # `Freetype` works too, but vcpkg doc recommends `freetype`
target_link_libraries(mylib freetype)  # Here, all-lowercase is required

In particular, the freetype-config.cmake config file provided by vcpkg defines the target freetype (not Freetype::Freetype like the builtin find module), and doesn't define any of the FREETYPE_LIBRARIES or FREETYPE_INCLUDE_DIRS variables.

What would be a proper way to keep my CMakeLists.txt compatible with both "traditional" ways of finding FreeType, but also vcpkg?

Assuming pre-CMake 3.10, I'm thinking of something along the lines of:

if(DEFINED VCPKG_TARGET_TRIPLET)
    find_package(freetype CONFIG REQUIRED)
    set(FREETYPE_LIBRARIES freetype)
    set(FREETYPE_INCLUDE_DIRS "")
else()
    find_package(Freetype REQUIRED)
endif()
target_link_libraries(mylib ${FREETYPE_LIBRARIES})
target_include_directories(mylib PRIVATE ${FREETYPE_INCLUDE_DIRS})

Would that seem like good practice? Any better idea?

It feels ugly, and besides, there is always the possibility of a developer wanting to use vcpkg for some other dependencies but not for FreeType (e.g., explicitly providing FREETYPE_DIR instead), so this trick wouldn't even be enough in all situations, and we would need another CMake option like MYLIB_IGNORE_VCPKG_FREETYPE which starts to be even uglier.

Boris Dalstein
  • 7,015
  • 4
  • 30
  • 59

2 Answers2

2

Would that seem like good practice? Any better idea?

No be agnostic about a possible package manager.

Do the following:

find_package(Freetype CONFIG) # should find freetype-config.cmake if available
find_package(Freetype REQUIRED) # Will not be executed if Freetype_FOUND ist already set
# if you do not want two find_package calls consider using CMAKE_FIND_PACKAGE_PREFER_CONFIG

Then test if the target Freetype::Freetype or freetype exists

 if(TARGET freetype AND NOT TARGET Freetype::Freetype)
     add_library(Freetype::Freetype ALIAS freetype) # target freetype is defined by freetype-targets.cmake
     # might need to add freetype to global scope if cmake errors here
     # alternativly if the above does not work for you you can use
     # add_library(Freetype::Freetype INTERFACE IMPORTED)
     # target_link_libraries(Freetype::Freetype INTERFACE freetype)
 endif()
 if(NOT TARGET Freetype::Freetype)
     # insert error here
     # or create the target correctly (see cmakes newer FindFreetype.cmake)
 endif()
 target_link_libraries(mylib PRIVATE Freetype::Freetype)

if you don't want to alias the target you could also define a variable called FREETYPE_TARGET and set it to the correct target for linking against.

Alexander Neumann
  • 1,479
  • 8
  • 17
  • This is a great idea! Thanks and welcome to Stack Overflow :-) I haven't tried yet but I'm already accepting the answer since it does seem like it should work, and I might otherwise forget to come back and accept the answer. – Boris Dalstein Apr 09 '20 at 20:19
  • I finally had some time. There were minor problems: we need "QUIET" instead of "REQUIRED" in the first call, and setting an ALIAS wasn't smooth: we need CMake 3.11+ to define an alias to an imported target (and CMake 3.18+ to a non-global imported target, although [IMPORTED_GLOBAL](https://stackoverflow.com/questions/45401212) helps). A solution that worked was to redefine a new target via add_library(Freetype::Freetype SHARED IMPORTED) and set all its properties. But then, I realized that none of this is in fact necessary: I posted a new answer. Anyway, thx a lot for your help! – Boris Dalstein Aug 11 '20 at 22:51
2

In older versions of vcpkg (< Jan 2020, see vcpkg#9311), there used to be the following message when installing Freetype:

The package freetype is compatible with built-in CMake targets:

    find_package(Freetype REQUIRED)
    target_link_libraries(main PRIVATE Freetype::Freetype)

In current versions, they instead recommend find_package(freetype CONFIG REQUIRED), which ensures that the config package vcpkg/<...>/freetype-config.cmake takes precedence over the CMake's built-in module package FindFreetype.cmake.

However, using the built-in module package still works correctly: it will find the vcpkg-installed library, and will define a Freetype::Freetype target (if CMake >= 3.10) rather than a freetype target.

Note that by default, find_package(Freetype REQUIRED) searches first for module packages, then for config packages. However, users can sets CMAKE_FIND_PACKAGE_PREFER_CONFIG, which would use the config package instead. Therefore, a robust approach to ensure that Freetype::Freetype is defined is to do the following:

find_package(Freetype MODULE REQUIRED)
target_link_libraries(mylib Freetype::Freetype)

However, if CMake < 3.10, this still doesn't define a Freetype::Freetype target. One option in this case is to vendor a copy of a more recent version of FindFreetype.cmake in your CMAKE_MODULE_PATH.

For even more robustness, you could even call it Find<Mylib>Freetype.cmake, in the unlikely case where your CMakeLists.txt is used as a subdirectory of another project which also modifies CMAKE_MODULE_PATH and provides an incompatible FindFreetype.cmake.

Boris Dalstein
  • 7,015
  • 4
  • 30
  • 59
  • Please no..... IMPORTED_LOCATION "${FREETYPE_LIBRARIES}" is wrong _LIBRARIES variables can contain the following: "optimized;somelib.lib;debug;someother.lib" which breaks your code. – Alexander Neumann Aug 13 '20 at 22:43
  • @AlexanderNeumann You're right, thanks for the info. I'm still learning a lot of these things. I've updated the answer with a more robust solution. I still find this solution ugly, but unfortunately, your answer doesn't seem to work with CMake < 3.10 :( – Boris Dalstein Aug 14 '20 at 11:04
  • there is really no valid reason to be stuck in a three year old CMake.... The only way to make sure CMake modules are stable across cmake versions is to vendor them yourself which is the `CMAKE_MODULE_PATH` solution you describe (This is also done by e.g. VTK) The absolute best solution is to provide custom `FindFreetype.cmake` with unique namespaced targets for your project (redirection approach). This way target collision are avoided. But your solution is still broken since users might set `CMAKE_FIND_PACKAGE_PREFER_CONFIG` which does net set the `LIBRARY` vars – Alexander Neumann Aug 14 '20 at 14:25
  • @AlexanderNeumann Ubuntu 16.04 LTS reaches end-of-life April 30th 2021. It ships with cmake 3.5.1 (https://launchpad.net/ubuntu/xenial/+source/cmake). I'd like folks on Ubuntu 16.04 to be able to easily compile my app without having to get newer versions of CMake not officially supported on their system. There is no reason to force people to have a cutting-edge CMake when workarounds exist. – Boris Dalstein Aug 14 '20 at 16:07
  • @AlexanderNeumann Your point about `CMAKE_FIND_PACKAGE_PREFER_CONFIG` is valid though. I suppose we can work around this by using `find_package(Freetype MODULE REQUIRED)`, or as you suggest even more robust `find_package(Freetype REQUIRED)`. – Boris Dalstein Aug 14 '20 at 16:16
  • @AlexanderNeumann I have updated the answer with the additional info, and removed the in-CMakeLists, which was quite ugly anyway. – Boris Dalstein Aug 14 '20 at 16:46
  • > I'd like folks on Ubuntu 16.04 to be able to easily compile my app without having to get newer versions of CMake not officially supported on their system Kitware actually supports it ..... https://apt.kitware.com/ and I expect developers on linux to be able to install a newer cmake version. Otherwise they should switch to windows ;) Furthermore correct usage of vcpkg basically requires at least the cmake version of vcpkg. It is not strictly enforced by vcpkg (but it should be....) due to changes in cmakes modules. – Alexander Neumann Aug 21 '20 at 09:34
  • @AlexanderNeumann Any error is frustrating, even with a clear error message saying what the problem is (e.g., CMake is outdated). As experts, we tend to forget how frustrating and intimidating it is. My goal is to lower the barrier of entry. I prefer to make the assumption that users trying to compile my apps have zero experience compiling, and zero knowledge of what cmake is, because everyone has a first time. Having happy users is paramount, and it's literally easier for me to support CMake 3.1 than explain how to upgrade CMake for various operating systems ;) – Boris Dalstein Aug 21 '20 at 13:51