我的软件包有一个二进制目标,该目标使用include_bytes!(…)
将一些预先计算的值的副本捆绑到已编译的二进制文件中。这是一种优化,但并非绝对必要:如果捆绑的数据片.is_empty()
,程序可以在运行时计算这些值。
该程序需要能够在没有这些数据的情况下进行构建。但是,如果目标文件不存在,include_bytes!("data/computed.bin")
会导致生成错误。
error: couldn't read src/data/computed.bin: No such file or directory (os error 2)
当前,我有一个Bash构建脚本,该脚本使用touch data/computed.bin
确保在构建之前文件存在。但是,我不想依赖于特定于平台的解决方案,例如Bash;我希望能够使用cargo build
在任何受支持的平台上构建该项目。
如果文件退出,我的Rust程序include_bytes!(…)
或include_str!(…)
如何从文件中退出,但是如果该文件不存在,而仅使用标准Cargo,则可以优雅地退回到其他值或行为构建工具?
答案 0 :(得分:3)
我们可以使用build script来确保包含的文件存在,然后out软件包尝试包含它。但是,构建脚本只能写入当前构建的唯一输出目录,因此我们不能直接在源目录中直接创建丢失的输入文件。
错误:无法验证包tarball
原因:
build.rs
期间cargo publish
修改了源目录。构建脚本不应修改OUT_DIR
之外的任何内容。
相反,我们的构建脚本可以在构建目录中创建要包含的文件,如果存在则复制源数据,并且我们可以更新包代码以从构建目录而不是源目录中包含此数据。 。在构建期间,构建路径将在OUT_DIR
环境变量中可用,因此我们可以从构建脚本中的std::env::var("OUT_DIR")
和包其余部分的env!("OUT_DIR")
中访问它。>
//! build.rs
use std::{fs, io};
fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
fs::create_dir_all(&format!("{}/src/data", out_dir))
.expect("unable to create data directory");
let path = format!("src/data/computed.bin", name);
let out_path = format!("{}/{}", out_dir, path);
let mut out_file = fs::OpenOptions::new()
.append(true)
.create(true)
.open(&out_path)
.expect("unable to open/create data file");
if let Ok(mut source_file) = fs::File::open(&path) {
io::copy(&mut source_file, &mut out_file).expect("failed to copy data after opening");
}
}
//! src/foo.rs
fn precomputed_data() -> Option<&'static [u8]> {
let data = include_bytes!(concat!(env!("OUT_DIR"), "/src/data/computed.bin")).as_ref();
if !data.is_empty() {
Some(data)
} else {
None
}
}