cmake_tutorial

日期:2025年12月11日

创作人:半两八斤

说明:便于个人快速回顾CMake的使用

Demo 01:编译运行/安装已有工程

  • 目标:在不修改 CMakeLists.txt 的前提下完成配置、编译、安装。
  • 示例目录:cmake_sq/demo01_existing_project(目录中已存在 CMakeLists.txt)。
bash 复制代码
# bash:在已有工程中构建和安装
mkdir build                                  # 创建构建目录(源码与构建分离)
cd build                                     # 进入构建目录
cmake ..                                     # 生成构建系统(Makefile 等)
# cmake .. -DCMAKE_INSTALL_PREFIX=../_install # 可选:修改安装前缀
make                                         # 编译
make install                                 # 按 INSTALL() 规则安装到前缀目录
  • 场景:接手别人已经写好的 C/C++ 工程,只需要"会用"。
  • 要点:始终使用 out-of-source 构建(mkdir build && cd build && cmake ..),避免污染源码目录。
  • 要点:CMAKE_INSTALL_PREFIX 是安装根目录,DESTINATION 通常写相对路径。
  • 常见错误:在源码目录直接 cmake . / make,导致生成文件夹杂在源码里。

Demo 02:最小可执行程序

  • 目标:从 0 写出最小 CMakeLists.txt,并演示源码 src 与头文件 include 分离。
  • 示例目录:cmake_sq/demo02_minimal_exe
c 复制代码
// include/demo02.h
#ifndef DEMO02_H
#define DEMO02_H

#define DEMO02_MSG "Hello from Demo02 (with include dir)\n"

#endif
c 复制代码
// src/main.c
#include <stdio.h>
#include "demo02.h"   /* 头文件位于 include/,不是当前目录 */

int main(void)
{
    printf(DEMO02_MSG);
    return 0;
}
cmake 复制代码
# CMakeLists.txt(Demo02 顶层)
cmake_minimum_required(VERSION 3.16)         # 1. 要求最低 CMake 版本
project(Demo02 LANGUAGES C)                  # 2. 定义工程名和语言

set(SRC_LIST src/main.c)                     # 3. 源码列表

add_executable(demo02 ${SRC_LIST})           # 4. 生成可执行程序 demo02

target_include_directories(demo02            # 5. 为 demo02 指定头文件搜索路径
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include  #    让编译器能找到 include/demo02.h
)

install(TARGETS demo02                       # 6. 安装 demo02 可执行程序
        RUNTIME DESTINATION bin)             #    放到前缀下的 bin 目录
bash 复制代码
# bash:构建并安装 Demo02
mkdir build                                   # 创建构建目录
cd build                                      # 进入构建目录
cmake .. -DCMAKE_INSTALL_PREFIX=../_install   # 生成构建文件并设置安装前缀
make                                          # 编译可执行程序
make install                                  # 安装到 ../_install/bin
  • 场景:项目采用 src/ + include/ 分离布局,需要告诉 CMake 头文件路径。
  • 要点:源码列表只写 .c,头文件目录通过 target_include_directories 提供给目标。
  • 要点:${CMAKE_CURRENT_SOURCE_DIR} 是当前 CMakeLists.txt 所在目录,适合作为 include 根。
  • 常见错误:只写了 src/main.c,但没设置 include 目录,导致编译期找不到 "demo02.h"

Demo 03:主程序链接内部库

  • 目标:在同一工程中拆分库和可执行,主程序链接内部库。
  • 示例目录:cmake_sq/demo03_lib_and_app
c 复制代码
// lib/hello.h
#ifndef HELLO_H
#define HELLO_H

void hello(const char *name);

#endif
c 复制代码
// lib/hello.c
#include <stdio.h>
#include "hello.h"

void hello(const char *name)
{
    printf("Hello, %s from Demo03\n", name);
}
cmake 复制代码
# lib/CMakeLists.txt
add_library(hello STATIC hello.c)            # 定义静态库 hello,源文件为 hello.c

target_include_directories(hello             # 为使用 hello 库的目标提供头文件路径
    PUBLIC                                   # PUBLIC:链接到 hello 的目标也继承该 include 路径
        ${CMAKE_CURRENT_SOURCE_DIR}          # 当前目录(包含 hello.h)
)
c 复制代码
// app/main.c
#include "hello.h"

int main(void)
{
    hello("world");
    return 0;
}
cmake 复制代码
# app/CMakeLists.txt
add_executable(hello_app main.c)             # 定义可执行程序 hello_app

target_link_libraries(hello_app              # 声明 hello_app 需要链接哪些库
    PRIVATE                                  # PRIVATE:只影响 hello_app 自身
        hello                                # 链接同一工程中的 hello 静态库
)
cmake 复制代码
# CMakeLists.txt(Demo03 顶层)
cmake_minimum_required(VERSION 3.16)         # 1. 最低 CMake 版本
project(Demo03 LANGUAGES C)                  # 2. 工程名和语言

add_subdirectory(lib)                        # 3. 引入 lib 子目录,得到 target: hello
add_subdirectory(app)                        #    引入 app 子目录,得到 target: hello_app

install(TARGETS hello                        # 4. 安装 hello 静态库
        ARCHIVE DESTINATION lib)             #    静态库用 ARCHIVE 安装到前缀下 lib

install(TARGETS hello_app                    # 5. 安装 hello_app 可执行程序
        RUNTIME DESTINATION bin)             #    可执行程序用 RUNTIME 安装到前缀下 bin

install(FILES lib/hello.h                    # 6. 安装头文件
        DESTINATION include/hello)           #    安装到前缀下 include/hello
bash 复制代码
# bash:构建并安装 Demo03
mkdir build                                   # 创建构建目录
cd build                                      # 进入构建目录
cmake .. -DCMAKE_INSTALL_PREFIX=../_install   # 生成构建文件并设置安装前缀
make                                          # 编译库和可执行程序
make install                                  # 安装库 / 头文件 / 可执行
  • 场景:典型"库 + 可执行"项目结构,希望主程序只依赖库的接口。
  • 要点:add_subdirectory(lib/app) 的作用是"把子目录里的 CMakeLists 纳入当前工程一起构建(获得子目标)"。
  • 要点:target_link_libraries(hello_app PRIVATE hello) 表示 hello_app 依赖 hello 库,PRIVATE 只影响 hello_app 自身。
  • 要点:install(TARGETS hello ARCHIVE DESTINATION lib) 安装静态库;动态库一般用 LIBRARY DESTINATION(完整组合见 Demo05)。
  • 要点:通过 target_include_directories 暴露头文件,再用 install() 把库和头文件一起安装出去。
  • 常见错误:忘记在顶层 add_subdirectory(lib)add_subdirectory(app),导致 target 未定义。

Demo 04:使用"已安装"的第三方库

  • 目标:把 Demo03 安装出的库,当成第三方库在新工程中使用。
  • 示例目录:cmake_sq/demo04_use_installed_lib
c 复制代码
// src/main.c
#include "hello.h"

int main(void)
{
    hello("from Demo04");
    return 0;
}
cmake 复制代码
# CMakeLists.txt(Demo04 顶层)
cmake_minimum_required(VERSION 3.16)         # 1. 最低 CMake 版本
project(Demo04 LANGUAGES C)                  # 2. 工程名和语言

set(HELLO_ROOT "../demo03_install")          # 3. 记录 hello 库的安装根目录

find_library(HELLO_LIB hello                 # 4. 在指定路径查找名为 hello 的库
    PATHS "${HELLO_ROOT}/lib"
    REQUIRED                                 #    找不到则配置失败
)

add_executable(demo04 src/main.c)            # 5. 定义可执行程序 demo04

target_include_directories(demo04            # 6. 为 demo04 提供头文件路径
    PRIVATE
        "${HELLO_ROOT}/include/hello"        #    指向安装后的 hello.h 所在目录
)

target_link_libraries(demo04                 # 7. 为 demo04 链接 hello 库
    PRIVATE
        "${HELLO_LIB}"                       #    使用上面 find_library 的结果
)
bash 复制代码
# bash:先在 Demo03 中安装 hello 库
cd ../demo03_lib_and_app                     # 从 Demo04 目录进入 Demo03
mkdir -p build                               # 创建构建目录(已存在则跳过)
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=../demo03_install  # 安装前缀设为 ../demo03_install
make
make install
bash 复制代码
# bash:在 Demo04 中构建并运行
cd ../demo04_use_installed_lib               # 回到 Demo04 工程目录
mkdir build
cd build
cmake ..                                     # 只使用已安装的 hello 库
make
./demo04                                     # 运行,验证链接成功
  • 场景:别人给了安装好的库和头文件路径,你在自己的工程中使用它们。
  • 要点:用变量(如 HELLO_ROOT)统一管理第三方库根目录,避免硬编码散落各处。
  • 要点:编译期需要 target_include_directories 提供头文件路径,链接期用 find_library + target_link_libraries
  • 常见错误:只设置 include 路径没设置库路径,或者 HELLO_ROOT 写错导致找不到库。

Demo 05:同时生成静态库和动态库

  • 目标:用同一套源码生成静态库和动态库,并安装头文件与库文件。
  • 示例目录:cmake_sq/demo05_static_and_shared
c 复制代码
// include/mylib/mylib.h
#ifndef MYLIB_MYLIB_H
#define MYLIB_MYLIB_H

int mylib_add(int a, int b);

#endif
c 复制代码
// src/mylib.c
#include "mylib/mylib.h"

int mylib_add(int a, int b)
{
    return a + b;
}
cmake 复制代码
# CMakeLists.txt(Demo05 顶层)
cmake_minimum_required(VERSION 3.16)         # 1. 最低 CMake 版本
project(Demo05 LANGUAGES C)                  # 2. 工程名和语言

add_library(mylib_shared SHARED src/mylib.c) # 3. 生成动态库 libmylib.so
add_library(mylib_static STATIC src/mylib.c) #    生成静态库 libmylib.a

set_target_properties(mylib_shared PROPERTIES
    OUTPUT_NAME mylib                        # 动态库实际文件名为 libmylib.so
    VERSION 1.0                              # 完整版本号,例如 libmylib.so.1.0
    SOVERSION 1                              # 接口版本号,例如 libmylib.so.1
)

set_target_properties(mylib_static PROPERTIES
    OUTPUT_NAME mylib                        # 静态库文件名为 libmylib.a
)

target_include_directories(mylib_shared      # 4. 为使用动态库的目标提供头文件路径
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)

target_include_directories(mylib_static      #    为使用静态库的目标提供头文件路径
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)

install(TARGETS mylib_shared mylib_static    # 5. 安装动态库和静态库
        LIBRARY DESTINATION lib              #    动态库安装到 lib(LIBRARY)
        ARCHIVE DESTINATION lib)             #    静态库安装到 lib(ARCHIVE)

install(DIRECTORY include/ DESTINATION include) # 6. 安装整个 include 目录
bash 复制代码
# bash:本地安装到项目内 _install
mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=../_install
make
make install
bash 复制代码
# bash:安装到系统前缀 /usr/local
mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local
make
sudo make install
  • 场景:需要同时提供 .so.a 两种形式的库给其他工程使用。
  • 要点:通过 OUTPUT_NAME 让静态库和动态库都叫 libmylib.*,路径一致更易于使用。
  • 要点:安装时静态库用 ARCHIVE DESTINATION,动态库用 LIBRARY DESTINATION,头文件用 install(DIRECTORY ...)
  • 常见错误:只安装库文件没安装头文件,或者把静态库/动态库的安装类型写反。

Demo 06:常用选项 / 变量 Cheat Sheet 工程

  • 目标:做一个"变量小抄工程",方便记忆常用选项和编译宏的写法。
  • 示例目录:cmake_sq/demo06_options_and_vars
c 复制代码
// main.c
#include <stdio.h>

int main(void)
{
#ifdef ENABLE_LOG
    printf("Demo06: log enabled\n");
#else
    printf("Demo06: log disabled\n");
#endif
    return 0;
}
cmake 复制代码
# CMakeLists.txt(Demo06 顶层)
cmake_minimum_required(VERSION 3.16)         # 1. 最低 CMake 版本
project(Demo06 LANGUAGES C)                  # 2. 工程名和语言

option(ENABLE_LOG "Enable log output" ON)    # 3. 定义布尔选项 ENABLE_LOG,默认 ON

set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE) # 4. 设置构建类型

set(SRC_LIST main.c)                         # 5. 源码列表

add_executable(demo06 ${SRC_LIST})           # 6. 生成 demo06 可执行程序

if(ENABLE_LOG)                               # 7. 根据选项设置编译宏
    target_compile_definitions(demo06 PRIVATE ENABLE_LOG=1)
endif()

message(STATUS "ENABLE_LOG = ${ENABLE_LOG}")          # 在配置阶段打印 ENABLE_LOG
message(STATUS "CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") # 打印当前构建类型
bash 复制代码
# bash:演示选项和变量的用法
mkdir build
cd build
cmake .. -DENABLE_LOG=OFF -DCMAKE_BUILD_TYPE=Release
make
./demo06
  • 场景:通过命令行快速切换"开关"和构建类型(Debug/Release)。
  • 要点:option()-DXXX=ON/OFF 搭配,配合 target_compile_definitions 控制编译宏。
  • 要点:CMAKE_BUILD_TYPE 只对 Makefile 这类单配置生成器有效,修改后需重新 cmake ..
  • 常见错误:以为改了变量就生效,实际没有重新配置导致仍然使用旧设置。

Demo 07:C 与 Python 互相调用示例

  • 目标:演示"C 库被 Python 调用"和"C++ 程序调用 Python 函数"两种常见互调方式。
  • 示例目录:cmake_sq/demo07_c_and_python

子示例 A:C 动态库被 Python ctypes 调用

c 复制代码
// c_lib/hello.h
#ifndef HELLO_H
#define HELLO_H

void hello_from_c(void);

#endif
c 复制代码
// c_lib/hello.c
#include <stdio.h>
#include "hello.h"

void hello_from_c(void)
{
    printf("Hello from C library (Demo07)\n");
}
cmake 复制代码
# c_lib/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)         # 1. 最低 CMake 版本
project(Demo07_CLib LANGUAGES C)             # 2. 工程名和语言

add_library(hello SHARED hello.c)            # 3. 生成共享库 libhello.so

target_include_directories(hello             # 4. 暴露头文件目录给使用者
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}          #    当前目录(包含 hello.h)
)
python 复制代码
# python/call_hello.py
import ctypes
import os

this_dir = os.path.dirname(__file__)
lib_path = os.path.join(this_dir, "..", "c_lib", "build", "libhello.so")

lib = ctypes.CDLL(lib_path)

lib.hello_from_c()
bash 复制代码
# bash:编译 C 动态库并在 Python 中调用
cd c_lib
mkdir build
cd build
cmake ..
make
cd ../python
python3 call_hello.py

子示例 B:C++ 程序嵌入 Python 解释器调用 .py 函数

python 复制代码
# embed/mymod.py
def add(a, b):
    return a + b
cpp 复制代码
// embed/main.cpp
#include <Python.h>
#include <iostream>

int main()
{
    Py_Initialize();

    PyObject* pName = PyUnicode_FromString("mymod");
    PyObject* pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (!pModule) {
        PyErr_Print();
        std::cerr << "Failed to load mymod.py\n";
        Py_Finalize();
        return 1;
    }

    PyObject* pFunc = PyObject_GetAttrString(pModule, "add");
    if (!pFunc || !PyCallable_Check(pFunc)) {
        PyErr_Print();
        std::cerr << "Cannot find function add\n";
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
        Py_Finalize();
        return 1;
    }

    PyObject* args = PyTuple_New(2);
    PyTuple_SetItem(args, 0, PyLong_FromLong(3));
    PyTuple_SetItem(args, 1, PyLong_FromLong(4));

    PyObject* result = PyObject_CallObject(pFunc, args);
    Py_DECREF(args);

    if (result) {
        long value = PyLong_AsLong(result);
        std::cout << "3 + 4 = " << value << std::endl;
        Py_DECREF(result);
    } else {
        PyErr_Print();
    }

    Py_XDECREF(pFunc);
    Py_DECREF(pModule);
    Py_Finalize();
    return 0;
}
cmake 复制代码
# embed/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)         # 1. 最低 CMake 版本
project(Demo07_Embed LANGUAGES CXX)          # 2. C++ 工程

find_package(Python3 COMPONENTS Interpreter Development REQUIRED) # 3. 查找 Python3 解释器与开发头文件

add_executable(embed_app main.cpp)           # 4. 生成 C++ 可执行程序 embed_app

target_link_libraries(embed_app              # 5. 为 embed_app 链接 Python3 库
    PRIVATE
        Python3::Python
)

add_custom_command(TARGET embed_app POST_BUILD # 6. 构建后复制 mymod.py 到运行目录
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
            ${CMAKE_CURRENT_SOURCE_DIR}/mymod.py
            ${CMAKE_CURRENT_BINARY_DIR}/mymod.py
)
bash 复制代码
# bash:编译 C++ 程序并调用 Python 函数
cd embed
mkdir build
cd build
cmake ..
make
./embed_app
  • 场景:在 Python 中调用 C 性能代码,或在 C++ 程序中调用一小段 Python 逻辑。
  • 要点:给 Python 用的一般是 SHARED 库;可以在 Python 内拼出库路径,避免依赖环境变量。
  • 要点:嵌入 Python 时要链接 Python3::Python,并确保 .py 文件能被解释器找到(复制到运行目录)。
  • 常见错误:Python 版本/架构与编译出来的库不匹配,或未安装 Python 开发包导致找不到 Python.h
相关推荐
欧云服务器6 天前
怎么让脚本命令可以同时在centos、debian、ubuntu执行?
ubuntu·centos·debian
智渊AI6 天前
Ubuntu 20.04/22.04 下通过 NVM 安装 Node.js 22(LTS 稳定版)
ubuntu·node.js·vim
The️6 天前
Linux驱动开发之Read_Write函数
linux·运维·服务器·驱动开发·ubuntu·交互
再战300年6 天前
Samba在ubuntu上安装部署
linux·运维·ubuntu
qwfys2006 天前
How to install golang 1.26.0 to Ubuntu 24.04
ubuntu·golang·install
木尧大兄弟6 天前
Ubuntu 系统安装 OpenClaw 并接入飞书记录
linux·ubuntu·飞书·openclaw
小虾爬滑丫爬6 天前
ubuntu上设置Tomcat 开机启动
ubuntu·tomcat·开机启动
老师用之于民6 天前
【DAY25】线程与进程通信:共享内存、同步机制及实现方案
linux·c语言·ubuntu·visual studio code
小虾爬滑丫爬6 天前
Ubuntu 上设置防火墙
ubuntu·防火墙
林开落L6 天前
解决云服务器内存不足:2 分钟搞定 Ubuntu swap 交换区配置(新手友好版)
运维·服务器·ubuntu·swap交换区