告别 Makefile 噩梦:redo 构建系统入门到精通指南

告别 Makefile 噩梦:redo 构建系统入门到精通指南

【免费下载链接】redo Smaller, easier, more powerful, and more reliable than make. An implementation of djb's redo. 【免费下载链接】redo 项目地址: https://gitcode.com/gh_mirrors/re/redo

你是否还在为 Makefile 的复杂语法而头疼?是否曾因依赖管理混乱导致构建错误?是否渴望一个更简单、更可靠且并行构建效率更高的工具?本文将带你全面掌握 redo——这个比 make 更小、更易用、更强大的递归构建系统,让你的项目构建流程化繁为简。

读完本文后,你将能够:

  • 理解 redo 的核心原理与优势
  • 快速安装并配置 redo 环境
  • 编写高效的 .do 脚本实现自动化构建
  • 掌握依赖管理、并行构建等高级特性
  • 解决实际开发中的常见构建难题

1. redo 简介:构建系统的新范式

1.1 什么是 redo?

redo 是一个基于 Daniel J. Bernstein 设计理念的递归构建系统(Recursive Build System),它旨在替代传统的 make 工具,提供更简洁的语法和更可靠的依赖管理。与 make 相比,redo 具有以下核心优势:

特性redomake
语法复杂度极低(纯 shell 脚本)高(特殊语法规则)
依赖管理精确到文件内容变化基于文件修改时间
并行构建原生支持且高效需要额外配置
递归构建安全且推荐被认为有害(Recursive Make Considered Harmful)
学习曲线平缓(熟悉 shell 即可)陡峭

1.2 redo 的工作原理

redo 的核心思想非常简单:每个目标文件都由对应的 .do 脚本构建。当你运行 redo target 时,redo 会自动查找并执行构建该目标所需的 .do 脚本。与 make 不同,redo 不使用特殊的语法规则,所有构建逻辑都通过标准 shell 脚本实现,这使得脚本更易于编写和维护。

mermaid

redo 使用 SQLite 数据库跟踪文件依赖关系和内容变化,确保只有真正需要重建的文件才会被处理,从而提高构建效率。

2. 环境准备:安装与配置

2.1 系统要求

  • Python 2.7(推荐安装 python-setproctitle 包以优化进程显示)
  • SQLite3 模块(通常已包含在 Python 标准库中)
  • GCC 或其他 C 编译器(用于构建测试用例)

2.2 安装步骤

2.2.1 从源码安装
# 克隆仓库
git clone https://gitcode.com/gh_mirrors/re/redo
cd redo

# 构建并测试
./do -j10 test

# 安装到系统
DESTDIR= PREFIX=/usr/local sudo -E ./do -j10 install
2.2.2 包管理器安装(推荐)

macOS(Homebrew)

brew install redo

Linux: 大多数发行版的软件仓库中已包含 redo,但可能不是最新版本。如需最新特性,建议从源码安装。

2.3 验证安装

# 检查版本
redo --version

# 查看帮助信息
redo --help

如果安装成功,你将看到类似以下的输出:

redo 0.42 (python)
Copyright (C) 2010-2020 Avery Pennarun <apenwarr@gmail.com>
...

3. 快速入门:第一个 redo 项目

3.1 经典 "Hello World" 示例

让我们通过一个简单的 C 程序来演示 redo 的基本用法。首先创建项目目录并编写源代码:

mkdir -p redo-demo && cd redo-demo

创建 hello.c 文件:

#include <stdio.h>

int main() {
    printf("hello, world!\n");
    return 0;
}

3.2 编写 .do 脚本

在 redo 中,每个目标都由对应的 .do 文件构建。创建 hello.do 文件:

#!/bin/sh
redo-ifchange hello.c
gcc -o $3 hello.c

让我们解析这个简单的脚本:

  • redo-ifchange hello.c:声明 hello.c 是当前目标的依赖项。如果 hello.c 发生变化,将重新构建当前目标。
  • gcc -o $3 hello.c:使用 gcc 编译 hello.c,输出文件名为 $3(这是 redo 提供的临时文件路径)。

3.3 运行构建

# 构建目标
redo hello

# 运行生成的程序
./hello

输出结果:

hello, world!

3.4 理解文件命名规则

redo 会按照特定规则查找目标对应的 .do 文件。对于目标 hello,redo 会依次查找:

  1. hello.do
  2. default.do

这种灵活的命名规则允许你为不同类型的文件定义通用构建规则。例如,创建 default.c.do 可以为所有 .c 文件提供默认构建规则。

4. 核心命令详解

4.1 redo:无条件构建目标

redo [options] [targets...]

功能:无条件构建指定目标,即使该目标已经存在且依赖项未发生变化。

常用选项

  • -j N:指定并行构建的最大作业数(推荐设置为 CPU 核心数)
  • -d:显示依赖检查详情,用于调试
  • -v: verbose 模式,显示 shell 脚本执行过程
  • -x:xtrace 模式,显示脚本执行的详细命令(变量会被展开)

示例

# 并行构建 all 目标,最多使用 4 个作业
redo -j4 all

# 调试模式构建 hello 目标
redo -d hello

4.2 redo-ifchange:条件构建目标

redo-ifchange [targets...]

功能:仅当目标不存在或依赖项发生变化时才构建目标。这是 redo 中最常用的命令,用于声明目标之间的依赖关系。

使用技巧

  1. 可以在构建命令之后调用 redo-ifchange 声明依赖,这对于自动生成依赖非常有用:
# 编译 C 文件并自动检测头文件依赖
gcc -c $2.c -o $3 -MMD -MF $2.deps
read DEPS < $2.deps
redo-ifchange ${DEPS#*:}  # 声明头文件依赖
  1. 尽量在一个 redo-ifchange 调用中声明多个依赖,以提高并行构建效率:
# 推荐:一次声明多个依赖
redo-ifchange a.o b.o c.o

# 不推荐:多次调用(会降低并行效率)
redo-ifchange a.o
redo-ifchange b.o
redo-ifchange c.o

4.3 其他常用命令

命令功能
redo-ifcreate当指定文件被创建时重建目标
redo-always强制目标始终被重建
redo-stamp标记目标内容未变化,避免级联重建
redo-ood列出所有过时(Out Of Date)的目标
redo-targets列出所有已知目标
redo-whichdo显示指定目标使用的 .do 文件

5. 高级特性与最佳实践

5.1 并行构建优化

redo 的并行构建能力比 make 更高效,主要得益于其精细的依赖管理和锁机制。要充分利用多核 CPU,建议:

  1. 使用 -j 参数指定合适的并行作业数(通常为 CPU 核心数 + 1)
  2. 在 .do 脚本中一次性声明所有依赖,避免串行化构建步骤
  3. 对独立目标使用 redo-shuffle 检测隐藏的依赖问题
# 随机顺序构建目标,检测依赖问题
redo --shuffle -j4 all

5.2 依赖管理高级技巧

5.2.1 自动依赖生成(C/C++ 项目)

结合编译器的自动依赖生成功能,可以轻松管理头文件依赖:

# hello.o.do
redo-ifchange $2.c
gcc -c $2.c -o $3 -MMD -MF $2.deps  # 生成依赖文件
# 解析依赖文件并声明
if [ -f $2.deps ]; then
    read DEPS < $2.deps
    redo-ifchange ${DEPS#*:}
fi
5.2.2 处理动态生成的文件

对于需要动态生成的文件,可以使用 redo-ifcreate 声明潜在的依赖:

# docs.html.do
redo-ifchange docs.md
markdown docs.md > $3
# 如果有图片文件被创建,需要重建文档
redo-ifcreate images/*.png

5.3 项目组织结构

推荐的 redo 项目结构如下:

project/
├── all.do           # 顶层构建目标
├── clean.do         # 清理目标
├── install.do       # 安装目标
├── src/             # 源代码目录
│   ├── hello.c
│   └── hello.o.do   # 编译规则
├── docs/            # 文档目录
│   ├── index.md
│   └── index.html.do # 文档生成规则
└── test/            # 测试目录
    ├── test.do      # 测试规则
    └── hello_test.c

这种结构清晰分离了不同类型的文件和构建规则,便于维护和扩展。

5.4 与其他构建系统集成

redo 可以与其他构建系统无缝集成,例如:

  1. 与 autoconf/automake 集成:使用 redo 作为最终构建工具,保留 autoconf 进行系统检测
  2. 与 CMake 集成:让 CMake 生成 .do 文件,再用 redo 执行构建
  3. 与 npm/yarn 集成:在 .do 脚本中调用 npm 命令管理 JavaScript 依赖
# jsapp.do - 集成 npm 的示例
redo-ifchange package.json
npm install
browserify src/main.js -o $3

6. 实战案例:构建多文件 C 项目

让我们通过一个更复杂的示例来巩固所学知识。我们将构建一个包含多个源文件的 C 项目,演示如何组织 .do 脚本和管理依赖关系。

6.1 项目结构

mathapp/
├── all.do
├── clean.do
├── main.c
├── main.o.do
├── math/
│   ├── add.c
│   ├── add.h
│   ├── add.o.do
│   ├── multiply.c
│   ├── multiply.h
│   └── multiply.o.do
└── mathapp.do

6.2 实现代码

main.c

#include <stdio.h>
#include "math/add.h"
#include "math/multiply.h"

int main() {
    int a = 2, b = 3;
    printf("%d + %d = %d\n", a, b, add(a, b));
    printf("%d × %d = %d\n", a, b, multiply(a, b));
    return 0;
}

math/add.c

#include "add.h"

int add(int a, int b) {
    return a + b;
}

math/multiply.c

#include "multiply.h"

int multiply(int a, int b) {
    return a * b;
}

6.3 编写 .do 脚本

all.do - 顶层目标:

redo-ifchange mathapp

mathapp.do - 链接可执行文件:

# 声明所有目标文件依赖
redo-ifchange main.o math/add.o math/multiply.o
# 链接生成可执行文件
gcc -o $3 main.o math/add.o math/multiply.o

main.o.do - 编译主程序:

redo-ifchange main.c
gcc -c main.c -o $3 -Imath -MMD -MF main.deps
# 自动处理头文件依赖
[ -f main.deps ] && read DEPS < main.deps && redo-ifchange ${DEPS#*:}

math/add.o.do - 编译加法模块:

redo-ifchange add.c add.h
gcc -c add.c -o $3 -MMD -MF add.deps
[ -f add.deps ] && read DEPS < add.deps && redo-ifchange ${DEPS#*:}

math/multiply.o.do - 编译乘法模块:

redo-ifchange multiply.c multiply.h
gcc -c multiply.c -o $3 -MMD -MF multiply.deps
[ -f multiply.deps ] && read DEPS < multiply.deps && redo-ifchange ${DEPS#*:}

clean.do - 清理目标:

rm -f mathapp *.o math/*.o *.deps math/*.deps

6.4 执行构建

# 构建项目
redo -j2 all

# 运行程序
./mathapp

# 清理构建产物
redo clean

输出结果:

2 + 3 = 5
2 × 3 = 6

7. 常见问题与解决方案

7.1 为什么 redo 总是重新构建我的目标?

这是初学者最常见的问题之一。原因在于 redo 命令总是重新构建指定的目标,无论其是否过时。要实现条件构建,应该使用 redo-ifchange

解决方案

  • 顶层目标使用 redo-ifchange
  • 只有在确实需要无条件重建时才使用 redo
# 错误:总是重建
redo hello

# 正确:条件重建
redo-ifchange hello

7.2 如何处理跨平台构建?

使用 redo 的配置文件和环境变量可以轻松实现跨平台构建:

# config.do - 生成配置文件
os=$(uname)
if [ "$os" = "Darwin" ]; then
    echo "CC=clang" > $3
else
    echo "CC=gcc" > $3
fi

# 在其他 .do 脚本中包含配置
. ./config
$CC -c $2.c -o $3

7.3 如何迁移现有的 Makefile 项目到 redo?

迁移步骤:

  1. 识别 Makefile 中的目标和依赖关系
  2. 为每个目标创建对应的 .do 脚本
  3. 将 Makefile 中的规则转换为 shell 命令
  4. 使用 redo-ifchange 声明依赖关系
  5. 测试并优化新的构建流程

迁移工具:可以使用 make2do(第三方工具)自动转换简单的 Makefile 为 .do 脚本。

8. 总结与展望

8.1 redo 的优势回顾

  • 简单易学:基于 shell 脚本,无需学习复杂语法
  • 可靠高效:精确的依赖管理和并行构建
  • 灵活强大:适用于各种规模和类型的项目
  • 易于扩展:可与现有工具链无缝集成

8.2 进阶学习资源

  1. 官方文档:https://redo.rtfd.io
  2. 源码仓库:https://gitcode.com/gh_mirrors/re/redo
  3. 邮件列表:redo-list@googlegroups.com

8.3 后续学习建议

  1. 深入研究 redo 的高级命令(如 redo-stampredo-always
  2. 探索 redoconf 工具,实现自动配置功能
  3. 为常用编程语言编写通用 .do 脚本库
  4. 参与 redo 社区,贡献代码或文档

通过掌握 redo,你已经迈出了构建系统现代化的重要一步。无论是小型项目还是大型复杂系统,redo 都能帮助你实现更简单、更可靠的构建流程。现在就开始将你的项目迁移到 redo,体验构建系统的新范式吧!

如果你觉得本文对你有帮助,请点赞、收藏并关注,后续将带来更多关于 redo 高级技巧和最佳实践的内容!

【免费下载链接】redo Smaller, easier, more powerful, and more reliable than make. An implementation of djb's redo. 【免费下载链接】redo 项目地址: https://gitcode.com/gh_mirrors/re/redo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值