cargo_e/e_collect.rs
1use crate::e_target::{CargoTarget, TargetKind, TargetOrigin};
2use crate::{e_workspace, prelude::*};
3use std::collections::HashMap;
4use std::process::Output;
5/// Helper function that runs a Cargo command with a given manifest path.
6/// If it detects the workspace error, it temporarily patches the manifest (by
7/// appending an empty `[workspace]` table), re-runs the command, and then restores
8/// the original file.
9fn run_cargo_with_opt_out(args: &[&str], manifest_path: &Path) -> Result<Output, Box<dyn Error>> {
10 // Run the initial command.
11 let output = Command::new("cargo")
12 .args(args)
13 .arg("--manifest-path")
14 .arg(manifest_path)
15 .output()?;
16
17 let stderr_str = String::from_utf8_lossy(&output.stderr);
18 let workspace_error_marker = "current package believes it's in a workspace when it's not:";
19
20 // If we detect the workspace error, patch the manifest.
21 if stderr_str.contains(workspace_error_marker) {
22 // Backup the original manifest.
23 let original = fs::read_to_string(manifest_path)?;
24
25 // Only patch if the manifest doesn't already opt out.
26 if !original.contains("[workspace]") {
27 // Append an empty [workspace] table.
28 let patched = format!("{}\n[workspace]\n", original);
29 fs::write(manifest_path, &patched)?;
30
31 // Re-run the command with the patched manifest.
32 let patched_output = Command::new("cargo")
33 .args(args)
34 .arg("--manifest-path")
35 .arg(manifest_path)
36 .output()?;
37
38 // Restore the original manifest.
39 fs::write(manifest_path, original)?;
40
41 return Ok(patched_output);
42 }
43 }
44
45 Ok(output)
46}
47/// Parses available binaries and examples from a given input string (e.g., from stdin),
48/// and returns a vector of CargoTarget instances for each found binary and example.
49pub fn collect_stdin_available(
50 prefix: &str,
51 manifest_path: &Path,
52 input: &str,
53 extended: bool,
54) -> Vec<CargoTarget> {
55 let bin_names = crate::parse_available(input, "binaries");
56 let example_names = crate::parse_available(input, "examples");
57
58 let mut targets = Vec::new();
59
60 targets.extend(bin_names.into_iter().map(|name| {
61 let target_kind = if let Some(parent) = manifest_path.parent() {
62 if parent.file_name().and_then(|s| s.to_str()) == Some("src-tauri") {
63 TargetKind::ManifestTauri
64 } else if extended {
65 TargetKind::ExtendedBinary
66 } else {
67 TargetKind::Binary
68 }
69 } else if extended {
70 TargetKind::ExtendedBinary
71 } else {
72 TargetKind::Binary
73 };
74 let display_name = if prefix.starts_with('$') {
75 format!("{} > binary > {}", prefix, name)
76 } else if extended {
77 format!("{} {}", prefix, name)
78 } else if prefix.starts_with("builtin") {
79 format!("builtin binary: {}", name)
80 } else {
81 format!("{} {}", prefix, name)
82 };
83 CargoTarget {
84 name: name.clone(),
85 display_name,
86 manifest_path: manifest_path.into(),
87 kind: target_kind,
88 extended,
89 toml_specified: true,
90 origin: Some(TargetOrigin::TomlSpecified(manifest_path.to_path_buf())),
91 }
92 }));
93
94 targets.extend(example_names.into_iter().map(|name| {
95 let target_kind = if let Some(parent) = manifest_path.parent() {
96 if parent.file_name().and_then(|s| s.to_str()) == Some("src-tauri") {
97 TargetKind::ManifestTauri
98 } else if extended {
99 TargetKind::ExtendedExample
100 } else {
101 TargetKind::Example
102 }
103 } else if extended {
104 TargetKind::ExtendedExample
105 } else {
106 TargetKind::Example
107 };
108 let display_name = name.clone();
109 CargoTarget {
110 name: name.clone(),
111 display_name,
112 manifest_path: manifest_path.into(),
113 kind: target_kind,
114 extended,
115 toml_specified: true,
116 origin: Some(TargetOrigin::TomlSpecified(manifest_path.to_path_buf())),
117 }
118 }));
119
120 targets
121}
122/// Runs `cargo run --bin` (without specifying a binary name) so that Cargo prints an error with
123/// a list of available binary targets. Then parses that list to return a vector of Example instances,
124/// using the provided prefix.
125pub fn collect_binaries(
126 prefix: &str,
127 manifest_path: &Path,
128 extended: bool,
129) -> Result<Vec<CargoTarget>, Box<dyn Error>> {
130 // Run the Cargo command using our helper.
131 let output = run_cargo_with_opt_out(&["run", "--bin"], manifest_path)?;
132 let stderr_str = String::from_utf8_lossy(&output.stderr);
133 debug!("DEBUG {} {} ", prefix, manifest_path.display());
134 debug!("DEBUG: stderr (binaries) = {:?}", stderr_str);
135
136 let bin_names = crate::parse_available(&stderr_str, "binaries");
137
138 let binaries = bin_names
139 .into_iter()
140 .map(|name| {
141 let target_kind = if let Some(parent) = manifest_path.parent() {
142 if parent.file_name().and_then(|s| s.to_str()) == Some("src-tauri") {
143 TargetKind::ManifestTauri
144 } else if extended {
145 TargetKind::ExtendedBinary
146 } else {
147 TargetKind::Binary
148 }
149 } else if extended {
150 TargetKind::ExtendedBinary
151 } else {
152 TargetKind::Binary
153 };
154 // let target_kind = TargetKind::Binary;
155
156 // let display_name = name.clone();
157 // format!("builtin binary: {}", name);
158 let display_name = if prefix.starts_with('$') {
159 format!("{} > binary > {}", prefix, name)
160 } else if extended {
161 format!("{} {}", prefix, name)
162 } else if prefix.starts_with("builtin") {
163 format!("builtin binary: {}", name)
164 } else {
165 format!("{} {}", prefix, name)
166 // name.clone()
167 };
168 CargoTarget {
169 name: name.clone(),
170 display_name,
171 manifest_path: manifest_path.into(),
172 kind: target_kind,
173 extended,
174 toml_specified: true,
175 origin: Some(TargetOrigin::TomlSpecified(manifest_path.to_path_buf())),
176 }
177 })
178 .collect();
179
180 Ok(binaries)
181}
182
183/// Runs `cargo run --example` so that Cargo lists available examples,
184/// then parses the stderr output to return a vector of Example instances.
185pub fn collect_examples(
186 prefix: &str,
187 manifest_path: &Path,
188 extended: bool,
189) -> Result<Vec<CargoTarget>, Box<dyn Error>> {
190 // Run the Cargo command using our helper.
191 let output = run_cargo_with_opt_out(&["run", "--example"], manifest_path)?;
192 debug!("DEBUG {} {} ", prefix, manifest_path.display());
193 let stderr_str = String::from_utf8_lossy(&output.stderr);
194 debug!("DEBUG: stderr (examples) = {:?}", stderr_str);
195
196 let names = crate::parse_available(&stderr_str, "examples");
197 if names.len() > 0 {
198 debug!("DEBUG: example names = {:?}", names);
199 }
200
201 let examples = names
202 .into_iter()
203 .map(|name| {
204 // let target_kind = if extended {
205 // TargetKind::ExtendedExample
206 // } else {
207 // TargetKind::Example
208 // };
209 // let target_kind = TargetKind::Example;
210 let target_kind = if let Some(parent) = manifest_path.parent() {
211 if parent.file_name().and_then(|s| s.to_str()) == Some("src-tauri") {
212 TargetKind::ManifestTauri
213 } else if extended {
214 TargetKind::ExtendedExample
215 } else {
216 TargetKind::Example
217 }
218 } else if extended {
219 TargetKind::ExtendedExample
220 } else {
221 TargetKind::Example
222 };
223
224 let display_name = name.clone();
225 // format!("builtin example: {}", name);
226 // if prefix.starts_with('$') {
227 // format!("{} > example > {}", prefix, name)
228 // } else if extended {
229 // format!("{} {}", prefix, name)
230 // } else if prefix.starts_with("builtin") {
231 // format!("builtin example: {}", name)
232 // } else {
233 // format!("{} {}", prefix, name)
234 // };
235 let target = CargoTarget {
236 name: name.clone(),
237 display_name,
238 manifest_path: manifest_path.into(),
239 kind: target_kind,
240 extended,
241 toml_specified: true,
242 origin: Some(TargetOrigin::TomlSpecified(manifest_path.to_path_buf())),
243 };
244 target
245 })
246 .collect();
247
248 Ok(examples)
249}
250
251// --- Concurrent or sequential collection ---
252pub fn collect_samples(
253 _workspace_mode: bool,
254 manifest_infos: Vec<(String, PathBuf, bool)>,
255 __max_concurrency: usize,
256) -> Result<Vec<CargoTarget>, Box<dyn Error>> {
257 let mut all_samples = Vec::new();
258
259 #[cfg(feature = "concurrent")]
260 {
261 use threadpool::ThreadPool;
262 let pool = ThreadPool::new(__max_concurrency);
263 let (tx, rx) = mpsc::channel();
264
265 let start_concurrent = Instant::now();
266 for (_prefix, manifest_path, _extended) in manifest_infos {
267 let tx = tx.clone();
268 let manifest_clone = manifest_path.clone();
269 pool.execute(move || {
270 let prefix_clone = _prefix.clone(); // Define prefix_clone here
271 // 1. Collect the builtin stuff
272 let mut builtin_examples = Vec::new();
273 if let Ok(mut ex) =
274 collect_examples(&prefix_clone, &manifest_clone, _workspace_mode)
275 {
276 builtin_examples.append(&mut ex);
277 }
278 let mut builtin_bins = Vec::new();
279 if let Ok(mut bins) =
280 collect_binaries(&prefix_clone, &manifest_clone, _workspace_mode)
281 {
282 builtin_bins.append(&mut bins);
283 }
284 debug!(
285 "DEBUG: {} builtin examples = {:?}",
286 &manifest_clone.display(),
287 builtin_examples
288 );
289 debug!(
290 "DEBUG: {} builtin binaries = {:?}",
291 &manifest_clone.display(),
292 builtin_bins
293 );
294 // 2. Get the “official” runnable ones
295 let (runnable_bins, runnable_examples, benches, tests) =
296 crate::e_manifest::get_runnable_targets(&manifest_clone).unwrap_or_default();
297
298 // if nothing at all, skip
299 if runnable_bins.is_empty()
300 && runnable_examples.is_empty()
301 && builtin_bins.is_empty()
302 && builtin_examples.is_empty()
303 {
304 return;
305 }
306
307 // 1) Start with all the builtins in order
308 let mut bins = builtin_bins;
309
310 // 2) For each runnable:
311 // – if it matches a builtin by name, overwrite that slot
312 // – otherwise push to the end
313 for runnable in runnable_bins {
314 if let Some(idx) = bins.iter().position(|b| b.name == runnable.name) {
315 bins[idx] = runnable;
316 } else {
317 bins.push(runnable);
318 }
319 }
320
321 let mut examples = builtin_examples;
322 for runnable in runnable_examples {
323 if let Some(idx) = examples.iter().position(|e| e.name == runnable.name) {
324 examples[idx] = runnable;
325 } else {
326 examples.push(runnable);
327 }
328 }
329
330 // // 3. Merge bins: start with runnable, then override/insert builtin by name
331 // let mut bins_map: HashMap<_, _> = runnable_bins
332 // .into_iter()
333 // .map(|bin| (bin.name.clone(), bin))
334 // .collect();
335 // // for bin in builtin_bins {
336 // // bins_map.insert(bin.name.clone(), bin);
337 // // }
338 // let bins: Vec<_> = bins_map.into_values().collect();
339
340 // // 4. Same for examples
341 // let mut ex_map: HashMap<_, _> = runnable_examples
342 // .into_iter()
343 // .map(|ex| (ex.name.clone(), ex))
344 // .collect();
345 // // for ex in builtin_examples {
346 // // ex_map.insert(ex.name.clone(), ex);
347 // // }
348 // let examples: Vec<_> = ex_map.into_values().collect();
349
350 debug!("DEBUG: merged examples = {:#?}", examples);
351 // 5. Now combine everything
352 let all_targets = bins
353 .into_iter()
354 .chain(examples)
355 .chain(benches)
356 .chain(tests)
357 .collect::<Vec<_>>();
358 // let mut results = Vec::new();
359 // let prefix_clone = _prefix.clone(); // Define prefix_clone here
360 // if let Ok(mut ex) = collect_examples(&prefix_clone, &manifest_clone, _workspace_mode) {
361 // results.append(&mut ex);
362 // }
363 // if let Ok(mut bins) = collect_binaries(&prefix_clone, &manifest_clone, _workspace_mode) {
364 // results.append(&mut bins);
365 // }
366 // // Retrieve the runnable targets from the manifest.
367 // let (bins, examples, benches, tests) =
368 // crate::e_manifest::get_runnable_targets(&manifest_clone).unwrap_or_default();
369
370 // // If there are no examples or binaries, return early.
371 // if bins.is_empty() && examples.is_empty() {
372 // return;
373 // }
374
375 // // Combine all targets.
376 // let all_targets = bins
377 // .into_iter()
378 // .chain(results)
379 // .chain(examples)
380 // .chain(benches)
381 // .chain(tests)
382 // .collect::<Vec<_>>();
383 // log::debug!("DEBUG: all_targets = {:?}", all_targets);
384 // Now refine each target using the new pure method.
385 // If you implemented it as an associated method `refined()`:
386
387 let refined_targets: Vec<_> = all_targets
388 .into_iter()
389 .map(|t| CargoTarget::refined_target(&t))
390 .collect();
391 tx.send(refined_targets).expect("Failed to send results");
392 // tx.send(all_targets).expect("Failed to send results");
393 // let mut results = Vec::new();
394 // results.extend(bins);
395 // results.extend(examples);
396 // results.extend(benches);
397 // results.extend(tests);
398 // tx.send(results).expect("Failed to send results");
399
400 // let etargets =
401 // crate::e_discovery::discover_targets(manifest_clone.parent().unwrap()).unwrap();
402 // for target in etargets {
403 // let m = target.manifest_path.clone();
404 // // let manifest_path = Path::new(&m);
405 // // if target.name.contains("html") {
406 // // std::process::exit(0);
407 // // }
408 // if let Some(existing) = results.iter_mut().find(|r| r.name == target.name) {
409 // debug!("REPLACING {}",target.name);
410 // *existing = target;
411 // } else {
412 // debug!("ADDING {}",target.name);
413 // results.push(target);
414 // }
415
416 // // Check if the target is extended and that its name is not already present in results.
417 // // if target.extended {
418 // // let new_prefix = format!("examples/{}", &prefix_clone);
419 // // // Collect extended examples.
420 // // if let Ok(ex_ext) = collect_examples(&new_prefix, manifest_path, true) {
421 // // for ex in ex_ext {
422 // // if !results.iter().any(|r| r.name == ex.name) {
423 // // results.push(ex);
424 // // }
425 // // }
426 // // }
427
428 // // // Collect extended binaries.
429 // // if let Ok(bins_ext) = collect_binaries(&new_prefix, manifest_path, true) {
430 // // for bin in bins_ext {
431 // // if !results.iter().any(|r| r.name == bin.name) {
432 // // results.push(bin);
433 // // }
434 // // }
435 // // }
436 // // }
437 // }
438 // tx.send(results).expect("Failed to send results");
439 });
440 }
441 drop(tx);
442 pool.join(); // Wait for all tasks to finish.
443 let duration_concurrent = start_concurrent.elapsed();
444 debug!(
445 "timing: {} threads took {:?}",
446 __max_concurrency, duration_concurrent
447 );
448
449 for samples in rx {
450 debug!("DEBUG: samples = {:#?}", samples);
451 all_samples.extend(samples);
452 }
453 }
454
455 // Sequential fallback: process one manifest at a time.
456 #[cfg(not(feature = "concurrent"))]
457 {
458 let start_seq = Instant::now();
459 for (_prefix, manifest_path, _extended) in manifest_infos {
460 let (bins, examples, benches, tests) =
461 crate::e_manifest::get_runnable_targets(&manifest_path).unwrap_or_default();
462
463 // Merge all targets into one collection.
464 all_samples.extend(bins);
465 all_samples.extend(examples);
466 all_samples.extend(benches);
467 all_samples.extend(tests);
468
469 // if let Ok(mut ex) = collect_examples(&prefix, &manifest_path, extended) {
470 // all_samples.append(&mut ex);
471 // }
472 // if let Ok(mut bins) = collect_binaries(&prefix, &manifest_path, extended) {
473 // all_samples.append(&mut bins);
474 // }
475 // let manifest_path = Path::new(&manifest_path);
476 // let new_prefix = format!("examples/{}", &prefix);
477 // // Collect extended examples.
478 // if let Ok(ex_ext) = collect_examples(&new_prefix, manifest_path, true) {
479 // for ex in ex_ext {
480 // if !all_samples.iter().any(|r| r.name == ex.name) {
481 // all_samples.push(ex);
482 // }
483 // }
484 // }
485
486 // Collect extended binaries.
487 // if let Ok(bins_ext) = collect_binaries(&new_prefix, manifest_path, true) {
488 // for bin in bins_ext {
489 // if !all_samples.iter().any(|r| r.name == bin.name) {
490 // all_samples.push(bin);
491 // }
492 // }
493 // }
494 }
495 let duration_seq = start_seq.elapsed();
496 debug!("timing: Sequential processing took {:?}", duration_seq);
497 }
498 // First, refine all collected targets.
499 let initial_targets: Vec<_> = all_samples
500 .into_iter()
501 .map(|t| CargoTarget::refined_target(&t))
502 .collect();
503
504 // Build a HashMap keyed by (manifest, name) to deduplicate targets.
505 let mut targets_map: HashMap<(String, String), CargoTarget> = HashMap::new();
506 // for target in initial_targets.into_iter() {
507 // let key = CargoTarget::target_key(&target);
508 // targets_map.entry(key).or_insert(target);
509 // }
510 for target in initial_targets {
511 let key = CargoTarget::target_key(&target);
512 targets_map
513 .entry(key)
514 .and_modify(|existing| {
515 // inline match‐upgrading, wrapped in dbg! to print old, new and result
516 existing.kind = match (&existing.kind, &target.kind) {
517 (TargetKind::Example, TargetKind::ExtendedExample) => {
518 // you had Example, new is ExtendedExample → upgrade
519 target.kind.clone()
520 }
521 (TargetKind::Binary, TargetKind::ExtendedBinary) => target.kind.clone(),
522 (_, TargetKind::ManifestTauriExample) | (_, TargetKind::ManifestTauri) => {
523 // println!("DEBUG: Tauri {}", target.name);
524 target.kind.clone()
525 }
526 // your custom case: anything → Dioxius
527 (_, TargetKind::ManifestDioxus) => {
528 // println!("DEBUG: Dioxus {}", target.name);
529 target.kind.clone()
530 }
531 (_, TargetKind::ManifestDioxusExample) => {
532 // println!("DEBUG: DioxusExample {}", target.name);
533 target.kind.clone()
534 }
535 // else keep old
536 (old_kind, _) => old_kind.clone(),
537 };
538 })
539 .or_insert(target);
540 }
541
542 // Expand subprojects in place.
543 CargoTarget::expand_subprojects_in_place(&mut targets_map)?;
544
545 // Finally, collect all unique targets.
546 let refined_targets: Vec<CargoTarget> = targets_map.into_values().collect();
547 // Now do an additional deduplication pass based on origin and name.
548 let deduped_targets = crate::e_target::dedup_targets(refined_targets);
549 Ok(deduped_targets)
550 // return Ok(refined_targets);
551
552 // let mut target_map: std::collections::HashMap<String, CargoTarget> =
553 // std::collections::HashMap::new();
554
555 // // Group targets by name and choose one based on workspace_mode:
556 // for target in all_samples {
557 // target_map
558 // .entry(target.name.clone())
559 // .and_modify(|existing| {
560 // if workspace_mode {
561 // // In workspace mode, extended targets override builtins.
562 // if target.extended && !existing.extended {
563 // *existing = target.clone();
564 // }
565 // } else {
566 // // In normal mode, builtin targets (non-extended) override extended.
567 // if !target.extended && existing.extended {
568 // *existing = target.clone();
569 // }
570 // }
571 // })
572 // .or_insert(target.clone());
573 // }
574
575 // let mut combined = Vec::new();
576 // for target in target_map.into_values() {
577 // combined.push(target);
578 // }
579
580 // Ok(combined)
581 // Ok(all_samples)
582}
583
584use std::fs;
585use std::path::Path;
586
587/// Returns the "depth" of a path (number of components)
588pub fn path_depth(path: &Path) -> usize {
589 path.components().count()
590}
591
592/// Deduplicates targets by their canonicalized origin, gives priority to `toml_specified`,
593/// and ensures single-file targets override default binaries when appropriate.
594pub fn dedup_single_file_over_default_binary(targets: Vec<CargoTarget>) -> Vec<CargoTarget> {
595 let mut map: HashMap<Option<String>, CargoTarget> = HashMap::new();
596
597 for target in targets {
598 // Compute canonical origin key
599 let origin_key = target.origin.as_ref().and_then(|origin| match origin {
600 TargetOrigin::SingleFile(path)
601 | TargetOrigin::DefaultBinary(path)
602 | TargetOrigin::SubProject(path) => fs::canonicalize(path)
603 .ok()
604 .map(|p| p.to_string_lossy().into_owned()),
605 _ => None,
606 });
607
608 // Use Some(key) or None as map key
609 let entry_key = origin_key.clone();
610
611 if let Some(existing) = map.get(&entry_key) {
612 // 1) Prioritize toml_specified
613 if target.toml_specified && !existing.toml_specified {
614 map.insert(entry_key.clone(), target);
615 continue;
616 }
617 if !target.toml_specified && existing.toml_specified {
618 // keep existing
619 continue;
620 }
621
622 // 2) If one is SingleFile and other DefaultBinary, keep SingleFile
623 match (&target.origin, &existing.origin) {
624 (Some(TargetOrigin::SingleFile(_)), Some(TargetOrigin::DefaultBinary(_))) => {
625 map.insert(entry_key.clone(), target);
626 continue;
627 }
628 (Some(TargetOrigin::DefaultBinary(_)), Some(TargetOrigin::SingleFile(_))) => {
629 continue;
630 }
631 _ => {}
632 }
633
634 // 3) Otherwise, choose deeper manifest path
635 let current_depth = path_depth(&target.manifest_path);
636 let existing_depth = path_depth(&existing.manifest_path);
637 if current_depth > existing_depth {
638 map.insert(entry_key.clone(), target);
639 }
640 } else {
641 // No collision yet, insert
642 map.insert(entry_key, target);
643 }
644 }
645
646 map.into_values().collect()
647}
648
649#[cfg(feature = "concurrent")]
650pub fn collect_all_targets_parallel(
651 manifest_paths: Vec<Option<std::path::PathBuf>>,
652 use_workspace: bool,
653 max_concurrency: usize,
654 be_silent: bool,
655) -> Result<Vec<CargoTarget>, Box<dyn std::error::Error>> {
656 use std::sync::mpsc;
657 use threadpool::ThreadPool;
658
659 let pool = ThreadPool::new(max_concurrency);
660 let (tx, rx) = mpsc::channel();
661
662 for manifest_path in manifest_paths {
663 let tx = tx.clone();
664 pool.execute(move || {
665 let result = collect_all_targets(
666 manifest_path,
667 use_workspace,
668 max_concurrency,
669 be_silent,
670 true,
671 );
672 if let Ok(targets) = result {
673 tx.send(targets).expect("Failed to send targets");
674 }
675 });
676 }
677
678 drop(tx);
679 pool.join();
680
681 let mut all_targets = Vec::new();
682 for targets in rx {
683 all_targets.extend(targets);
684 }
685
686 Ok(all_targets)
687}
688
689pub fn collect_all_targets(
690 manifest_path: Option<std::path::PathBuf>,
691 use_workspace: bool,
692 max_concurrency: usize,
693 be_silent: bool,
694 print_parent: bool,
695) -> Result<Vec<CargoTarget>, Box<dyn std::error::Error>> {
696 use std::path::PathBuf;
697 let mut manifest_infos: Vec<(String, PathBuf, bool)> = Vec::new();
698
699 // Locate the package manifest in the current directory.
700 let bi = if let Some(ref path) = manifest_path {
701 path.clone()
702 } else {
703 PathBuf::from(crate::locate_manifest(false)?)
704 };
705 // We're in workspace mode if the flag is set or if the current Cargo.toml is a workspace manifest.
706 let in_workspace = use_workspace || e_workspace::is_workspace_manifest(bi.as_path());
707
708 // let manifest_path_cloned = manifest_path.clone();
709 // if manifest_path_cloned.is_some() {
710 // bi.clone_from(&manifest_path_cloned.as_ref().unwrap());
711 // }
712 // Print some info about the manifest and workspace state
713 if in_workspace {
714 // Use an explicit workspace manifest if requested; otherwise, assume the current Cargo.toml is the workspace manifest.
715 let ws = if manifest_path.is_some() {
716 manifest_path.unwrap()
717 } else if use_workspace {
718 PathBuf::from(crate::locate_manifest(true)?)
719 } else {
720 bi.clone()
721 };
722 // Get workspace members (each member's Cargo.toml) using your helper.
723 let ws_members =
724 e_workspace::get_workspace_member_manifest_paths(ws.as_path()).unwrap_or_default();
725 // Build a numbered list of member names (using just the member directory name).
726 let member_displays: Vec<String> = ws_members
727 .iter()
728 .enumerate()
729 .map(|(i, (member, _))| format!("{}. {}", i + 1, member))
730 .collect();
731 // Print the workspace line: "<workspace_root>/<package>/Cargo.toml [1. member, 2. member, ...]"
732 if !be_silent {
733 println!(
734 "workspace: {} [{}]",
735 format_workspace(&ws, &bi),
736 member_displays.join(", ")
737 );
738 // Always print the package line.
739 println!("package: {}", format_package(&bi));
740 }
741 manifest_infos.push(("-".to_string(), bi.clone(), false));
742 for (member, member_manifest) in ws_members {
743 debug!(" member: {}", format_package(&member_manifest));
744 manifest_infos.push((format!("${}", member), member_manifest, true));
745 }
746 } else {
747 // Not in workspace mode: simply print the package manifest.
748 if !be_silent {
749 if print_parent {
750 let parent_str = bi.display().to_string();
751 let parent_str = parent_str
752 .trim_start_matches(".\\")
753 .trim_start_matches("./");
754 let parent_str = parent_str
755 .replace("\\", std::path::MAIN_SEPARATOR_STR)
756 .replace("/", std::path::MAIN_SEPARATOR_STR);
757 println!("package: {}", parent_str);
758 } else {
759 println!("package: {}", format_package(&bi));
760 }
761 }
762 manifest_infos.push(("-".to_string(), bi.clone(), false));
763 }
764
765 let samples = collect_samples(use_workspace, manifest_infos, max_concurrency)?;
766 // Deduplicate targets: if a SingleFile and DefaultBinary share the same origin, keep only the SingleFile.
767 // let deduped_samples = dedup_single_file_over_default_binary(samples);
768 // Ok(deduped_samples)
769 Ok(samples)
770}
771
772/// Same as `collect_all_targets` but does not print workspace/package debug info.
773pub fn collect_all_targets_silent(
774 use_workspace: bool,
775 max_concurrency: usize,
776) -> Result<Vec<CargoTarget>, Box<dyn std::error::Error>> {
777 use std::path::PathBuf;
778 let mut manifest_infos: Vec<(String, PathBuf, bool)> = Vec::new();
779
780 // Locate the package manifest
781 let bi = PathBuf::from(crate::locate_manifest(false)?);
782 let in_workspace = use_workspace || e_workspace::is_workspace_manifest(bi.as_path());
783
784 if in_workspace {
785 let ws = if use_workspace {
786 PathBuf::from(crate::locate_manifest(true)?)
787 } else {
788 bi.clone()
789 };
790 let ws_members =
791 e_workspace::get_workspace_member_manifest_paths(ws.as_path()).unwrap_or_default();
792 manifest_infos.push(("-".to_string(), bi.clone(), false));
793 for (member, member_manifest) in ws_members {
794 manifest_infos.push((format!("${}", member), member_manifest, true));
795 }
796 } else {
797 manifest_infos.push(("-".to_string(), bi.clone(), false));
798 }
799
800 let samples = collect_samples(use_workspace, manifest_infos, max_concurrency)?;
801 let deduped_samples = dedup_single_file_over_default_binary(samples);
802 Ok(deduped_samples)
803}
804
805// Formats the package manifest as "<package>/Cargo.toml"
806fn format_package(manifest: &Path) -> String {
807 let pkg = manifest
808 .parent()
809 .and_then(|p| p.file_name())
810 .map(|s| s.to_string_lossy())
811 .unwrap_or_default();
812 let file = manifest.file_name().unwrap().to_string_lossy();
813 format!("{}/{}", pkg, file)
814}
815
816// Formats the workspace manifest as "<workspace_root>/<package>/Cargo.toml"
817fn format_workspace(ws_manifest: &Path, bi: &Path) -> String {
818 let ws_root = ws_manifest.parent().expect("No workspace root");
819 let ws_name = ws_root
820 .file_name()
821 .map(|s| s.to_string_lossy())
822 .unwrap_or_default();
823 let bi_pkg = bi
824 .parent()
825 .and_then(|p| p.file_name())
826 .map(|s| s.to_string_lossy())
827 .unwrap_or_default();
828 format!(
829 "{}/{}/{}",
830 ws_name,
831 bi_pkg,
832 ws_manifest.file_name().unwrap().to_string_lossy()
833 )
834}