日期: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。