Introduction to CMake

imageCMake is a cross-platform utility for automatically building a program from source code. At the same time, CMake itself is not directly involved in the assembly, but is a front-end. Various versions of make and Ninja can act as back-ends. CMake also allows you to create projects for CodeBlocks, Eclipse, KDevelop3, MS VC ++ and Xcode. It is worth noting that most of the projects are not created natively, but all with the same back-end.

In order to build a project using CMake, you need to place the CMakeLists.txt file in the root of the source tree, which stores the rules and goals of the assembly, and take a few simple steps.
Let's look at examples.

Example 1. Hello, World:


To begin with, we will write the simplest worldword and create the project structure:
main.cpp
#include 
int main(int argc, char** argv)
{
	std::cout << "Hello, World!" << std::endl;
	return 0;
}


CMakeLists.txt
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
									# Если версия установленой программы
									# старее указаной, произайдёт аварийный выход.
add_executable(main main.cpp)		# Создает исполняемый файл с именем main
									# из исходника main.cpp



The CMake syntax is similar to the bash syntax, everything after the "#" character is a comment and will not be processed by the program. CMake allows not to clog the source tree with temporary files - it is very simple and without any extra gestures, the assembly is performed “Out-of-Source”.

Create an empty directory for temporary files and go there.
fshp @ panica-desktop: ~ $ mkdir tmp
fshp @ panica-desktop: ~ $ cd tmp /
fshp @ panica-desktop: ~ / tmp $

Now run the cmake command, passing it the path to the source folder as a parameter:
fshp @ panica-desktop: ~ / tmp $ cmake ~ / cmake / example_1 /
...
- Build files have been written to: / home / fshp / tmp
fshp @ panica-desktop: ~ / tmp $
fshp @ panica-desktop: ~ / tmp $ ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
fshp @ panica-desktop: ~ / tmp $

We see that several temporary files necessary for building the project appeared in the folder.
Now you can run make directly:
fshp @ panica-desktop: ~ / tmp $ make
Scanning dependencies of target main
[100%] Building CXX object CMakeFiles / main.dir / main.cpp.o
Linking CXX executable main
[100%] Built target main
fshp @ panica-desktop : ~ / tmp $ ./main
Hello, World!
fshp @ panica-desktop: ~ / tmp $

So, our program has gathered.
The tmp folder can be cleaned / deleted without the risk of breaking the source. If CMakeLists.txt has been changed, then a call to make will automatically start cmake. If the sources were moved, then you need to clear the temporary directory and run cmake manually.

Example 2. Libraries:


If your project contains a library, then CMake will build it without any problems.
To do this, we complicate the example.
foo.h
void hello_world();


foo.cpp
#include 
void hello_world()
{
	std::cout << "Hello, World!" << std::endl;
}


main.cpp
#include "foo.h"
int main(int argc, char** argv)
{
	hello_world();
	return 0;
}


CMakeLists.txt
cmake_minimum_required(VERSION 2.8)	 # Проверка версии CMake.
										# Если версия установленой программы
										# старее указаной, произайдёт аварийный выход.
project(hello_world)			# Название проекта
set(SOURCE_EXE main.cpp)		# Установка переменной со списком исходников для исполняемого файла
set(SOURCE_LIB foo.cpp)			# Тоже самое, но для библиотеки
add_library(foo STATIC ${SOURCE_LIB})	# Создание статической библиотеки с именем foo
add_executable(main ${SOURCE_EXE})	# Создает исполняемый файл с именем main
target_link_libraries(main foo)		# Линковка программы с библиотекой



Variables can store lists of values ​​separated by spaces / tabs / hyphens:
set(SOURCE main.cpp foo.cpp)
set(HEADER main.h
			foo.h)

Both options are correct.
To get the value of a variable, we use the construction:
${var_name}

So, this version of our project includes one static library compiled from source. If we replace “STATIC” with “SHARED”, we get a dynamic library. If the type of the library is not specified, by default it will be assembled as static.
When linking, all necessary libraries are indicated:
target_link_libraries(main  foo
							ogg
							vorbis)

As with manual compilation, library names are specified without the standard “lib” prefix.
So, building libraries with CMake does not cause problems, while the type of library static / dynamic changes with only one parameter.

Example 3. Subprojects:


Subprojects are very convenient if your program is divided into several libraries or the project consists of several programs.
Each subproject is essentially a full-fledged project and can be used independently.
Now we have “foo” located in the subdirectory and the CMakeLists.txt subproject is also located there.
CMakeLists.txt
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
									# Если версия установленой программы
									# старее указаной, произайдёт аварийный выход.
project(hello_world)				# Название проекта
set(SOURCE_EXE main.cpp)			# Установка переменной со списком исходников
include_directories(foo)			# Расположение заголовочных файлов
add_executable(main ${SOURCE_EXE})	# Создает исполняемый файл с именем main
add_subdirectory(foo)				# Добавление подпроекта, указывается имя дирректории
target_link_libraries(main foo)		# Линковка программы с библиотекой


main.cpp
#include "foo.h"
int main(int argc, char** argv)
{
	hello_world();
	return 0;
}


foo / CMakeLists.txt
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
									# Если версия установленой программы
									# старее указаной, произайдёт аварийный выход.
project(foo)				# Название проекта
set(SOURCE_LIB foo.cpp)		# Установка переменной со списком исходников
add_library(foo STATIC ${SOURCE_LIB})# Создание статической библиотеки


foo / foo.h
void hello_world();


foo / foo.cpp
#include 
void hello_world()
{
	std::cout << "Hello, World!" << std::endl;
}



There is nothing new for you in the subproject file. And here are the new commands in the main file:

include_directories(foo)
main.cpp we did not change, and foo.h transferred. The command tells the compiler where to look for the header files. It can be called several times. Headers will be searched in all specified directories.

add_subdirectory(foo)
Specify the directory with the subproject, which will be assembled as an independent.
Conclusion: projects on CMake can be combined into rather complex hierarchical structures, and each subproject in reality is an independent project, which in turn can itself consist of subprojects. This makes it easy to split your program into the required number of individual modules. An example of this approach is KDE.

Example 4. Searching for libraries:


CMake has quite developed means of searching for installed libraries, although they are not built-in, but are implemented as separate modules. There are quite a few modules in the standard package, but some projects (for example, Ogre) supply their own. They allow the system to automatically detect the availability of libraries necessary for linking a project.
On debian, the modules are located in /usr/share/cmake-2.8/Modules/ (your version may vary). The libraries called FindNAME.cmake are responsible for the library search, where NAME is the name of the library.
find_package(SDL REQUIRED)
if(NOT SDL_FOUND)
	message(SEND_ERROR "Failed to find SDL")
	return()
else()
	include_directories(${SDL_INCLUDE_DIR})
endif()
##########################################################
find_package(LibXml2 REQUIRED)
if(NOT LIBXML2_FOUND)
	message(SEND_ERROR "Failed to find LibXml2")
	return()
else()
	include_directories(${LIBXML2_INCLUDE_DIR})
endif()
##########################################################
find_package(Boost COMPONENTS thread-mt REQUIRED)
if(NOT Boost_FOUND)
	message(SEND_ERROR "Failed to find boost::thread-mt.")
	return()
else()
	include_directories(${Boost_INCLUDE_DIRS})
endif()
##########################################################
target_link_libraries(${TARGET} 
								${SDL_LIBRARY}
								${LIBXML2_LIBRARIES}
								${Boost_LIBRARIES})

I think the meaning should be clear. The first and second block is a library search. If the system does not have one, an error message is displayed and cmake completes. The third block is similar, but it is not looking for a whole package of libraries, but only the necessary component. Each such automated search determines after execution at least 3 variables:
SDL_FOUND, LIBXML2_FOUND, Boost_FOUND - a sign of the presence of a library;
SDL_LIBRARY, LIBXML2_LIBRARIES, Boost_LIBRARIES - library names for linking;
SDL_INCLUDE_DIR, LIBXML2_INCLUDE_DIR, Boost_INCLUDE_DIRS - paths to header files.
If the former is more or less clear, then the latter and the third have given me a lot of trouble - half have names in the singular, half in the plural. But it turned out to be easy to track. In each module, at the beginning there are comments, the defined variables are described there. See, for example, /usr/share/cmake-2.8/Modules/FindLibXml2.cmake.
As you can see, CMake is able to determine the presence and location of the necessary libraries and header files. In principle, any automatic assembly system should be able to do this, otherwise is there any point in it?

Example 5. External libraries and object files:


If you write for "uncle", and the evil "uncle" loves self-written libraries and does not want to share the source code, so he sends the finished library, then you are at the address.
Object files in CMake are on a par with the source - just include the object in the list of files for compilation.
With libraries tighter. As you know, a static library is nothing more than an ar-archive, inside of which there are ordinary objects that are not connected in any way. You probably already guessed what I did first. Yes, I just gutted the library. But then a more elegant way was found:
add_library(netutil STATIC IMPORTED)
set_property(TARGET netutil PROPERTY
             IMPORTED_LOCATION Binary/game_client/libnetutil.a)

The word "IMPORTED" indicates that the library is taken from the outside.
In CMake, each target has parameters, and set_property allows you to change them.
Such a library is linked as standard:
target_link_libraries(${TARGET} netutil)

For dynamic libraries, everything is similar, only the type is "SHARED", the extension is ".so".
Unfortunately, support for non-system libraries is implemented a bit crutally. Perhaps I just do not know the right option, so I will be glad if you "poke a muzzle." On the other hand, this is not a fancy exoskeleton with a life support system, but a simple crutch of two lines.

Generators:


As said at the beginning, CMake can generate many different kinds of projects. This is convenient and allows you to use CMake for almost any popular IDE.
If you run cmake without parameters, the available generators will be described at the end. Use like this:
fshp @ panica-desktop: ~ / tmp $ cmake ~ / cmake / example_3 / -G "KDevelop3 - Unix Makefiles"

Conclusion:


This is not a translation of the manual, but the result of using CMake in one commercial project. I would be glad if the article helps at least one person - in Russian such documentation is pretty small.

What I personally liked CMake:
  • one project - one file. No need to store a bunch of configuration, assembly, and other junk scripts;
  • Speed ​​compared to autotools;
  • simple and understandable syntax, of course, you can’t compete with the elegance of a python, but it’s also not a brainfax, after all .;
  • is the front-end for many IDEs;
  • displaying progress is quite convenient;
  • color conclusion - in gray everyday life a little paint does not hurt;

There is a plugin for Sublime Text that adds CMake syntax highlighting, it’s called “CMake”.
Examples

Also popular now: