日期:2024年1月15日标签:C/C++

cmake 教程 #

为什么要使用 cmake #

cmake 允许开发者通过编写 CMakeList.txt 文件来管理项目的编译流程,根据目标用户平台生成对应的 makefile 和工程文件,例如可以生成 unix 的 Makefile 文件或者 windows 的 visual studio 工程。从而达到 "Write once,run everywhere"。

另外,一般手写 makefile 是一件很痛苦的事,cmake 脚本语言是一种类似 C 的语言,所以更适合程序员理解记忆。避免在 makefile 的逻辑中迷失自我。

接下来先简单介绍一些 cmake 的一些概念。然后再通过实际例子,使用 cmake 编译程序运行。

cmake 命令 #

cmake 的命令(command)类似与 C++ 中的函数方法,可以接受一些参数执行特定的任务。

cmake 的命令名称是不区分大小写的。

cmake commands: https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html

常用的命令:

cmake 还支持结构化语句:

  • if, endif
  • elif, endif
  • while, endwhile
  • foreach, endforeach
  • list
  • return
  • set_property

cmake 不会关注缩进,但是为了阅读性最好添加缩进空格。每行语句结束也不用加上 ";"。

cmake 环境变量(environment variables) #

环境变量用于配置编译器标志、链接器标志、常规构建过程的测试配置。可以通过 set()unset() 设置环境变量的值。

cmake 环境变量列表: https://cmake.org/cmake/help/latest/manual/cmake-env-variables.7.html

cmake 环境变量介绍:https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#cmake-language-environment-variables

Language-wide flags #

CMAKE_<LANG>_FLAGS 用于配置指定语言编译器的标识。

  • CMAKE_C_FLAGS
  • CMAKE_CXX_FLAGS
  • CMAKE_CUDA_FLAGS
  • CMAKE_Fortran_FLAGS
  • CMAKE_CSharp_FLAGS
  • CMAKE_HIP_FLAGS
  • CMAKE_ISPC_FLAGS

上面的变量会被特定的环境变量初始化,例如 CMAKE_CXX_FLAGS 被 CXXFLAGS 初始化,CXXFLAGS 是一个环境变量,CXXFLAGS 的初始值来自调用环境,为 cxx(c++) 文件编译提供编译选项。cmake 将这个环境变量的值和 cmake 默认的编译选项合并后,存储到 CMAKE_CXX_FLAGS 变量中(即,CMAKE_CXX_FLAGS 初始化自 CXXFLAGS),然后提供给编译工具链。但是如果定义了 CMAKE_CXX_FLAGS 变量,那么 CXXFLAGS 的值就会被忽略。

例如 gcc 命令的 -Wall 用于打印所有的警告信息,我们可以使用 set 命令设置该编译选项。

# 不建议使用这种,它会覆盖原有的选项
set(CMAKE_CXX_FLAGS "-Wall")
# 使用这种,标识在原有的参数后面增加 -Wall,没有覆盖原有的命令 flag
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

cmake 变量 #

可以使用 set 命令定义一个变量,cmake 中也包含一些预定义的变量,例如上面的 CMAKE_CXX_FLAGS

与命令不同,变量是区分大小写的。

cmake 变量:https://cmake.org/cmake/help/v3.0/manual/cmake-language.7.html#variables

一些常用的预定义变量(暂时不知道如何翻译比较好,先保留英文):

  • CMAKE_BINARY_DIR: Full path to top level of build tree and binary output folder, by default it is defined as top level of build tree.
  • CMAKE_HOME_DIRECTORY: Path to top of source tree
  • CMAKE_SOURCE_DIR: Full path to top level of source tree.
  • CMAKE_INCLUDE_PATH: Path used to find file, path

可以通过 ${variable_name} 获取变量值。

message("CXX Standard: ${CMAKE_CXX_STANDARD}")
set(CMAKE_CXX_STANDARD 14)

可以定义一个新变量。

set(TRIAL_VARIABLE "VALUE")
message("${TRIAL_VARIABLE}")

cmake 列表 #

cmake 中所有的值都被存储为字符串形式,但是一个字符串在特定的上下文环境可以被当成list (列表) 使用。

设置 files 变量为一个列表,实际存储的是以分号隔开的字符串。

set(files a.txt b.txt c.txt)
# sets files to "a.txt;b.txt;c.txt"

遍历 files 列表:

foreach(file ${files})
    message("Filename: ${file}")
endforeach()

cmake 生成器表达式 #

生成器表达式:https://cmake.org/cmake/help/v3.3/manual/cmake-generator-expressions.7.html

代码实践 #

一个简单的例子 #

创建一个 `main.cpp`` 文件,内容如下:

#include <iostream>
int main() {
    std::cout<<"Hello CMake!"<<std::endl;
}

我们可以直接使用 g++ 编译该文件:

$ g++ main.cpp -o cmake_hello

但是我们的目的是使用 cmake 输出 makefile 文件,管理项目的构建过程,在 main.cpp 增加一个 CMakeLists.txt 文件,内容如下:

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
add_executable(cmake_hello main.cpp)

CMakeLists 的三行代码分别做了下面三件事:

  • 指定 cmake 的最小兼容版本
  • 指定项目的名称
  • 指定通过 main.cpp 创建可执行文件 cmake_hello

接下来通过 cmake 生成 makefile:

$ cmake CMakeLists.txt 
-- The C compiler identification is GNU 11.4.0
-- The CXX compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done

cmake 命令执行过程中,可以自动识别了 c(xx) 编译器的版本信息。通过这些信息生成一些默认的配置(CMakeCaceh.txt 等)和 makefile。

接下来可以执行 make 命令构建项目:

$ make all
# or
$ make cmake_hello

make 命令执行完毕后,当前目录会出现可执行文件 cmake_hello,该文件名由 add_executable 命令指定。

指定 C++ standard #

有时候我们用到了一些 C++ 新语法,例如你用到了 C++14 中的语法,所以我们需要指定 C++ 的版本。

main.cpp 改为如下内容:

#include <iostream>

auto sum(int a, int b) {
    return a + b;    
}

int main() {
    std::cout << "Hello CMake!" << std::endl;
    std::cout << "Sum of 3 + 4: " << sum(3, 4) << std::endl;
}

auto 为 C++14 新增语法,所以编译时需要指定 C++14。

更新 CMakeLists.txt

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
set(CMAKE_CXX_STANDARD 14)
add_executable(cmake_hello main.cpp)

通过设置 CMAKE_CXX_STANDARD 变量值,设定 C++14。如果 C++ 版本不正确,构建会出错。

跨平台编译 #

如果你的项目需要编译至多平台:

  • 为 windows、mac 和 linux 生成可执行文件
  • 根据 linux 内核版本添加不同的宏(macros)

cmake 提供了一些变量可以用来检测系统相关信息:

  • CMAKE_SYSTEM: 存储了完整的系统信息,例如 "Linux-2.4.22", "FreeBSD-5.4-RELEASE", "Windows 5.1"。
  • CMAKE_SYSTEM_NAME: 生成的可执行文件的平台名称,常用的有 Windows、Darwin、Linux、Android、FreeBSD 等,例如需要构建 windows 上的可执行程序,可以设置该变量值为 Windows,如果没有设置该值,该变量值默认为 CMAKE_HOST_SYSTEM_NAME。
  • CMAKE_SYSTEM_VERSION: 系统的内核版本
  • CMAKE_SYSTEM_PROCESSOR: 系统的处理器名称(e.g. "Intel(R) Pentium(R) M processor 2.00GHz")
  • CMAKE_HOST_SYSTEM_NAME: cmake 脚本运行的系统名称。

我们可以使用下面的脚本,判断当前系统的信息:

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)

# UNIX, WIN32, WINRT, CYGWIN, APPLE are environment variables as flags set by default system
if (UNIX)
    message("This is a ${CMAKE_SYSTEM_NAME} system")
elseif (WIN32)
    message("This is a Window System")
endif()

if (${CMAKE_SYSTEM_NAME} MATCHES Darwin)
    message("This is a ${CMAKE_SYSTEM_NAME} system")
elseif(${CMAKE_SYSTEM_NAME} MATCHES Windows)
    message("This is a Windows System")
endif()

add_executable(cmake_hello main.cpp)

定义宏(Macros) #

可以使用 add_definitions 命令定义宏,在宏名称前需要加上 -D 标识,例如定义 CMAKEMACROSAMPLE 宏。

add_definitions(-DCMAKEMACROSAMPLE="Apple MacOS")

修改 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
set(CMAKE_CXX_STANDARD 14)
# or use MATCHES to see if actual system name 
# Darwin is Apple's system name
if(${CMAKE_SYSTEM_NAME} MATCHES Darwin)
    add_definitions(-DCMAKEMACROSAMPLE="Apple MacOS")
elseif(${CMAKE_SYSTEM_NAME} MATCHES Windows)
    add_definitions(-DCMAKEMACROSAMPLE="Windows PC")
elseif(${CMAKE_SYSTEM_NAME} MATCHES Linux)
    add_definitions(-DCMAKEMACROSAMPLE="Linux PC")
endif()
add_executable(cmake_hello main.cpp)

在 main.cpp 中打印宏:

#include <iostream>

#ifndef CMAKEMACROSAMPLE
#define CMAKEMACROSAMPLE "NO SYSTEM NAME"
#endif

int main() {
    std::cout << "Hello CMake!" << std::endl;
    std::cout << "CMAKEMACROSAMPLE: " << CMAKEMACROSAMPLE << std::endl;
    return 0;
}

文件夹管理 #

构建应用时,我们希望保持源文件树干净整洁,将自动生成的文件和源文件放在不同的位置。

很多开发者习惯在源文件的根目录下创建一个 build 文件夹,然后再 build 文件夹下执行 cmake 命令。

所以,首先创建一个 build 文件夹:

$ mkdir build
$ ls -al
total 32
drwxr-xr-x  4 fangyan fangyan  4096 Jan 16 23:10 .
drwxr-xr-x 16 fangyan fangyan 12288 Jan 16 22:06 ..
drwxr-xr-x  7 fangyan fangyan  4096 Jan 16 23:10 .git
-rw-r--r--  1 fangyan fangyan   494 Jan 16 23:04 CMakeLists.txt
drwxr-xr-x  2 fangyan fangyan  4096 Jan 16 23:10 build
-rw-r--r--  1 fangyan fangyan   246 Jan 16 23:05 main.cpp

在 build 文件夹执行 cmake 命令。

$ cd build
$ cmake ..
$ ls -al
total 40
drwxr-xr-x 3 fangyan fangyan  4096 Jan 16 23:12 .
drwxr-xr-x 4 fangyan fangyan  4096 Jan 16 23:10 ..
-rw-r--r-- 1 fangyan fangyan 13861 Jan 16 23:12 CMakeCache.txt
drwxr-xr-x 5 fangyan fangyan  4096 Jan 16 23:12 CMakeFiles
-rw-r--r-- 1 fangyan fangyan  5268 Jan 16 23:12 Makefile
-rw-r--r-- 1 fangyan fangyan  1660 Jan 16 23:12 cmake_install.cmake

上面的命令在 build 文件夹下生成了 build 文件。另外一般习惯将 build 文件夹添加到 .gitignore 文件夹中。

但是我们不希望每次都创建一个 build 文件夹,再进入(cd) build 文件夹,然后执行 cmake .. 命令。

我们可以使用 cmake 命令的 -H-B 选项:

  • -H: 指定源文件根目录
  • -B: 指定 build 文件夹路径

所以我们可以直接再源文件根目录下执行下面的命令达到同样的效果:

$ cmake -H. -Bbuild
-- The C compiler identification is GNU 11.4.0
-- The CXX compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/fangyan/projects/program/cmake-study/build

还可以再 CMakeLists.txt 文件中设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY 或者 EXECUTABLE_OUTPUT_PATH 指定库文件和可执行文件生成的位置。

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_executable(cmake_hello main.cpp)

执行 cmake -H. -Bbuild 后,然后进入 build 文件夹,执行 make 命令,生成的可执行文件在 build/bin/cmake_hello

还有一些变量可以设置动态库(.dll,.so)的路径:

  • LIBRARY_OUTPUT_PATH
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY

或者设置静态库(.a 或者 .lib)的路径:

  • CMAKE_ARCHIVE_OUTPUT_DIRECTORY
  • ARCHIVE_OUTPUT_PATH

使用 CMake 构建库 #

1.准备文件 #

首先准备需要编译的代码文件,在当前目录增加两个文件:

// ============ lib/math/operations.hpp
#ifndef CMAKEHELLO_OPERATIONS_HPP
#define CMAKEHELLO_OPERATIONS_HPP
namespace math {
    class operations {
        public:
        int sum(const int& a, const int& b);
        int mult(const int& a, const int& b);
        int div(const int& a, const int& b);
        int sub(const int& a, const int& b);
    };
}
#endif  

// ============ lib/math/operations.cpp
#include "operations.hpp"
#include <exception>
#include <iostream>
#include <stdexcept>

int math::operations::sum(const int& a, const int& b) {
    return a + b;
}

int math::operations::mult(const int& a, const int& b) {
    return a * b;
}

int math::operations::div(const int& a, const int& b) {
    if (b == 0) {
        throw std::overflow_error("Divide by zero exception");
    }
    return a / b;
}

int math::operations::sub(const int& a, const int& b) {
    return a - b;
}

然后更新 main.cpp 如下:

#include <iostream>
#include "lib/math/operations.hpp"

#ifndef CMAKEMACROSAMPLE
    #define CMAKEMACROSAMPLE "NO SYSTEM NAME"
#endif

int main() {
    std::cout << "Hello CMake!" << std::endl;
    math::operations op;
    int sum = op.sum(3, 4);

    std::cout << "Sum of 3 + 4 :" << sum << std::endl;
    return 0;
}

静态库 vs 动态库:https://pengfeixc.com/blogs/clang/gcc#%E9%9D%99%E6%80%81%E5%BA%93%E5%92%8C%E5%85%B1%E4%BA%AB%E5%BA%93

2.将库文件编译到可执行程序中 #

我们可以将库文件(lib/math)一起打包到最后生成的可执行文件中,只需要在 add_executable 中添加需要编译的文件即可。

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_executable(cmake_hello main.cpp lib/math/operations.cpp lib/math/operations.hpp)

然后执行 cmake -H. -Bbuild 即生成 makefile,执行 make 命令可以生成最终可执行文件(cmake_hello)。

你可以创建一个 SOURCES 变量,将需要编译的文件路径放存储到 source list 中。

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set (SOURCES main.cpp lib/math/operations.cpp lib/math/operations.hpp)
add_executable(cmake_hello ${SOURCES})

3.单独构建库文件 #

我们可以将库文件打包成分离的共享库(动态库)或者静态库。

将库文件打包成分离的库文件,在 build 时,还需要一个链接的过程。

需要做以下操作:

  • 设置库输出路径:LIBRARY_OUT_PATH
  • 创建动态库(SHARED)或者静态库(STATIC)
  • 链接库 target_link_libraries

下面是一个创建静态库的例子:

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib)

message(${CMAKE_BINARY_DIR})

# add_library(math SHARED lib/math/operations.cpp)
add_library(math STATIC lib/math/operations.cpp)

add_executable(cmake_hello main.cpp)

target_link_libraries(cmake_hello math)

执行 cmake -H. -Bbuild,然后再 build 目录下,执行 make 命令,在 build/lib 目录下会生成一个静态库。build/bin 目录下生成了最终可执行文件程序(cmake_hello),因为生成的是静态库,它已经被链接打包到了最终的可执行文件中,所以即使此时被删除,cmake_hello 也能正常执行。

创建动态库,只需要将 add_library(math STATIC lib/math/operations.cpp) 改为 add_library(math SHARED lib/math/operations.cpp) 即可。但是最终生成的可执行文件再运行过程中也需要库文件。

将库文件作为 cmake 的一个子模块(sub-module) #

也可以在库文件目录 lib/math 中,创建一个新的 CMakeLists.txt 文件,在生成可执行文件前,单独构建库文件。

创建文件 lib/math/CMakeLists.txt:

cmake_minimum_required(VERSION 3.9.1)
set(LIBRARY_OUTPUT_PATH  ${CMAKE_BINARY_DIR}/lib)
add_library(math SHARED operations.cpp)

然后将根目录下的 CMakeLists.txt 文件作出如下更改:

  • 删除其中的 add_library 和设置 LIBRARY_OUTPUT_PATH 命令
  • 使用 add_subdirectory 添加新的构件路径。这个命令可以使 cmake 命令查找指定目录下包含的 CMakeLists.txt 文件。
cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

message(${CMAKE_BINARY_DIR})

add_subdirectory(lib/math)

add_executable(cmake_hello main.cpp)

target_link_libraries(cmake_hello math)

执行 cmake 和 make 后,你会发现结果与之前一致。

使用 CMake 查找使用其他库文件 #

很多情况下,你找到一些开源库,你想使用这个库中的 API。例如你使用 apt-get 安装了 boost 库。

你可以使用 CMake 的条件语句判断库是否存在。然后决定是否继续构建程序。

cmake 能够在系统默认的库文件位置查找库文件,例如 /usr/lib;/usr/local/lib

可以使用包管理器(apt-get,brew)安装 Boost 库。在构建程序前,可以通过 cmake 的 find_package 命令检查 library 是否已经安装。

接下来,进行实操练习。

更新 main.cpp 文件,使用 boost 库 API:

#include <boost/random.hpp>
#include <iostream>
#include "lib/math/operations.hpp"

int main() {
    std::cout << "Hello CMake!" << std::endl;

    math::operations op;

    int sum = op.sum(3, 4);

    std::cout << "Sum of 3 + 4: " << sum << std::endl;

    // Boost Random Sample
    boost::mt19937 rng;
    double mean = 2.3;
    double std = 0.34;

    auto normal_dist = boost::random::normal_distribution<double>(mean, std);

    boost::variate_generator<boost::mt19937&,
                             boost::normal_distribution<> >
        random_generator(rng, normal_dist);
    for (int i = 0; i < 2; i++) {
        auto rand_val = random_generator();
        std::cout << "Random Val " << i + 1 << " :" << rand_val << std::endl;
    }
    return 0;
}

接下来更新 CMakeLists.txt 文件,我们需要检查 Boost 库是否存在,如果存在则使用 include_directories 将 Boost 头文件添加到编译器的头文件搜索路径之下,最后链接库。

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

message(${CMAKE_BINARY_DIR})

add_subdirectory(lib/math)

add_executable(cmake_hello main.cpp)

find_package(Boost 1.66)

# check for library, if found print message, include dirs and link libraries
if (Boost_FOUND)
    message("Boost Found")
    include_directories(${Boost_INCLUDE_DIRS})
    target_link_libraries(cmake_hello ${Boost_LIBRARIES})
elseif(NOT Boost_FOUND)
    error("Boost Not Found")
endif()

target_link_libraries(cmake_hello math)

如果系统没有安装 Boost,执行 cmake 命令会报错。因为判断库文件不存在直接报错。可以执行 sudo apt-get install libboost-all-dev 安装 Boost,再执行 cmake 命令,将构建成功。

当使用 find_package 查找库文件时,下面的变量会自动创建:

  • <NAME>_FOUND: 标识是否查找到库文件。
  • <NAME>_INCLUDE_DIRS 或者 <NAME>_INCLUDES: 库文件的头文件目录
  • <NAME>_LIBRARIES 或者 <NAME>_LIBRARIES 或者<NAME>_LIBS:库源文件
  • <NAME>_DEFINITIONS

但是如果,想要链接的 lib 在一个自定义文件夹中,而不是在默认库文件目录,也不再 cmake 构建的 source tree 中呢?

使用 g++ 链接指定目录的库文件,命令是这样的:

$ g++ main.cpp -o cmake_hello -I/home/fangyan/libraries/boost/include -L/home/fangyan/libraries/boost -lBoost

上面的命令做了以下几件事:

  • -I<DIRECTORY>: 指定库头文件的搜索路径
  • -L<DIRECTORY>: 指定库函数的搜索路径
  • -l<LIBRARY_NAME>: 指定连接的库文件名称

CMakeLists.txt 需要完成同样的逻辑。

include_directories(/Users/User/Projects/libraries/include)
link_directories(/Users/User/Projects/libraries/libs)
# elseif case can be 
elseif(NOT Boost_FOUND)
message("Boost Not Found")
    include_directories(/Users/User/Projects/libraries/include)
    link_directories(/Users/User/Projects/libraries/libs)
    target_link_libraries(cmake_hello Boost)
endif()

还有很多其他的方式,例如你可以写自定义的 cmake 方法。这里最重要的是理解 C/C++ 的编译链接逻辑。

gcc 和 g++: https://pengfeixc.com/blogs/clang/gcc

目标平台配置 #

你希望你开发的程序能够在不同平台运行。希望当程序运行在一个 intel 系统上时,能够包含 intel 相关的库文件。或者你希望能够交叉编译,在 windows 平台开发一个能够在其他系统或者嵌入式系统运行的程序。

所以你需要在 CMake 脚本中处理所有检查和宏(macros)。

1.改变构建的编译器和连接器 #

现在你需要将你的程序进行交叉编译,能够在不同平台运行。所以当前 host 系统需要安装目标系统的编译器和链接器。

cmake 官方的交叉编译的例子:https://cmake.org/cmake/help/v3.6/manual/cmake-toolchains.7.html#cross-compiling-for-linux

下面是一个为树莓派系统构建应用程序的例子,使用树莓派上的 C/C++ 编译器和工具。

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_SYSROOT /home/devel/rasp-pi-rootfs)
set(CMAKE_STAGING_PREFIX /home/devel/stage)
set(tools /home/devel/gcc-4.7-linaro-rpi-gnueabihf)
set(CMAKE_C_COMPILER ${tools}/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${tools}/bin/arm-linux-gnueabihf-g++)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

上面的脚本最重要的地方是设置编译器和编译工具(linker)的路径。

这里就不详细说了,但是你得知道 cmake 可以实现交叉编译的配置。

设置编译器和链接器 #

设置编译器和链接器的命令项可以定义程序构建过程中的一些行为,例如打印警告信息,可以调试等等。

1.设置编译命令项 #

set(CMAKE_CXX_FLAGS "-std=c++0x -Wall")
# suggested way is to keep previous flags in mind and append new ones
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall")
# Alternatively, you can use generator expressions, which are conditional expressions. Below says that, if compiper is c++ then set it to c++11
add_compile_options("$<$<STREQUAL:$<TARGET_PROPERTY:LINKER_LANGUAGE>,CXX>:-std=c++11>")

2.设置链接命令项 #

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl")

Debug 和 Release 配置 #

一般我们会用两种方式构建程序, Debug 和 Release,Debug 用于调试,Release 用于发布。用于发布的程序一般会去掉一些调试信息。

CMake 可以帮助你构建两种不同的版本。

使用下面两个命令可以创建不同的版本在不同的文件夹下:

$ cmake -H. -Bbuild/Debug
$ cmake -H. -Bbuild/Release

当然上面的两个命令最终生成的文件是一致的,我们需要设置 CMAKE_BUILD_TYPE 的值,来设置创建版本类型。

$ cmake -DCMAKE_BUILD_TYPE=Debug -H.  -Bbuild/Debug
$ cmake -DCMAKE_BUILD_TYPE=Release -H. -Bbuild/Release

在 CMakeLists.txt 文件中也能使用 CMAKE_BUILD_TYPE 变量。

if(${CMAKE_BUILD_TYPE} MATCHES Debug)
    message("Debug Build")
elseif(${CMAKE_BUILD_TYPE} MATCHES Release)
    message("Release Build")
endif()

所以根据 build 类型可以单独设置编译器和链接器的选项。

CMAKE 安装和部署配置 #

CMake 还提供了很多命令帮助打包程序,这里有一篇很不错的文章介绍:https://cmake.org/cmake/help/v3.0/command/install.html

Reference #

(完)

目录