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 文件共同作用,实现灵活而强大的构建过程。

  • 25
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值