@@ -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.
282297fn 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.)
300316fn 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}
0 commit comments