我有一个 rust 二进制板条箱(嗯,它还有一个用于测试的 lib.rs 文件),并且正在尝试编写一个构建脚本。此构建脚本需要使用 serde 从静态 rust 对象(自定义 struct crate::datatypes::ErrorMarkup)生成 JSON 文件,该文件由二进制 crate 导入。我知道我可以只引用二进制板条箱中的静态对象,但是二进制板条箱必须导入一个 JSON 文件,该文件最终可能来自其他来源,或者可能在构建和运行阶段之间进行修改(即,目的是发送构建的文件和 JSON 文件)。
如何从构建脚本引用 crate 数据类型和函数?
项目结构:
- cargo.lock
- cargo.toml
- markup.json (target file)
- build.rs
- src
- main.rs
- lib.rs (exports)
- datatypes.rs
- tests
- verify.rs
我已经尝试了
use pump_log_tool
(我的板条箱)和use crate::src
以及十亿个其他变体。
我知道这可能是由于构建依赖项和运行时依赖项而导致的问题。有没有明显更好的方法来实现这个目标?
Cargo 将
build.rs
编译到自己的板条箱中,并具有自己的依赖项。通过使构建脚本依赖于它正在构建的板条箱,您创建了 Cargo 无法解析的循环依赖关系。
典型的解决方案是将(大概)在
datatypes.rs
中定义的项目移动到它们自己的单独的板条箱中,这不需要构建脚本。 (这也是过程宏的常见模式。)您的新项目结构可能如下所示
/
|- Cargo.toml
|- build.rs
|- src/
| |- lib.rs
| \- main.rs
|- pump_log_tool_types/
| |- Cargo.toml
| \- src/lib.rs
然后,在
/Cargo.toml
中,创建一个工作区部分并添加新的板条箱:
[workspace]
# This is a path relative to the workspace root.
members = ["pump_log_tool_types"]
在
pump_log_tool/Cargo.toml
中,添加 pump_log_tool_types
箱作为普通依赖项和构建依赖项:
[dependencies]
pump_log_tool_types = { path = "pump_log_tool_types" }
[build-dependencies]
pump_log_tool_types = { path = "pump_log_tool_types" }
现在,当您构建
pump_log_tool
时,pump_log_tool_types
板条箱将被构建一次并在构建脚本和板条箱本身之间共享。
对于数据定义等简单的事情,您可以在构建脚本中使用
include!()
宏。
假设我想在构建时查找引用自定义类型的表:数据定义(没有其他内容)位于
src/parser/field_type.rs
:
#[derive(Debug)]
#[repr(u64)]
#[allow(dead_code)]
pub(crate) enum FieldType {
Unknown = 0,
Encoded,
Numeric,
NumericDec,
NumericHex,
NumericOct,
Alphanumeric,
Alphabet,
}
在构建脚本中我包含该代码:
include!("src/parser/field_type.rs");
所以我不需要在多个地方都有定义。然后我构建一个 FST...
let fields: Vec<(String, String)> = { /* read and process a CSV */ };
let mut build =
fst::MapBuilder::new(File::create(PathBuf::from(out_dir).join("fields.fst"))?)?;
build.extend_iter(fields.iter().map(|(name, ty)| {
let ty = match ty.as_str() {
"encoded" => FieldType::Encoded,
"numeric" => FieldType::Numeric,
"numeric decimal" => FieldType::NumericDec,
"numeric hexadecimal" => FieldType::NumericHex,
"numeric octal" => FieldType::NumericOct,
"alphanumeric" => FieldType::Alphanumeric,
"alphabet" => FieldType::Alphabet,
_ => panic!("unrecognized {} for {}", ty, name),
} as u64;
(name, ty)
}))?;
build.finish()?;
…在板条箱的另一部分使用它来按名称查找
FieldType
:
use lazy_static::lazy_static;
lazy_static! {
static ref FIELD_TYPES: fst::Map<Vec<u8>> = {
fst::Map::new(Vec::from(
include_bytes!(concat!(env!("OUT_DIR"), "/fields.fst")).as_slice(),
))
.unwrap()
};
}
fn parse_hint(key: &[u8]) -> FieldType {
let h = FIELD_TYPES.get(key).unwrap_or_default();
unsafe { std::mem::transmute(h) }
}
甚至应该可以将该代码移至
field_type.rs
并告诉编译器在编译 build.rs
时忽略它,但对于我的用例来说,到目前为止这已经足够了。