diff --git a/examples/graph.rs b/examples/graph.rs index f99d345..df6576a 100644 --- a/examples/graph.rs +++ b/examples/graph.rs @@ -5,7 +5,7 @@ use std::fmt; #[derive(Parser, Debug)] struct Args { #[arg(short, long)] - manifest_path: String, + manifest_path: Option, #[arg(long)] features: Vec, #[arg(long)] @@ -14,6 +14,8 @@ struct Args { no_default_features: bool, #[arg(long)] no_dev: bool, + #[arg(long, conflicts_with = "manifest_path")] + json: Option, } pub struct Simple { @@ -41,27 +43,42 @@ impl fmt::Display for Simple { fn main() { let args = Args::parse(); - let cmd = { - let mut cmd = krates::Cmd::new(); - if args.all_features { - cmd.all_features(); - } - if args.no_default_features { - cmd.no_default_features(); + let graph: Graph = if let Some(manifest_path) = args.manifest_path { + let cmd = { + let mut cmd = krates::Cmd::new(); + if args.all_features { + cmd.all_features(); + } + if args.no_default_features { + cmd.no_default_features(); + } + if !args.features.is_empty() { + cmd.features(args.features); + } + cmd.manifest_path(manifest_path); + cmd + }; + + let mut builder = krates::Builder::new(); + if args.no_dev { + builder.ignore_kind(krates::DepKind::Dev, krates::Scope::All); } - if !args.features.is_empty() { - cmd.features(args.features); + + builder.build(cmd, krates::NoneFilter).unwrap() + } else if let Some(json) = args.json { + let mut builder = krates::Builder::new(); + if args.no_dev { + builder.ignore_kind(krates::DepKind::Dev, krates::Scope::All); } - cmd.manifest_path(args.manifest_path); - cmd - }; - let mut builder = krates::Builder::new(); - if args.no_dev { - builder.ignore_kind(krates::DepKind::Dev, krates::Scope::All); - } + let json = std::fs::read(json).expect("failed to read json"); + let md: krates::cm::Metadata = + serde_json::from_slice(&json).expect("failed to deserialize metadata from json"); - let graph: Graph = builder.build(cmd, krates::NoneFilter).unwrap(); + builder.build_with_metadata(md, krates::NoneFilter).unwrap() + } else { + panic!("must specify either --manifest-path or --json"); + }; let dot = krates::petgraph::dot::Dot::new(graph.graph()).to_string(); diff --git a/src/builder.rs b/src/builder.rs index b020701..90679a9 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1055,6 +1055,10 @@ impl Builder { // enabled explicitly on each edge let mut feature_stack: Vec<_> = rnode.features.iter().map(|s| s.as_str()).collect(); + // Apparently features can have cycles, so we need to ensure we don't enter an + // infinite loop https://github.com/EmbarkStudios/krates/issues/48 + let mut simple_features = std::collections::BTreeSet::new(); + let mut features: Vec = Vec::new(); while let Some(feat) = feature_stack.pop() { @@ -1073,7 +1077,9 @@ impl Builder { let (krate, feature) = match pf.feat() { Feature::Simple(feat) => { - feature_stack.push(feat); + if simple_features.insert(feat) { + feature_stack.push(feat); + } continue; }, Feature::Krate(_krate) => { continue; } diff --git a/tests/features.rs b/tests/features.rs index 7fc3a11..b42c123 100644 --- a/tests/features.rs +++ b/tests/features.rs @@ -218,3 +218,18 @@ fn ensure_weak_features_dont_add_edges() { ] ); } + +/// Ensures we handle cyclic features +#[test] +fn handles_cyclic_features() { + let mut cmd = krates::Cmd::new(); + cmd.manifest_path("tests/features/Cargo.toml") + .no_default_features() + .features(feats!(["cycle"])); + + let mut builder = krates::Builder::new(); + builder.include_targets([("x86_64-unknown-linux-musl", vec![])]); + let md: krates::Krates = builder.build(cmd, krates::NoneFilter).unwrap(); + + assert_features!(md, "features-galore", ["cycle", "midi", "subfeatcycle"]); +} diff --git a/tests/features/Cargo.toml b/tests/features/Cargo.toml index b0124c1..9696367 100644 --- a/tests/features/Cargo.toml +++ b/tests/features/Cargo.toml @@ -35,3 +35,5 @@ stream = ["reqest?/stream"] tls = ["tls-no-reqwest", "reqest?/rustls-tls"] tls-no-reqwest = ["rustls"] zlib = ["git/zlib-ng-compat", "reqest?/deflate"] +cycle = ["subfeatcycle", "midi"] +subfeatcycle = ["cycle"]