CMake

Build

Posted by frankie on November 12, 2019

Below Example Source code

Features

  1. Open Source
  2. Cross Platform
  3. Fit for large project, e.g. KDE4
  4. Simplified Tool Chain: cmake + make
  5. Efficiency
  6. Scalable

Drawbacks

  1. Not simple and stupid solution
  2. CMake language and syntax
  3. Not working well with pkgconfig

Learning Tips

  1. Practice and Project Driven
  2. For simple project with just a few files, the Makefile is the best option
  3. Use CMake for C/C++/Java only
  4. For java, better use Maven/ant/Gradle

Installation

  1. Built-in installation in many Linux Distributions (may be not the latest version)
  2. Source Code Build

First Trial with helloworld example

//main.c 文件内容:
//main.c

#include <stdio.h>

int main()
{
  printf("Hello World from t1 Main!\n");
  return 0;
}
CmakeLists.txt 文件内容:
PROJECT (HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir " ${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})

Imgur

Basic Syntax

PROJECT(projectname [CXX] [C] [Java])

1. ${HELLO_BINARY_DIR}
2. ${HELLO_SOURCE_DIR}
3. ${PROJECT_SOURCE_DIR}
4. ${PROJECT_SOURCE_DIR}
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
e.g.
1. SET(SRC_LIST main.c)
2. SET(SRC_LIST main.c t1.c t2.c)
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)
1. SEND_ERROR, 产生错误,生成过程被跳过
2. SATUS, 输出前缀为--的信息
3. FATAL_ERROR, 立即终止所有cmake过程
ADD_EXECUTABLE(hello ${SRC_LIST})

ADD_EXECUTABLE(hello main.c func.c) or ADD_EXECUTABLE(hello main.c;func.c)

Recap

  1. Using ${} to refer variables, but in IF statement, variables are directly used without ${}
  2. Command(para1 para2 …) - parameters are separated by space or semicolon
  3. Command is case insensitive, while parameters and variables are case sensitive. In practice, it is recommended to use all CAPITALS.
  4. The project name in PROJECT() is not the generated executable file as ADD_EXECUTABLE()
  5. SET(SRC_LIST main.c) == SET(SRC_LIST “main.c”) {SET(SRC_LIST “fu nc.c”)}
  6. make clean
  7. in-source build vs. out-of-source build (No impact on original source code folder)
-- This is BINARY dir /home/aiq/Downloads/cmake/t1/build
-- This is SOURCE dir /home/aiq/Downloads/cmake/t1

Imgur

Out Of Source Build

mkdir ./t2/src

vim ./t2/src/CMakeLists.txt

ADD_EXECUTABLE(hello main.c)

---

vim ./t2/CMakeLists.txt

PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)

mkdir  ./t2/build
cmake ..
make

Imgur

ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
INSTALL
CMAKE_INSTALL_PREFIX
cmake -DCMAKE_INSTALL_PREFIX=/usr .


INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...])

-> {CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径> (relative path in DESTINATION)


e.g.
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)


INSTALL(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
->
OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ,即644权限

TINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
->
OWNER_EXECUTE, GROUP_EXECUTE, 和WORLD_EXECUTE,即755权限


INSTALL(DIRECTORY dirs... DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
->
abc 和 abc/有很大的区别。
如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc,如果目录名以/结尾,
代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。

e.g.
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_READ)

Better Build

Files Layout

Imgur

#main folder CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

PROJECT(HELLO)

INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t3)
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t3)

ADD_SUBDIRECTORY(src bin)
#src folder CMakeLists

cmake_minimum_required(VERSION 3.10)

INSTALL(PROGRAMS ${PROJECT_BINARY_DIR}/bin/hello DESTINATION bin)
MESSAGE(STATUS "This is BINARY dir " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "This is BINARY dir " ${PROJECT_SOURCE_DIR})
ADD_EXECUTABLE(hello main.c)
cd build
cmake -DCMAKE_INSTALL_PREFIX=/tmp/t3/usr ..
make
make install

Imgur

Static and Dynamic Library Build

#main folder CMakeLists.txt

PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
#lib folder CMakeLists

SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
cmake ..
make

Imgur

ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)

#SHARED,动态库
#STATIC,静态库
#MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。

e.g.
SET(LIBHELLO_SRC hello.c)

ADD_LIBRARY(hello_shared SHARED ${LIBHELLO_SRC})
#ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) -> Does not work since the target (hello) is duplicated
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

Imgur

SET_TARGET_PROPERTIES,其基本语法是:
  SET_TARGET_PROPERTIES(target1 target2 ...
      PROPERTIES prop1 value1
      prop2 value2 ...)

#lib CMakeLists

SET(LIBHELLO_SRC hello.c)

ADD_LIBRARY(hello_shared SHARED ${LIBHELLO_SRC})
#ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) -> Does not work since the target (hello) is duplicated
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")

Imgur

cmake_minimum_required(VERSION 3.10)
SET(LIBHELLO_SRC hello.c)

ADD_LIBRARY(hello_shared SHARED ${LIBHELLO_SRC})
#ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) -> Does not work since the target (hello) is duplicated
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")


GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS "This is the hello_static OUTPUT_NAME:" ${OUTPUT_VALUE})

Imgur

cmake_minimum_required(VERSION 3.10)
SET(LIBHELLO_SRC hello.c)

ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
#ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) -> Does not work since the target (hello) is duplicated
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})


SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)

GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS "This is the hello_static OUTPUT_NAME:" ${OUTPUT_VALUE})

Imgur

cmake_minimum_required(VERSION 3.10)
SET(LIBHELLO_SRC hello.c)

ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
#ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) -> Does not work since the target (hello) is duplicated
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})


SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)

GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)

MESSAGE(STATUS "This is the hello_static OUTPUT_NAME:" ${OUTPUT_VALUE})

INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
cd ./build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install

Imgur

Imgur

How to use the external shared library and header file

INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

LINK_DIRECTORIES(directory1 directory2 ...)

TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2...)
cmake_minimum_required(VERSION 3.10)

INCLUDE_DIRECTORIES(/usr/include/hello)

ADD_EXECUTABLE(main main.c)

#TARGET_LINK_LIBRARIES(main hello)
TARGET_LINK_LIBRARIES(main libhello.so)

Imgur

cmake_minimum_required(VERSION 3.10)

INCLUDE_DIRECTORIES(/usr/include/hello)

ADD_EXECUTABLE(main main.c)

#TARGET_LINK_LIBRARIES(main hello)
#TARGET_LINK_LIBRARIES(main libhello.so)
TARGET_LINK_LIBRARIES(main libhello.a)

Imgur

Common Variables and Environment Variables

$ENV{NAME}
SET(ENV{NAME} VALUE)

Common Commands

ADD_DEFINITIONS(-DENABLE_DEBUG) -> activate #ifdef DENABLE_DEBUG #endif block


ADD_TEST(testname Exename arg1 arg2 ...)
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
ENABLE_TESTING() -> make test


AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})


CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)


EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE
LS_RVALUE)
IF(not LS_RVALUE)
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(not LS_RVALUE)


FILE(WRITE filename "message to write"... )


INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])


FIND_LIBRARY 示例:
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR "libX not found")
ENDIF(NOT libX)


IF(expression)
# THEN section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ELSE(expression)
# ELSE section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDIF(expression) -> IF & ENDIF is always paired


SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)


WHILE 指令的语法是:
WHILE(condition)
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
ENDWHILE(condition)


FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDFOREACH(loop_var)

e.g.
AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)


FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)

e.g.
FOREACH(VAR RANGE 10)
MESSAGE(${VAR})
ENDFOREACH(VAR)


FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
e.g.
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)

CMake Predefined Find Module

//src/main.c
//
// Created by aiq on 14/11/19.
//

#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
FILE *fp;
int write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
    int written = fwrite(ptr, size, nmemb, (FILE *)fp);
    return written;
}
int main()
{
    const char * path = "/tmp/curl-test";
    const char * mode = "w";
    fp = fopen(path,mode);
    curl_global_init(CURL_GLOBAL_ALL);
    CURLcode res;
    CURL *curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, "http://www.chinaunix.net/");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
    res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
}
#project CmakeLists

cmake_minimum_required(VERSION 3.10)

PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src bin)
#src/CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

ADD_EXECUTABLE(curltest main.c)

INCLUDE_DIRECTORIES(/usr/include)
TARGET_LINK_LIBRARIES(curltest curl)

Method 2 - using FindCURL

#src/CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

ADD_EXECUTABLE(curltest main.c)

#INCLUDE_DIRECTORIES(/usr/include)
#TARGET_LINK_LIBRARIES(curltest curl)

FIND_PACKAGE(CURL)
IF(CURL_FOUND)
    INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
    TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})

    MESSAGE(STATUS "CURL library found")
ELSE(CURL_FOUND)
    MESSAGE(FATAL_ERROR "CURL library not found")
ENDIF(CURL_FOUND)

Build Custom FindHello Module

mkdir cmake

#FindHELLO.cmake
FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/include/hello /usr/local/include/hello)

FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib /usr/local/lib)

IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
    SET(HELLO_FOUND TRUE)
ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)

MESSAGE(STATUS "Start ................")

IF (HELLO_FOUND)
    IF (NOT HELLO_FIND_QUIETLY)
        MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
    ENDIF (NOT HELLO_FIND_QUIETLY)
ELSE (HELLO_FOUND)
    IF (HELLO_FIND_REQUIRED)
        MESSAGE(FATAL_ERROR "Could not find hello library")
    ENDIF (HELLO_FIND_REQUIRED)
ENDIF (HELLO_FOUND)

#src/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

FIND_PACKAGE(HELLO REQUIRED)

IF(HELLO_FOUND)
    ADD_EXECUTABLE(hello main.c)
    INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR})
    TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY})
ENDIF(HELLO_FOUND)


#main CMakeLists
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

Imgur

Reference