【问题标题】:Falling back to alternative value if include_bytes!(…) target is missing如果缺少 include_bytes!(...) 目标,则回退到替代值
【发布时间】:2021-08-15 19:26:39
【问题描述】:

我的包有一个二进制目标,它使用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 构建工具?

【问题讨论】:

    标签: rust rust-cargo


    【解决方案1】:

    我们可以使用build script 来确保包含的文件在 out 包尝试包含它之前存在。但是,构建脚本只能写入当前构建的唯一输出目录,所以我们不能直接在源目录中创建缺少的输入文件。

    错误:无法验证包 tarball

    原因:
    源目录在cargo publish 期间被build.rs 修改。构建脚本不应修改 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
        }
    }
    

    【讨论】:

    • 我不确定,但这可能会影响 Rust 检测源文件何时更改并需要重建的能力。
    【解决方案2】:

    虽然使用构建脚本(如 in this answer)会起作用,但我不喜欢该解决方案:

    • 构建脚本会复制文件 - 根据文件大小,这可能会非常昂贵。虽然可以改用硬链接来解决这个问题。
    • 空文件可能是完美的数据 - 该解决方案会将空文件误检测为丢失。但是,根据用例,空文件实际上可能完全有效。
    • 非常冗长——这会将一个简单的 include_bytes! 变成一个大约 20 行的构建脚本,并且在包含处理 data.is_empty() 情况时还额外增加了几行。
    • 普通读者很难理解这里发生了什么——为什么这个脚本包含来自$OUT_DIR 的内容?读者可能需要一点时间才能了解此处可能涉及构建脚本。
    • 无法很好地扩展 - 如果有多个文件需要可选地包含在内,大多数问题会变得更糟。

    因此我决定编写程序宏 crate include_optional 来解决这个问题(目前仅适用于夜间 Rust,因为它依赖于一些不稳定的特性)。

    有了这个,这个问题的解决方案就是一条线:

    use include_optional::include_bytes_optional;
    
    fn precomputed_data() -> Option<&'static [u8]> {
        include_bytes_optional!("./computed.bin")
    }
    

    还有包装include_str!include!的宏。

    【讨论】:

      猜你喜欢
      • 2011-01-20
      • 2021-01-18
      • 2014-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多