Rust 中构建和编译项目的工具——Makefile 与 build.rs

起因

最近在写一个适用于 Rust 操作系统的组件,过程中犯了一个非常粗心的错误,导致这两天一直在了解 Makefile 文件与 build.rs 文件是如何共同作用从而构建和编译 Rust 项目的,由于在此之前确实不太了解这块内容,遂将最近所学记录一下,以便之后查阅。
先来讲讲这个因粗心导致的问题,我的目录结构如下:

.
|_os	//某操作系统内核代码
	|_src
	|_build.rs
	|_Cargo.toml
	|_Makefile
|_tool	//一个脚本工具
	|_src
		|_main.rs
	|_Cargo.toml
|_Makefile
|_...

tool 是我自己写的一个 crate,其他都是某开源操作系统的源码,在 os 目录下 make run 即可编译运行,我需要在 make run 后先运行 tool 向内核中注入一些代码,再编译内核。而 os 需要在 no_std 环境及 RISC-V 架构下运行,tool 则是在正常服务器环境下运行的,于是我向 build.rsMakefile 中添加了这些内容:

// os/build.rs
let status = Command::new("cargo")
        .args(&[
            "run",
            "--manifest-path",
            "../tool/Cargo.toml",
            "--target",
            "x86_64-unknown-linux-gnu",
        ])
        .status()
        .expect("Failed to run tool");

    if !status.success() {
        panic!("trace_event_tool failed");
    }
// os/makefile
build: ... tool ...

tool:
	@echo "Building and running tool"
	cd ../tool && cargo run --target x86_64-unknown-linux-gnu

运行后一直报各种各样的错,包括但不限于:无法编译 tool 的依赖库,找不到 tool 的链接文件,不支持 tool 中某个依赖库引入 std …

我觉得很奇怪,按道理说 tool 应该是运行在服务器环境上的,不受 os 的环境条件限制,而且它只是一个在构建过程中读取配置文件并修改代码的工具,并不涉及裸机编程或需要特定内存布局的场景,不应该需要自定义链接文件,且在 tool 目录下单独运行是能成功的,所以一定是 build.rsMakefile 中的代码出了问题,我猜想是哪里导致 tool 运行在 riscv64gc-unknown-none-elf 架构下,然后就在调试,改了很多地方都没有成功,真的是很苦闷,后来有一次尝试删除了 build.rs 中增加的内容,竟然可以成功运行,但是只运行了 os 本身,没有先运行 tool,于是我就去检查 makefile 文件,然后发现…我修改的竟然一直是根目录下的 makefile 而不是 os 目录下的 makefile !!!

发现这一点之后突然感觉这些调试时间都错付了,这个错误简直太粗心。于是我将原先放错位置的 Makefile 代码放到 os/Makefile 中,成功运行。于是发现根本不需要修改 build.rs,同样都是构建和编译项目的工具,什么时候应该修改哪个?针对这个问题,我进行了一些学习

Makefile

Makefile 是一种构建自动化工具,广泛应用于各种编程语言和项目,它可以用于定义如何编译和链接程序的规则,它的主要功能有:

  1. 指定编译规则:定义如何从源文件生成目标文件和可执行文件;
  2. 管理依赖关系:确保在源文件发生变化时重新编译相关文件;
  3. 自动化常见任务:如清理临时文件、运行测试等。

Makefile 的基本语法就不在这里阐述了,网上有很多教程,而且大家也很少会自己写,网上 copy 一个或者用 gpt 生成一个都是很好的提高生产力的手段。它的关键就是适用于定义复杂的构建过程,特别是多个文件和目标之间存在依赖关系的情况,通过变量和条件语句从而实现灵活的配置,然后用户使用 make 命令执行构建过程。

build.rs

build.rs 是 Rust 项目中的一个特殊构建脚本,用于在编译过程中执行一些自定义的构建逻辑。主要用途有:

  1. 生成代码:如自动生成绑定代码、构建过程中的代码注入等;
  2. 配置依赖:根据环境变量或配置文件生成依赖配置;
  3. 执行前置任务:如编译前生成或检查资源文件。

build.rs 中,可以使用 Rust 标准库中的各种功能来实现所需的构建逻辑,比较常用的是使用 println!("cargo:rerun-if-changed=path/to/file") 可以通知 Cargo 在指定文件发生变化时重新运行构建脚本。

Rust 语言圣经的 9.3.10 章节(https://course.rs/cargo/reference/build-script/intro.html)讲述了更多关于 build.rs 的内容。

两者关系

  • Makefile 通常用于管理整个项目的构建过程,特别是跨平台、跨语言或者需要调用多种编译工具的项目,能够统一管理各种构建步骤和依赖;
  • build.rs 会在 cargo build 过程中自动被调用,主要用于单个 Rust crate 的构建,尤其是那些需要在编译过程中动态生成或配置的部分;
  • Makefile 中可以调用 cargo build,从而间接运行 build.rs

结论

所以假设有一个 Rust 项目,其中需要在构建前执行一些额外的操作(例如通过 tool 修改代码),就可以通过 Makefile 先调用 tool,无论 tool 的架构是否与原项目一致,然后再调用 cargo build,触发 os crate 的编译,包括 build.rs 中的自定义构建逻辑Makefile 文件与 build.rs 文件共同作用,实现灵活而强大的构建过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值