Linux 之 Shell脚本:CMake自动化构建
摘要:在 Linux C++ 开发体系中,CMake 负责解决构建规则与跨平台编译 问题,而 Shell 脚本则负责解决流程自动化、多场景构建、参数标准化、工具链切换 问题。手写 CMake 命令在多架构、多工具链、跨系统编译、团队协作中极易出现命令遗漏、参数错误、流程不统一等问题。本文以CMake 工程化构建为核心,讲解 Shell 脚本基础,结合工具链选择、跨架构/跨系统编译、命令行传参、标准化目录(scripts/)等需求,打造一份Linux C++ Shell 自动化构建笔记。
一、为什么 Linux C++ 开发必须学 Shell 脚本?
在 Linux C++ + CMake 开发流程中,纯手动操作存在五大痛点:
- 重复操作效率极低 :每次编译都要输入
cmake -S/-B、cmake --build,调试/发布反复切换耗时耗力; - 多场景构建极易出错:原生编译、ARM 交叉编译、Debug/Release 切换、Make/Ninja 生成器切换,命令参数冗长易写错;
- 团队构建流程不统一:成员手动敲命令导致「你能跑、我报错」,工程化规范缺失;
- 工具链/跨系统编译无管理:交叉编译工具链路径、跨架构配置无统一入口,维护成本高;
- 无容错与日志:编译失败仍继续执行、无错误提示、构建结果不可追溯。
Shell + CMake 核心价值 :一次编写脚本,全场景自动化构建 ,通过 scripts/ 目录统一管理构建脚本,实现清理→配置→编译→测试→安装→运行全流程闭环,同时支持工具链切换、跨系统编译、自定义参数透传,统一团队构建规范。
二、核心概念与工程规范
2.1 核心概念(Shell + CMake 联动)
| 概念 | 含义 | 工程化作用 |
|---|---|---|
scripts/ |
构建脚本统一存放目录 | 标准化工程结构,所有 Shell 脚本集中管理 |
toolchain/ |
交叉编译工具链配置目录 | 存放 ARM/ARM64/RISC-V 跨架构工具链文件 |
Shebang(#!/bin/bash) |
脚本解释器声明 | 指定 bash 执行,Shell 脚本第一行必写 |
脚本严格模式(set -euo pipefail) |
错误自动中断机制 | 编译/配置失败直接退出,避免无效执行 |
| 命令行传参 | $1/$2/$@ 接收参数 |
实现 debug/release/clean/cross 模式切换 |
| 工具链文件 | xxx.cmake |
CMake 交叉编译配置,指定跨架构编译器 |
| 源码外构建 | build/ 目录 |
配合 CMake 保持源码干净,脚本自动管理 |
2.2 标准目录结构
C++ 工程采用以下结构,scripts/ 存放所有构建脚本,是本文核心规范:
your_cpp_project/
├── CMakeLists.txt # 项目根CMake配置
├── build/ # 构建产物目录(自动生成,.gitignore忽略)
├── include/ # 项目头文件
├── src/ # 项目源码
├── test/ # 单元测试
├── scripts/ # 【核心规范】Shell构建脚本统一存放
│ └── build.sh # 主构建脚本(全场景通用)
├── toolchain/ # 交叉编译工具链配置目录
│ ├── arm-linux-gnueabihf.cmake # ARM32工具链
│ └── aarch64-linux-gnu.cmake # ARM64工具链
└── output/ # 安装输出目录(脚本自动生成)
2.3 基础环境准备
bash
# 安装基础依赖
sudo apt install cmake ninja-build gcc g++ bash -y
# 查看环境版本
bash --version # 建议4.0+
cmake --version # 建议3.10+
三、基础入门:最简 Shell + CMake 构建脚本
从零开始,先实现一键清理→配置→编译→运行,适配单文件 CMake 项目。
3.1 脚本位置
scripts/build_simple.sh
3.2 脚本代码
bash
#!/bin/bash
# 极简Shell+CMake构建脚本:入门版
# ==================== 基础配置 ====================
SRC_DIR=".." # 脚本在scripts/,源码在上一级
BUILD_DIR="../build" # 构建目录
TARGET="hello_cmake" # 可执行目标名
JOBS=4 # 并行编译线程数
# ==================================================
# 1. 清理构建目录
echo "===== 1. 清理构建目录 ====="
rm -rf ${BUILD_DIR}
# 2. CMake配置
echo -e "\n===== 2. CMake配置 ====="
cmake -S ${SRC_DIR} -B ${BUILD_DIR}
# 3. 编译
echo -e "\n===== 3. 开始编译 ====="
cmake --build ${BUILD_DIR} -j ${JOBS}
# 4. 运行程序
echo -e "\n===== 4. 运行程序 ====="
./${BUILD_DIR}/${TARGET}
3.3 使用方法
bash
# 进入脚本目录
cd scripts
# 添加执行权限
chmod +x build_simple.sh
# 执行脚本
./build_simple.sh
四、核心语法:Shell + CMake 高频用法
所有语法围绕CMake 自动化构建设计,是工程化脚本的基础。
4.1 变量定义(统一管理路径/参数)
bash
# 工程通用变量
SRC_DIR=".." # 源码目录
BUILD_DIR="../build" # 构建目录
TOOLCHAIN_DIR="../toolchain"# 工具链目录
JOBS=$(nproc) # 自动获取CPU核心数(最优并行)
4.2 脚本严格模式
bash
set -euo pipefail
# -e:命令执行失败立即退出
# -u:使用未定义变量直接报错
# -o pipefail:管道命令失败也触发错误
4.3 命令行参数解析(模式切换)
bash
# 获取第一个参数:./build.sh debug → MODE=debug
MODE=$1
# 判断参数
if [ "${MODE}" = "clean" ]; then
rm -rf ${BUILD_DIR}
fi
4.4 错误判断与彩色日志
bash
# 彩色输出
GREEN="\033[32m"
NC="\033[0m"
echo -e "${GREEN}[INFO] 编译成功${NC}"
# 判断上一条命令是否成功
cmake --build ${BUILD_DIR}
if [ $? -ne 0 ]; then
echo "[ERROR] 编译失败"
exit 1
fi
4.5 工具链加载(跨编译核心)
bash
# 传递工具链文件给CMake
TOOLCHAIN_FILE="${TOOLCHAIN_DIR}/arm-linux-gnueabihf.cmake"
cmake -S ${SRC_DIR} -B ${BUILD_DIR} -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}
4.6 参数透传(自定义 CMake 配置)
bash
# 向CMake透传宏定义、安装目录等参数
cmake -S ${SRC_DIR} -B ${BUILD_DIR} -DENABLE_LOG=1 -DCMAKE_INSTALL_PREFIX=../output
五、工程化实战:通用构建脚本(核心)
本脚本覆盖需求:clean/debug/release/cross 模式、工具链选择、跨系统/跨架构编译、命令行传参、生成器切换、测试、安装,直接用于生产环境。
5.1 脚本位置
scripts/build.sh
5.2 完整脚本代码(逐行注释)
bash
#!/bin/bash
# ==================== Shell+CMake构建脚本 ====================
# 功能:清理/原生编译/交叉编译/测试/安装/参数透传/工具链切换
# 规范:脚本放scripts/,工具链放toolchain/,构建产物放build/
# =================================================================
set -euo pipefail # 严格模式:错误立即退出,禁止未定义变量
# ==================== 【工程配置区】根据项目修改 ====================
PROJECT_NAME="MyCppProject"
SRC_DIR=".." # 源码目录(脚本在scripts/)
BUILD_DIR="../build" # 构建目录
TARGET="my_app" # 可执行目标
DEFAULT_GENERATOR="Ninja" # 默认生成器:Ninja/Make
DEFAULT_JOBS=$(nproc) # 默认并行数:CPU全核心
DEFAULT_INSTALL_PREFIX="../output"# 默认安装目录
TOOLCHAIN_DIR="../toolchain" # 工具链配置目录
# ==================================================================
# ==================== 彩色日志定义 ====================
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
NC="\033[0m"
info() { echo -e "${GREEN}[INFO] $*${NC}"; }
error() { echo -e "${RED}[ERROR] $*${NC}"; exit 1; }
warn() { echo -e "${YELLOW}[WARN] $*${NC}"; }
# ==================== 帮助说明 ====================
usage() {
cat <<EOF
Linux C++ CMake 自动化构建脚本
Usage:
./$(basename "$0") <模式> [选项]
【模式】
clean 清理构建目录(必选)
debug Debug原生编译
release Release原生编译
cross <工具链名> 交叉编译(如:arm-linux-gnueabihf)
【选项】
-j N 并行编译线程数(默认:CPU全核心)
-g 生成器 指定生成器:Ninja / Unix Makefiles
-p 路径 指定安装目录(默认:../output)
-D VAR=VALUE 向CMake透传参数(如:-D ENABLE_LOG=1)
【示例】
./build.sh clean # 清理
./build.sh debug -j8 # Debug编译
./build.sh release -p /opt/myapp # Release编译+自定义安装
./build.sh cross arm-linux-gnueabihf -D ENABLE_CROSS=1 # ARM交叉编译
EOF
exit 1
}
# ==================== 参数校验 ====================
[[ $# -lt 1 ]] && usage # 无参数则打印帮助
MODE=$1; shift # 获取第一个参数:构建模式
# ==================== 默认参数初始化 ====================
JOBS=$DEFAULT_JOBS
GENERATOR=$DEFAULT_GENERATOR
INSTALL_PREFIX=$DEFAULT_INSTALL_PREFIX
TOOLCHAIN_FILE=""
CMAKE_EXTRA_ARGS=() # 存储透传给CMake的参数
# ==================== 解析命令行选项 ====================
while [[ $# -gt 0 ]]; do
case "$1" in
-j) JOBS="$2"; shift 2 ;; # 并行线程
-g) GENERATOR="$2"; shift 2 ;; # 生成器
-p) INSTALL_PREFIX="$2"; shift 2 ;; # 安装目录
-D) CMAKE_EXTRA_ARGS+=("-D$2"); shift 2 ;; # CMake透传参数
*) warn "未知参数:$1"; shift ;;
esac
done
# ==================== 1. 清理模式 ====================
if [[ "${MODE}" == "clean" ]]; then
info "清理构建目录:${BUILD_DIR}"
rm -rf "${BUILD_DIR}"
info "清理完成!"
exit 0
fi
# ==================== 2. 交叉编译:工具链处理 ====================
if [[ "${MODE}" == "cross" ]]; then
[[ $# -lt 1 ]] && error "交叉编译必须指定工具链名!示例:arm-linux-gnueabihf"
TOOLCHAIN_NAME="$1"; shift
TOOLCHAIN_FILE="${TOOLCHAIN_DIR}/${TOOLCHAIN_NAME}.cmake"
# 校验工具链文件是否存在
[[ -f "${TOOLCHAIN_FILE}" ]] || error "工具链文件不存在:${TOOLCHAIN_FILE}"
CMAKE_EXTRA_ARGS+=("-DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}")
info "启用交叉编译:${TOOLCHAIN_NAME}"
fi
# ==================== 3. 编译类型判断 ====================
case "${MODE}" in
debug) BUILD_TYPE="Debug" ;;
release) BUILD_TYPE="Release" ;;
cross) BUILD_TYPE="Release" ;; # 交叉编译默认Release
*) error "不支持的模式:${MODE}" ;;
esac
# ==================== 4. CMake配置 ====================
info "========== CMake 配置开始 =========="
info "构建类型:${BUILD_TYPE}"
info "生成器:${GENERATOR}"
info "并行线程:${JOBS}"
info "安装目录:${INSTALL_PREFIX}"
cmake -S "${SRC_DIR}" \
-B "${BUILD_DIR}" \
-G "${GENERATOR}" \
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
"${CMAKE_EXTRA_ARGS[@]}"
info "========== CMake 配置完成 =========="
# ==================== 5. 编译 ====================
info "========== 开始编译 =========="
cmake --build "${BUILD_DIR}" -j "${JOBS}"
# ==================== 6. 单元测试 ====================
info "========== 运行单元测试 =========="
# 交叉编译不运行测试(程序无法在本机执行)
if [[ -z "${TOOLCHAIN_FILE}" ]]; then
ctest --test-dir "${BUILD_DIR}" -j "${JOBS}" --output-on-failure
else
warn "交叉编译环境,跳过单元测试"
fi
# ==================== 7. 安装 ====================
info "========== 安装程序到:${INSTALL_PREFIX} =========="
cmake --install "${BUILD_DIR}"
info "========== 构建+安装全部完成! =========="
六、跨系统/跨架构编译:工具链文件配置
交叉编译的核心是工具链文件 ,存放在 toolchain/ 目录,CMake 通过该文件指定跨架构编译器。
6.1 ARM32 工具链:toolchain/arm-linux-gnueabihf.cmake
cmake
# ARM32 交叉编译工具链配置
set(CMAKE_SYSTEM_NAME Linux) # 目标系统:Linux
set(CMAKE_SYSTEM_PROCESSOR arm) # 目标架构:ARM32
# 指定交叉编译器(根据你的环境修改)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER 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)
6.2 ARM64 工具链:toolchain/aarch64-linux-gnu.cmake
cmake
# ARM64 交叉编译工具链配置
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# 指定交叉编译器
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-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)
七、使用示例(直接复制执行)
7.1 基础操作
bash
# 进入脚本目录
cd scripts
# 添加执行权限
chmod +x build.sh
# 1. 清理构建目录
./build.sh clean
# 2. Debug原生编译
./build.sh debug
# 3. Release原生编译
./build.sh release
7.2 自定义传参(线程/安装目录/宏定义)
bash
# 16线程编译+安装到/opt/myapp+开启日志宏
./build.sh release -j16 -p /opt/myapp -D ENABLE_LOG=1
# 切换为Make生成器(不用Ninja)
./build.sh debug -g "Unix Makefiles"
7.3 跨架构/跨系统编译(ARM32/ARM64)
bash
# ARM32交叉编译
./build.sh cross arm-linux-gnueabihf
# ARM64交叉编译+自定义参数
./build.sh cross aarch64-linux-gnu -D ENABLE_CROSS=1
八、Shell + CMake 常用命令速查表
8.1 Shell 脚本基础命令
| 目的 | 命令 |
|---|---|
| 添加执行权限 | chmod +x build.sh |
| 自动获取CPU核心数 | JOBS=$(nproc) |
| 严格错误模式 | set -euo pipefail |
| 判断命令是否成功 | if [ $? -ne 0 ]; then exit 1; fi |
8.2 Shell + CMake 联动命令
| 目的 | 脚本封装命令 |
|---|---|
| CMake 配置 | cmake -S .. -B ../build -DCMAKE_BUILD_TYPE=Debug |
| 并行编译 | cmake --build ../build -j $(nproc) |
| 加载工具链 | -DCMAKE_TOOLCHAIN_FILE=../toolchain/xxx.cmake |
| 单元测试 | ctest --test-dir ../build -j $(nproc) |
| 自定义安装 | cmake --install ../build --prefix ../output |
| 清理构建 | rm -rf ../build |
九、避坑指南
9.1 目录路径坑(脚本在 scripts/ 核心坑)
- 错误:直接写
./路径,导致找不到源码/CMakeLists; - 解决:所有路径用相对上层 ,
SRC_DIR=".."、BUILD_DIR="../build"。
9.2 交叉编译坑
- 错误:交叉编译后运行测试,导致程序无法执行;
- 解决:脚本中判断工具链,交叉编译跳过测试。
9.3 CMake 缓存坑
- 错误:切换模式/工具链后不清理,导致缓存不兼容;
- 解决:切换构建模式必须先执行
clean。
9.4 权限坑
- 错误:脚本无法执行,提示
Permission denied; - 解决:首次使用必执行
chmod +x build.sh。
9.5 变量未定义坑
- 错误:脚本运行时报
unbound variable; - 解决:开启
set -u,提前初始化所有变量。
十、工程化实践
- 目录规范 :所有构建脚本放
scripts/,工具链放toolchain/,构建产物放build/; - 脚本统一化 :全公司/全团队使用同一套
build.sh,禁止手写 CMake 命令; - 严格模式必开 :
set -euo pipefail杜绝隐性错误; - 交叉编译隔离:原生/交叉编译产物分开,避免缓存冲突;
- 参数透传标准化 :所有自定义配置通过
-D透传,不修改脚本; - .gitignore 配置 :忽略
build/、output/、*.log等产物文件。
十一、总结
在 Linux C++ 开发体系中,CMake 是构建核心,Shell 脚本是自动化灵魂:
- 标准化 :通过
scripts/统一构建脚本,彻底解决团队流程不统一问题; - 全场景:一套脚本支持原生编译、ARM/ARM64 跨架构、跨系统编译;
- 高灵活:支持命令行传参、生成器切换、安装目录自定义、宏定义透传;
- 低失误:严格错误模式+彩色日志+路径校验,避免手动操作坑。