Skip to content

Commit 8005909

Browse files
committed
cleanup: reduce/combine a few ops
1 parent 37f3430 commit 8005909

File tree

4 files changed

+109
-47
lines changed

4 files changed

+109
-47
lines changed

CREDITS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Package: cargo-bashman
33
Version: 0.6.1
44
Target: x86_64-unknown-linux-gnu
5-
Generated: 2024-10-31 08:09:00 UTC
5+
Generated: 2024-10-31 19:36:40 UTC
66

77
| Package | Version | Author(s) | License |
88
| ---- | ---- | ---- | ---- |

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ assets = [
2626
name = "Cargo BashMan"
2727
bash-dir = "./release/completions"
2828
man-dir = "./release/man"
29-
credits-dir = "./"
3029

3130
[[package.metadata.bashman.switches]]
3231
short = "-h"

src/parse/metadata.rs

Lines changed: 100 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ struct RawNode<'a> {
9494

9595
#[serde(default)]
9696
#[serde(borrow)]
97+
#[serde(deserialize_with = "deserialize_node_deps")]
9798
/// # Dependency Details.
99+
///
100+
/// Note that dev-only dependencies are pruned during deserialization.
98101
deps: Vec<RawNodeDep<'a>>,
99102
}
100103

@@ -277,6 +280,18 @@ where D: Deserializer<'de> {
277280
)
278281
}
279282

283+
#[expect(clippy::unnecessary_wraps, reason = "We don't control this signature.")]
284+
/// # Deserialize: Node Dependencies.
285+
///
286+
/// This method won't fail, but dev-only dependencies will be pruned before
287+
/// return.
288+
fn deserialize_node_deps<'de, D>(deserializer: D) -> Result<Vec<RawNodeDep<'de>>, D::Error>
289+
where D: Deserializer<'de> {
290+
let mut out = <Vec<RawNodeDep>>::deserialize(deserializer).unwrap_or_default();
291+
out.retain(|nd| Dependency::FLAG_DEV != nd.dep_kinds & Dependency::FLAG_CONTEXT);
292+
Ok(out)
293+
}
294+
280295
#[expect(clippy::unnecessary_wraps, reason = "We don't control this signature.")]
281296
/// # Deserialize: Target.
282297
fn deserialize_target<'de, D>(deserializer: D) -> Result<bool, D::Error>
@@ -295,79 +310,67 @@ where D: Deserializer<'de> {
295310
/// Parse the raw JSON output from a `cargo metadata` command and return
296311
/// the relevant dependencies, deduped and sorted.
297312
///
298-
/// This is called by `Manifest::dependencies` twice, with and without
299-
/// features enabled to classify required/optional dependencies.
313+
/// This is called by `Manifest::dependencies` up to two times depending on
314+
/// whether or not the root has features (so we can figure out what is
315+
/// optional.)
300316
fn from_json(raw: &[u8], targeted: bool)
301317
-> Result<(BTreeSet<Dependency>, bool), BashManError> {
302-
let Raw { packages, mut resolve } = serde_json::from_slice(raw)
318+
let Raw { packages, resolve } = serde_json::from_slice(raw)
303319
.map_err(|e| BashManError::ParseCargoMetadata(e.to_string()))?;
304320

305-
// Remove dev-only dependencies from the node list.
306-
for node in &mut resolve.nodes {
307-
node.deps.retain(|nd| Dependency::FLAG_DEV != nd.dep_kinds & Dependency::FLAG_CONTEXT);
308-
}
309-
310-
// We don't want dev dependencies so have to build up the dependency tree
311-
// manually. Let's start by reorganizing the nodes.
321+
// As we don't care about dev-only dependencies, we have to manually
322+
// traverse the tree to figure out what is actually used. Before we do
323+
// that, let's build a map of parent/child pairs to make all those lookups
324+
// less terrible.
312325
let nice_resolve: HashMap<&str, Vec<&str>> = resolve.nodes.iter()
313-
.map(|n| {
314-
(
315-
n.id,
316-
n.deps.iter().map(|nd| nd.id).collect()
317-
)
318-
})
326+
.map(|n| (n.id, n.deps.iter().map(|nd| nd.id).collect()))
319327
.collect();
320328

321-
// Now let's build up a terrible queue to go back and forth for a while.
329+
// The traversal itself is simple if stupid: starting with the root
330+
// package, enqueue its dependencies, then each of their dependencies, and
331+
// so on until we run out of fresh references.
322332
let mut used: HashSet<&str> = HashSet::with_capacity(packages.len());
323333
let mut queue = vec![resolve.root];
324334
while let Some(next) = queue.pop() {
335+
// To prevent infinite recursion, only enqueue a given project's
336+
// dependencies the first time it comes up.
325337
if used.insert(next) {
326338
if let Some(next) = nice_resolve.get(next) {
327339
queue.extend_from_slice(next);
328340
}
329341
}
330342
}
331343

332-
// Now once through again to come up with the cumulative flags for all
333-
// dependencies as they might appear multiple times.
334-
let mut flags = HashMap::<&str, u8>::with_capacity(packages.len());
335-
for dep in resolve.nodes.iter().flat_map(|r| r.deps.iter()) {
344+
// Dependencies can be shared and used in different contexts, so let's
345+
// quickly calculate the combined values for each individual package.
346+
let mut flags = HashMap::<&str, u8>::with_capacity(used.len());
347+
for dep in resolve.nodes.iter().filter(|r| used.contains(r.id)).flat_map(|r| r.deps.iter()) {
336348
match flags.entry(dep.id) {
337349
Entry::Occupied(mut e) => { *e.get_mut() |= dep.dep_kinds; },
338350
Entry::Vacant(e) => { e.insert(dep.dep_kinds); },
339351
}
340352
}
341353

342-
// Perform a little cleanup.
343-
for flag in flags.values_mut() {
344-
// Strip target-specific flag if this search was targeted.
345-
if targeted { *flag &= ! Dependency::FLAG_TARGET; }
346-
347-
// The dev flag has served its purpose and can be removed.
348-
*flag &= ! Dependency::FLAG_DEV;
349-
}
350-
351-
// Does the root package have features?
352-
let features = packages.iter().find_map(|p|
353-
if p.id == resolve.root { Some(p.features) }
354-
else { None }
355-
).unwrap_or(false);
356-
357-
// Strip the root node; this is about crediting _others_. Haha.
358-
flags.remove(resolve.root);
354+
// The root node isn't needed in the output; the easiest way to filter it
355+
// out is to pretend it wasn't used.
359356
used.remove(resolve.root);
360357

358+
// The context flags aren't needed in the output either, and if we're doing
359+
// a targeted lookup there's no point noting target-specificness. For now,
360+
// let's just build the mask so it can be easily applied to the final
361+
// assignment.
362+
let antiflags =
363+
if targeted { Dependency::FLAG_CONTEXT | Dependency::FLAG_TARGET }
364+
else { Dependency::FLAG_CONTEXT };
365+
361366
// All that's left to do is compile an authoritative set of the used
362367
// dependencies in a friendly format.
368+
let mut features = false;
363369
let out: BTreeSet<Dependency> = packages.into_iter()
364370
.filter_map(|p|
365371
if used.contains(p.id) {
366372
// Figure out the context flags.
367-
let mut context = flags.get(p.id).copied().unwrap_or(0);
368-
if 0 == context & Dependency::FLAG_CONTEXT {
369-
context |= Dependency::FLAG_RUNTIME;
370-
}
373+
let mut context = flags.remove(p.id).map_or(0, |f| f & ! antiflags);
371374
if 0 == context & Dependency::FLAG_PLATFORM {
372375
context |= Dependency::FLAG_ANY;
373376
}
@@ -381,7 +384,12 @@ fn from_json(raw: &[u8], targeted: bool)
381384
context,
382385
})
383386
}
384-
else { None }
387+
else {
388+
// Before we throw away the root package, note whether or not
389+
// it had crate features.
390+
if p.id == resolve.root { features = p.features }
391+
None
392+
}
385393
)
386394
.collect();
387395

@@ -413,4 +421,53 @@ mod tests {
413421
assert!(raw1.iter().any(|d| d.context().contains("target-specific")));
414422
assert!(raw2.iter().all(|d| ! d.context().contains("target-specific")));
415423
}
424+
425+
#[test]
426+
/// # Node Dependency Kind Deserialization.
427+
///
428+
/// We're deviating quite a bit from the natural structure, so it's a good
429+
/// idea to verify the data gets crunched correctly.
430+
fn t_raw_dep_kind() {
431+
// No values.
432+
let kind: RawNodeDepKind = serde_json::from_str(r#"{"kind": null, "target": null}"#)
433+
.expect("Failed to deserialize RawNodeDepKind");
434+
assert!(! kind.dev);
435+
assert!(! kind.target);
436+
assert_eq!(kind.as_flag(), Dependency::FLAG_RUNTIME | Dependency::FLAG_ANY);
437+
438+
// Build.
439+
let kind: RawNodeDepKind = serde_json::from_str(r#"{"kind": "build", "target": null}"#)
440+
.expect("Failed to deserialize RawNodeDepKind");
441+
assert!(! kind.dev);
442+
assert!(! kind.target);
443+
assert_eq!(kind.as_flag(), Dependency::FLAG_RUNTIME | Dependency::FLAG_ANY);
444+
445+
// Build and Target.
446+
let kind: RawNodeDepKind = serde_json::from_str(r#"{"kind": "build", "target": "cfg(unix)"}"#)
447+
.expect("Failed to deserialize RawNodeDepKind");
448+
assert!(! kind.dev);
449+
assert!(kind.target);
450+
assert_eq!(kind.as_flag(), Dependency::FLAG_RUNTIME | Dependency::FLAG_TARGET);
451+
452+
// Target.
453+
let kind: RawNodeDepKind = serde_json::from_str(r#"{"kind": null, "target": "cfg(target_os = \"hermit\")"}"#)
454+
.expect("Failed to deserialize RawNodeDepKind");
455+
assert!(! kind.dev);
456+
assert!(kind.target);
457+
assert_eq!(kind.as_flag(), Dependency::FLAG_RUNTIME | Dependency::FLAG_TARGET);
458+
459+
// Dev.
460+
let kind: RawNodeDepKind = serde_json::from_str(r#"{"kind": "dev", "target": null}"#)
461+
.expect("Failed to deserialize RawNodeDepKind");
462+
assert!(kind.dev);
463+
assert!(! kind.target);
464+
assert_eq!(kind.as_flag(), Dependency::FLAG_DEV);
465+
466+
// Dev and target.
467+
let kind: RawNodeDepKind = serde_json::from_str(r#"{"kind": "dev", "target": "cfg(target_os = \"wasi\")"}"#)
468+
.expect("Failed to deserialize RawNodeDepKind");
469+
assert!(kind.dev);
470+
assert!(kind.target);
471+
assert_eq!(kind.as_flag(), Dependency::FLAG_DEV); // Dev takes priority.
472+
}
416473
}

src/parse/pkg.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,14 @@ impl Dependency {
7070
/// # Feature-Specific.
7171
pub(super) const FLAG_OPTIONAL: u8 = 0b0000_0001;
7272

73-
/// # Runtime Context.
73+
/// # Build/Runtime Context.
74+
///
75+
/// This flag is only used during deserialization.
7476
pub(super) const FLAG_RUNTIME: u8 = 0b0000_0010;
7577

76-
/// # Dev Context.
78+
/// # Dev/Testing Context.
79+
///
80+
/// This flag is only used during deserialization.
7781
pub(super) const FLAG_DEV: u8 = 0b0000_0100;
7882

7983
/// # Not Target-Specific.
@@ -83,6 +87,8 @@ impl Dependency {
8387
pub(super) const FLAG_TARGET: u8 = 0b0001_0000;
8488

8589
/// # Context Flags.
90+
///
91+
/// This flag is only used during deserialization.
8692
pub(super) const FLAG_CONTEXT: u8 = Self::FLAG_DEV | Self::FLAG_RUNTIME;
8793

8894
/// # Platform Flags.

0 commit comments

Comments
 (0)