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}