use std::path::Path;
use cargo_metadata::MetadataCommand;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use crate::error::{CargoError, Result};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FeaturesManifest {
pub version: u32,
pub crates: IndexMap<String, CrateFeatures>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CrateFeatures {
pub name: String,
pub version: String,
pub features: IndexMap<String, Vec<String>>,
pub resolved_features: Vec<String>,
pub dep_features: IndexMap<String, Vec<String>>,
pub optional_deps: Vec<String>,
pub deps: Vec<CrateDep>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CrateDep {
pub name: String,
pub package: String,
pub kind: String,
pub target: Option<String>,
pub features: Vec<String>,
pub uses_default_features: bool,
pub optional: bool,
}
pub fn generate(workspace_root: &Path) -> Result<FeaturesManifest> {
let manifest_path = workspace_root.join("Cargo.toml");
if !manifest_path.exists() {
return Err(CargoError::Io {
path: manifest_path,
source: std::io::Error::new(
std::io::ErrorKind::NotFound,
"no Cargo.toml at workspace root",
),
});
}
let meta = MetadataCommand::new()
.manifest_path(&manifest_path)
.exec()
.map_err(|e| CargoError::Io {
path: manifest_path.clone(),
source: std::io::Error::new(std::io::ErrorKind::Other, e.to_string()),
})?;
let resolved_per_package: IndexMap<String, Vec<String>> = match &meta.resolve {
None => IndexMap::new(),
Some(r) => r
.nodes
.iter()
.map(|n| (n.id.repr.clone(), n.features.iter().map(String::from).collect()))
.collect(),
};
let resolved_dep_features: IndexMap<String, IndexMap<String, Vec<String>>> = match &meta.resolve {
None => IndexMap::new(),
Some(r) => r
.nodes
.iter()
.map(|n| {
let mut deps: IndexMap<String, Vec<String>> = IndexMap::new();
for d in &n.deps {
let feats: Vec<String> = d
.dep_kinds
.iter()
.flat_map(|k| k.target.as_ref().map(|_| Vec::<String>::new()).unwrap_or_default())
.collect();
deps.insert(d.name.clone(), feats);
}
(n.id.repr.clone(), deps)
})
.collect(),
};
let mut crates: IndexMap<String, CrateFeatures> = IndexMap::new();
for pkg in &meta.packages {
let pkg_id = pkg.id.repr.clone();
let key = format!("{}/{}", pkg.name, pkg.version);
let features = pkg
.features
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let resolved_features = resolved_per_package
.get(&pkg_id)
.cloned()
.unwrap_or_default();
let dep_features = resolved_dep_features
.get(&pkg_id)
.cloned()
.unwrap_or_default();
let optional_deps: Vec<String> = pkg
.dependencies
.iter()
.filter(|d| d.optional)
.map(|d| d.name.clone())
.collect();
let deps: Vec<CrateDep> = pkg
.dependencies
.iter()
.map(|d| CrateDep {
name: d.rename.clone().unwrap_or_else(|| d.name.clone()),
package: d.name.clone(),
kind: match d.kind {
cargo_metadata::DependencyKind::Normal => "normal".to_string(),
cargo_metadata::DependencyKind::Development => "dev".to_string(),
cargo_metadata::DependencyKind::Build => "build".to_string(),
_ => "normal".to_string(),
},
target: d.target.as_ref().map(|p| p.to_string()),
features: d.features.iter().map(String::from).collect(),
uses_default_features: d.uses_default_features,
optional: d.optional,
})
.collect();
crates.insert(
key,
CrateFeatures {
name: pkg.name.to_string(),
version: pkg.version.to_string(),
features,
resolved_features,
dep_features,
optional_deps,
deps,
},
);
}
Ok(FeaturesManifest {
version: 1,
crates,
})
}
pub fn generate_and_write(workspace_root: &Path) -> Result<std::path::PathBuf> {
let manifest = generate(workspace_root)?;
let out = workspace_root.join("Cargo.features.json");
let body = serde_json::to_string_pretty(&manifest).map_err(|source| CargoError::Io {
path: out.clone(),
source: std::io::Error::new(std::io::ErrorKind::Other, source.to_string()),
})?;
std::fs::write(&out, body).map_err(|source| CargoError::Io {
path: out.clone(),
source,
})?;
Ok(out)
}