Linux 之 Shell脚本:CMake自动化构建

Linux 之 Shell脚本:CMake自动化构建

摘要:在 Linux C++ 开发体系中,CMake 负责解决构建规则与跨平台编译 问题,而 Shell 脚本则负责解决流程自动化、多场景构建、参数标准化、工具链切换 问题。手写 CMake 命令在多架构、多工具链、跨系统编译、团队协作中极易出现命令遗漏、参数错误、流程不统一等问题。本文以CMake 工程化构建为核心,讲解 Shell 脚本基础,结合工具链选择、跨架构/跨系统编译、命令行传参、标准化目录(scripts/)等需求,打造一份Linux C++ Shell 自动化构建笔记。


一、为什么 Linux C++ 开发必须学 Shell 脚本?

在 Linux C++ + CMake 开发流程中,纯手动操作存在五大痛点

  • 重复操作效率极低 :每次编译都要输入 cmake -S/-Bcmake --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,提前初始化所有变量。

十、工程化实践

  1. 目录规范 :所有构建脚本放 scripts/,工具链放 toolchain/,构建产物放 build/
  2. 脚本统一化 :全公司/全团队使用同一套 build.sh,禁止手写 CMake 命令;
  3. 严格模式必开set -euo pipefail 杜绝隐性错误;
  4. 交叉编译隔离:原生/交叉编译产物分开,避免缓存冲突;
  5. 参数透传标准化 :所有自定义配置通过 -D 透传,不修改脚本;
  6. .gitignore 配置 :忽略 build/output/*.log 等产物文件。

十一、总结

在 Linux C++ 开发体系中,CMake 是构建核心,Shell 脚本是自动化灵魂

  1. 标准化 :通过 scripts/ 统一构建脚本,彻底解决团队流程不统一问题;
  2. 全场景:一套脚本支持原生编译、ARM/ARM64 跨架构、跨系统编译;
  3. 高灵活:支持命令行传参、生成器切换、安装目录自定义、宏定义透传;
  4. 低失误:严格错误模式+彩色日志+路径校验,避免手动操作坑。
相关推荐
寻寻觅觅☆7 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
时代的凡人7 小时前
0208晨间笔记
笔记
fpcc8 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
小白同学_C8 小时前
Lab4-Lab: traps && MIT6.1810操作系统工程【持续更新】 _
linux·c/c++·操作系统os
今天只学一颗糖8 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
2601_949146538 小时前
Shell语音通知接口使用指南:运维自动化中的语音告警集成方案
运维·自动化
0思必得08 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
testpassportcn9 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
ceclar1239 小时前
C++使用format
开发语言·c++·算法
Gofarlic_OMS9 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化