Build Scripts — build.rs in Depth 🟢
构建脚本:深入理解 build.rs 🟢
What you’ll learn:
本章将学到什么:
- How
build.rsfits into the Cargo build pipeline and when it runsbuild.rs在 Cargo 构建流程中的位置,以及它到底什么时候运行- Five production patterns: compile-time constants, C/C++ compilation, protobuf codegen,
pkg-configlinking, and feature detection
五种生产级用法:编译期常量、C/C++ 编译、protobuf 代码生成、pkg-config链接和 feature 检测- Anti-patterns that slow builds or break cross-compilation
哪些反模式会拖慢构建,或者把交叉编译搞坏- How to balance traceability with reproducible builds
如何在可追踪性与可复现构建之间取得平衡Cross-references: Cross-Compilation uses build scripts for target-aware builds ·
no_std& Features extendscfgflags set here · CI/CD Pipeline orchestrates build scripts in automation
交叉阅读: 交叉编译 里会继续用build.rs做目标感知构建;no_std与 feature 会用到这里设置的cfg标志;CI/CD 流水线 负责把这些构建脚本放进自动化流程。
Every Cargo package can include a file named build.rs at the crate root. Cargo compiles and executes this file before compiling your crate. The build script communicates back to Cargo through println! instructions on stdout.
每个 Cargo 包都可以在 crate 根目录放一个名为 build.rs 的文件。Cargo 会在编译 crate 本体之前,先把它编译并执行一遍。构建脚本和 Cargo 的通信方式也很朴素,就是往标准输出里打印特定格式的 println! 指令。
What build.rs Is and When It Runs
build.rs 是什么,它何时运行
┌─────────────────────────────────────────────────────────┐
│ Cargo Build Pipeline │
│ │
│ 1. Resolve dependencies │
│ 2. Download crates │
│ 3. Compile build.rs ← ordinary Rust, runs on HOST │
│ 4. Execute build.rs ← stdout → Cargo instructions │
│ 5. Compile the crate (using instructions from step 4) │
│ 6. Link │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Cargo 构建流水线 │
│ │
│ 1. 解析依赖 │
│ 2. 下载 crate │
│ 3. 编译 build.rs ← 普通 Rust 程序,运行在 HOST 上 │
│ 4. 执行 build.rs ← stdout 回传 Cargo 指令 │
│ 5. 编译 crate 本体 ← 使用第 4 步给出的配置 │
│ 6. 链接 │
└─────────────────────────────────────────────────────────┘
Key facts:
关键事实有这几条:
build.rsruns on the host machine, not the target. During cross-compilation, the build script runs on your development machine even when the final binary targets a different architecture.build.rs运行在 host 机器上,不是 target。哪怕最后产物是别的架构,构建脚本也还是在当前开发机上执行。- The build script’s scope is limited to its own package. It cannot directly control how other crates compile, unless the package declares
linksand emits metadata for dependents.
构建脚本的作用域只限于当前 package。它本身改不了其他 crate 的编译方式,除非 package 用了links,再通过 metadata 往依赖方传数据。 - It runs every time Cargo thinks something relevant changed, unless you use
cargo::rerun-if-changedorcargo::rerun-if-env-changedto缩小重跑范围。
如果不主动用cargo::rerun-if-changed或cargo::rerun-if-env-changed缩小范围,Cargo 很容易在很多构建里重复执行它。 - It can emit cfg flags, environment variables, linker arguments, and generated file paths for the main crate to consume.
它可以输出cfg标志、环境变量、链接参数,以及生成文件路径,让主 crate 在后续编译中使用。
Note (Rust 1.71+): Since Rust 1.71, Cargo fingerprints the compiled
build.rsbinary. If the binary itself stays identical, Cargo may skip rerunning it even when timestamps changed. Even so,cargo::rerun-if-changed=build.rsstill matters a lot, because without any rerun rule, Cargo treats changes to any file in the package as a reason to rerun the script.
补充说明(Rust 1.71+):从 Rust 1.71 起,Cargo 会给编译出的build.rs二进制做指纹检查。如果二进制内容没变,它可能会跳过重跑。但cargo::rerun-if-changed=build.rs依然非常重要,因为只要没有显式 rerun 规则,Cargo 就会把 package 里任何文件的变化都当成重跑理由。
The minimal Cargo.toml entry:
最小的 Cargo.toml 写法是这样:
[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"
build = "build.rs" # default — Cargo looks for build.rs automatically
# build = "src/build.rs" # or put it elsewhere
The Cargo Instruction Protocol
Cargo 指令协议
Your build script communicates with Cargo by printing instructions to stdout. Since Rust 1.77, the preferred prefix is cargo:: instead of the older cargo: form.
构建脚本和 Cargo 的通信方式,就是往 stdout 打指令。从 Rust 1.77 开始,推荐使用 cargo:: 前缀,而不是老的 cargo:。
| Instruction 指令 | Purpose 作用 |
|---|---|
cargo::rerun-if-changed=PATH | Only re-run build.rs when PATH changes 只有当指定路径变化时才重跑 build.rs。 |
cargo::rerun-if-env-changed=VAR | Only re-run when environment variable VAR changes 只有环境变量变化时才重跑。 |
cargo::rustc-link-lib=NAME | Link against native library NAME 链接本地库。 |
cargo::rustc-link-search=PATH | Add PATH to library search path 把路径加入库搜索目录。 |
cargo::rustc-cfg=KEY | Set a #[cfg(KEY)] flag设置 #[cfg(KEY)] 标志。 |
cargo::rustc-cfg=KEY="VALUE" | Set a #[cfg(KEY = "VALUE")] flag设置带值的 cfg 标志。 |
cargo::rustc-env=KEY=VALUE | Set an env var visible via env!()设置后续可被 env!() 读取的环境变量。 |
cargo::rustc-cdylib-link-arg=FLAG | Pass linker arg to cdylib targets 给 cdylib 目标传链接参数。 |
cargo::warning=MESSAGE | Display a warning during compilation 在编译时打印警告。 |
cargo::metadata=KEY=VALUE | Store metadata for dependent crates 给依赖当前包的 crate 传递元数据。 |
// build.rs — minimal example
fn main() {
// Only re-run if build.rs itself changes
println!("cargo::rerun-if-changed=build.rs");
// Set a compile-time environment variable
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs().to_string())
.unwrap_or_else(|_| "0".into());
println!("cargo::rustc-env=BUILD_TIMESTAMP={timestamp}");
}
Pattern 1: Compile-Time Constants
模式 1:编译期常量
The most common use case is embedding build metadata into the binary, such as git hash, build profile, target triple, or build timestamp.
最常见的用法就是把构建元数据嵌进二进制里,例如 git hash、构建配置、target triple 或构建时间。
// build.rs
use std::process::Command;
fn main() {
println!("cargo::rerun-if-changed=.git/HEAD");
println!("cargo::rerun-if-changed=.git/refs");
// Git commit hash
let output = Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.expect("git not found");
let git_hash = String::from_utf8_lossy(&output.stdout).trim().to_string();
println!("cargo::rustc-env=GIT_HASH={git_hash}");
// Build profile (debug or release)
let profile = std::env::var("PROFILE").unwrap_or_else(|_| "unknown".into());
println!("cargo::rustc-env=BUILD_PROFILE={profile}");
// Target triple
let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown".into());
println!("cargo::rustc-env=BUILD_TARGET={target}");
}
#![allow(unused)]
fn main() {
// src/main.rs — consuming the build-time values
fn print_version() {
println!(
"{} {} (git:{} target:{} profile:{})",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
env!("GIT_HASH"),
env!("BUILD_TARGET"),
env!("BUILD_PROFILE"),
);
}
}
Built-in Cargo variables that do not require
build.rs:CARGO_PKG_NAME、CARGO_PKG_VERSION、CARGO_PKG_AUTHORS、CARGO_PKG_DESCRIPTION、CARGO_MANIFEST_DIR。
Cargo 自带的环境变量 其实已经有不少,像CARGO_PKG_NAME、CARGO_PKG_VERSION、CARGO_PKG_AUTHORS、CARGO_PKG_DESCRIPTION、CARGO_MANIFEST_DIR,这些都不需要build.rs就能直接用。
Pattern 2: Compiling C/C++ Code with the cc Crate
模式 2:用 cc crate 编译 C/C++
When your Rust crate wraps a C library or needs a small native helper, the cc crate is the standard choice inside build.rs.
如果 Rust crate 需要包一层 C 库,或者本身就要带一点小型原生辅助代码,那 cc 基本就是 build.rs 里的标准答案。
# Cargo.toml
[build-dependencies]
cc = "1.0"
// build.rs
fn main() {
println!("cargo::rerun-if-changed=csrc/");
cc::Build::new()
.file("csrc/ipmi_raw.c")
.file("csrc/smbios_parser.c")
.include("csrc/include")
.flag("-Wall")
.flag("-Wextra")
.opt_level(2)
.compile("diag_helpers");
}
#![allow(unused)]
fn main() {
// src/lib.rs — FFI bindings to the compiled C code
extern "C" {
fn ipmi_raw_command(
netfn: u8,
cmd: u8,
data: *const u8,
data_len: usize,
response: *mut u8,
response_len: *mut usize,
) -> i32;
}
pub fn send_ipmi_command(netfn: u8, cmd: u8, data: &[u8]) -> Result<Vec<u8>, IpmiError> {
let mut response = vec![0u8; 256];
let mut response_len: usize = response.len();
let rc = unsafe {
ipmi_raw_command(
netfn,
cmd,
data.as_ptr(),
data.len(),
response.as_mut_ptr(),
&mut response_len,
)
};
if rc != 0 {
return Err(IpmiError::CommandFailed(rc));
}
response.truncate(response_len);
Ok(response)
}
}
For C++ code, add .cpp(true) and the right language standard flag:
如果要编 C++,就再加上 .cpp(true) 和对应的标准参数。
fn main() {
println!("cargo::rerun-if-changed=cppsrc/");
cc::Build::new()
.cpp(true)
.file("cppsrc/vendor_parser.cpp")
.flag("-std=c++17")
.flag("-fno-exceptions")
.compile("vendor_helpers");
}
Pattern 3: Protocol Buffers and Code Generation
模式 3:Protocol Buffers 与代码生成
Build scripts are also perfect for compile-time code generation. A classic example is protobuf generation via prost-build:
构建脚本特别适合做编译期代码生成。最典型的例子就是用 prost-build 生成 protobuf 代码。
[build-dependencies]
prost-build = "0.13"
fn main() {
println!("cargo::rerun-if-changed=proto/");
prost_build::compile_protos(
&["proto/diagnostics.proto", "proto/telemetry.proto"],
&["proto/"],
)
.expect("Failed to compile protobuf definitions");
}
#![allow(unused)]
fn main() {
pub mod diagnostics {
include!(concat!(env!("OUT_DIR"), "/diagnostics.rs"));
}
pub mod telemetry {
include!(concat!(env!("OUT_DIR"), "/telemetry.rs"));
}
}
OUT_DIRis the Cargo-provided directory meant for generated files. Never write generated Rust source back intosrc/during the build.OUT_DIR是 Cargo 专门给生成文件准备的目录。构建过程中生成的 Rust 代码别往src/里硬写,老老实实放进OUT_DIR。
Pattern 4: Linking System Libraries with pkg-config
模式 4:用 pkg-config 链接系统库
For system libraries that ship .pc files, the pkg-config crate can probe the system and emit the right link flags.
如果系统库自带 .pc 文件,那 pkg-config 就能帮忙探测环境,并自动吐出合适的链接参数。
[build-dependencies]
pkg-config = "0.3"
fn main() {
pkg_config::Config::new()
.atleast_version("3.6.0")
.probe("libpci")
.expect("libpci >= 3.6.0 not found — install pciutils-dev");
if pkg_config::probe_library("libsystemd").is_ok() {
println!("cargo::rustc-cfg=has_systemd");
}
}
#![allow(unused)]
fn main() {
#[cfg(has_systemd)]
mod systemd_notify {
extern "C" {
fn sd_notify(unset_environment: i32, state: *const std::ffi::c_char) -> i32;
}
pub fn notify_ready() {
let state = std::ffi::CString::new("READY=1").unwrap();
unsafe { sd_notify(0, state.as_ptr()) };
}
}
#[cfg(not(has_systemd))]
mod systemd_notify {
pub fn notify_ready() {}
}
}
Pattern 5: Feature Detection and Conditional Compilation
模式 5:特性检测与条件编译
Build scripts can inspect the compilation environment and emit cfg flags used by the main crate for conditional code paths.
构建脚本还可以探测当前编译环境,再往主 crate 里塞 cfg 标志,让代码走不同分支。
fn main() {
println!("cargo::rerun-if-changed=build.rs");
let target = std::env::var("TARGET").unwrap();
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
if target.starts_with("x86_64") {
println!("cargo::rustc-cfg=has_x86_64");
}
if target.starts_with("aarch64") {
println!("cargo::rustc-cfg=has_aarch64");
}
if target_os == "linux" && std::path::Path::new("/dev/ipmi0").exists() {
println!("cargo::rustc-cfg=has_ipmi_device");
}
}
⚠️ Anti-pattern demonstration — the following approach looks tempting but should not be used in production.
⚠️ 反面示范:下面这种写法看着诱人,实际上很坑,生产环境别这么干。
fn main() {
if std::process::Command::new("accel-query")
.arg("--query-gpu=name")
.arg("--format=csv,noheader")
.output()
.is_ok()
{
println!("cargo::rustc-cfg=has_accel_device");
}
}
#![allow(unused)]
fn main() {
pub fn query_gpu_info() -> GpuResult {
#[cfg(has_accel_device)]
{
run_accel_query()
}
#[cfg(not(has_accel_device))]
{
GpuResult::NotAvailable("accel-query not found at build time".into())
}
}
}
⚠️ Why this is wrong: runtime hardware should usually be detected at runtime, not baked in at build time. Otherwise the binary becomes tied to the build machine’s hardware layout.
⚠️ 这为什么是错的:硬件是否存在,通常应该在运行时检测,而不是在构建时写死。否则产物会莫名其妙地和构建机的硬件环境绑定在一起。
Anti-Patterns and Pitfalls
反模式与常见坑
| Anti-Pattern 反模式 | Why It’s Bad 为什么糟糕 | Fix 修正方式 |
|---|---|---|
No rerun-if-changed不写 rerun-if-changed | build.rs runs on every build每次构建都重跑,拖慢开发 | Always emit at least cargo::rerun-if-changed=build.rs最少也要写上 build.rs 自己。 |
| Network calls in build.rs 在 build.rs 里打网络 | Breaks offline and reproducible builds 离线构建和可复现构建都会出问题 | Vendor files or split into a fetch step 把文件预置好,或者把下载挪到单独步骤。 |
Writing to src/往 src/ 写生成代码 | Cargo does not expect sources to mutate during build Cargo 不期待源文件在构建中被改动 | Write to OUT_DIR改写到 OUT_DIR。 |
| Heavy computation 在 build.rs 里做重计算 | Slows every cargo build所有构建都跟着变慢 | Cache in OUT_DIR and gate reruns把结果缓存起来,再配合 rerun 规则。 |
| Ignoring cross-compilation 无视交叉编译环境 | Raw gcc commands often break on non-native targets手写 gcc 命令很容易在跨平台时炸 | Prefer cc crate优先用 cc crate。 |
| Panicking without context 直接 unwrap() 爆掉 | Error message is opaque 报错又臭又短,看不明白 | Use .expect("...") or cargo::warning=给出明确上下文。 |
Application: Embedding Build Metadata
应用场景:嵌入构建元数据
The project currently uses env!("CARGO_PKG_VERSION") for version reporting. A build.rs would let it report richer metadata such as git hash, build epoch, and target triple.
当前工程已经用 env!("CARGO_PKG_VERSION") 输出版本号了。如果再补一个 build.rs,就能把 git hash、构建时间戳、target triple 这些信息一起嵌进去。
fn main() {
println!("cargo::rerun-if-changed=.git/HEAD");
println!("cargo::rerun-if-changed=.git/refs");
println!("cargo::rerun-if-changed=build.rs");
if let Ok(output) = std::process::Command::new("git")
.args(["rev-parse", "--short=10", "HEAD"])
.output()
{
let hash = String::from_utf8_lossy(&output.stdout).trim().to_string();
println!("cargo::rustc-env=APP_GIT_HASH={hash}");
} else {
println!("cargo::rustc-env=APP_GIT_HASH=unknown");
}
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs().to_string())
.unwrap_or_else(|_| "0".into());
println!("cargo::rustc-env=APP_BUILD_EPOCH={timestamp}");
let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown".into());
println!("cargo::rustc-env=APP_TARGET={target}");
}
#![allow(unused)]
fn main() {
pub struct BuildInfo {
pub version: &'static str,
pub git_hash: &'static str,
pub build_epoch: &'static str,
pub target: &'static str,
}
pub const BUILD_INFO: BuildInfo = BuildInfo {
version: env!("CARGO_PKG_VERSION"),
git_hash: env!("APP_GIT_HASH"),
build_epoch: env!("APP_BUILD_EPOCH"),
target: env!("APP_TARGET"),
};
}
Key insight from the project: having zero
build.rsfiles across a large codebase is often a good sign. If the project is pure Rust, does not wrap C code, does not generate code, and does not need system library probing, then not having build scripts means the architecture stayed clean.
结合当前工程的一点观察:一个大代码库里完全没有build.rs,很多时候反而是好事。如果项目是纯 Rust、没有 C 依赖、没有代码生成、也不需要探测系统库,那没有构建脚本就说明架构相当干净。
Try It Yourself
动手试一试
-
Embed git metadata: Create a
build.rsthat emitsAPP_GIT_HASHandAPP_BUILD_EPOCH, consume them withenv!()inmain.rs, and verify the hash changes after a commit.
1. 嵌入 git 元数据:写一个build.rs输出APP_GIT_HASH和APP_BUILD_EPOCH,在main.rs里用env!()读取,并验证提交后 hash 会变化。 -
Probe a system library: Use
pkg-configto probelibz, emitcargo::rustc-cfg=has_zlibwhen found, and letmain.rsprint whether zlib is available.
2. 探测系统库:用pkg-config探测libz,找到时输出has_zlib,再让main.rs在构建后打印 zlib 是否可用。 -
Trigger a build failure intentionally: Remove
rerun-if-changedand observe how oftenbuild.rsreruns duringcargo buildandcargo test, then add it back and compare.
3. 故意制造一次不合理重跑:先删掉rerun-if-changed,看看cargo build和cargo test时build.rs会重跑多少次,再把它加回来做对比。
Reproducible Builds
可复现构建
Chapter 1 encourages embedding timestamps and git hashes into binaries for traceability. But that directly conflicts with reproducible builds, where the same source should produce the same binary.
这一章前面提倡把时间戳和 git hash 嵌进二进制,方便追踪来源。但这件事和“可复现构建”天然是有冲突的,因为后者要求同一份源码产出完全一致的二进制。
The tension:
两者的拉扯关系:
| Goal 目标 | Achievement 得到什么 | Cost 代价 |
|---|---|---|
| Traceability 可追踪性 | APP_BUILD_EPOCH in binary二进制里带构建信息 | Every build is unique 每次构建都不一样 |
| Reproducibility 可复现性 | Same source → same output 同源码得同产物 | No live build timestamp 实时构建信息会受限制 |
Practical resolution:
更务实的处理方式:
# 1. Always use --locked in CI
cargo build --release --locked
# 2. For reproducible builds, set SOURCE_DATE_EPOCH
SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) cargo build --release --locked
#![allow(unused)]
fn main() {
let timestamp = std::env::var("SOURCE_DATE_EPOCH")
.unwrap_or_else(|_| {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs().to_string())
.unwrap_or_else(|_| "0".into())
});
println!("cargo::rustc-env=APP_BUILD_EPOCH={timestamp}");
}
Best practice: respect
SOURCE_DATE_EPOCHinbuild.rs. That way, release builds can stay reproducible while local development builds still keep convenient live timestamps.
更好的实践:在build.rs里优先读取SOURCE_DATE_EPOCH。这样发布构建还能维持可复现,本地开发构建也仍然能保留实时时间戳。
Build Pipeline Decision Diagram
构建脚本决策图
flowchart TD
START["Need compile-time work?<br/>需要编译期处理吗?"] -->|No<br/>不需要| SKIP["No build.rs needed<br/>不用 build.rs"]
START -->|Yes<br/>需要| WHAT{"What kind?<br/>属于哪类需求?"}
WHAT -->|"Embed metadata<br/>嵌元数据"| P1["Pattern 1<br/>Compile-Time Constants"]
WHAT -->|"Compile C/C++<br/>编 C/C++"| P2["Pattern 2<br/>cc crate"]
WHAT -->|"Code generation<br/>代码生成"| P3["Pattern 3<br/>prost-build / tonic-build"]
WHAT -->|"Link system lib<br/>链接系统库"| P4["Pattern 4<br/>pkg-config"]
WHAT -->|"Detect features<br/>检测 feature"| P5["Pattern 5<br/>cfg flags"]
P1 --> RERUN["Always emit<br/>cargo::rerun-if-changed"]
P2 --> RERUN
P3 --> RERUN
P4 --> RERUN
P5 --> RERUN
style SKIP fill:#91e5a3,color:#000
style RERUN fill:#ffd43b,color:#000
style P1 fill:#e3f2fd,color:#000
style P2 fill:#e3f2fd,color:#000
style P3 fill:#e3f2fd,color:#000
style P4 fill:#e3f2fd,color:#000
style P5 fill:#e3f2fd,color:#000
🏋️ Exercises
🏋️ 练习
🟢 Exercise 1: Version Stamp
🟢 练习 1:版本戳
Create a minimal crate with a build.rs that embeds the current git hash and build profile into environment variables. Print them from main(). Verify the output changes between debug and release builds.
创建一个最小 crate,用 build.rs 把当前 git hash 和 build profile 写进环境变量,再在 main() 里打印出来,并验证 debug 与 release 构建结果不同。
Solution 参考答案
// build.rs
fn main() {
println!("cargo::rerun-if-changed=.git/HEAD");
println!("cargo::rerun-if-changed=build.rs");
let hash = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_else(|_| "unknown".into());
println!("cargo::rustc-env=GIT_HASH={hash}");
println!("cargo::rustc-env=BUILD_PROFILE={}", std::env::var("PROFILE").unwrap_or_default());
}
fn main() {
println!("{} v{} (git:{} profile:{})",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
env!("GIT_HASH"),
env!("BUILD_PROFILE"),
);
}
cargo run
cargo run --release
🟡 Exercise 2: Conditional System Library
🟡 练习 2:条件系统库探测
Write a build.rs that probes for both libz and libpci using pkg-config. Emit a cfg flag for each one found. In main.rs, print which libraries were detected at build time.
写一个 build.rs,用 pkg-config 探测 libz 和 libpci。哪个找到就发哪个 cfg 标志,然后在 main.rs 里打印构建时探测到了哪些库。
Solution 参考答案
[build-dependencies]
pkg-config = "0.3"
fn main() {
println!("cargo::rerun-if-changed=build.rs");
if pkg_config::probe_library("zlib").is_ok() {
println!("cargo::rustc-cfg=has_zlib");
}
if pkg_config::probe_library("libpci").is_ok() {
println!("cargo::rustc-cfg=has_libpci");
}
}
fn main() {
#[cfg(has_zlib)]
println!("✅ zlib detected");
#[cfg(not(has_zlib))]
println!("❌ zlib not found");
#[cfg(has_libpci)]
println!("✅ libpci detected");
#[cfg(not(has_libpci))]
println!("❌ libpci not found");
}
Key Takeaways
本章要点
build.rsruns on the host at compile time — always emitcargo::rerun-if-changedto avoid unnecessary rebuildsbuild.rs运行在 host 上,想避免莫名其妙地重跑,就一定要写cargo::rerun-if-changed。- Use the
cccrate, not rawgcccommands, for C/C++ compilation
编译 C/C++ 时优先用cccrate,别自己手搓gcc命令。 - Write generated files to
OUT_DIR, never tosrc/
生成文件放进OUT_DIR,别污染src/。 - Prefer runtime detection over build-time detection for optional hardware
可选硬件能力更适合运行时探测,而不是构建时写死。 - Use
SOURCE_DATE_EPOCHwhen you need reproducible builds with embedded timestamps
既想嵌时间戳,又想保留可复现构建,就去用SOURCE_DATE_EPOCH。