cmake使用

 

参考

配置

cmake是跨平台C/C++构建文件生成工具, 通过CMakeList.txt生成项目构建文件

安装

命令行

sudo apt install -y cmake
卸载
sudo apt --purge remove cmake

源码

  • 示例, 安装amd64架构cmake-3.31
sudo apt install libssl-dev

wget https://cmake.org/files/v3.31/cmake-3.31.4.tar.gz

tar -xvzf cmake-3.31.4.tar.gz                        

cd cmake-3.31.4 && mkdir build && cd build

../bootstrap

# 编译安装
make -j

sudo make install      

# 拷贝可执行文件
sudo cp bin/* /usr/bin/

命令

版本

cmake --version

流程

graph LR;
    A1[/CMakeLists.txt/]--> B[cmake]-->B1[/构建文件/]-->C[构建工具]--> C1[/可执行程序/库/]

CMakeLists.txt是cmake配置文件, 用于定义项目构建过程

cmake根据其中指令生成构建系统(如 Makefile 等), 随后通过构建工具进行编译和链接, 生成可执行文件或库

编写

  • 示例, 通过cmake编译main.c
// main.c
#include <stdio.h>

int main() {
    printf("Hello\n");
    return 0;
}
# CMakeLists.txt
# 设置CMake最低版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(main)

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

# 定义可执行文件
add_executable(main main.c)

# 可执行文件安装位置为根目录bin/
install(TARGETS main RUNTIME DESTINATION ${CMAKE_SOURCE_DIR}/bin)
graph LR;
    X(语法)
    X-->A(cmake_minimum_required)-->A1(cmake版本)
    X-->B(project)-->B1(项目名)
    X-->C(set)-->C1(设置参数)
    X-->D(add_executable/add_library)-->D1(定义生成文件)
    X-->E(install)-->E1(安装)

生成

cmake读取解析CMakeLists.txt, 检查系统环境、依赖库、编译器等设置, 生成对应平台构建文件, 例如在Unix系统上会生成Makefile

graph LR;
    A[/CMakeLists.txt/]-->B[cmake]-->C[/构建文件/]

命令

当前目录生成(不推荐)
.
├── CMakeLists.txt
└── main.c

会产生大量中间文件, 影响整洁

cmake .

在build目录下生成(推荐)
.
├── CMakeLists.txt
├── build
└── main.c
cmake -B build

或者

mkdir build && cd build
cmake ../

使用其他目录CMakeLists.txt

使用source/CMakeList.txt, 在build/下生成构建文件

.
├── source
│   ├── main.c
│   └── CMakeLists.txt
└── build
cmake -S source -B build

构建

构建工具(如make)调用构建文件进行实际编译和链接

graph LR;
    A[/构建文件/]-->B[构建工具]-->C[/可执行文件/库/]

命令

cmake调用
  • 使用当前目录下构建文件
cmake --build .

  • 使用build目录下构建文件
cmake --build build

make调用

进入构建文件目录

make

安装

将构建产物按CMakeLists.txt中设置安装到指定位置

命令

cmake调用
cmake --install 构建目录 (--prefix 安装根路径, 仅在CMakeLists.txt中未指定安装根路径时)
  • 示例, 构建目录为build/, 安装构建产物
cmake --install build

  • 示例, CMakeLists.txt中未指定根路径情况

若CMakeLists.txt中未指定安装根路径, 可通过–prefix手动指定

install(TARGETS main RUNTIME DESTINATION bin)

设构建目录为build, 安装时使用test/作为根路径

cmake --install build --prefix test

可执行文件会安装到test/bin路径下

make调用

进入构建目录

make install

语法

设置

cmake最低版本

cmake_minimum_required(VERSION major.minor)
graph LR
    P(参数)
    P-->A(VERSION)-->A1(关键字, 表示所需CMake 版本号)
    P-->B(major)-->B1(主版本号)
    P-->C(minor)-->C1(次版本号)
  • 示例, 设置工程最低cmake版本为3.10
cmake_minimum_required(VERSION 3.10)
...

项目名

project(项目名 (VERSION 版本信息, 可选))
  • 示例, 设置项目名为main
cmake_minimum_required(VERSION 3.10)
project(main)
...

变量

set(variable value [PARENT_SCOPE])
graph LR
    P(参数)
    P-->A(variable)-->A1(变量名, 通常由大写字母组成, 以区别于cmake内置变量和函数)
    P-->B(value)-->B1(变量值, 可以是字符串、列表或其他cmake变量)
    P-->C(PARENT_SCOPE, 可选)-->C1(如果指定, 变量将在父作用域中设置)
一般变量
  • 示例, 设置变量MY_VAR
set(MY_VAR "Hello, World!")
列表变量
  • 示例, 设置SRC_LIST存储源文件名
set(SRC_LIST main.cpp test.cpp)
set(MY_LIST "item1" "item2" "item3")

foreach(item IN LISTS MY_LIST)
    message(STATUS "List item: ${item}")
endforeach()
父作用域中设置
  • 示例, 父作用域设置变量
function(my_func)
    set(MY_VAR_INSIDE "inside function" PARENT_SCOPE)
endfunction()

my_func()
message(STATUS "The value of MY_VAR_INSIDE after function call is: ${MY_VAR_INSIDE}")

my_func 函数内指定 PARENT_SCOPE 选项在父作用域中设置 MY_VAR_INSIDE 变量

即使在函数调用之后, MY_VAR_INSIDE 也可以在外部作用域中访问

使用

cmake中通过${变量名}获取变量值

  • 示例, 使用变量ANOTHER_VAR
set(ANOTHER_VAR "Another Value")

set(MY_VAR2 ${ANOTHER_VAR})

常量

运行环境

项目名

对应指令project所声明项目名称

PROJECT_NAME
  • 示例, 指定项目名称为main
project(main)

message(${PROJECT_NAME})
目标平台

CMake编译生成目标文件所运行操作系统名称, 交叉编译时可用来指定目标平台类型

CMAKE_SYSTEM_NAME
  • 示例, 指定目标平台为linux
cmake -DCMAKE_SYSTEM_NAME=Linux ..

或者

# CMakeLists.txt
...
set(CMAKE_SYSTEM_NAME "Linux")
...

编译器

C/C++编译器
CMAKE_C_COMPILER

CMAKE_CXX_COMPILER
  • 示例, 指定编译器为aarch64-linux-gnu-gcc
cmake -DCMAKE_C_COMPILER=/usr/bin/aarch64-linux-gnu-gcc -DCMAKE_CXX_COMPILER=/usr/bin/aarch64-linux-gnu-g++

或者

# CMakeLists.txt
...
set(CMAKE_C_COMPILER "/usr/bin/aarch64-linux-gnu-gcc")
set(CMAKE_CXX_COMPILER "/usr/bin/aarch64-linux-gnu-g++")
...

路径

编译目录
CMAKE_BINARY_DIR

PROJECT_BINARY_DIR

如果是在源代码目录中编译, 指工程顶层目录

如果是在源代码目录之外的目录中编译, 指工程编译发生的目录

工程根目录
CMAKE_SOURCE_DIR

PROJECT_SOURCE_DIR
当前路径

CMakeLists.txt文件所在完整路径

CMAKE_CURRENT_SOURCE_DIR

创建

可执行文件

将一组源文件编译成可执行文件

add_executable(target item1 ...)
graph LR
    P(参数)
    P-->A(target)-->A1(目标名称)
    P-->D(item1)-->D1(包含源文件)
  • 示例, 生成可执行文件main
// main.c
#include <stdio.h>

int main() {
    printf("Hello\n");
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(main)

add_executable(main main.c)

库文件

创建库文件

库名对应于逻辑目标名称, 在工程全局域内必须唯一

add_library(target [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 source2 ...)
graph LR
    X(参数)
    X-->A(STATIC)-->A1(目标文件归档文件, 在链接其它目标时使用)
    X-->B(SHARED)-->B1(会被动态链接, 在运行时被加载)
    X-->C(MODULE)-->C1(不会被链接到其它目标中插件, 但可能会在运行时使用dlopen-系列函数动态链)
  • 示例, 生成libmain.so文件
// main.h
#include <stdio.h>

void Hello();
// main.c
#include "main.h"

void Hello() {
    printf("Hello\n");
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(main)

add_library(main SHARED main.c)

添加

头文件路径

指定编译目标(可执行文件或库)应包含头文件目录

target_include_directories(
    target
    [SYSTEM]
    [AFTER|BEFORE]
    <PRIVATE|PUBLIC|INTERFACE>
    <directories>
)
graph LR
    P(参数)
    P-->A(target)-->A1(目标名称, 表示可执行文件或库)
    P-->B(SYSTEM, 可选)-->B1(告诉编译器包含目录是系统级目录)
    P-->C(AFTER、BEFORE, 可选)-->C1(指定包含目录插入位置)
        C1-->C11(AFTER 表示在现有目录之后)
        C1-->C12(BEFORE 表示在现有目录之前)
    P-->D(INTERFACE PRIVATE PUBLIC)-->D1(指定包含目录作用域)
        D1-->D11(PUBLIC)-->D111(对编译目标本身及其依赖均可见)
        D1-->D12(PRIVATE)-->D121(仅对编译目标本身可见)
        D1-->D13(INTERFACE)-->D131(仅对依赖于该目标其他目标使用)
    P-->E(directories)-->E1(要包含头文件目录列表)
  • 示例, 为目标文件添加依赖头文件目录
.
├── CMakeLists.txt
├── src
│   ├── main.c
│   └── module
│       └── test_api
│           └── test_api.h
└── third_party
    └── hello_lib
        └── hello.h
// third_party/hello_lib/hello.h
#include <stdio.h>

extern int libVersion;

int libVersion = 0xFFF;
// src/module/test_api/test_api.h
#include <stdio.h>

extern int apiVersion;

int apiVersion = 0x111;

cmake里会添加依赖头文件目录, 可省略头文件所在目录路径

// src/main.c
#include "hello.h"
#include "test_api.h"

int main() {
    printf("%d\n%d\n", libVersion, apiVersion);
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(main)

add_executable(${PROJECT_NAME} src/main.c)
target_include_directories(${PROJECT_NAME} PRIVATE
    ${CMAKE_SOURCE_DIR}/third_party/hello_lib
    ${CMAKE_SOURCE_DIR}/src/module/test_api
)

源文件

给目标文件添加依赖源文件路径

target_sources(<target> <INTERFACE|PUBLIC|PRIVATE> [items1...])
graph LR
    P(参数)
    P-->A(target)-->A1(目标名称)
    P-->D(INTERFACE PRIVATE PUBLIC)-->D1(指定包含源文件可见性)
        D1-->D11(PUBLIC)-->D111(对编译目标及目标依赖均可见)
        D1-->D12(PRIVATE)-->D121(仅对编译目标本身可见)
        D1-->D13(INTERFACE)-->D131(仅对依赖于该目标其他目标使用)
    P-->E(items1)-->E1(源文件路径)
  • 示例, 添加源文件hello.c、main.c编译
.
├── CMakeLists.txt
└── src
    ├── hello
    │   ├── hello.c
    │   └── hello.h
    └── main.c
// src/hello/hello.h
#include <stdio.h>

void Hello();
// src/hello/hello.c
#include "hello.h"

void Hello() {
    printf("Hello\n");
}
// src/main.c
#include "hello/hello.h"

int main() {
    Hello();
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(main)

add_executable(${PROJECT_NAME} "")
target_sources(${PROJECT_NAME} PRIVATE
    ${CMAKE_SOURCE_DIR}/src/main.c
    ${CMAKE_SOURCE_DIR}/src/hello/hello.c
)

链接依赖

指定链接给定目标和/或其依赖项

target_link_libraries(<target> <INTERFACE|PUBLIC|PRIVATE> items...)
graph LR
    P(参数)
    P-->A(target)-->A1(目标名称, 可以是可执行文件、共享库或静态库)
    P-->D1(可见性)
        D1-->D11(PUBLIC)-->D111(指定库用于链接目标本身, 并传播给目标依赖项)
        D1-->D12(PRIVATE)-->D121(指定库仅用于链接目标本身, 不传播给目标依赖项)
        D1-->D13(INTERFACE)-->D131(指定库不会用于链接目标本身, 但会传播给目标依赖项)
    P-->E(items)-->E1(库文件)

如果项目需要链接系统库, 可以直接使用库名称(如 pthread、dl、m 等)或系统库变量(如 ${CMAKE_THREAD_LIBS_INIT})

  • 示例, 链接系统POSIX线程库
// main.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* ThreadFunction(void* arg) {
    int threadNum = *(int*)arg;
    printf("Hello from thread  %d!\n", threadNum);
    sleep(1);
    return NULL;
}

int main() {
    const int numThreads = 5;
    pthread_t threads[numThreads];
    int threadArgs[numThreads];

    for (int i = 0; i < numThreads; ++i) {
        threadArgs[i] = i;
        pthread_create(&threads[i], NULL, ThreadFunction, &threadArgs[i]);
    }

    for (int i = 0; i < numThreads; ++i) {
        pthread_join(threads[i], NULL);
    }

    std::cout << "all threads completed\n";
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(main)

add_executable(${PROJECT_NAME} main.c)

# 链接 POSIX 线程库
target_link_libraries(${PROJECT_NAME} pthread)

生成

子路径生成

为构建添加一个子路径

add_subdirectory([source_dir] [binary_dir] [EXCLUDE_FROM_ALL])
graph LR
    P(参数)
    P-->A(source_dir)-->A1(指定源 CMakeLists.txt 和代码文件路径)
    P-->B(binary_dir)-->B1(指定放置输出文路径)
    P-->C(EXCLUDE_FROM_ALL, 可选)-->C1(子目录中目标将不包括在父目录ALL目标中, 用户必须在子目录中显式构建目标)
  • 示例, 子路径调用
.
├── CMakeLists.txt
└── src
    ├── hello
    │   ├── CMakeLists.txt
    │   └── hello.c
    └── main.c
// src/hello/hello.c
#include <stdio.h>

int main() {
    printf("Hello\n");
    return 0;
}
# src/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(hello)

add_executable(${PROJECT_NAME} hello.c)
// src/main.c
#include "hello/hello.h"

int main() {
    Hello();
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(main)

# 执行子目录cmake
add_subdirectory(src/hello)
add_executable(${PROJECT_NAME} src/main.c)

安装

目标文件

可执行文件
install(TARGETS 文件名 RUNTIME DESTINATION 安装路径)
  • 示例, 安装可执行文件到根目录bin目录下
// main.c
#include <stdio.h>

int main() {
    printf("Hello\n");
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(main)

add_executable(${PROJECT_NAME} main.c)

install(TARGETS ${PROJECT_NAME} RUNTIME  DESTINATION ${CMAKE_SOURCE_DIR}/bin)

动态库
install(TARGETS LIBRARY DESTINATION )
静态库
install(TARGETS ARCHIVE DESTINATION )

目录

install(DIRECTORY DESTINATION )
文件匹配过滤
install(DIRECTORY DESTINATION FILES_MATCHING PATTERN )
  • 示例, 仅安装.h和.hpp
install(DIRECTORY ${CMAKE_SOURCE_DIR}/src DESTINATION shared FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp")

示例

单文件

将单个源文件生成可执行文件

graph LR
    X(语法)
    X-->A("${PROJECT_NAME}")-->A1(项目名称)
    X-->B(add_executable)-->B1(生成可执行文件)
  • 示例, 编译main.cpp
// main.cpp
#include <iostream>

int main() {
    std::cout << "Hello World" << std::endl;
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(main)

add_executable(${PROJECT_NAME} main.cpp)

多目录

将多个目录下源文件生成可执行文件

graph LR
    X(语法)
    X-->A(target_include_directories)-->A1(添加头文件目录)
    X-->B(target_sources)-->B1(添加源文件)
    X-->C("${CMAKE_SOURCE_DIR}")-->C1(表示项目根目录)
  • 示例, 将hello_1、hello_2目录下源文件生成可执行文件
.
├── CMakeLists.txt
└── src
    ├── hello_1
    │   ├── hello_1.cpp
    │   └── hello_1.hpp
    ├── hello_2
    │   ├── hello_2.cpp
    │   └── hello_2.hpp
    └── main.cpp
// src/hello_1/hello_1.hpp
#include <iostream>

void Hello_1();
// src/hello_2/hello_2.hpp
#include <iostream>

void Hello_2();
// src/hello_1/hello_1.cpp
#include "hello_1.hpp"

void Hello_1() {
    std::cout << "Hello_1" << std::endl;
}
// src/hello_2/hello_2.cpp
#include "hello_2.hpp"

void Hello_2() {
    std::cout << "Hello_2" << std::endl;
}
// main.cpp
#include "hello_1.hpp"
#include "hello_2.hpp"

int main() {
    Hello_1();
    Hello_2();
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(main)

add_executable(${PROJECT_NAME} "")

# 添加可执行文件所依赖头文件目录
target_include_directories(${PROJECT_NAME} PRIVATE
    ${CMAKE_SOURCE_DIR}/src/hello_1
    ${CMAKE_SOURCE_DIR}/src/hello_2
)

# 添加依赖源文件
target_sources(${PROJECT_NAME} PRIVATE
    ${CMAKE_SOURCE_DIR}/src/hello_1/hello_1.cpp
    ${CMAKE_SOURCE_DIR}/src/hello_2/hello_2.cpp
    ${CMAKE_SOURCE_DIR}/src/main.cpp
)

生成库

将源文件生成库文件

graph LR
    X(语法)
    X-->A(target_include_directories)-->A1(添加头文件目录)
    X-->B(target_sources)-->B1(添加源文件)
    X-->C(add_library)-->C1(生成库)
  • 示例, 将源文件生成静态库与动态库
.
├── CMakeLists.txt
└── src
    └── test_api
        ├── test_api.cpp
        └── test_api.hpp
// src/test_api/test_api.hpp
#ifndef __INCLUDE_TEST_API_HPP__
#define __INCLUDE_TEST_API_HPP__
#include <iostream>

#ifdef _WIN32
    #define __EXPORT __declspec(dllexport)
#else
    #define __EXPORT __attribute__((visibility("default")))
#endif

extern "C" {
    void Display();
    int Add(int x, int y);
}
#endif // __INCLUDE_TEST_API_HPP__
// src/test_api/test_api.cpp
#include "test_api.hpp"

void Display() {
    std::cout << "Print test_api success!" << std::endl;
}

int Add(int x, int y) {
    return x + y;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(test_api)

# 设置库生成目录为项目根目录下lib
set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/lib)

# 生成库文件
add_library(${PROJECT_NAME}_shared SHARED "")
add_library(${PROJECT_NAME}_static STATIC "")

# 将动态库与静态库名称保存在LIB_NAME变量中
foreach(LIB_NAME ${PROJECT_NAME}_shared ${PROJECT_NAME}_static)
    target_include_directories(${LIB_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/test_api)
    target_sources(${LIB_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/test_api/test_api.cpp)
    # 设置库名
    set_target_properties(${LIB_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
endforeach()

链接库

生成可执行文件并链接现有库文件

graph LR
    X(语法)
    X-->A(target_include_directories)-->A1(添加头文件目录)
    X-->B(target_sources)-->B1(添加源文件)
    X-->C(target_link_libraries)-->C1(添加链接库文件)
    X-->D(find_library)-->D1(按名称查找库路径)
  • 示例, 生成可执行文件并链接动态库
.
├── CMakeLists.txt
├── lib
│   ├── libtest_api.a
│   └── libtest_api.so
└── src
    ├── main.cpp
    └── test_api
        ├── test_api.cpp
        └── test_api.hpp
// main.cpp
#include "test_api.hpp"

int main(void) {
    Display();

    int res = Add(1, 2);
    std::cout << "res = " << res << std::endl;
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(main)

# 查找test_api库路径, 存储在FUNC_LIB中
find_library(FUNC_LIB test_api ${CMAKE_SOURCE_DIR}/lib)

add_executable(${PROJECT_NAME} "")
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/test_api)
target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/main.cpp)
# 链接库
target_link_libraries(${PROJECT_NAME} PRIVATE ${FUNC_LIB})

子目录编译

可通过划分子目录与主目录CMakeLists.txt, 实现构建文件生成像结构

例如通过多CMakeLists.txt, 实现生成库并链接库生成可执行文件

graph LR
    X(语法)
    X-->A(add_subdirectory)-->A1(执行子目录CMakeLists.txt)
  • 示例, 子目录中生成动态库并链接
.
├── CMakeLists.txt
└── src
    ├── main.cpp
    └── test_api
        ├── CMakeLists.txt
        ├── test_api.cpp
        └── test_api.hpp
// src/test_api/test_api.hpp
#ifndef __INCLUDE_TEST_API_HPP__
#define __INCLUDE_TEST_API_HPP__
#include <iostream>

#ifdef _WIN32
    #define __EXPORT __declspec(dllexport)
#else
    #define __EXPORT __attribute__((visibility("default")))
#endif

extern "C" {
    int Add(int x, int y);
    void Print();
}
#endif // __INCLUDE_TEST_API_HPP__
// src/test_api/test_api.cpp
#include "test_api.hpp"

void Print() {
    std::cout << "Print test_api success!" << std::endl;
}

int Add(int x, int y) {
    return x + y;
}
// src/main.cpp
#include "test_api.hpp"

int main(void) {
    Display();

    int res = Add(0xFF, 0xAA);
    std::cout << "res = " << res << std::endl;
    return 0;
}
# src/test_api/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(test_api)

add_library(${PROJECT_NAME} SHARED "")
target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/test_api/test_api.cpp)
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(main)

# 执行子目录cmake
add_subdirectory(src/test_api)

add_executable(${PROJECT_NAME} "")
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/main.cpp)
target_link_libraries(${PROJECT_NAME} test_api)

三方库

通过cmake使用三方库中内容

find_library

  • 示例, 调用三方库opencv(假设其安装在系统目录下)
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/opencv.hpp>

int main() {
    cv::Mat image = cv::imread("./a.png");
    cv::imshow("test", image);
    return 0;
}
cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
project(main)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_SYSTEM_NAME Linux)

find_package(OpenCV REQUIRED)

add_executable(${PROJECT_NAME} "")
target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/main.cpp)

target_link_libraries(${PROJECT_NAME} "${OpenCV_LIBS}")

FetchContent

cmake 3.11及以上版本引入FetchContent模块, 可直接下载第三方库编译

  • 示例, 下载编译三方库fmt
// main.cpp
#include "fmt/core.h"

int main(){
    std::string world = fmt::format("Hello {0}", "World");
    fmt::print("{}\n", world);
    return 0;
}   
方式1 CMakeLists.txt调用
.
├── CMakeLists.txt
├── main.cpp
└── extern
# CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(main)

set(CMAKE_CXX_STANDARD 14)

# 引入FetchContent
include(FetchContent)

FetchContent_Declare(fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 9.1.0
    SOURCE_DIR ${CMAKE_SOURCE_DIR}/extern/fmt
)
# 构建库
FetchContent_MakeAvailable(fmt)

add_executable(${PROJECT_NAME} "")
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/extern/fmt/include)
target_sources(${PROJECT_NAME} PUBLIC main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt)

方式2 模块调用

可通过.cmake模块实现三方库下载过程, 并在主CMakeLists.txt中调用

.
├── CMakeLists.txt
├── main.cpp
├── cmake
│   └── FMT.cmake
└── extern

新建cmake/FMT.cmake

# cmake/FMT.cmake
include(FetchContent)

set(FMT_LIB fmt)

FetchContent_Declare(${FMT_LIB}
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 9.1.0
    SOURCE_DIR ${CMAKE_SOURCE_DIR}/extern/${FMT_LIB}
)
FetchContent_MakeAvailable(${FMT_LIB})

修改根目录CMakeLists.txt, 将fmt库安装逻辑解耦

# CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(main)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)

# 导入FMT.cmake模块
include(FMT)

add_executable(${PROJECT_NAME} "")
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/extern/fmt/include)
target_sources(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE ${FMT_LIB}::${FMT_LIB})

cross compiling(交叉编译)

通过交叉编译器可在AMD64平台编译生成ARM架构文件

配置工具链

# 32位
sudo apt install -y arm-linux-gnueabihf-g++

# 64位
sudo apt install -y g++-aarch64-linux-gnu

查看版本

aarch64-linux-gnu-gcc -v

查看工具链路径

  • 示例, 交叉编译aarch64架构可执行文件
// main.c
#include <stdio.h>

int main() {
    printf("Hello World\n");
    return 0;
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(main)

add_executable(${PROJECT_NAME} main.c)

构建

命令调用

通过条件编译宏指定交叉编译器

cmake -DCMAKE_C_COMPILER=gcc路径 -DCMAKE_CXX_COMPILER=g++路径 -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=aarch64 -B build

模块调用

创建交叉编译工具链, 设置编译环境, 通过-DCMAKE_TOOLCHAIN_FILE指定工具链路径

# 指定 C 编译器
set(CMAKE_C_COMPILER 编译器路径)

# 指定 C++ 编译器
set(CMAKE_CXX_COMPILER 编译器路径)

# 指定目标系统(可选)
set(CMAKE_SYSTEM_NAME 平台)
set(CMAKE_SYSTEM_PROCESSOR 架构)
cmake -DCMAKE_TOOLCHAIN_FILE=工具链路径 ...
  • 示例, 使用工具链交叉编译

新建cmake/toolchain.cmake

# cmake/toolchain.cmake
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++)

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake -B build