Use Cmake to automatically generate makefiles in projects

  • Tutorial
  The introduction is great, as it explains in detail why cmake is needed. You can immediately under the cut, if you already know.

Introduction


  Compiling a project with your hands is a waste of time. This is actually an axiom and those who program know about it. But for everything to compile automatically, you need to set the rules, right? Often and in the old way they use makefile for * nix or some kind of nmake for windows.
  Although I have been programming for more than a year, I have made simple auto-assemblers of projects based on makefile with my hands, but it’s worth forgetting a bit and you have to re-learn how to compose this clever scheme. Basically, you have to do projects designed for any one system, be it linux or windows, and often not cross-compiled among themselves. For makefile portability, automake and autogen are used.but their syntax is even more confusing. I will not say that the choice is perfect, but for myself I decided to switch to cmake, since it is ported to everything available. It seemed to me more human-readable. I’ll try to explain the basics. You write the rules with words, and a makefile is generated from them, which you already run in the standard way.

Educational program

  Why is it needed? So that when transferring to another machine, with other paths, you put together a project with two commands without fixing anything in the makefile. But is there configure? This is an alternative. And configure is not cross-platform, for its generation you need autoconf / autogen, for which there is still a set of rules. Only benefits? Compiling with an auto-generated makefile is slightly slower than the old method. For example, KDE-4 is an official release tool .

Getting started


  In the current tutorial, I will talk about the simplest C ++ project template that should be compiled on Linux. No portability will be discussed, different compilers will not be assigned, etc. It will not fit into one article, and a pile of conventions will confuse the beginner too much.
  First, about the unofficially accepted structure of projects on cmake. Usually, the root of the project is the src folder (for closed source), include (for public headers), lib (for plug-in libraries, if they are not system libraries), an empty build folder (if not, then create it), I’ll create add bin (or out, for the resulting binaries). The presence of files in the root is also requiredAUTHOTS, COPYING, INSTALL, NEWS, README, ChangeLo g. They may be empty, but availability is required. An optional may be a config.h.in file (more on that later). The cmake configuration files are in each include folder called CMakeLists.txt . This name was coined by cmake and does not change.
  Documentation is initially difficult to understand, although complete. This is another minus. But compared to autotools ...
A list of the types of project files cmake can generate:
Hidden text
Borland Makefiles
MSYS Makefiles
MinGW Makefiles
NMake Makefiles
NMake Makefiles JOM
Ninja
Unix Makefiles
Visual Studio 10
Visual Studio 10 IA64
Visual Studio 10 Win64
Visual Studio 11
Visual Studio 11 ARM
Visual Studio 11 Win64
Visual Studio 6
Visual Studio 7
Visual Studio 7 .NET 2003
Visual Studio 8 2005
Visual Studio 8 2005 Win64
Visual Studio 9 2008
Visual Studio 9 2008 IA64
Visual Studio 9 2008 Win64
Watcom WMake
Xcode
CodeBlocks - MinGW Makefiles
CodeBlocks - NMake Makefiles
CodeBlocks - Ninja
CodeBlocks - Unix Makefiles
Eclipse CDT4 - MinGW Makefiles
Eclipse CDT4 - NMake Makefiles
Eclipse CDT4 - Ninja
Eclipse CDT4 - Unix Makefiles


  Suppose we have some source.cpp from which we get the software - put it in the src folder. But we have the same project, so there are a few more files core.cpp, core.hpp, common.hpp, types.hpp , which we also put in src and need some kind of library, for example pthread . We figured out the source, proceed to the description of the project and preparing it for auto-compilation.

  It all starts with creating a CMakeLists.txt file in the project root. Cmake rules are similar to a scripting language, a cross between javascript and php. Only much easier. There are conditions, functions, variables, constants, plug-ins.

  I will break the CMakeLists.txt file into several parts to explain them. Part 1:
# комментарии начинаются с решётки
cmake_minimum_required (VERSION 2.6) 
cmake_policy(SET CMP0011 NEW)
cmake_policy(SET CMP0003 OLD)
OPTION(WITH_DEBUG_MODE "Build with debug mode" ON)
if ( NOT UNIX )
    message (FATAL_ERROR "Not Unix!")
endif ()

  Here's the cmake_minimum_required version check function.
Operators are written as if () opening and endif () closing. Similar to foreach () and endforeach ().
The message function displays our message. I used the FATAL_ERROR flag to indicate the type of message.
Also note that no semicolon (;) is placed at the end of commands. There is one line - one team. The brackets are moved away from the operators just for readability.
Each team usually has several options for setting parameters, so without looking at the manual does not do.
Quick introduction and a simple exampleis in the documentation, but in my opinion are too simple.

Part 2:
message ("Starting cmake")
# я вынес настройки путей, флаги компиляции в отдельный фаил, чтобы не громоздить здесь лишнего
include (myproj.cmake)
# создаём новый проект
set (PROJECT myproj)
# в текущем проекте ничего не нужно дополнительно компилировать
set (LIBRARIES)
# следующий код нужен для компиляции и подключения сторонних библиотек 
    foreach (LIBRARY ${LIBRARIES})
        find_library("${LIBRARY}_FOUND" ${LIBRARY})
        message(STATUS "Check the ${LIBRARY} is installed: " ${${LIBRARY}_FOUND})
        if ( "${${LIBRARY}_FOUND}" STREQUAL "${LIBRARY}_FOUND-NOTFOUND" )
            message(STATUS "Adding library sources")
            add_subdirectory (../${LIBRARY} lib/${LIBRARY})
        endif ()
    endforeach ()
# никаких дополнительных целей нет
set (TARGETS "")
set (HEADERS "")
message ( STATUS "SOURCES: ${SOURCES}")
add_subdirectory (src)

  The set () function creates or overwrites variables. If there is no value, then the variable will be empty. The variables set here are named according to the meaning that they carry in the generation.
include () includes an external file with a piece of configuration in the current location. There is no explanation.
And add_subdirectory (src) indicates where to continue building the makefile of the project.

  If only general rules were set here, then inside the src directory in CMakeLists.txt options for combining specific source files will be set.

  Not mentioned yet cmake_policy (). I decided not to puzzle now with an understanding of this. Let it hang here © It just makes assembly easier.
The foreach () loop and libraries will be described later. Skip for now.

  So what was taken out in a separate cmake file? Let's consider:

set ("${PROJECT}_BINARY_DIR"  bin)
set ("${PROJECT}_SOURCE_DIR" src:include)
set ("${PROJECT}_LIB_DIR" lib)
set (CMAKE_INCLUDE_PATH ${${PROJECT}_SOURCE_DIR})
set (CMAKE_LIBRARY_PATH ${${PROJECT}_LIB_DIR})
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/${${PROJECT}_BINARY_DIR})
set (CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_BUILD_TYPE Debug)
set (ERR_NO_UNIX "Cannot build on non Unix systems")
if ( WITH_DEBUG_MODE )
     ADD_DEFINITIONS( -DMY_DEBUG_MODE=1)
endif()
if ( CMAKE_COMPILER_IS_GNUCXX )
    set(MY_CXX_FLAGS  "-Wall -std=c++0x -fmessage-length=0 -v -L/usr/local/lib -L/usr/lib")
    set(CMAKE_CXX_FLAGS "-O0 ${MY_CXX_FLAGS}")
    # я отключил настройку разных флагов для релиза и отладки. Пока что не нужно.
    #set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -fno-reorder-blocks -fno-schedule-insns -fno-inline")
    #set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
else ()
    message (FATAL_ERROR ${ERR_NO_UNIX})
endif ()

  We see that set () uses a strange (or already familiar) construction in the form of $ {name} - it substitutes the previously defined variable using the same set () or cmake itself (all cmake defined variables are in the documentation). For example, $ {PROJECT} will insert the value myproj from the previously defined PROJECT variable.
The src: include entry is just a string, which in Linux means an enumeration of paths (they are separated by a colon, and not a semicolon).
  The first three lines are the variables I set. And here are the necessary variables required by cmake. They can be omitted explicitly, they are already defined with the launch, but they will not point to where you want with this folder structure.

  Sometimes you need to make a variable name based on other variables, and this is possible: $ {$ {PROJECT} _SOURCE_DIR}. First, the PROJECT is dereferenced to get $ {myproj_SOURCE_DIR}, which is already defined at the beginning. As a result, its value will be. All these are necessary difficulties, so that if you change the name of the project from myproj to superpuper you do not have to climb throughout the code changing the names of variables and other things.

  The if block (CMAKE_COMPILER_IS_GNUCXX) determined which flags are used for compilation. It’s not yet cross-platform, but using branches and various variable-flags created at startup, you can assign different dependent builds for different platforms and compilers. The entire list of variables is in the Variables section.and this section is quite large. In addition, this block can be completely removed from the config or prepared using the extensive cmake functionality. But it would take up too much space.

  if (WITH_DEBUG_MODE) - there is a special kind of variables that start with WITH_. They are created using the option () function, a function that defines user options (it was in the code at the very beginning). These variables accept only two ON or OFF values ​​and can be used as Boolean values, for example. And ADD_DEFINITION (-DMY_DEBUG_MODE = 1) will add the -DMY_DEBUG_MODE option to the compiler if debug mode is enabled. For the C ++ compiler (in this case), this will mean adding a define.

Working with source code


So we got to the src folder. CMakeLists.txt is also required in it, we will consider it in more detail.
set ("${PROJECT}_VERSION_MAJ" 0)
set ("${PROJECT}_VERSION_MIN" 1)
set ("${PROJECT}_VERSION_A" 1)
set ("${PROJECT}_VERSION_B" 1)
set ("${PROJECT}_VERSION" ${${PROJECT}_VERSION_MAJ}0${${PROJECT}_VERSION_MIN}0${${PROJECT}_VERSION_A}0${${PROJECT}_VERSION_B})
message(STATUS ${${PROJECT}_VERSION})
# основной файл программы
set (MAIN_SOURCES
    source.cpp
    )
# непубличные пары исходников
set (PRIVATE_CLASSES
        core
    )
# файлы только заголовочные, у которых нет пары-исходника
SET (HEADERS_ONLY
        types
        common
    )
# публичные исходники
set (PUBLIC_CLASSES)
# используемые в программе библиотеки
set (ADDITIONAL_LIBRARIES
    stdc++
    pthread
    )
set (PUBLIC_HEADERS)
set (SOURCES)
foreach (class ${PRIVATE_CLASSES})
    LIST (APPEND SOURCES ${class}.cpp)
    LIST (APPEND HEADERS ${class}.hpp)
endforeach ()
foreach (class ${HEADERS_ONLY})
    LIST (APPEND HEADERS ${class}.hpp)
endforeach ()
foreach (class ${PUBLIC_CLASSES})
    LIST (APPEND SOURCES ${class}.cpp)
    LIST (APPEND HEADERS ../include/${PROJECT}/${class}.hpp)
    LIST (APPEND PUBLIC_HEADERS ../include/${PROJECT}/${class}.hpp)
endforeach ()
add_executable (${PROJECT} ${MAIN_SOURCES} ${SOURCES})
target_link_libraries (${PROJECT} ${ADDITIONAL_LIBRARIES})
set_target_properties(${PROJECT} PROPERTIES VERSION "${${PROJECT}_VERSION}" SOVERSION "0")
INSTALL (
    TARGETS
    ${PROJECT}
    DESTINATION
    lib${LIB_SUFFIX}
)
INSTALL (
    FILES
    ${PUBLIC_HEADERS}
    DESTINATION
    include/${PROJECT}
)

  Specific files and their assembly are recorded here. First, we determined the version of the program. Next, we define the groups (or as they are called here, lists) of source codes, all names are written with a space. MAIN_SOURCES - one main source, PRIVATE_CLASSES - a list of source pairs (source.cpp-header.hpp with a common name), PUBLIC_CLASSES - empty for this project, HEADERS_ONLY - a list of only header files, ADDITIONAL_LIBRARIES - libraries connected to the C ++ project.

  The following cycles combine the lists of headers and sources into one pluggable list (I note that the previous division into public and hidden was purely conditional). And finally, the rule indicating "assemble the project into a executable file" add_executable(). After assembling the binary, you need to add link libraries to it using target_link_libraries () and that’s it. Project rules are defined.
For convenience, you can add installation-installation rules using install ().

  Go to the build directory, which is at the root of the project, and run
cmake ..
from the command line. If everything went well (and it should go well), we get
- Configuring done
- Generating done
- Build files have been written to: / home / username / tmp / fooproj / build

and next to the makefile
Then in the usual way
make

Instead of a conclusion


I found this method easier and more convenient than config configure. If you suddenly want to argue about this, then I will refrain from commenting. Because why?
If you changed something and cmake throws errors, first delete the entire file cache from the build directory (roughly speaking everything) or at least CMakeCache.txt from the same build directory.

What was not considered in the tutorial:
1) Connecting the Qt or Boost libraries
2) Search for installed libraries
3) Multi-platform
4) Options for various compilers

Documentation:

1) The main page of documentation
2) Documentation for version 2.8.9
3) The source of this disgrace is attached .

Also popular now: