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