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
相关推荐
HIT_Weston1 小时前
59、【Ubuntu】【Gitlab】拉出内网 Web 服务:Gitlab 配置审视(三)
前端·ubuntu·gitlab
bank_dreamer1 小时前
【ubuntu】vim作为默认编辑器
ubuntu·编辑器·vim
xiaojianhx1 小时前
Ubuntu24 安装 Redis 及常用命令
redis·ubuntu
starvapour9 小时前
Ubuntu的桌面级程序开机自启动
linux·ubuntu
老前端的功夫10 小时前
前端高可靠架构:医疗级Web应用的实时通信设计与实践
前端·javascript·vue.js·ubuntu·架构·前端框架
【上下求索】10 小时前
学习笔记095——Ubuntu 安装 lrzsz 服务?
运维·笔记·学习·ubuntu
松涛和鸣18 小时前
DAY27 Linux File IO and Standard IO Explained: From Concepts to Practice
linux·运维·服务器·c语言·嵌入式硬件·ubuntu
starvapour18 小时前
配置ollama的显卡和模型保存路径(Ubuntu, systemd)
linux·ubuntu·ollama
风华同学18 小时前
【系统移植篇】ubuntu-base系统构建
驱动开发·ubuntu·arm