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 无法直接使用 dependenciesdev-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 高效工程化的设计理念。

相关推荐
向上的车轮19 小时前
从零实现一个高性能 HTTP 服务器:深入理解 Tokio 异步运行时与 Pin 机制
rust·系统编程·pin·异步编程·tokio·http服务器
AI自动化工坊1 天前
OpenFang实战指南:用Rust构建高并发AI Agent操作系统
开发语言·人工智能·ai·rust·agent·ai agent
gsls2008081 天前
tauri开发环境搭建
rust·npm·tauri
Binarydog_Lee1 天前
Tauri2 开发入门:应用是如何启动的
前端·rust·tauri
changzehai1 天前
RustRover + J-Link 一键调试 STM32 教程
stm32·单片机·嵌入式硬件·rust·rustrover
咸甜适中1 天前
rust序列化和反序列化(json、yaml、toml)详解
开发语言·rust·json
IT 行者1 天前
CentOS 下源码编译安装完整版 Redis 8.0 指南(附 Rust 工具链详解)
redis·rust·centos
暴躁小师兄数据学院1 天前
【WEB3.0零基础转换笔记】Rust编程篇-第4讲:控制流
开发语言·笔记·rust·web3·区块链·智能合约
武汉唯众智创1 天前
Rust系统安全实训入门:唯众网络安全实训室搭建与边缘节点并发优化实操指南
人工智能·rust·网络安全实训室建设·rust系统安全实训