Rust 的构建脚本是什么?今天一次性搞懂它

Rust 的构建脚本是什么?今天一次性搞懂它

对于刚刚接触 Rust 的程序员来说,大概率会在一些成熟项目中看到一个特殊的文件 build.rs,它是干什么的呢?今天我们一次性搞懂它。

build.rs 是什么

简单来说,build.rs,也就是构建脚本,是 Rust 提供的编译期执行脚本 ,它本质上是一个独立的 Rust 程序,会在 Cargo 编译你的项目(即编译 src 目录下的代码)之前被编译并执行。

它的执行流程很简单,当 Cargo 检测到项目根目录如果存在 build.rs 文件,那么就会先编译 build.rs 生成可执行文件并执行,然后才会去编译项目代码。

为什么要 Rust 设计 build.rs

Rust 设计 build.rs 最根本的原因是为了打造统一的构建流程。Rust 是一门注重工程化的编程语言,需要一套统一的构建机制来处理复杂场景,比如:链接 C/C++ 库、检测系统环境、生成 codegen 文件等。在其他编程语言中,开发者需要可能需要编写 shell 脚本、使用 CMake 等工具,这会导致构建流程碎片化,也可能会存在着跨平台兼容性的问题。

而 build.rs 作为 Rust 原生支持的构建脚本,与 Cargo 无缝集成,能统一处理各类构建需求。所有的复杂构建场景都能在 build.rs 中统一实现,减少跨平台兼容性问题,极大的降低了工程化的维护成本。

Cargo 与 build.rs 的无缝集成

build.rs 之所以能成为 Rust 构建流程的核心组成部分,关键在于其与 Cargo 构建工具的无缝集成。Cargo 默认会把根目录的 build.rs 文件自动识别为构建脚本,在编译项目核心代码前执行该脚本。同时 Cargo 还支持自定义脚本名称,可在 Cargo.toml 中配置:

toml 复制代码
[package]
# 自定义构建脚本名称
build = "custom_build_name.rs"
# 禁用构建脚本
# build = false

build.rs 无法直接使用 dependencies 和 dev-dependencies 下的库,如需单独添加到 build-dependencies:

shell 复制代码
cargo add anyhow --build

Cargo 在执行 build.rs 前,会自动注入一系列环境变量,供 build.rs 获取项目、构建相关的信息,比如 OUT_DIR(编译输出目录)、TARGET(编译目标平台)等这些最常用的环境变量。

而同样的,build.rs 可通过 println! 输出以 cargo: 开头的指令,Cargo 会解析这些指令并调整后续的构建行为,常用指令包括:

  • cargo:rerun-if-changed=文件路径:仅当指定文件变化时重新编译 build.rs(增量构建)。
  • cargo:rerun-if-env-changed=环境变量名:与上一条指令类似,仅当指定环境变量变化时重新编译。
  • cargo:rustc-link-lib=库名:链接系统库或第三方库(常用于 FFI 场景)。
  • cargo:info=日志信息:输出构建日志(通过 cargo build -vv 查看)。

这里我们提供一个 build.rs 打印环境变量的示例供你参考:

rust 复制代码
use std::env;

fn main() {
    // 当 build.rs 发生变更时,重新编译 build.rs
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:info==== 打印所有环境变量 ===");
    for (key, value) in env::vars() {
        println!("cargo:info={}={}", key, value);
    }
}

执行以下命令查看效果:

shell 复制代码
# 清除编译缓存
cargo clean
# 编译并查看详细日志
cargo build -vv

一个完整的开发示例

Prost 是 Rust 生态中最主流的 Protobuf 实现,它提供了编译期代码生成和运行时序列化/反序列化能力,而其代码生成过程,正是通过 build.rs 在编译期完成的,下面我们将一步步实现这个示例。

环境准备

首先需要安装 Protobuf 官方编译器 protoc,prost 依赖它解析 .proto 文件。

shell 复制代码
# windows
winget install Google.Protobuf
# macOS(Homebrew)
brew install protobuf
# Linux(Ubuntu)
sudo apt install protobuf-compiler

新建项目并配置依赖

执行 cargo new rust-prost-demo 后进入项目目录修改 Cargo.toml,添加相关依赖:

toml 复制代码
[package]
name = "rust-prost-demo"
version = "0.1.0"
edition = "2024"

[dependencies]
prost = "0.14"
prost-types = "0.14"

[build-dependencies]
prost-build = "0.14.1"

定义 Protobuf 文件

创建 protos/items.proto 文件,定义一个简单的服装信息结构:

proto 复制代码
syntax = "proto3";

// 定义包名,生成的 Rust 代码会对应这个包名
package snazzy.items;

message Shirt {
    enum Size {
        SMALL = 0;
        MEDIUM = 1;
        LARGE = 2;
    }
    string color = 1;
    Size size = 2;
}

编写 build.rs 脚本

在项目根目录创建 build.rs 文件,核心逻辑是:调用 prost-build 编译 protos/items.proto 文件,生成对应的 Rust 代码。

rust 复制代码
use std::io::Result;

fn main() -> Result<()> {
    println!("cargo::rerun-if-changed=protos/items.proto");
    println!("cargo::rerun-if-changed=build.rs");

    prost_build::Config::new()
        // 可指定输出目录,这里不展示
        //.out_dir("path_to")
        .compile_protos(&["protos/items.proto"], &["protos/"])?;

    Ok(())
}

接下来,执行 cargo build 命令后,就可以去看编译后的 Rust 文件了,完整路径为:

plaintext 复制代码
target/[debug/release]/build/rust-prost-demo-xxxxxxx/out/snazzy.items.rs

在项目中使用生成的代码

由于默认生成的代码不在 src 目录当中,所以我们需要使用 include!() 宏引入生成的代码,生成的代码文件名与 proto 包名一致,即 snazzy.items.rs。

rust 复制代码
pub mod snazzy {
    pub mod items {
        include!(concat!(env!("OUT_DIR"), "/snazzy.items.rs"));
    }
}

use prost::Message;
use snazzy::items;

fn main() {
    let mut shirt = items::Shirt::default();
    shirt.color = String::from("red");
    shirt.set_size(items::shirt::Size::Large);
    println!("{:?}", shirt);

    // 序列化
    let mut buf = Vec::new();
    shirt.encode(&mut buf).expect("序列化失败");
    println!("序列化后的字节数组:{:?}", buf);

    // 反序列化
    let decoded_shirt = items::Shirt::decode(&buf[..]).expect("反序列化失败");
    println!("反序列化后:{:?}", decoded_shirt);
}

运行测试

执行 cargo run 后就会看到以下输出:

plaintext 复制代码
Shirt { color: "red", size: Large }
序列化后的字节数组:[10, 3, 114, 101, 100, 16, 2]
反序列化后:Shirt { color: "red", size: Large }

总结

看到这里,相信你已经彻底搞懂 build.rs 是什么了,它是 Rust 为编译期自定义需求而设计的功能特性,打造统一构建流程、跨平台适配与生态集成,完美契合了 Rust 高效工程化的设计理念。

相关推荐
niyongsheng9 小时前
3D 装箱算法工程化:数据结构、放置启发式与支撑规则
数据结构·算法·rust
Hello.Reader10 小时前
tsrs 技术解析用 Rust 移植 TypeScript 7,类型检查还能怎样提速?
开发语言·rust·typescript
王仔小奶12 小时前
Rust 正在接管金融圈?深度拆解一份高薪 FinTech 岗位 JD
开发语言·金融·rust
你为她披上外套时我正站在窗外13 小时前
subtitler v2.10 发布:3 个社区 PR、一个一天修掉的 issue,和关于 uuencode 的真相
rust
Thneonl13 小时前
熔断不是开关是状态机:Claude Code 路由内核的 3 条红线
人工智能·rust
Thneonl13 小时前
一个 Claude Code 本地代理,凭啥配了 3 个观测出口?
人工智能·rust
西风测评16 小时前
终端粘贴原理:bracketed paste、4095 字节行限制与 UTF-8 分片
rust
geovindu19 小时前
rust: Read-Write Lock Pattern
开发语言·后端·设计模式·rust·读写锁模式·同步型模式
怪我冷i19 小时前
Rust智能指针Rc<T> 使用总结
开发语言·后端·rust
Kapaseker19 小时前
删掉 1.1 万行 C 代码,Rust 正式接手 Android Binder
android·rust