#[cfg(feature = "znippy-handler")]
use std::collections::HashMap;
#[cfg(feature = "znippy-handler")]
use znippy_common::arrow::datatypes::{DataType, Field};
#[cfg(feature = "znippy-handler")]
use znippy_common::plugin::{
ArchiveTypePlugin, ExtensionRow, ExtensionValue, HandlerCommand, HandlerMeta,
};
pub mod member;
pub use member::{classify, component_of, is_rust_dev_bundle, MemberKind};
pub const RUST_TOOLCHAIN_TYPE_ID: i8 = 41;
pub const BUNDLE_LAYOUT: &str = "\
rust-dev-rhel8-<rustc-ver>.znippy
├── manifest/
│ ├── bundle.json # rustc/cargo version, host triple, source-URL provenance
│ ├── toolchain.pins.json # per-payload sha256 + blake3 (the \"pinned like the kernel\" table)
│ └── vendor.lock # exact crate set = a copy of the source Cargo.lock
├── toolchain/ # the standalone (rustup-dist) toolchain, UNPACKED into the tree
│ ├── bin/ # rustc, cargo, rustdoc, rust-lld, …
│ ├── lib/ # librustc driver .so's, std .rlibs
│ └── lib/rustlib/<triple>/ # the rust-std for the target
├── vendor/ # `cargo vendor` output — the \"basic crates\" set
│ └── <crate>-<ver>/… # already-compressed .crate contents expanded to source
└── cargo-config/
└── config.toml # source-replacement → vendor/, offline hard-on";
#[cfg(feature = "znippy-handler")]
pub struct NativeRustToolchainPlugin;
#[cfg(feature = "znippy-handler")]
impl NativeRustToolchainPlugin {
pub fn new() -> Self {
NativeRustToolchainPlugin
}
}
#[cfg(feature = "znippy-handler")]
impl Default for NativeRustToolchainPlugin {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "znippy-handler")]
fn json_str_field(data: &[u8], key: &str) -> Option<String> {
let v: serde_json::Value = serde_json::from_slice(data).ok()?;
v.get(key)?.as_str().map(str::to_string)
}
#[cfg(feature = "znippy-handler")]
impl ArchiveTypePlugin for NativeRustToolchainPlugin {
fn name(&self) -> &str {
"rust-toolchain"
}
fn type_id(&self) -> i8 {
RUST_TOOLCHAIN_TYPE_ID
}
fn meta(&self) -> HandlerMeta {
HandlerMeta {
name: "rust-toolchain".into(),
aliases: vec!["rust-dev".into(), "rustdev".into(), "toolchain".into()],
type_id: RUST_TOOLCHAIN_TYPE_ID,
ecosystem: "Airgapped Rust developer bundle (rustup-dist toolchain + cargo-vendor set)"
.into(),
extensions: vec![
".pins.json".into(),
".lock".into(),
".rlib".into(),
".rmeta".into(),
".toml".into(),
],
description: "Tags each rust-dev bundle entry (toolchain bin / rust-std / \
vendored crate / cargo-config / pins manifest) into the index so \
a sealed offline-Rust bundle is self-describing and queryable"
.into(),
commands: vec![
HandlerCommand::new(
"classify",
"Print the member_kind / component for a bundle path",
),
HandlerCommand::new("layout", "Print the canonical rust-dev bundle layout"),
],
}
}
fn run_command(&self, cmd: &str, args: &[String]) -> anyhow::Result<()> {
match cmd {
"layout" => {
println!("{BUNDLE_LAYOUT}");
Ok(())
}
"classify" => {
let path = args
.first()
.ok_or_else(|| anyhow::anyhow!("usage: rust-toolchain classify <bundle-path>"))?;
match classify(path) {
Some(kind) => {
println!("path: {path}");
println!("member_kind: {}", kind.as_str());
println!("component: {}", component_of(kind, path).as_deref().unwrap_or("-"));
Ok(())
}
None => {
anyhow::bail!("'{path}' is not a recognised rust-dev bundle member")
}
}
}
other => anyhow::bail!("rust-toolchain: unknown subcommand '{other}'"),
}
}
fn matches_path(&self, path: &str) -> bool {
classify(path).is_some()
}
fn schema_fields(&self) -> Vec<Field> {
vec![
Field::new("member_kind", DataType::Utf8, true),
Field::new("component", DataType::Utf8, true),
Field::new("logical_path", DataType::Utf8, true),
Field::new("toolchain_version", DataType::Utf8, true),
Field::new("host_triple", DataType::Utf8, true),
]
}
fn extract_metadata(&self, path: &str, data: &[u8]) -> Option<ExtensionRow> {
let kind = classify(path)?;
let mut f: HashMap<String, ExtensionValue> = HashMap::new();
f.insert("member_kind".into(), ExtensionValue::Str(kind.as_str().into()));
f.insert("logical_path".into(), ExtensionValue::Str(path.into()));
if let Some(c) = component_of(kind, path) {
f.insert("component".into(), ExtensionValue::Str(c));
}
if kind == MemberKind::Manifest {
if let Some(v) = json_str_field(data, "version")
.or_else(|| json_str_field(data, "rustc_version"))
{
f.insert("toolchain_version".into(), ExtensionValue::Str(v));
}
if let Some(h) = json_str_field(data, "host")
.or_else(|| json_str_field(data, "host_triple"))
{
f.insert("host_triple".into(), ExtensionValue::Str(h));
}
}
Some(ExtensionRow { fields: f })
}
}
#[cfg(all(test, feature = "znippy-handler"))]
mod tests {
use super::*;
fn get<'a>(row: &'a ExtensionRow, k: &str) -> Option<&'a ExtensionValue> {
row.fields.get(k)
}
#[test]
fn tags_a_vendored_crate() {
let p = NativeRustToolchainPlugin::new();
let row = p.extract_metadata("vendor/serde-1.0.203/src/lib.rs", b"pub fn x() {}").unwrap();
assert_eq!(get(&row, "member_kind"), Some(&ExtensionValue::Str("vendor-crate".into())));
assert_eq!(get(&row, "component"), Some(&ExtensionValue::Str("serde-1.0.203".into())));
assert!(get(&row, "toolchain_version").is_none());
}
#[test]
fn tags_the_rust_std_with_its_triple() {
let p = NativeRustToolchainPlugin::new();
let row = p
.extract_metadata(
"toolchain/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd.rlib",
b"\x00rlib",
)
.unwrap();
assert_eq!(get(&row, "member_kind"), Some(&ExtensionValue::Str("rust-std".into())));
assert_eq!(
get(&row, "component"),
Some(&ExtensionValue::Str("x86_64-unknown-linux-gnu".into()))
);
}
#[test]
fn parses_bundle_manifest_version_and_host() {
let p = NativeRustToolchainPlugin::new();
let json = br#"{"version":"1.97.1","host":"x86_64-unknown-linux-gnu","source":"https://static.rust-lang.org"}"#;
let row = p.extract_metadata("manifest/bundle.json", json).unwrap();
assert_eq!(get(&row, "toolchain_version"), Some(&ExtensionValue::Str("1.97.1".into())));
assert_eq!(
get(&row, "host_triple"),
Some(&ExtensionValue::Str("x86_64-unknown-linux-gnu".into()))
);
}
#[test]
fn manifest_with_garbage_json_falls_back_never_panics() {
let p = NativeRustToolchainPlugin::new();
let row = p.extract_metadata("manifest/bundle.json", b"not json at all }{").unwrap();
assert_eq!(get(&row, "member_kind"), Some(&ExtensionValue::Str("manifest".into())));
assert!(get(&row, "toolchain_version").is_none());
assert!(get(&row, "host_triple").is_none());
}
#[test]
fn does_not_claim_foreign_paths() {
let p = NativeRustToolchainPlugin::new();
assert!(!p.matches_path("README.md"));
assert!(!p.matches_path("s3/doc.pdf"));
assert!(p.matches_path("toolchain/bin/rustc"));
assert!(p.extract_metadata("README.md", b"x").is_none());
}
#[test]
fn schema_and_meta_are_consistent() {
let p = NativeRustToolchainPlugin::new();
assert_eq!(p.type_id(), RUST_TOOLCHAIN_TYPE_ID);
assert_eq!(p.meta().type_id, RUST_TOOLCHAIN_TYPE_ID);
assert_ne!(p.type_id(), 40);
assert_eq!(p.schema_fields().len(), 5);
}
}