1use crate::{e_workspace, prelude::*};
2use crate::{Example, TargetKind};
3use std::process::Output;
4fn run_cargo_with_opt_out(args: &[&str], manifest_path: &Path) -> Result<Output, Box<dyn Error>> {
9 let output = Command::new("cargo")
11 .args(args)
12 .arg("--manifest-path")
13 .arg(manifest_path)
14 .output()?;
15
16 let stderr_str = String::from_utf8_lossy(&output.stderr);
17 let workspace_error_marker = "current package believes it's in a workspace when it's not:";
18
19 if stderr_str.contains(workspace_error_marker) {
21 let original = fs::read_to_string(manifest_path)?;
23
24 if !original.contains("[workspace]") {
26 let patched = format!("{}\n[workspace]\n", original);
28 fs::write(manifest_path, &patched)?;
29
30 let patched_output = Command::new("cargo")
32 .args(args)
33 .arg("--manifest-path")
34 .arg(manifest_path)
35 .output()?;
36
37 fs::write(manifest_path, original)?;
39
40 return Ok(patched_output);
41 }
42 }
43
44 Ok(output)
45}
46
47pub fn collect_binaries(
51 prefix: &str,
52 manifest_path: &Path,
53 extended: bool,
54) -> Result<Vec<Example>, Box<dyn Error>> {
55 let output = run_cargo_with_opt_out(&["run", "--bin"], manifest_path)?;
57 let stderr_str = String::from_utf8_lossy(&output.stderr);
58
59 let bin_names = crate::parse_available(&stderr_str, "binaries");
60
61 let binaries = bin_names
62 .into_iter()
63 .map(|name| {
64 let display_name = if prefix.starts_with('$') {
65 format!(
66 "{} > binary > {} {}",
67 prefix,
68 name,
69 manifest_path.to_string_lossy().into_owned()
70 )
71 } else if extended {
72 format!("{} {}", prefix, name)
73 } else if prefix.starts_with("builtin") {
74 format!("builtin binary: {}", name)
75 } else {
76 format!("{} {}", prefix, name)
77 };
79
80 Example {
81 name: name.clone(),
82 display_name,
83 manifest_path: manifest_path.to_string_lossy().to_string(),
84 kind: if extended {
85 TargetKind::ExtendedBinary
86 } else {
87 TargetKind::Binary
88 },
89 extended,
90 }
91 })
92 .collect();
93
94 Ok(binaries)
95}
96
97pub fn collect_examples(
100 prefix: &str,
101 manifest_path: &Path,
102 extended: bool,
103) -> Result<Vec<Example>, Box<dyn Error>> {
104 let output = run_cargo_with_opt_out(&["run", "--example"], manifest_path)?;
106 let stderr_str = String::from_utf8_lossy(&output.stderr);
107 debug!("DEBUG: stderr (examples) = {:?}", stderr_str);
108
109 let names = crate::parse_available(&stderr_str, "examples");
110 debug!("DEBUG: example names = {:?}", names);
111
112 let examples = names
113 .into_iter()
114 .map(|name| {
115 let display_name = if prefix.starts_with('$') {
116 format!("{} > example > {}", prefix, name)
117 } else if extended {
118 format!("{} {}", prefix, name)
119 } else if prefix.starts_with("builtin") {
120 format!("builtin example: {}", name)
121 } else {
122 format!("{} {}", prefix, name)
123 };
125
126 Example {
127 name: name.clone(),
128 display_name,
129 manifest_path: manifest_path.to_string_lossy().to_string(),
130 kind: if extended {
131 TargetKind::ExtendedExample
132 } else {
133 TargetKind::Example
134 },
135 extended,
136 }
137 })
138 .collect();
139
140 Ok(examples)
141}
142
143pub fn collect_samples(
145 workspace_mode: bool,
146 manifest_infos: Vec<(String, PathBuf, bool)>,
147 __max_concurrency: usize,
148) -> Result<Vec<Example>, Box<dyn Error>> {
149 let mut all_samples = Vec::new();
150
151 #[cfg(feature = "concurrent")]
152 {
153 use threadpool::ThreadPool;
154 let pool = ThreadPool::new(__max_concurrency);
155 let (tx, rx) = mpsc::channel();
156
157 let start_concurrent = Instant::now();
158 for (prefix, manifest_path, extended) in manifest_infos {
159 let tx = tx.clone();
160 let prefix_clone = prefix.clone();
161 let manifest_clone = manifest_path.clone();
162 pool.execute(move || {
163 let mut results = Vec::new();
164 if let Ok(mut ex) = collect_examples(&prefix_clone, &manifest_clone, extended) {
165 results.append(&mut ex);
166 }
167 if let Ok(mut bins) = collect_binaries(&prefix_clone, &manifest_clone, extended) {
168 results.append(&mut bins);
169 }
170 let etargets =
171 crate::e_discovery::discover_targets(manifest_clone.parent().unwrap()).unwrap();
172 for target in etargets {
173 if target.extended && !results.iter().any(|r| r.name == target.name) {
175 let manifest_path = Path::new(&target.manifest_path);
176 let new_prefix = format!("examples/{}", &prefix_clone);
177 if let Ok(ex_ext) = collect_examples(&new_prefix, manifest_path, true) {
179 for ex in ex_ext {
180 if !results.iter().any(|r| r.name == ex.name) {
181 results.push(ex);
182 }
183 }
184 }
185
186 if let Ok(bins_ext) = collect_binaries(&new_prefix, manifest_path, true) {
188 for bin in bins_ext {
189 if !results.iter().any(|r| r.name == bin.name) {
190 results.push(bin);
191 }
192 }
193 }
194 }
206 }
207 tx.send(results).expect("Failed to send results");
211 });
212 }
213 drop(tx);
214 pool.join(); let duration_concurrent = start_concurrent.elapsed();
216 debug!(
217 "timing: {} threads took {:?}",
218 __max_concurrency, duration_concurrent
219 );
220
221 for samples in rx {
222 all_samples.extend(samples);
223 }
224 }
225
226 #[cfg(not(feature = "concurrent"))]
228 {
229 let start_seq = Instant::now();
230 for (prefix, manifest_path, extended) in manifest_infos {
231 if let Ok(mut ex) = collect_examples(&prefix, &manifest_path, extended) {
232 all_samples.append(&mut ex);
233 }
234 if let Ok(mut bins) = collect_binaries(&prefix, &manifest_path, extended) {
235 all_samples.append(&mut bins);
236 }
237 let manifest_path = Path::new(&manifest_path);
238 let new_prefix = format!("examples/{}", &prefix);
239 if let Ok(ex_ext) = collect_examples(&new_prefix, manifest_path, true) {
241 for ex in ex_ext {
242 if !all_samples.iter().any(|r| r.name == ex.name) {
243 all_samples.push(ex);
244 }
245 }
246 }
247
248 if let Ok(bins_ext) = collect_binaries(&new_prefix, manifest_path, true) {
250 for bin in bins_ext {
251 if !all_samples.iter().any(|r| r.name == bin.name) {
252 all_samples.push(bin);
253 }
254 }
255 }
256 }
257 let duration_seq = start_seq.elapsed();
258 debug!("timing: Sequential processing took {:?}", duration_seq);
259 }
260 let mut target_map: std::collections::HashMap<String, crate::Example> =
261 std::collections::HashMap::new();
262
263 for target in all_samples {
265 target_map
266 .entry(target.name.clone())
267 .and_modify(|existing| {
268 if workspace_mode {
269 if target.extended && !existing.extended {
271 *existing = target.clone();
272 }
273 } else {
274 if !target.extended && existing.extended {
276 *existing = target.clone();
277 }
278 }
279 })
280 .or_insert(target.clone());
281 }
282
283 let mut combined = Vec::new();
284 for target in target_map.into_values() {
285 combined.push(target);
286 }
287
288 Ok(combined)
289 }
291
292pub fn collect_all_targets(
293 use_workspace: bool,
294 max_concurrency: usize,
295) -> Result<Vec<Example>, Box<dyn std::error::Error>> {
296 use std::path::PathBuf;
297 let mut manifest_infos: Vec<(String, PathBuf, bool)> = Vec::new();
298
299 let bi = PathBuf::from(crate::locate_manifest(false)?);
301 let in_workspace = use_workspace || e_workspace::is_workspace_manifest(bi.as_path());
303
304 if in_workspace {
305 let ws = if use_workspace {
307 PathBuf::from(crate::locate_manifest(true)?)
308 } else {
309 bi.clone()
310 };
311 let ws_members =
313 e_workspace::get_workspace_member_manifest_paths(ws.as_path()).unwrap_or_default();
314 let member_displays: Vec<String> = ws_members
316 .iter()
317 .enumerate()
318 .map(|(i, (member, _))| format!("{}. {}", i + 1, member))
319 .collect();
320 println!(
322 "workspace: {} [{}]",
323 format_workspace(&ws, &bi),
324 member_displays.join(", ")
325 );
326 println!("package: {}", format_package(&bi));
328 manifest_infos.push(("-".to_string(), bi.clone(), false));
329 for (member, member_manifest) in ws_members {
330 manifest_infos.push((format!("${}", member), member_manifest, true));
331 }
332 } else {
333 println!("package: {}", format_package(&bi));
335 manifest_infos.push(("-".to_string(), bi.clone(), false));
336 }
337
338 let samples = collect_samples(use_workspace, manifest_infos, max_concurrency)?;
339 Ok(samples)
340}
341
342fn format_package(manifest: &Path) -> String {
344 let pkg = manifest
345 .parent()
346 .and_then(|p| p.file_name())
347 .map(|s| s.to_string_lossy())
348 .unwrap_or_default();
349 let file = manifest.file_name().unwrap().to_string_lossy();
350 format!("{}/{}", pkg, file)
351}
352
353fn format_workspace(ws_manifest: &Path, bi: &Path) -> String {
355 let ws_root = ws_manifest.parent().expect("No workspace root");
356 let ws_name = ws_root
357 .file_name()
358 .map(|s| s.to_string_lossy())
359 .unwrap_or_default();
360 let bi_pkg = bi
361 .parent()
362 .and_then(|p| p.file_name())
363 .map(|s| s.to_string_lossy())
364 .unwrap_or_default();
365 format!(
366 "{}/{}/{}",
367 ws_name,
368 bi_pkg,
369 ws_manifest.file_name().unwrap().to_string_lossy()
370 )
371}