Expand FindModule.cmake logic with a wrapper file - c++

There are a lot of libraries' packages which do not provide a CMake config file, and in order to find and use them with cmake, one would have to resort to using a FindPackage.cmake script. Some scripts (i.e. SDL) are available within the cmake itself, so finding a package is relatively easy.
Though in my case, SDL-searching scripts (SDL, SDL_image, SDL_mixer) are available almost since the dawn of modern cmake (at least 3.1), they do not provide the means for the modern approach - they do not define imported cmake Targets. SDL as a target is available only since 3.19, and it does not define IMPORTED_LOCATION property.
So, the logical thing is to define those targets and properties.
A naive approach would possibly be to just copy the contents of FindSDL.cmake from a newer cmake bundle and paste it with modifications.
But I would like to keep those files from a cmake bundle (or another good enough script from external source) intact and just wrap them.
So, the main CMakeLists.txt would be like:
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules")
find_package(SDL REQUIRED)
find_package(SDL_mixer REQUIRED)
find_package(SDL_image REQUIRED)
cmake/modules/FindSDL.cmake:
find_package(SDL REQUIRED)
if (NOT TARGET SDL::SDL)
# add target and properties here
endif()
However, the way I wrote it will not work because of the endless recursion.
How could I resolve the endless recursion with find_package when writing a wrapper FindPackage.cmake that uses the original FindPackage.cmake file?
This looks promising:
How to execute CMake's default find module from my own find module with the same name?

The first thing that comes to mind is to delete the current path from CMAKE_MODULE_PATH before calling find_package(), and then restore it.
list(REMOVE_ITEM CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
find_package(SDL)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
if (NOT TARGET SDL::SDL)
# add target and properties here
endif()

Related

Setting up a Vulkan project with CMake on Windows

Until this point I have installed MinGW, CMake, and the Vulkan SDK. I also downloaded the GLFW precompiled binaries, GLM and PkgConfig according to this answer. Then I created a CMake project in CLion. This is the content of the CMakeLists.txt (which I got from here):
cmake_minimum_required(VERSION 3.16)
project(VulkanTest)
set(CMAKE_CXX_STANDARD 17)
add_executable(VulkanTest main.cpp)
find_package(Vulkan REQUIRED)
target_include_directories(${PROJECT_NAME} PUBLIC ${Vulkan_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan)
find_package(PkgConfig REQUIRED)
pkg_search_module(GLM REQUIRED glm)
include_directories(${GLM_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${GLM_LIBRARY_DIRS})
find_package(glfw3 3.2 REQUIRED)
include_directories(${GLFW_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${GLFW_LIBRARIES})
The error message is as follows:
CMake Error at CMakeLists.txt:15 (find_package):
By not providing "Findglfw3.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "glfw3", but
CMake did not find one.
Could not find a package configuration file provided by "glfw3" (requested
version 3.2) with any of the following names:
glfw3Config.cmake
glfw3-config.cmake
Add the installation prefix of "glfw3" to CMAKE_PREFIX_PATH or set
"glfw3_DIR" to a directory containing one of the above files. If "glfw3"
provides a separate development package or SDK, be sure it has been
installed.
I also tried replacing find_package(glfw3 3.2 REQUIRED) with pkg_search_module(GLFW REQUIRED glfw3) as described on the GLFW website, but I get the errors "None of the required 'glfw3' found" and "None of the required 'glm' found".
First, the question, as this is likely what the all of the people visiting this thread are interested in. The problem demonstrated in the post seems very different from the question others probably want answered.
Answer For Visitors: You need to do three things in order to link with the Vulkan library on Windows when using cmake.
set(ENV{VULKAN_SDK} "Path/To/Vulkan/Version/Installation")
find_package(Vulkan REQUIRED)
target_link_libraries(target ${Vulkan_LIBRARIES})
The path should reference the specific version of Vulkan you are using. For me, this is C:/VulkanSDK/1.2.198.1, but it will be different for you depending on where Vulkan is installed and the version you want to use.
Don't forget to also add an include directory with something along the lines of target_include_directories(target PUBLIC "C:/VulkanSDK/1.2.198.1/Include") to avoid using absolute include paths for Vulkan headers in your code.
Explanation: The find_package command will search through a directory within your cmake installation for details about the package. For me, this directory is <cmake_install_dir>/share/cmake-3.18/Modules (3.18 should be replaced with the version you have installed.) In this directory you'll find a nice chunk of files named Find<PackageName>.cmake and FindVulkan.cmake should be among them. This file is what find_package is running under the hood. You'll notice a few instances of $ENV{VULKAN_SDK} in that file. This is why that VULKAN_SDK environment variable must be set before calling find_package. Cmake will throw errors if it isn't.
lizardsudoku's Problem (even though you probably already figured it out): As explained above, cmake is expecting to find a Findglfw3.cmake entry in that Modules directory and it didn't. Instead of creating one of those files yourself, it's easier to specify the glfw3 library directly in your CMakeLists.txt file like so.
list(APPEND CMAKE_PREFIX_PATH "path/to/lib/directory")
find_library(glfw NAMES glfw3 REQUIRED)
target_link_libraries(target ${glfw})
As someone mentioned, you want to make use of CMAKE_PREFIX_PATH to specify the directory the .lib is in. The find_library call can then search that directory for the glfw3.lib entry before it is specified as a linker input. Even though CMAKE_PREFIX_PATH affects what directories are searched through when find_package is used, the package file does not exist, hence the error does not change.

CMakeLists C++ Beginner

I've started playing a little bit with C++ and to make it happen I decided to write a simple game engine.
For this purpose, I'm using CLion as my IDE and it works all good but adding libraries is just a nightmare. First I've installed all required libraries like glew, glfw or glm using brew, all went fine. Then I spent almost 2 hours to get it to work on my project.
My biggest mystery is the reason why it works, I've worked with build systems in java, python or golang and everything was always clear to me. However, I have no idea why it works the way it works and I'd love to know!
Here is my CMakeLists file.
cmake_minimum_required(VERSION 3.10)
project(untitled2)
find_package(GLEW REQUIRED)
find_package(GLFW3 REQUIRED)
set(CMAKE_CXX_STANDARD 17)
add_executable(untitled2 main.cpp)
target_link_libraries(untitled2 ${GLEW_LIBRARIES})
target_link_libraries(untitled2 glfw)
Now I have a few questions:
1. Why am I able to use GLM library without including it in the CMakeLists?
2. Why do I need to include glfw and glew but not glm?
3. Why do I need to use ${GLEW_LIBRARIES} and not some name like glew? (I tried different names, but nothing worked.)
btw. I'm using macOS.
The first thing to remember is that C++ doesn't (yet) have a real module system like newer languages. It just has a list of directories that it searches for header files, a list of directories that it searches for libraries, and a list of libraries to search for symbols when linking. The target_link_libraries directive just adds compiler flags that add to those three lists.
Now, on to this specific scenario. Most of the magic happens in the find_package directive. That directive really just ends up running cmake scripts. Sometimes those are packaged with cmake, sometimes they're installed along with the package you're finding. In the end, those scripts can do basically whatever they want. They all have the same objective, to give you a way to add the appropriate compiler flags to use the package, but there are a couple common ways they do that.
The older way is to set variables that you can use to tell the compiler what directories to search for headers and libraries and what libraries to link to. That's the approach GLEW seems to have taken. It sets the variables GLEW_LIBRARIES and GLEW_INCLUDE_DIRS and you then have to use link_libraries and include_directories to tell the compiler what to do. This was the only approach available in older versions of cmake (pre 2.8 IIRC), so while it's not as nice to use, it is still how many libraries' find_package scripts work.
The newer way is to create imported targets. Those targets have appropriate properties set so that any targets that link to the imported target inherit the appropriate include directories and library flags. This is the approach GLFW took. It creates an imported target named glfw that has the INTERFACE_INCLUDE_DIRECTORIES and INTERFACE_LINK_LIBRARIES properties set. When you pass that to target_link_libraries, your untitled2 target will inherit those include directories and libraries.
Finally, GLM is a header-only library. There are no library files to link to, so as long as the appropriate directory is added to the compilers header search path you'll be able to include and use GLM.
Since you used homebrew to install your libraries, all of their headers are likely under the same base directory; most likely "/usr/local/include". All of their library files are similarly likely under the same directory; probably something "/usr/local/lib". That means that your compiler will be able to find any of their headers and libraries is you tell it to search "/usr/local/include" for headers and "/usr/local/lib" for libraries.
So, to finally answer the question: Thing's work because the glfw target told cmake that it should set the compiler flags to add "/usr/local/include" to its list of include directories. Since that's the same directory it needs to search for GLM and GLEW, the compiler is able to find the headers for all of your libraries. The compiler is also able to find the library files it need to link to because cmake told it to look for them explicitly via the list GLEW_LIBRARIES and the inherited properties from the glfw target. GLM doesn't have any library files to link to, so there's nothing to tell it about.
You really shouldn't rely on everything being in the same place though. You should be able to tell the compiler about everything like this (note that I haven't actually tested this):
cmake_minimum_required(VERSION 3.10)
project(untitled2)
set(CMAKE_CXX_STANDARD 17)
add_executable(untitled2 main.cpp)
# This will fill the variables GLEW_INCLUDE_DIRES and GLEW_LIBRARIES
# that you can use to add the appropriate compiler flags
find_package(GLEW REQUIRED)
# This will create an imported target named glfw that you can link to
# to inherit the appropriate include directories and libraries
find_package(GLFW3 REQUIRED)
# This also creates an imported target named glm that you can "link to"
# to inherit the appropriate include directories
find_package(glm REQUIRED)
# GLEW uses an old-style find_package script, so you have to
# explicitly tell cmake about GLEW's include directories
target_include_directories(untitled2 PUBLIC ${GLEW_INCLUDE_DIRS})
# And the library files to link to
target_link_libraries(untitled ${GLEW_LIBRARIES})
# cmake will automatically add the appropriate include directories
# and library files that the imported glfw target tells it about
target_link_libraries(untitled2 glfw)
# You use the target_link_libraries directive with the glm imported target
# even though you're not actually linking to any libraries. It's just how
# you tell cmake you want your untitled2 target to inherit the appropriate
# include directories from the imported glm target
target_link_libraries(untitled2 glm)

Link external library CMakeLists.txt on Windows

I'm trying to realize a CMakeLists.txt to compile my project under Linux and Windows. I use two libraries : SFML and boost. However, I want that my project can be built under Linux or Windows without the installation of libraries from a user.
I explain. Libraries SFML and boost are compiled and placed in my folder project and I want that no matter on which computer I download my project, I can build it.
And here is my problem, I can't link externally from the libraries folder in my folder project, the SFML and boost libraries.
Here is my CMakeLists.txt :
cmake_minimum_required(VERSION 3.0)
project(r-type_client CXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
include_directories(./boost/include
./SFML/include
./include
)
if(UNIX)
set(BOOST_LIBRARY
-L./boost/linux
-lboost_regex
)
set(SFML_LIBRARY
-L./SFML/linux
-lsfml-graphics
-lsfml-window
-lsfml-system
)
add_executable(r-type_client
srcs/Main.cpp
)
target_link_libraries(r-type_client
${BOOST_LIBRARY}
${SFML_LIBRARY}
)
elseif(WIN32)
add_library(bar SHARED IMPORTED)
set_property(TARGET bar PROPERTY IMPORTED_LOCATION
C:/Users/Hugo/Downloads/Old/client/SFML/windows/sfml-graphics.dll)
set_property(TARGET bar PROPERTY IMPORTED_IMPLIB
C:/Users/Hugo/Downloads/Old/client/SFML/windows/sfml-graphics.lib)
add_library(bor SHARED IMPORTED)
set_property(TARGET bor PROPERTY IMPORTED_LOCATION
C:/Users/Hugo/Downloads/Old/client/SFML/windows/sfml-window.dll)
set_property(TARGET bor PROPERTY IMPORTED_IMPLIB
C:/Users/Hugo/Downloads/Old/client/SFML/windows/sfml-window.lib)
add_library(bur SHARED IMPORTED)
set_property(TARGET bur PROPERTY IMPORTED_LOCATION
C:/Users/Hugo/Downloads/Old/client/SFML/windows/sfml-system.dll)
set_property(TARGET bur PROPERTY IMPORTED_IMPLIB
C:/Users/Hugo/Downloads/Old/client/SFML/windows/sfml-system.lib)
add_executable(r-type_client srcs/Main.cpp)
target_link_libraries(r-type_client bar bor bur)
endif(UNIX)
As I've mentioned in the comments, you should set up your CMake project in a proper way by using find_package() to properly locate everything.
CMake is a tool to allow people to creation files needed for compilation on the fly no matter the host system. By hardcoding paths and (unneeded) per-platform branches, you're basically negating the whole purpose of CMake.
To include a third party library in CMake, you'll typically want to use find_package(), which includes the necessary logic to locate the library and setup variables properly (it essentially does what you've done by hand so far).
Let's do it for SFML, since it's basically the same for Boost (and I don't really have Boost ready to test everything, because the variable names might differ from project to project).
First, you'll tell CMake that your project wants to use SFML:
find_package(SFML)
Depending on the actual "package", you can extend this. In SFML's case we can even define which minimum version we want and which sub libraries we actually want to use:
find_package(SFML 2.3 COMPONENTS graphics window system REQUIRED)
The REQUIRED flag makes the whole thing mandatory, i.e. CMake will error out, if it's not found.
Once this line succeeded, it will have set a few variables with the proper paths and libraries, which we can then use when defining a target:
include_directories(${SFML_INCLUDE_DIR})
add_executable(myprogram ${MY_SOURCE_FILES})
target_link_libraries(myprogram ${SFML_LIBRARIES} ${SFML_DEPENDENCIES})
And you're done. As you can see, I can setup a CMake project in less than 10 lines without any platform specific paths, code, or knowledge.
However, when running this, you'll most likely run into one error:
CMake by default might not be able to find a FindSFML.cmake file and complain.
You'll find this file in your SFML directory under the cmake path. Copy it to your project and tell CMake where to look for it, for example:
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
Once this is done as well, CMake might not be able to locate the actual SFML files, especially on Windows. To tell it where to look for it, you'd define SFML_ROOT when invoking CMake:
cmake -DSFML_ROOT=C:/Users/Hugo/Downloads/Old/client/SFML path/to/source
This gives you the big advantage that you won't have to hardcode the path to SFML (or any other library such as Boost). On your linux machine, you typically won't even have to provide any path and everything should just work out of the box.

CMake cannot find freeglut on windows in Clion

I've been stuck for a while now and I can't figure out how to get freeglut working. I thought I knew what it was asking me to do, so I added that set(prefix_path) line but it didn't do anything. Am I supposed to write my own freeglut-config.cmake or what?
Note: I am using the freeglut for MinGW package from this website
CMake File:
cmake_minimum_required(VERSION 3.7)
project(HW1)
set(CMAKE_CXX_STANDARD 11)
set(SOURCE_FILES Triangle.cpp)
set(CMAKE_PREFIX_PATH "C:/freeglut")
find_package(GLEW REQUIRED STATIC)
find_package(FREEGLUT REQUIRED)
find_package(OPENGL REQUIRED)
include_directories(${FREEGLUT_INCLUDE_DIRS} ${GLEW_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIRS})
link_directories(${FREEGLUT_LIBRARY_DIRS} ${GLEW_LIBRARY_DIRS} ${OPENGL_LIBRARY_DIRS})
add_definitions(${FREEGLUT_DEFINITIONS} ${GLEW_DEFINITIONS} ${OPENGL_DEFINITIONS})
add_executable(HW1 ${SOURCE_FILES})
target_link_libraries(HW1 ${FREEGLUT_LIBRARIES} ${GLEW_LIBRARIES} ${OPENGL_LIBRARIES})
Full error:
CMake Error at CMakeLists.txt:8 (find_package):
By not providing "FindFREEGLUT.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "FREEGLUT",
but CMake did not find one.
Could not find a package configuration file provided by "FREEGLUT" with any
of the following names:
FREEGLUTConfig.cmake
freeglut-config.cmake
Add the installation prefix of "FREEGLUT" to CMAKE_PREFIX_PATH or set
"FREEGLUT_DIR" to a directory containing one of the above files. If
"FREEGLUT" provides a separate development package or SDK, be sure it has
been installed.
If your application is GLUT-compatible, that it doesn't use any extension of freeglut, then it is better to search GLUT instead of FREEGLUT:
find_package(GLUT REQUIRED)
"Find" script used by this command is already shipped into CMake distro, and it searches freeglut too.
(Note, that with that command variables for include directories and linking libraries are GLUT_INCLUDE_DIR and GLUT_LIBRARY correspondingly).
If your application requires exactly freeglut (that is, uses some of its extensions incompatible with other GLUT implementations), you need to ship your package with FindFREEGLUT.cmake script and adjust CMAKE_MODULE_PATH variable correspondingly:
# Assuming you have <source-dir>/cmake/FindFREEGLUT.cmake
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
find_package(FREEGLUT REQUIRED)
You may find existing script in the net, or write it by yourself, like here.
In any case, if you have freeglut installed into non-system location, you need to hint CMake about that. E.g., by adjusting CMAKE_PREFIX_PATH.

How to export libraries with components in CMAKE?

I have tried finding this information on the official CMAKE wiki as well as searching SO (currently waiting for boost to download so I can wade through the source looking for how they do it). I was hoping someone here may be able to help with how this is done, or point me in the right direction to the answers!
I have a project that has several components. Right now, the project has subdirectories for libraries, and for applications. I am attempting to refactor the project and have applications in individual repositories and have the libraries exported.
How do other projects make it possible to use the following command (specifically, specifying which components):
FIND_PACKAGE (Boost REQUIRED COMPONENTS system date_time filesystem)
I would like to use the same system for my own project:
FIND_PACKAGE (Project REQUIRED COMPONENTS view gui execution analysis)
Any help you could provide would be greatly appreciated.
A good guide on how to write finders you may find in CMake documentation (old CMake packages for popular distros had /usr/share/cmake/Modules/readme.txt file, but it seems nowadays it's absent or doesn't have any helpful info anymore ;-). Particularly it explains how to write a correct module w/ supporting "standard" syntax (REQUIRE, COMPONENTS & etc) using find_package_handle_standard_args. This is applicable for non-CMake-based packages and turns the find_package into module mode.
For CMake-based projects, there is a better (native) way to export targets (aka config mode of the find_package). The modern CMake can help you to generate the needed *.cmake files, and your project ought to install() 'em into a proper location. So, dependent projects could use find_package() to import your targets (libraries or executables).
How to find packages, and also how to write your own find modules is described here:
http://www.cmake.org/Wiki/CMake:How_To_Find_Libraries
here is a very simple find module of mine. It´s located in the top dir where all my librarys are located, so in this case finding the correct paths is rather trivial.
# AsmjitConfig.cmake
# - Config file for the Asmjit package
# sets:
# Asmjit_FOUND
# Asmjit_INCLUDE_DIR
# Asmjit_LIBRARIES
set(Asmjit_FOUND FALSE)
find_library(Asmjit_LIBRARY NAMES asmjit HINTS ${CMAKE_CURRENT_LIST_DIR}/asmjit)
find_path(Asmjit_INCLUDE_DIR asmjit/asmjit.h HINTS ${CMAKE_CURRENT_LIST_DIR}/asmjit/src)
message(STATUS "${Asmjit_INCLUDE_DIR}")
message(STATUS "${Asmjit_LIBRARY}")
if(NOT Asmjit_LIBRARY OR NOT Asmjit_INCLUDE_DIR)
set(Asmjit_FOUND FALSE)
else()
set(Asmjit_FOUND TRUE)
endif()
in your CMakeLists.txt tell cmake where it can find your modules:
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "path/to/librarys")
after that find_package should work just fine.