1pub mod cli;
2
3use cargo_metadata::TargetKind;
4use cli::CliArgs;
5use serde::{Deserialize, Serialize};
6use std::{fs::File, path::Path};
7
8#[derive(Debug, Deserialize, Serialize)]
9pub struct FileConfig {
10 pub host: Option<String>,
11 pub port: Option<u16>,
12 pub username: Option<String>,
13 pub password: Option<String>,
14 pub key: Option<String>,
15 pub target_folder: String,
16 pub target: Option<String>,
17 pub remote_folder: Option<String>,
18 pub profile: Option<String>,
19}
20
21impl FileConfig {
22 pub fn from_file(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
23 let file = File::open(path)?;
24 let reader = std::io::BufReader::new(file);
25 let mut config: FileConfig = serde_json::from_reader(reader)?;
26
27 config.resolve_relative_paths(path)?;
29
30 Ok(config)
31 }
32
33 fn resolve_relative_paths(&mut self, config_file_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
35 let config_dir = config_file_path.parent()
37 .ok_or("Config file has no parent directory")?;
38
39 let target_path = Path::new(&self.target_folder);
41 if target_path.is_relative() {
42 self.target_folder = config_dir.join(target_path)
43 .canonicalize()
44 .unwrap_or_else(|_| config_dir.join(target_path))
45 .to_string_lossy()
46 .to_string();
47
48 if !self.target_folder.ends_with('/') && !self.target_folder.ends_with('\\') {
50 self.target_folder.push('/');
51 }
52 }
53
54 if let Some(ref key_path) = self.key {
56 if key_path.starts_with("~/") {
57 if let Ok(home_dir) = std::env::var("HOME") {
59 let expanded_path = key_path.replacen("~", &home_dir, 1);
60 self.key = Some(expanded_path);
61 }
62 } else {
63 let key_path_obj = Path::new(key_path);
64 if key_path_obj.is_relative() {
65 let resolved_key = config_dir.join(key_path_obj)
66 .canonicalize()
67 .unwrap_or_else(|_| config_dir.join(key_path_obj));
68 self.key = Some(resolved_key.to_string_lossy().to_string());
69 }
70 }
71 }
72
73 Ok(())
74 }
75
76 pub fn cli_overide(&mut self, cli: &CliArgs) {
77 if let Some(host) = &cli.host {
78 self.host = Some(host.clone());
79 }
80 if let Some(port) = cli.port {
81 self.port = Some(port);
82 }
83 if let Some(username) = &cli.username {
84 self.username = Some(username.clone());
85 }
86 if let Some(password) = &cli.password {
87 self.password = Some(password.clone());
88 }
89 if let Some(key) = &cli.key {
90 self.key = Some(key.clone());
91 }
92 if let Some(remote_folder) = &cli.remote_folder {
93 self.remote_folder = Some(remote_folder.clone());
94 }
95 if !cli.profile.is_empty() {
96 self.profile = Some(cli.profile.clone());
97 }
98 if let Some(target_folder) = &cli.target_folder {
99 self.target_folder = target_folder.clone();
100 self.target = None;
101 }
102 }
103
104 pub fn create_empty(&self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
105 let file = File::create(path)?;
106 serde_json::to_writer_pretty(file, self)?;
107 Ok(())
108 }
109}
110
111#[derive(Debug)]
112pub struct Config {
113 pub host: String,
114 pub port: u16,
115 pub username: String,
116 pub password: String,
117 pub key: Option<String>,
118 pub target_folder: String,
119 pub target: String,
120 pub remote_folder: String,
121 pub debug: bool,
122 pub build: bool,
123 pub binaries: Vec<String>,
124 pub profile: String,
125}
126
127impl Config {
128 pub fn from_cli(cli: CliArgs, file_config: FileConfig) -> Self {
129 let host = cli.host.unwrap_or(file_config.host.unwrap());
130 let port = cli.port.unwrap_or(file_config.port.unwrap_or(22));
131 let username = cli.username.unwrap_or(file_config.username.unwrap());
132 let password = cli.password.unwrap_or(file_config.password.unwrap_or_default());
133 let key = cli.key.or(file_config.key.clone());
134 let target_folder = cli.target_folder.unwrap_or(file_config.target_folder);
135 let target = cli.target.unwrap_or(file_config.target.unwrap());
136 let remote_folder = cli.remote_folder.unwrap_or(
137 file_config
138 .remote_folder
139 .unwrap_or("/tmp/binaries/".to_string()),
140 );
141 let debug = cli.debug;
142
143 let binaries = if cli.binaries.is_empty() {
145 match detect_cargo_binaries() {
146 Ok(auto_binaries) if !auto_binaries.is_empty() => {
147 println!("Auto-detected binaries: {}", auto_binaries.join(", "));
148 auto_binaries
149 }
150 Ok(_) => {
151 eprintln!("Warning: No binaries detected automatically. Please specify --binaries manually.");
152 cli.binaries
153 }
154 Err(e) => {
155 eprintln!("Warning: Failed to auto-detect binaries ({}). Please specify --binaries manually.", e);
156 cli.binaries
157 }
158 }
159 } else {
160 cli.binaries
161 };
162
163 let profile = file_config.profile.unwrap_or_else(|| cli.profile.clone());
164
165 let build = if cli.build {
167 true
168 } else {
169 should_auto_build(&target_folder, &target, &binaries, &profile, debug)
171 };
172
173 Config {
174 host,
175 port,
176 username,
177 password,
178 key,
179 target_folder,
180 target,
181 remote_folder,
182 debug,
183 build,
184 binaries,
185 profile,
186 }
187 }
188}
189
190pub fn detect_cargo_binaries() -> Result<Vec<String>, Box<dyn std::error::Error>> {
192 let metadata = cargo_metadata::MetadataCommand::new().exec()?;
193
194 let root_package = metadata.root_package()
196 .ok_or("No root package found - are you in a Cargo project?")?;
197
198 let binaries: Vec<String> = root_package
200 .targets
201 .iter()
202 .filter_map(|target| {
203 if target.kind.iter().any(|k| k == &TargetKind::Bin) {
205 Some(target.name.clone())
206 } else {
207 None
208 }
209 })
210 .collect();
211 Ok(binaries)
212}
213
214fn should_auto_build(target_folder: &str, target: &str, binaries: &[String], profile: &str, debug: bool) -> bool {
216 if !Path::new("Cargo.toml").exists() {
218 return false; }
220
221 let mut binary_path = std::path::PathBuf::from(target_folder);
223 if !target.is_empty() {
224 binary_path.push(target);
225 }
226
227 if debug {
229 binary_path.push("debug");
230 } else {
231 binary_path.push(profile);
232 }
233
234 for binary_name in binaries {
236 let full_binary_path = binary_path.join(binary_name);
237
238 if !full_binary_path.exists() {
240 println!("Auto-build: Binary '{}' not found at {}", binary_name, full_binary_path.display());
241 return true;
242 }
243
244 if let (Ok(cargo_meta), Ok(binary_meta)) = (
246 std::fs::metadata("Cargo.toml"),
247 std::fs::metadata(&full_binary_path)
248 ) {
249 if let (Ok(cargo_time), Ok(binary_time)) = (
250 cargo_meta.modified(),
251 binary_meta.modified()
252 ) {
253 if cargo_time > binary_time {
254 println!("Auto-build: Binary '{}' is older than Cargo.toml", binary_name);
255 return true;
256 }
257 }
258 }
259 }
260
261 println!("Auto-build: Binaries appear up-to-date, skipping build");
263 false
264}