Cmake入门之cmake-examples

本系列是记录 https://github.com/ttroy50/cmake-examples 的学习过程,通过cmake examples仓库中的例子来一步步学习cmake。

1 搭建环境

搭建cmake编译环境可以参考 cmake-examples/README.adoc,本文直接使用docker方式,方便快捷。

# pull docker image
sudo docker pull matrim/cmake-examples:3.5.1

docker run -it matrim/cmake-examples:3.5.1

# clone cmake-examples用例
cd ~ 
git clone https://github.com/ttroy50/cmake-examples.git code

# 测试整个 cmake-examples
cd code
./test.sh

后续:按照文件夹的结构一步一步学习cmake

2 cmake的基本命令列表

先将cmake常用命令制作成表,方便以后查看

常用命令 作用
项目:
project (myProject) 设置项目名,myProject为设置的项目名,这条命令会设置PROJECT_NAME变量
cmake_minimum_required(VERSION 3.5) 指定 cmake 的最小版本[可选]
添加编译目标:
add_executable(myexe ${SRC_LIST}) 编译可执行程序(myexe),以及生成该程序需要的源文件$
add_library(mylib [STATIC|SHARED] ${SRC_LIST}) 编译静态库或动态链接库(mylib)
搜索源文件:
file(<GLOB|GLOB_RECURSE> ) 按照正则表达式搜索路径下的文件,比如:file(GLOB SRC_LIST "./src/*.cpp")
添加头文件目录:
include_directories() 为该位置之后的target链接头文件目录(不推荐)
target_include_directories( <PUBLIC|INTERFACE|PRIVATE]> ) 为特定的目标链接头文件目录
添加依赖库:
link_libraries() 为该位置之后的target链接依赖库
target_link_libraries( ) 为特定的目标链接依赖库
添加子目录:
add_subdirectories(<文件夹>) 子目录中要有CMakeLists.txt文件,否则会报错
包含其他cmake文件:
include(./path/to/tool.cmake) 相当于将tool.cmake的内容直接包含进来
定义变量:
set( ... [PARENT_SCOPE]) 设置变量
选项:
add_option(MY_OPTION <ON| OFF>) 会定义一个选项。在使用cmake命令时,可以通过-D改变选项的值。比如cmake .. -DMY_OPTION=ON
add_compile_options(-std=c++11) 如果想要指定具体的编译器的选项,可以使用make_cxx_flags()cmake_c_flags()

3 cmake-examples 正文

01-basic

A-hello-cmake

一个最基本的的hello world例子。

文件结构:

A-hello-cmake$ tree
.
├── CMakeLists.txt
├── main.cpp

其中:

CMakeLists.txt之于Cmake 相当于Makefile之于make。CMakeLists.txt包含所有Cmake命令,Cmake执行时如果找不到CMakeLists.txt将会报错。

这个工程的CMakeLists.txt如下:

# 指定 cmake 的最小版本[可选],提醒用户升级到该版本,再执行cmake
cmake_minimum_required(VERSION 3.5)

# 设置项目名称,以便在使用多个项目时更容易引用某些变量${PROJECT_NAME}。
project (hello_cmake)

# 生成可执行文件,指定生成可执行程序的名称,以及生成该程序需要的源文件列表。
add_executable(hello_cmake main.cpp)

Note:人们经常将工程名与可执行程序名取同一个名字,这时CMakeLists.txt也可以这么写:

cmake_minimum_required(VERSION 3.5)
project (hello_cmake)
add_executable(${PROJECT_NAME} main.cpp)

另外,cmake支持In-Place构建与Out-of-Source构建,它们之间的差别是:

  • In-Place构建生成的文件(object文件与Makefiles等)与源码文件放在同一个目录。

  • Out-of-Source需要先新建一个文件夹,构建生成的文件与源码文件放在不同的目录,当你希望重新构建时,可以将该文件夹的内容删掉,然后重新构建。

In-Place构建:

A-hello-cmake$ cmake .
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/A-hello-cmake

通过tree命令查看:

A-hello-cmake$ tree
.
├── CMakeCache.txt
├── CMakeFiles
│   ├── 2.8.12.2
│   │   ├── CMakeCCompiler.cmake
│   │   ├── CMakeCXXCompiler.cmake
│   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   ├── CMakeSystem.cmake
│   │   ├── CompilerIdC
│   │   │   ├── a.out
│   │   │   └── CMakeCCompilerId.c
│   │   └── CompilerIdCXX
│   │       ├── a.out
│   │       └── CMakeCXXCompilerId.cpp
│   ├── cmake.check_cache
│   ├── CMakeDirectoryInformation.cmake
│   ├── CMakeOutput.log
│   ├── CMakeTmp
│   ├── hello_cmake.dir
│   │   ├── build.make
│   │   ├── cmake_clean.cmake
│   │   ├── DependInfo.cmake
│   │   ├── depend.make
│   │   ├── flags.make
│   │   ├── link.txt
│   │   └── progress.make
│   ├── Makefile2
│   ├── Makefile.cmake
│   ├── progress.marks
│   └── TargetDirectories.txt
├── cmake_install.cmake
├── CMakeLists.txt
├── main.cpp
├── Makefile
----

可见:In-Place构建生成的文件(object文件与Makefiles等)与源码文件放在同一个目录。

Out-of-Source构建:

A-hello-cmake$ mkdir build

A-hello-cmake$ cd build/

matrim@freyr:~/workspace/cmake-examples/01-basic/A-hello-cmake/build$ cmake ..
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/matrim/workspace/cmake-examples/01-basic/A-hello-cmake/build

通过tree命令查看:

A-hello-cmake/build$ cd ..

A-hello-cmake$ tree
.
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   ├── 2.8.12.2
│   │   │   ├── CMakeCCompiler.cmake
│   │   │   ├── CMakeCXXCompiler.cmake
│   │   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   │   ├── CMakeSystem.cmake
│   │   │   ├── CompilerIdC
│   │   │   │   ├── a.out
│   │   │   │   └── CMakeCCompilerId.c
│   │   │   └── CompilerIdCXX
│   │   │       ├── a.out
│   │   │       └── CMakeCXXCompilerId.cpp
│   │   ├── cmake.check_cache
│   │   ├── CMakeDirectoryInformation.cmake
│   │   ├── CMakeOutput.log
│   │   ├── CMakeTmp
│   │   ├── hello_cmake.dir
│   │   │   ├── build.make
│   │   │   ├── cmake_clean.cmake
│   │   │   ├── DependInfo.cmake
│   │   │   ├── depend.make
│   │   │   ├── flags.make
│   │   │   ├── link.txt
│   │   │   └── progress.make
│   │   ├── Makefile2
│   │   ├── Makefile.cmake
│   │   ├── progress.marks
│   │   └── TargetDirectories.txt
│   ├── cmake_install.cmake
│   └── Makefile
├── CMakeLists.txt
├── main.cpp
----

可见Out-of-Source构建生成的文件与源码文件放在不同的目录。

cmake在构建时,自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。In-Place构建时,这两个变量等价,表示同一目录。而对于Out-of-Source构建PROJECT_SOURCE_DIR代码源码文件的目录(这个例子是A-hello-cmake),PROJECT_BINARY_DIR表示构建目录(这个例子是A-hello-cmake/build,可以任意设定某个目录)。

Out-of-Source构建比较清晰,所以后面的例子都采用这种构建方式。

make

cmake生成Makefile后,下一步需要make,make生成可执行程序。

$ make
Scanning dependencies of target hello_cmake
[100%] Building CXX object CMakeFiles/hello_cmake.dir/hello_cmake.cpp.o
Linking CXX executable hello_cmake
[100%] Built target hello_cmake

执行程序

$ ./hello_cmake
Hello CMake!
----

B-hello-headers

这个例子展示当源文件和头文件不在同一目录下时如何使用cmake构建项目。

文件结构:

B-hello-headers$ tree
.
├── CMakeLists.txt
├── include
│   └── Hello.h
└── src
    ├── Hello.cpp
    └── main.cpp

这个工程的CMakeLists.txt如下(如非必要,不再介绍已介绍过的命令):

cmake_minimum_required(VERSION 3.5)
project (hello_headers)

# set直接设置变量值,也可以用搜索命令 file(GLOB SOURCES "src/*.cpp") 当文件众多,用通配符匹配较便利。
set(SOURCES
    src/Hello.cpp
    src/main.cpp
)

# 根据源文件列表,构建可执行程序
add_executable(hello_headers ${SOURCES})

# 包含头文件 在gcc中相当于 -I/directory/path/
target_include_directories(hello_headers
    PRIVATE 
        ${PROJECT_SOURCE_DIR}/include
)

其构建方式与运行方式与第一个用例一样,也是make之后执行可执行程序,后面都不再赘述。

C-static-library

本例展示了如何创建和链接一个静态库。这个例子将Hello.cpp打包成静态库hello_library,在编译最终可执行程序时,链接此静态库

文件目录结构如下:

$ tree
.
├── CMakeLists.txt
├── include
│   └── static
│       └── Hello.h
└── src
    ├── Hello.cpp
    └── main.cpp

这个工程的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.5)
project(hello_library)

# 将hello.cpp打包成静态库
add_library(hello_library STATIC 
    src/Hello.cpp
)
# PUBLIC 为范围说明
# * PRIVATE  当前该目标(库)构建时,引入该包含目录
# * INTERFACE  链接该目标(库)的目标构建时,引入该包含目录
# * PUBLIC 构建目标(库)和构建链接该目标(库)的目标时,都引入该包含目录
target_include_directories(hello_library
    PUBLIC 
        ${PROJECT_SOURCE_DIR}/include
)

# Add an executable with the above sources
add_executable(hello_binary 
    src/main.cpp
)

# 对add_library或add_executable生成的文件进行链接操作
# link the new hello_library target with the hello_binary target
target_link_libraries( hello_binary
    PRIVATE 
        hello_library
)

D-shared-library

本例展示了使用CMake构建动态链接库的过程。文件结构如下:

$ tree
.
├── CMakeLists.txt
├── include
│   └── shared
│       └── Hello.h
└── src
    ├── Hello.cpp
    └── main.cpp

这个工程的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.5)
project(hello_library)

# 将hello.cpp打包成动态库
#Generate the shared library from the library sources
add_library(hello_library SHARED 
    src/Hello.cpp
)
# 给动态库hello_library起一个别名hello::library
add_library(hello::library ALIAS hello_library)

target_include_directories(hello_library
    PUBLIC 
        ${PROJECT_SOURCE_DIR}/include
)

# Add an executable with the above sources
add_executable(hello_binary
    src/main.cpp
)

# 添加动态链接库
# link the new hello_library target with the hello_binary target
target_link_libraries( hello_binary
    PRIVATE 
        hello::library
)

E-installing

这个例子展示了如何生成一个“make install”目标来安装可执行程序。例子用到了D-shared-library的工程。

其文件结构如下:

$ tree
.
├── cmake-examples.conf
├── CMakeLists.txt
├── include
│   └── installing
│       └── Hello.h
├── README.adoc
└── src
    ├── Hello.cpp
    └── main.cpp

这个工程的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.5)

project(cmake_examples_install)

#Generate the shared library from the library sources
add_library(cmake_examples_inst SHARED
    src/Hello.cpp
)

target_include_directories(cmake_examples_inst
    PUBLIC 
        ${PROJECT_SOURCE_DIR}/include
)

# Add an executable with the above sources
add_executable(cmake_examples_inst_bin
    src/main.cpp
)

# link the new hello_library target with the hello_binary target
target_link_libraries( cmake_examples_inst_bin
    PRIVATE 
        cmake_examples_inst
)
#---------------------------安装---------------------------------------
# CMake允许用户使用“make install”指令安装二进制程序、lib库及其他文件。安装路径由
# CMAKE_INSTALL_PREFIX决定,这个变量可以通过ccmake设置,或者通过命令参数设置,
# “cmake .. -DCMAKE_INSTALL_PREFIX=/install/location”

# 将二进制文件cmake_examples_inst_bin安装在${CMAKE_INSTALL_PREFIX}/bin目录下
# Binaries
install (TARGETS cmake_examples_inst_bin
    DESTINATION bin)

# 将库cmake_examples_inst安装在${CMAKE_INSTALL_PREFIX}/lib目录下
# Library
# Note: may not work on windows
install (TARGETS cmake_examples_inst
    LIBRARY DESTINATION lib)

# 将头文件{PROJECT_SOURCE_DIR}/include/拷贝到${CMAKE_INSTALL_PREFIX}/include目录下
# Header files
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ 
    DESTINATION include)

# 将配置文件{PROJECT_SOURCE_DIR}/cmake-examples.conf/安装到目标目录下。
# Config
install (FILES cmake-examples.conf
    DESTINATION etc)

F-build-type

Cmake可以设置一系列构建选项来编译工程,可以用来指定优化级别以及bin文件是否包含debug信息。

提供如下选项:

  • Release - 添加 -O3 -DNDEBUG 标记

  • Debug - 添加-g 标记

  • MinSizeRel - 添加 -Os -DNDEBUG 标记

  • RelWithDebInfo - 添加 -O2 -g -DNDEBUG 标记

代码结构如下:

$ tree
.
├── CMakeLists.txt
├── main.cpp

这个工程的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.5)

# 如果CMAKE_BUILD_TYPE 与 CMAKE_CONFIGURATION_TYPES没有设定,则设置默认构建选项
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message("Setting build type to 'RelWithDebInfo' as none was specified.")
  set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
  # 设置cmake-gui的构建选项
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
    "MinSizeRel" "RelWithDebInfo")
endif()

project (build_type)

add_executable(cmake_examples_build_type main.cpp)

G-compile-flags

Camke支持不同的设置编译标志的方式:

  • 使用 target_compile_definitions()

  • 使用CMAKE_C_FLAGS 和 CMAKE_CXX_FLAGS等变量

代码结构如下:

$ tree
.
├── CMakeLists.txt
├── main.cpp

这个工程的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.5)

# 设置CMAKE_CXX_FLAGS变量,强制写入缓存
# 类似的,C编译器设置 CMAKE_C_FLAGS
# 链接标记设置 CMAKE_LINKER_FLAGS
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE)

# Set the project name
project (compile_flags)

# Add an executable
add_executable(cmake_examples_compile_flags main.cpp)

# 当编译目标时,添加-DEX3标志
target_compile_definitions(cmake_examples_compile_flags 
    PRIVATE EX3
)

H-third-party-library

几乎所有重要的项目都要求包含第三方库、头文件或程序。CMake支持查找这些工具的路径,使用find_package()的函数从“CMAKE_MODULE_PATH”文件夹列表中找到“cmake”。在linux上的默认搜索路径将包含' /usr/share/cmake/Modules '。
代码结构如下:

$ tree
.
├── CMakeLists.txt
├── main.cpp

这个工程的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.5)

# Set the project name
project (third_party_include)


# 找一个Boost库
#   * Boost -库名字, 在指定路径找FindBoost.cmake文件
#  * 1.46.1 - 库的最低版本
#  * REQUIRED - 如果没有找到就会fail
#  * COMPONENTS - 在库中寻找的目标
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)

# check if boost was found,多数included 包会设置XXX_FOUND用来判断包是否存在
if(Boost_FOUND)
    message ("boost found")
else()
    message (FATAL_ERROR "Cannot find Boost")
endif()

# Add an executable
add_executable(third_party_include main.cpp)

# 链接boot库
target_link_libraries( third_party_include
    PRIVATE
        Boost::filesystem
)

I-compiling-with-clang

当使用CMake构建时,可以设置C和c++编译器。这个例子与A-hello-cmake相同,A-hello-cmake使用的编译器是gcc++,这个例子用的编译器是clang。

代码结构如下:

$ tree
.
├── CMakeLists.txt
├── main.cpp

这个工程的CMakeLists.txt与A-hello-cmake相同,如下:

cmake_minimum_required(VERSION 3.5)

# Set the project name
project (hello_cmake)

# Add an executable
add_executable(hello_cmake main.cpp)

在run_test.sh中

#!/bin/bash
# Ubuntu supports multiple versions of clang to be installed at the same time.
# The tests need to determine the clang binary before calling cmake
clang_bin=`which clang`
clang_xx_bin=`which clang++`

if [ -z $clang_bin ]; then
    clang_ver=`dpkg --get-selections | grep clang | grep -v -m1 libclang | cut -f1 | cut -d '-' -f2`
    clang_bin="clang-$clang_ver"
    clang_xx_bin="clang++-$clang_ver"
fi

echo "Will use clang [$clang_bin] and clang++ [$clang_xx_bin]"

# 设置 CMAKE_C_COMPILER=clang CMAKE_CXX_COMPILER=clang++
mkdir -p build.clang && cd build.clang && \
    cmake .. -DCMAKE_C_COMPILER=$clang_bin -DCMAKE_CXX_COMPILER=$clang_xx_bin && make

J-building-with-ninja

如前所述,Cmake用于为其它多种构建工具来生成构建文件。

Ninja 是 Google推出的注重速度的构建工具,一般在 Unix/Linux 上的程序通过 make/makefile 来构建编译,而 Ninja 通过将编译任务并行组织,大大提高了构建速度。

代码结构如下:

$ tree
.
├── CMakeLists.txt
├── main.cpp

这个工程的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.5)

# Set the project name
project (hello_cmake)

# Add an executable
add_executable(hello_cmake main.cpp)

其编译脚本为:

#!/bin/bash
# Travis-ci cmake version doesn't support ninja, so first check if it's supported
ninja_supported=`cmake --help | grep Ninja`

if [ -z $ninja_supported ]; then
    echo "Ninja not supported"
    exit
fi

# cmake -G Ninja 将生成所需的ninja构建文件,这些文件可以通过ninja命令运行
mkdir -p build.ninja && cd build.ninja && \
    cmake .. -G Ninja && ninja

K-imported-targets

当前来看与H-third-party-library相同

L-cpp-standard

这个工程用来展示怎么通过cmake来设置C++标准的三种不同方法。

[common-method]. 一个简单的版本,应该可以与大多数版本的CMake一起使用
[cxx-standard]. 使用CMake v3.1中引入的CMAKE_CXX_STANDARD变量
[compile-features]. 使用CMake v3.1中引入的target_compile_features函数

方法1:一个简单的版本

代码结构如下:

A-hello-cmake$ tree
.
├── CMakeLists.txt
├── main.cpp

其CMakeLists.txt如下:

cmake_minimum_required(VERSION 2.8)

# Set the project name
project (hello_cpp11)

# 尝试使用-std=c++11编译程序,并将结果存储在变量COMPILER_SUPPORTS_CXX11中
include(CheckCXXCompilerFlag) # 告诉Cmake此函数可以用
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)

# check results and add flag
if(COMPILER_SUPPORTS_CXX11)#
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)#
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
else()
    message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()

# Add an executable
add_executable(hello_cpp11 main.cpp)

方法2:使用CMAKE_CXX_STANDARD变量

代码结构如下:

A-hello-cmake$ tree
.
├── CMakeLists.txt
├── main.cpp

其CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.1)

project (hello_cpp11)

# 设置 C++ 标准为 C++ 11
set(CMAKE_CXX_STANDARD 11)

# Add an executable
add_executable(hello_cpp11 main.cpp)

方法3:使用target_compile_features函数:

代码结构如下:

A-hello-cmake$ tree
.
├── CMakeLists.txt
├── main.cpp

其CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.1)
project (hello_cpp11)
add_executable(hello_cpp11 main.cpp)

# 使用target_compile_features函数将检查传入的功能,并由CMake确定正确的用于目标的编译器标志。
target_compile_features(hello_cpp11 PUBLIC cxx_auto_type)

# Print the list of known compile features for this version of CMake
message("List of compile features: ${CMAKE_CXX_COMPILE_FEATURES}")

02-sub-projects

大型项目都有较好的层次设计,将不同功能的源文件放到不同的目录,方便阅读和维护。

这个项目展示了多层目录结构下,顶层CMakeLists.txt调用子文件夹CMakeLists.txt过程。

  • sublibrary1 - 静态链接库

  • sublibrary2 - 头文件库

  • subbinary - 可执行文件

代码结构如下:

$ tree
.
├── CMakeLists.txt
├── subbinary      # 可执行文件
│   ├── CMakeLists.txt
│   └── main.cpp
├── sublibrary1    # 静态链接库
│   ├── CMakeLists.txt
│   ├── include
│   │   └── sublib1
│   │       └── sublib1.h
│   └── src
│       └── sublib1.cpp
└── sublibrary2    # 头文件库
    ├── CMakeLists.txt
    └── include
        └── sublib2
            └── sublib2.h
  • 顶级CMakeLists.txt
cmake_minimum_required (VERSION 3.5)

project(subprojects)

# Add sub directories
add_subdirectory(sublibrary1)
add_subdirectory(sublibrary2)
add_subdirectory(subbinary)
  • [subbinary/CMakeLists.txt] - 生成可执行文件
project(subbinary)

# Create the executable
add_executable(${PROJECT_NAME} main.cpp)

# 链接subproject1文件夹下的静态库 sub::lib1
# 链接subproject2文件夹下的头文件库 sub::lib2
target_link_libraries(${PROJECT_NAME}
    sub::lib1
    sub::lib2
)
  • [sublibrary1/CMakeLists.txt] - 创建静态库
# Set the project name
project (sublibrary1)

# Add a library with the above sources
add_library(${PROJECT_NAME} src/sublib1.cpp)
add_library(sub::lib1 ALIAS ${PROJECT_NAME})

target_include_directories( ${PROJECT_NAME}
    PUBLIC ${PROJECT_SOURCE_DIR}/include
)
  • [sublibrary2/CMakeLists.txt] - 创建静态库
# Set the project name
project (sublibrary2)

# cmake支持接口目标,创建为只包含头文件的库(允许在没有任何构建输出的情况下创建目标)
add_library(${PROJECT_NAME} INTERFACE)
add_library(sub::lib2 ALIAS ${PROJECT_NAME})

target_include_directories(${PROJECT_NAME}
    INTERFACE
        ${PROJECT_SOURCE_DIR}/include
)

03-code-generation

代码生成可用于从通用描述文件创建不同语言的源代码,这可以减少手工代码的编写量,并增加互操作性。

这个项目展示了使用CMake中的变量和一些常用工具生成的代码。

  • configure-files: 使用CMake configure_file()函数输入CMake变量
  • protobuf:使用谷歌protobuf生成c++源代码

1. configure-files

代码结构如下:

$ tree
.
├── CMakeLists.txt
├── main.cpp
├── path.h.in
├── ver.h.in

其中path.h.in与ver.h.in是两个输入文件,通过CMakeLists.txt中的命令,将会生成新的output文件。

其CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.5)

# Set the project name
project (cf_example)

# set a project version
set (cf_example_VERSION_MAJOR 0)
set (cf_example_VERSION_MINOR 2)
set (cf_example_VERSION_PATCH 1)
set (cf_example_VERSION "${cf_example_VERSION_MAJOR}.${cf_example_VERSION_MINOR}.${cf_example_VERSION_PATCH}")

# 调用configure_file函数,将输入文件ver.h.in生成输出文件ver.h
# ver.h.in中允许使用像CMake变量一样的${}或@@定义变量
configure_file(ver.h.in ${PROJECT_BINARY_DIR}/ver.h)

# 调用configure_file函数,将输入文件path.h.in生成输出文件path.h
# path.h.in文件中使用了外部变量,所以只能使用@@来引用变量
configure_file(path.h.in ${PROJECT_BINARY_DIR}/path.h @ONLY)

# Add an executable
add_executable(cf_example
    main.cpp
)

# include the directory with the new files
target_include_directories( cf_example
    PUBLIC
        ${CMAKE_BINARY_DIR}
)

cmake构建后,在构建目录可以找到ver.h与path.h两个新生成的文件。

2. protobuf

使用谷歌protobuf工具生成c++源代码(略)

04-static-analysis

静态分析是指不运行代码只是通过对代码的静态扫描对程序进行分析。可以发现一些常见的编码bug,如下:

  • 越界错误
  • 内存泄漏
  • 使用未初始化的变量
  • 使用非安全函数

clang-analyzer:

以下例子展示如何调用Clang Static Analyzer来进行静态分析。

代码结构如下:

$ tree
.
├── CMakeLists.txt
├── subproject1
│   ├── CMakeLists.txt
│   └── main1.cpp
└── subproject2
    ├── CMakeLists.txt
    └── main2.cpp

运行这个用例需要安装clang analyzer,在Ubuntu中安装命令如下:

$ sudo apt-get install clang

# 运行如下命令,查看scan-build是否可用
$ scan-build-3.6

在编译的过程中可以使用scan-build进行静态分析。调用方法如下:

$ scan-build-3.6 cmake ..
$ scan-build-3.6 make

上述使用的是默认编译器,Ubuntu使用的是gcc,也可以指定自定义的编译器。用如下的指令:

$ scan-build-3.6 --use-cc=clang-3.6 --use-c++=clang++-3.6 -o ./scanbuildout/ make

scan-build 的输出结果:

运行scan-build

mkdir -p build \
	&& cd build \
	&& scan-build-3.6 -o scanbuildout cmake .. \
	&& scan-build-3.6 -o scanbuildout make

scan-build只会在编译时输出警告,还会生成一个HTML文件列表,其中包含对错误的详细分析。

编译过程的警告:

warning: Dereference of null pointer (loaded from variable 'x')
   std::cout << *x << std::endl;

HTML文件列表:

$ cd scanbuildout/
$ tree
.
└── 2017-07-03-213514-3653-1
    ├── index.html
    ├── report-42eba1.html
    ├── scanview.css
    └── sorttable.js

1 directory, 4 files

clang-format:

这个用例展示怎么调用Clang Format工具来检查代码是否符合规范(规范可以自定义),同时可修复不规范的代码。

代码结构如下:

$ tree
.
├── .clang-format                      # 代码格式
├── CMakeLists.txt
├── cmake
│   ├── modules
│   │   ├── clang-format.cmake        # 设置格式目标的脚本
│   │   └── FindClangFormat.cmake     # 查找clang格式的二进制文件
│   └── scripts
│       └── clang-format-check-changed # 一个辅助脚本,用于检查git中已更改的文件
├── subproject1
│   ├── CMakeLists.txt
│   └── main1.cpp
└── subproject2
    ├── CMakeLists.txt
    └── main2.cpp

运行这个用例需要安装clang format工具,在Ubuntu中安装命令如下:

$ sudo apt-get install clang-format-3.6
# 运行如下命令,查看clang-format是否可用
$ clang-format-3.6

如果你安装了不同的clang-format版本,您可以编辑根目录下CMakeLists.txt中的CLANG_FORMAT_BIN_NAME 变量。

clang-format可以扫描源码,并可修复代码[可选],使代码符合公司的代码规范,clang-format内置有默认的规范,也可修改.clang-format自定义自己的规范。

如:.clang-format

Language:        Cpp
# BasedOnStyle:  LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false

这个用例完成3个目标:

 * format
 * format-check
 * format-check-changed

format:

格式化,检查不符合clang-format样式的代码,并修复,注意:format会修改代码

format-check:

格式检查,如果代码格式不符合clang-format样式,将会报错

format-check-changed:

格式检查,检查git status的输出并扫描文件,检查它们是否匹配样式。

在本例中,实际的检查是clang-format-check-changed.py完成的。这个脚本将运行' git status——porcelain——ignore-submodules ' 获得更改文件的列表,然后运行clang-format检查代码格式是否符合clang-format样式。

使用方法如下:

cmake/scripts/clang-format-check-changed.py --file-extensions ".cpp,*.cpp,*.h,*.cxx,*.hxx,*.hpp,*.cc,*.ipp" --exclude=build/ --exclude=/CMakeFiles/ --exclude=cmake --clang-format-bin /usr/bin/clang-format-3.6

cppcheck:

这个用例展示怎么调用CppCheck来进行静态检查。

包含三个目标:

  1. 寻找cppcheck
  2. 为每个子项目添加cppcheck和目标
  3. 生成top构建目标 make analysis

代码结构如下:

$ tree
.
├── cmake
│   ├── analysis.cmake
│   └── modules
│       └── FindCppCheck.cmake
├── CMakeLists.txt
├── subproject1
│   ├── CMakeLists.txt
│   └── main1.cpp
└── subproject2
    ├── CMakeLists.txt
    └── main2.cpp

运行这个用例需要安装CppCheck utility工具,在Ubuntu中安装命令如下:

$ sudo apt-get install cppcheck

05-unit-testing

06-installer

CMake能够使用一个程序为多个平台创建安装程序。

这个例子展示了如何生成Linux安装程序。

代码结构如下:

$ tree
.
├── cmake-examples.conf
├── CMakeLists.txt
├── include
│   └── Hello.h
└── src
    ├── Hello.cpp
    └── main.cpp

CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.5)

project(cmake_examples_deb)

# set a project version
set (deb_example_VERSION_MAJOR 0)
set (deb_example_VERSION_MINOR 2)
set (deb_example_VERSION_PATCH 2)
set (deb_example_VERSION "${deb_example_VERSION_MAJOR}.${deb_example_VERSION_MINOR}.${deb_example_VERSION_PATCH}")


############################################################
# Create a library
############################################################

#Generate the shared library from the library sources
add_library(cmake_examples_deb SHARED src/Hello.cpp)

target_include_directories(cmake_examples_deb
    PUBLIC
        ${PROJECT_SOURCE_DIR}/include
)
############################################################
# Create an executable
############################################################

# Add an executable with the above sources
add_executable(cmake_examples_deb_bin src/main.cpp)

# link the new hello_library target with the hello_binary target
target_link_libraries( cmake_examples_deb_bin
    PUBLIC
        cmake_examples_deb
)

############################################################
# Install
############################################################

# Binaries 安装可执行文件
install (TARGETS cmake_examples_deb_bin
    DESTINATION bin)

# Library 安装库
# Note: may not work on windows
install (TARGETS cmake_examples_deb
    LIBRARY DESTINATION lib)

# Config 安装文件
install (FILES cmake-examples.conf
    DESTINATION etc)

############################################################
# Create DEB
############################################################

# Tell CPack to generate a .deb package
set(CPACK_GENERATOR "DEB")

# Set a Package Maintainer.
# This is required
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Thom Troy")

# Set a Package Version
set(CPACK_PACKAGE_VERSION ${deb_example_VERSION})

# Include CPack
include(CPack)

07-package-management

对于c++和CMake,目前还没有普遍接受的管理和打包依赖关系的方法。但是近年来出现了一些新的、有趣的包管理系统。虽然这些都是CMake的独立系统,但它们中的大多数都被设计成直接集成到基于CMake的构建系统中。

参考:

  1. cmake指令汇总
  2. cmake学习
  3. CMake 的常用命令
  4. CMake 教程
posted @ 2022-12-22 19:03  sureZ_ok  阅读(754)  评论(0编辑  收藏  举报