My package has a binary target that uses include_bytes!(…)
to bundle a copy of some precomputed values into the compiled binary. This is an optimization, but isn't strictly necessary: the program is capable of calculating these values at run time if the bundled data slice .is_empty()
.
The program needs to be able to build without this data. However, include_bytes!("data/computed.bin")
causes a build error if the target file does not exist.
error: couldn't read src/data/computed.bin: No such file or directory (os error 2)
Currently, I have a Bash build script that uses touch data/computed.bin
to ensure the file exists before building. However, I don't want to depend on platform-specific solutions like Bash; I want to be able to build this project on any supported platform using cargo build
.
How can my Rust program include_bytes!(…)
or include_str!(…)
from a file if it exits, but gracefully fall back to an alternative value or behaviour if the file doesn't exist, while only using the standard Cargo build tools?
We can use a build script to ensure that the included file exists before out package tries to include it. However, build scripts can only write to the current build's unique output directory, so we can't just create the missing input files in the source directory directly.
error: failed to verify package tarball
Caused by:
Source directory was modified bybuild.rs
duringcargo publish
. Build scripts should not modify anything outside ofOUT_DIR
.
Instead, our build script can create the file-to-include in the build directory, copying the source data if it exists, and we can update our package code to include this data from the build directory instead of from the source directory. The build path will be available in the OUT_DIR
environment variable during the build, so we can access it from std::env::var("OUT_DIR")
in our build script and from env!("OUT_DIR")
in the rest of our package.
//! 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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With