1use std::{convert::Infallible, os::unix::process::CommandExt, path::PathBuf, process::Command};
2
3use cargo_metadata::{MetadataCommand, camino::Utf8Path};
4use walkdir::WalkDir;
5
6use crate::args::Args;
7
8mod args;
9mod conf;
10
11type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
12
13pub fn attach(args: Args) -> Result<Infallible> {
14 if args.release && args.debug {
15 return Err("the release and debug flags may not be used together".into());
16 }
17
18 if args.bin.is_some() && args.example.is_some() {
19 return Err("the bin and example options may not be used together".into());
20 }
21
22 let metadata = (MetadataCommand::new().no_deps().exec())
23 .map_err(|e| e.to_string().trim().to_owned())?;
25
26 let Some(package) = metadata.root_package() else {
27 return Err("could not determine which package to use binaries from".into());
28 };
29
30 let config = if args.probe_args.is_empty() || args.target.is_none() {
31 conf::load_config(package)?
32 } else {
33 None
34 };
35
36 let binary_names = if let Some(binary) = args.bin.or(args.example) {
37 vec![binary]
38 } else {
39 let targets = package.targets.iter();
40 let binaries = targets.filter(|t| t.is_bin() || t.is_example());
41
42 binaries.map(|t| t.name.to_owned()).collect()
43 };
44
45 let build_mode = if args.release {
46 Some("release".to_owned())
47 } else if args.debug {
48 Some("debug".to_owned())
49 } else {
50 None
51 };
52
53 let build_target = if args.target.is_none() {
54 if let Some(config) = &config {
55 conf::find_build_target(config)
56 } else {
57 None
58 }
59 } else {
60 args.target
61 };
62
63 let probe_args = if args.probe_args.is_empty() {
64 if let Some(config) = &config {
65 conf::find_probe_args(config, build_target.as_ref())?
66 } else {
67 vec![]
68 }
69 } else {
70 args.probe_args
71 };
72
73 let Some(executable) = find_executable(
74 &metadata.target_directory,
75 binary_names,
76 build_mode,
77 build_target,
78 ) else {
79 return Err(format!("no matching executable found for package {}", package.name).into());
80 };
81
82 let error = Command::new("probe-rs")
83 .arg("attach")
84 .args(probe_args)
85 .arg(executable)
86 .exec();
87
88 Err(error.into())
89}
90
91fn find_executable(
92 base: &Utf8Path,
93 binary_names: Vec<String>,
94 build_mode: Option<String>,
95 build_target: Option<String>,
96) -> Option<PathBuf> {
97 let target_files = WalkDir::new(base)
98 .max_depth(4)
99 .into_iter()
100 .filter_map(|e| e.ok())
101 .filter(|e| e.file_type().is_file());
102
103 let executables = target_files
104 .filter(|e| binary_names.iter().any(|x| x.as_str() == e.file_name()))
105 .filter(|e| {
106 let path = e.path().strip_prefix(base).unwrap().parent().unwrap();
107
108 let matches_build_mode = build_mode
109 .as_deref()
110 .is_none_or(|m| path.iter().any(|c| c == m));
111
112 let matches_target_triple = build_target
113 .as_deref()
114 .is_none_or(|t| path.iter().any(|c| c == t));
115
116 matches_build_mode && matches_target_triple
117 });
118
119 executables
120 .max_by_key(|e| e.metadata().unwrap().modified().unwrap())
121 .map(|e| e.into_path())
122}