workspacer_tree/
build_workspace_subtree.rs1crate::ix!();
3
4#[async_trait]
5pub trait BuildWorkspaceSubTree {
6 async fn build_workspace_subtree(
7 &self,
8 crate_name: &str,
9 levels: usize,
10 verbose: bool,
11 ) -> Result<WorkspaceDependencyTree, WorkspaceError>;
12}
13
14#[async_trait]
15impl<P, H> BuildWorkspaceSubTree for Workspace<P, H>
16where
17 P: From<PathBuf> + AsRef<Path> + Clone + Send + Sync + 'static,
18 H: CrateHandleInterface<P> + Send + Sync + 'static,
19{
20 async fn build_workspace_subtree(
23 &self,
24 crate_name: &str,
25 levels: usize,
26 verbose: bool,
27 ) -> Result<WorkspaceDependencyTree, WorkspaceError> {
28 use petgraph::visit::EdgeRef;
29 use petgraph::{graph::Graph, Direction};
30 use std::collections::BTreeMap;
31 use tracing::{debug, error, info, trace};
32
33 trace!(
34 "Entering build_workspace_subtree(crate_name={:?}, levels={:?}, verbose={:?})",
35 crate_name,
36 levels,
37 verbose
38 );
39
40 let all_crates = self.crates();
42 debug!("Number of crates in workspace: {}", all_crates.len());
43
44 let mut graph = Graph::<String, ()>::new();
46 let mut name_to_idx = BTreeMap::new();
47
48 let mut crate_info: Vec<(String, Option<SemverVersion>, PathBuf, Vec<String>)> = Vec::new();
50
51 for arc_h in all_crates {
53 let guard = arc_h.lock().await;
54 let nm = guard.name().to_string();
55 trace!("Collecting metadata for crate: {}", nm);
56
57 let ver = match guard.version() {
58 Ok(v) => Some(v),
59 Err(e) => {
60 error!("Error retrieving version for crate `{}`: {:?}", nm, e);
61 None
62 }
63 };
64
65 let crate_path: PathBuf = guard.as_ref().to_path_buf();
66 let internal_deps = guard.internal_dependencies().await.unwrap_or_else(|e| {
67 error!(
68 "Failed to retrieve internal_dependencies for crate `{}`: {:?}",
69 nm, e
70 );
71 vec![]
72 });
73
74 let node_idx = graph.add_node(nm.clone());
75 name_to_idx.insert(nm.clone(), node_idx);
76
77 crate_info.push((nm, ver, crate_path, internal_deps));
78 }
79
80 for (src_name, _ver, _path, deps) in &crate_info {
82 let src_idx = name_to_idx[src_name];
83 for dep_name in deps {
84 if let Some(&dst_idx) = name_to_idx.get(dep_name) {
85 graph.add_edge(src_idx, dst_idx, ());
86 } else {
87 debug!("Dependency `{}` not found in name_to_idx, ignoring", dep_name);
88 }
89 }
90 }
91
92 let root_idx = match name_to_idx.get(crate_name) {
94 Some(idx) => *idx,
95 None => {
96 error!(
97 "Requested root crate `{}` not found in this workspace",
98 crate_name
99 );
100 return Err(WorkspaceError::InvalidWorkspace {
101 invalid_workspace_path: self.as_ref().to_path_buf(),
102 });
103 }
104 };
105
106 let (root_ver, root_path) = crate_info_for_name(&crate_info, crate_name);
108 let mut root_node = WorkspaceTreeNode::new(crate_name.to_string(), root_ver, root_path);
109
110 if levels > 0 {
112 build_children_rec(
113 &graph,
114 &crate_info,
115 root_idx,
116 1,
117 levels,
118 &mut root_node,
119 );
120 }
121
122 let tree = WorkspaceDependencyTree::new(vec![root_node]);
124 info!(
125 "build_workspace_subtree => returning tree with 1 root: `{}`",
126 crate_name
127 );
128 Ok(tree)
129 }
130}
131
132#[cfg(test)]
133mod test_build_workspace_subtree {
134 use super::*;
135
136 async fn create_mock_workspace_with_deps() -> PathBuf {
140 let tmp = tempdir().expect("Failed to create temp dir");
141 let root_path = tmp.path().to_path_buf();
142
143 let cargo_toml_content = r#"
145 [workspace]
146 members = ["crateA","crateB"]
147 "#;
148 {
149 let mut f = std::fs::File::create(root_path.join("Cargo.toml"))
150 .expect("Failed to create workspace Cargo.toml");
151 f.write_all(cargo_toml_content.as_bytes())
152 .expect("Failed to write workspace Cargo.toml");
153 }
154
155 let crate_b = root_path.join("crateB");
157 std::fs::create_dir(&crate_b).expect("Failed to create crateB dir");
158 {
159 let mut f = std::fs::File::create(crate_b.join("Cargo.toml"))
160 .expect("Failed to create crateB Cargo.toml");
161 let contents = r#"
162 [package]
163 name = "crateB"
164 version = "0.1.0"
165
166 [dependencies]
167 # no internal dependencies
168 "#;
169 f.write_all(contents.as_bytes())
170 .expect("Failed to write crateB Cargo.toml");
171 }
172
173 let crate_a = root_path.join("crateA");
175 std::fs::create_dir(&crate_a).expect("Failed to create crateA dir");
176 {
177 let mut f = std::fs::File::create(crate_a.join("Cargo.toml"))
178 .expect("Failed to create crateA Cargo.toml");
179 let contents = r#"
180 [package]
181 name = "crateA"
182 version = "0.2.0"
183
184 [dependencies]
185 crateB = { path = "../crateB" }
186 "#;
187 f.write_all(contents.as_bytes())
188 .expect("Failed to write crateA Cargo.toml");
189 }
190
191 root_path
192 }
193
194 #[traced_test]
195 async fn test_build_workspace_subtree_crateA() {
196 let ws_path = create_mock_workspace_with_deps().await;
197 let workspace = Workspace::<PathBuf, CrateHandle>::new(&ws_path)
198 .await
199 .expect("Should build workspace from mock directory");
200
201 let tree_a = workspace
203 .build_workspace_subtree("crateA", 5, false)
204 .await
205 .expect("Should build subtree for crateA");
206
207 let rendered_a = tree_a.render(true, false);
208 info!("Subtree for crateA:\n{}", rendered_a);
209 assert!(
210 rendered_a.contains("crateA"),
211 "Should contain crateA in subtree"
212 );
213 assert!(
214 rendered_a.contains("crateB"),
215 "Should contain crateB in crateA's subtree"
216 );
217
218 let tree_b = workspace
220 .build_workspace_subtree("crateB", 5, false)
221 .await
222 .expect("Should build subtree for crateB");
223
224 let rendered_b = tree_b.render(true, false);
225 trace!("Subtree for crateB:\n{}", rendered_b);
226 assert!(
227 rendered_b.contains("crateB"),
228 "Should contain crateB in subtree"
229 );
230 assert!(
231 !rendered_b.contains("crateA"),
232 "crateB has no parent => should not contain crateA"
233 );
234 }
235}