cm_graph/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use directed_graph::DirectedGraph;
6use fidl_fuchsia_component_decl as fdecl;
7use std::fmt;
8
9#[cfg(fuchsia_api_level_at_least = "25")]
10macro_rules! get_source_dictionary {
11    ($decl:ident) => {
12        $decl.source_dictionary.as_ref()
13    };
14}
15#[cfg(fuchsia_api_level_less_than = "25")]
16macro_rules! get_source_dictionary {
17    ($decl:ident) => {
18        None
19    };
20}
21
22/// A node in the DependencyGraph. The first string describes the type of node and the second
23/// string is the name of the node.
24#[derive(Copy, Clone, Hash, Ord, Debug, PartialOrd, PartialEq, Eq)]
25pub enum DependencyNode<'a> {
26    Self_,
27    Child(&'a str, Option<&'a str>),
28    Collection(&'a str),
29    Environment(&'a str),
30    Capability(&'a str),
31}
32
33impl<'a> fmt::Display for DependencyNode<'a> {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            DependencyNode::Self_ => write!(f, "self"),
37            DependencyNode::Child(name, None) => write!(f, "child {}", name),
38            DependencyNode::Child(name, Some(collection)) => {
39                write!(f, "child {}:{}", collection, name)
40            }
41            DependencyNode::Collection(name) => write!(f, "collection {}", name),
42            DependencyNode::Environment(name) => write!(f, "environment {}", name),
43            DependencyNode::Capability(name) => write!(f, "capability {}", name),
44        }
45    }
46}
47
48fn ref_to_dependency_node<'a>(ref_: Option<&'a fdecl::Ref>) -> Option<DependencyNode<'a>> {
49    match ref_? {
50        fdecl::Ref::Self_(_) => Some(DependencyNode::Self_),
51        fdecl::Ref::Child(fdecl::ChildRef { name, collection }) => {
52            Some(DependencyNode::Child(name, collection.as_ref().map(|s| s.as_str())))
53        }
54        fdecl::Ref::Collection(fdecl::CollectionRef { name }) => {
55            Some(DependencyNode::Collection(name))
56        }
57        fdecl::Ref::Capability(fdecl::CapabilityRef { name }) => {
58            Some(DependencyNode::Capability(name))
59        }
60        fdecl::Ref::Framework(_)
61        | fdecl::Ref::Parent(_)
62        | fdecl::Ref::Debug(_)
63        | fdecl::Ref::VoidType(_) => None,
64        #[cfg(fuchsia_api_level_at_least = "HEAD")]
65        fdecl::Ref::Environment(_) => None,
66        _ => None,
67    }
68}
69
70// Generates the edges of the graph that are from a components `uses`.
71fn get_dependencies_from_uses<'a>(
72    strong_dependencies: &mut DirectedGraph<DependencyNode<'a>>,
73    decl: &'a fdecl::Component,
74) {
75    if let Some(uses) = decl.uses.as_ref() {
76        for use_ in uses.iter() {
77            #[allow(unused_variables)]
78            let (dependency_type, source, source_name, dict) = match use_ {
79                fdecl::Use::Service(u) => {
80                    (u.dependency_type, &u.source, &u.source_name, get_source_dictionary!(u))
81                }
82                fdecl::Use::Protocol(u) => {
83                    (u.dependency_type, &u.source, &u.source_name, get_source_dictionary!(u))
84                }
85                fdecl::Use::Directory(u) => {
86                    (u.dependency_type, &u.source, &u.source_name, get_source_dictionary!(u))
87                }
88                fdecl::Use::EventStream(u) => (
89                    Some(fdecl::DependencyType::Strong),
90                    &u.source,
91                    &u.source_name,
92                    None::<&String>,
93                ),
94                #[cfg(fuchsia_api_level_at_least = "HEAD")]
95                fdecl::Use::Runner(u) => (
96                    Some(fdecl::DependencyType::Strong),
97                    &u.source,
98                    &u.source_name,
99                    get_source_dictionary!(u),
100                ),
101                #[cfg(fuchsia_api_level_at_least = "HEAD")]
102                fdecl::Use::Config(u) => (
103                    Some(fdecl::DependencyType::Strong),
104                    &u.source,
105                    &u.source_name,
106                    get_source_dictionary!(u),
107                ),
108                // Storage can only be used from parent, which we don't track.
109                fdecl::Use::Storage(_) => continue,
110                _ => continue,
111            };
112            if dependency_type != Some(fdecl::DependencyType::Strong) {
113                continue;
114            }
115
116            let dependency_nodes = match &source {
117                Some(fdecl::Ref::Child(fdecl::ChildRef { name, collection })) => {
118                    vec![DependencyNode::Child(name, collection.as_ref().map(|s| s.as_str()))]
119                }
120                Some(fdecl::Ref::Self_(_)) => {
121                    #[cfg(fuchsia_api_level_at_least = "25")]
122                    if dict.as_ref().is_some() {
123                        if let Some(source_name) = source_name.as_ref() {
124                            vec![DependencyNode::Capability(source_name)]
125                        } else {
126                            vec![]
127                        }
128                    } else {
129                        vec![]
130                    }
131
132                    #[cfg(fuchsia_api_level_less_than = "25")]
133                    vec![]
134                }
135                Some(fdecl::Ref::Collection(fdecl::CollectionRef { name })) => {
136                    let mut nodes = vec![];
137                    if let Some(children) = decl.children.as_ref() {
138                        for child in children {
139                            if let Some(child_name) = child.name.as_ref() {
140                                nodes.push(DependencyNode::Child(child_name, Some(name)));
141                            }
142                        }
143                    }
144                    nodes
145                }
146                _ => vec![],
147            };
148
149            for source_node in dependency_nodes {
150                strong_dependencies.add_edge(source_node, DependencyNode::Self_);
151            }
152        }
153    }
154}
155
156fn get_dependencies_from_capabilities<'a>(
157    strong_dependencies: &mut DirectedGraph<DependencyNode<'a>>,
158    decl: &'a fdecl::Component,
159) {
160    if let Some(capabilities) = decl.capabilities.as_ref() {
161        for cap in capabilities {
162            match cap {
163                #[cfg(fuchsia_api_level_at_least = "25")]
164                fdecl::Capability::Dictionary(dictionary) => {
165                    if dictionary.source_path.as_ref().is_some() {
166                        if let Some(name) = dictionary.name.as_ref() {
167                            // If `source_path` is set that means the dictionary is provided by the program,
168                            // which implies a dependency from `self` to the dictionary declaration.
169                            strong_dependencies
170                                .add_edge(DependencyNode::Self_, DependencyNode::Capability(name));
171                        }
172                    }
173                }
174                fdecl::Capability::Storage(storage) => {
175                    if let (Some(name), Some(_backing_dir)) =
176                        (storage.name.as_ref(), storage.backing_dir.as_ref())
177                    {
178                        if let Some(source_node) = ref_to_dependency_node(storage.source.as_ref()) {
179                            strong_dependencies
180                                .add_edge(source_node, DependencyNode::Capability(name));
181                        }
182                    }
183                }
184                _ => continue,
185            }
186        }
187    }
188}
189
190fn get_dependencies_from_environments<'a>(
191    strong_dependencies: &mut DirectedGraph<DependencyNode<'a>>,
192    decl: &'a fdecl::Component,
193) {
194    if let Some(environment) = decl.environments.as_ref() {
195        for environment in environment {
196            if let Some(name) = &environment.name {
197                let target = DependencyNode::Environment(name);
198                if let Some(debugs) = environment.debug_capabilities.as_ref() {
199                    for debug in debugs {
200                        if let fdecl::DebugRegistration::Protocol(o) = debug {
201                            if let Some(source_node) = ref_to_dependency_node(o.source.as_ref()) {
202                                strong_dependencies.add_edge(source_node, target);
203                            }
204                        }
205                    }
206                }
207                if let Some(runners) = environment.runners.as_ref() {
208                    for runner in runners {
209                        if let Some(source_node) = ref_to_dependency_node(runner.source.as_ref()) {
210                            strong_dependencies.add_edge(source_node, target);
211                        }
212                    }
213                }
214                if let Some(resolvers) = environment.resolvers.as_ref() {
215                    for resolver in resolvers {
216                        if let Some(source_node) = ref_to_dependency_node(resolver.source.as_ref())
217                        {
218                            strong_dependencies.add_edge(source_node, target);
219                        }
220                    }
221                }
222            }
223        }
224    }
225}
226
227fn get_dependencies_from_children<'a>(
228    strong_dependencies: &mut DirectedGraph<DependencyNode<'a>>,
229    decl: &'a fdecl::Component,
230) {
231    if let Some(children) = decl.children.as_ref() {
232        for child in children {
233            if let Some(name) = child.name.as_ref() {
234                if let Some(env) = child.environment.as_ref() {
235                    let source = DependencyNode::Environment(env.as_str());
236                    let target = DependencyNode::Child(name, None);
237                    strong_dependencies.add_edge(source, target);
238                }
239            }
240        }
241    }
242}
243
244fn get_dependencies_from_collections<'a>(
245    strong_dependencies: &mut DirectedGraph<DependencyNode<'a>>,
246    decl: &'a fdecl::Component,
247    dynamic_children: &Vec<(&'a str, &'a str)>,
248) {
249    if let Some(collections) = decl.collections.as_ref() {
250        for collection in collections {
251            if let Some(env) = collection.environment.as_ref() {
252                if let Some(name) = collection.name.as_ref() {
253                    let source = DependencyNode::Environment(env.as_str());
254                    let target = DependencyNode::Collection(name.as_str());
255                    strong_dependencies.add_edge(source, target);
256
257                    for child_name in dynamic_children_in_collection(dynamic_children, &name) {
258                        strong_dependencies
259                            .add_edge(source, DependencyNode::Child(child_name, Some(&name)));
260                    }
261                }
262            }
263        }
264    }
265}
266
267fn find_offer_node<'a>(
268    offer: &'a fdecl::Offer,
269    source: Option<&'a fdecl::Ref>,
270    source_name: &'a Option<String>,
271    _dictionary: Option<&'a String>,
272) -> Option<DependencyNode<'a>> {
273    if source.is_none() {
274        return None;
275    }
276
277    match source? {
278        fdecl::Ref::Child(fdecl::ChildRef { name, collection }) => {
279            Some(DependencyNode::Child(name, collection.as_ref().map(|s| s.as_str())))
280        }
281        #[cfg(fuchsia_api_level_at_least = "25")]
282        fdecl::Ref::Self_(_) if _dictionary.is_some() => {
283            let root_dict = _dictionary.unwrap().split('/').next().unwrap();
284            return Some(DependencyNode::Capability(root_dict));
285        }
286        fdecl::Ref::Self_(_) => {
287            if let Some(source_name) = source_name {
288                #[cfg(fuchsia_api_level_at_least = "25")]
289                if matches!(offer, fdecl::Offer::Dictionary(_)) {
290                    return Some(DependencyNode::Capability(source_name));
291                }
292                if matches!(offer, fdecl::Offer::Storage(_)) {
293                    return Some(DependencyNode::Capability(source_name));
294                }
295            }
296
297            Some(DependencyNode::Self_)
298        }
299        fdecl::Ref::Collection(fdecl::CollectionRef { name }) => {
300            Some(DependencyNode::Collection(name))
301        }
302        fdecl::Ref::Capability(fdecl::CapabilityRef { name }) => {
303            Some(DependencyNode::Capability(name))
304        }
305        fdecl::Ref::Parent(_) | fdecl::Ref::Framework(_) | fdecl::Ref::VoidType(_) => None,
306        _ => None,
307    }
308}
309
310fn dynamic_children_in_collection<'a>(
311    dynamic_children: &Vec<(&'a str, &'a str)>,
312    collection: &'a str,
313) -> Vec<&'a str> {
314    dynamic_children
315        .iter()
316        .filter_map(|(n, c)| if *c == collection { Some(*n) } else { None })
317        .collect()
318}
319
320fn add_offer_edges<'a>(
321    source_node: Option<DependencyNode<'a>>,
322    target_node: Option<DependencyNode<'a>>,
323    strong_dependencies: &mut DirectedGraph<DependencyNode<'a>>,
324    dynamic_children: &Vec<(&'a str, &'a str)>,
325) {
326    if source_node.is_none() {
327        return;
328    }
329
330    let source = source_node.unwrap();
331
332    if let DependencyNode::Collection(name) = source {
333        for child_name in dynamic_children_in_collection(dynamic_children, &name) {
334            strong_dependencies.add_edge(
335                DependencyNode::Child(&child_name, Some(&name)),
336                DependencyNode::Collection(name),
337            );
338        }
339    }
340
341    if target_node.is_none() {
342        return;
343    }
344
345    let target = target_node.unwrap();
346
347    strong_dependencies.add_edge(source, target);
348
349    if let DependencyNode::Collection(name) = target {
350        for child_name in dynamic_children_in_collection(dynamic_children, &name) {
351            strong_dependencies.add_edge(source, DependencyNode::Child(child_name, Some(&name)));
352        }
353    }
354}
355
356// Populates a dependency graph of a component's `offers`.
357fn get_dependencies_from_offers<'a>(
358    strong_dependencies: &mut DirectedGraph<DependencyNode<'a>>,
359    decl: &'a fdecl::Component,
360    dynamic_children: &Vec<(&'a str, &'a str)>,
361    dynamic_offers: &'a Vec<fdecl::Offer>,
362) {
363    let mut all_offers: Vec<&fdecl::Offer> = vec![];
364
365    for dynamic_offer in dynamic_offers.iter() {
366        all_offers.push(dynamic_offer);
367    }
368
369    if let Some(offers) = decl.offers.as_ref() {
370        for offer in offers.iter() {
371            all_offers.push(offer);
372        }
373    }
374
375    for offer in all_offers {
376        let (source_node, target_node) = match offer {
377            fdecl::Offer::Protocol(o) => {
378                let source_node = find_offer_node(
379                    offer,
380                    o.source.as_ref(),
381                    &o.source_name,
382                    get_source_dictionary!(o),
383                );
384
385                if let Some(fdecl::DependencyType::Strong) = o.dependency_type.as_ref() {
386                    let target_node = find_offer_node(offer, o.target.as_ref(), &None, None);
387
388                    (source_node, target_node)
389                } else {
390                    continue;
391                }
392            }
393            #[cfg(fuchsia_api_level_at_least = "25")]
394            fdecl::Offer::Dictionary(o) => {
395                let source_node = find_offer_node(
396                    offer,
397                    o.source.as_ref(),
398                    &o.source_name,
399                    get_source_dictionary!(o),
400                );
401
402                if let Some(fdecl::DependencyType::Strong) = o.dependency_type.as_ref() {
403                    let target_node = find_offer_node(offer, o.target.as_ref(), &None, None);
404
405                    (source_node, target_node)
406                } else {
407                    continue;
408                }
409            }
410            fdecl::Offer::Directory(o) => {
411                let source_node = find_offer_node(
412                    offer,
413                    o.source.as_ref(),
414                    &o.source_name,
415                    get_source_dictionary!(o),
416                );
417                if let Some(fdecl::DependencyType::Strong) = o.dependency_type.as_ref() {
418                    let target_node = find_offer_node(offer, o.target.as_ref(), &None, None);
419
420                    (source_node, target_node)
421                } else {
422                    continue;
423                }
424            }
425            fdecl::Offer::Service(o) => {
426                let source_node = find_offer_node(
427                    offer,
428                    o.source.as_ref(),
429                    &o.source_name,
430                    get_source_dictionary!(o),
431                );
432
433                #[cfg(fuchsia_api_level_at_least = "HEAD")]
434                {
435                    if &fdecl::DependencyType::Strong
436                        == o.dependency_type.as_ref().unwrap_or(&fdecl::DependencyType::Strong)
437                    {
438                        let target_node = find_offer_node(offer, o.target.as_ref(), &None, None);
439
440                        (source_node, target_node)
441                    } else {
442                        continue;
443                    }
444                }
445
446                #[cfg(fuchsia_api_level_less_than = "HEAD")]
447                {
448                    let target_node = find_offer_node(offer, o.target.as_ref(), &None, None);
449
450                    (source_node, target_node)
451                }
452            }
453            fdecl::Offer::Storage(o) => {
454                let source_node = find_offer_node(offer, o.source.as_ref(), &o.source_name, None);
455
456                let target_node = find_offer_node(offer, o.target.as_ref(), &None, None);
457
458                (source_node, target_node)
459            }
460            fdecl::Offer::Runner(o) => {
461                let source_node = find_offer_node(
462                    offer,
463                    o.source.as_ref(),
464                    &o.source_name,
465                    get_source_dictionary!(o),
466                );
467
468                let target_node = find_offer_node(offer, o.target.as_ref(), &None, None);
469
470                (source_node, target_node)
471            }
472            fdecl::Offer::Resolver(o) => {
473                let source_node = find_offer_node(
474                    offer,
475                    o.source.as_ref(),
476                    &o.source_name,
477                    get_source_dictionary!(o),
478                );
479
480                let target_node = find_offer_node(offer, o.target.as_ref(), &None, None);
481
482                (source_node, target_node)
483            }
484            fdecl::Offer::Config(o) => {
485                let source_node = find_offer_node(
486                    offer,
487                    o.source.as_ref(),
488                    &o.source_name,
489                    get_source_dictionary!(o),
490                );
491
492                let target_node = find_offer_node(offer, o.target.as_ref(), &None, None);
493
494                (source_node, target_node)
495            }
496            _ => continue,
497        };
498
499        add_offer_edges(source_node, target_node, strong_dependencies, dynamic_children);
500    }
501}
502
503// Populates a dependency graph of the disjoint sets of graphs.
504pub fn generate_dependency_graph<'a>(
505    strong_dependencies: &mut DirectedGraph<DependencyNode<'a>>,
506    decl: &'a fdecl::Component,
507    dynamic_children: &Vec<(&'a str, &'a str)>,
508    dynamic_offers: &'a Vec<fdecl::Offer>,
509) {
510    get_dependencies_from_uses(strong_dependencies, decl);
511    get_dependencies_from_offers(strong_dependencies, decl, dynamic_children, dynamic_offers);
512    get_dependencies_from_capabilities(strong_dependencies, decl);
513    get_dependencies_from_environments(strong_dependencies, decl);
514    get_dependencies_from_children(strong_dependencies, decl);
515    get_dependencies_from_collections(strong_dependencies, decl, dynamic_children);
516}