CI配置时使用Makefile有哪些好处?

一、什么是 Makefile?

Makefile 本质上是一个包含了一系列规则的文件。这些规则定义了如何编译和链接程序,每一个规则都由以下三个部分组成:

  • 目标(Target): 想要生成的文件或想要执行的操作的名称,比如 installtestclean 等。
  • 依赖(Prerequisites): 为了生成目标,所需要依赖的文件或其他目标。
  • 命令(Commands): 为了通过依赖来生成目标所需要执行的 shell 命令。

一个 Makefile 的基本结构如下:

1
2
3
target: prerequisite1 prerequisite2
command1
command2

关键特性:

  1. 自动化: make 工具会自动读取 Makefile 并执行指定的命令,从而自动化编译、测试、部署等一系列任务。
  2. 抽象化: 可以将复杂的、多步骤的 shell 命令封装在一个简单的 make 命令背后。例如,make test 可能包含了安装依赖、启动数据库、运行测试套件、生成报告等多个步骤。

二、为什么在 CI 配置中使用 Makefile?

Makefile 引入 CI 配置文件可以带来以下核心优势:

1. 本地与云端环境统一 (Dry-run Locally)

这是使用 Makefile 最显著的好处之一。开发者可以在自己的开发机上执行 make testmake build,并期望得到与 CI服务器上完全一致的执行结果。这使得开发者在提交代码前就能快速地对 CI 流程进行测试和调试,而无需等待漫长的 CI pipeline 运行,从而极大地缩短了反馈周期。

  • 没有 Makefile 的情况: 开发者需要阅读冗长的 .gitlab-ci.yml 或其他 CI 配置文件,并手动在本地终端执行其中复杂的 script 片段。这个过程不仅繁琐,而且容易出错。

  • 使用 Makefile 的情况: CI 配置文件变得异常简洁。

    • .github-ci.yml 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      build:
      runs-on: ${{ matrix.os }}
      strategy:
      matrix:
      os:
      - ubuntu-latest
      - macos-latest
      - windows-latest
      steps:
      - uses: actions/checkout@v4

      - name: Setup Rust toolchain
      uses: ./.github/actions/setup-builder

      - name: Cache Rust artifacts
      uses: Swatinem/rust-cache@v2

      - name: Build
      run: make build

Makefile中的build:

1
2
build:
cargo build --all-targets --all-features --workspace

开发者在本地和 CI 服务器上都只需要执行相同的命令:make build

2. 简化与抽象化 CI 配置

现代 CI/CD 流程可能非常复杂,包含代码检查、单元测试、集成测试、构建 Docker 镜像、推送到制品库、部署到不同环境等众多步骤。如果将所有这些逻辑都直接写在 CI 平台的 YAML 配置文件中,会导致文件变得异常冗长、难以阅读和维护。

Makefile 通过将这些复杂的脚本逻辑封装在简单的目标(target)中,极大地简化了 CI 配置文件。CI 的配置文件只负责调用任务,而 Makefile 负责定义任务的具体执行方式。

3. 提高可重用性和可移植性

Makefile 是一个与具体 CI/CD 平台无关的通用工具。

  • 跨平台重用: 如果团队决定从 Jenkins 迁移到 GitLab CI,或者需要同时支持多个 CI 平台,无需重写所有的构建和测试脚本。只需要在新的 CI 平台的配置文件中调用相同的 make 命令即可。核心的业务逻辑(Makefile 中的内容)可以被无缝迁移和重用。
  • 项目间重用: 对于遵循相似构建流程的多个项目,它们可以共享或复用 Makefile 的大部分内容,只需针对特定项目进行微调。

4. 利用 make 的增量构建能力

make 的设计初衷就是为了处理依赖关系和增量构建。虽然在无状态的 CI 环境中(每次都从零开始),这个优势可能不那么明显。但在某些场景下,例如配置了缓存(caching)的 CI pipeline,make 可以智能地判断哪些部分无需重新构建,从而显著加快 CI 的运行速度。这个对于Rust项目没什么优势😄,cargo本身自带了增量构建的能力。