diff --git a/src/config.rs b/src/config.rs index d409c3a..8c79472 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,16 +1,27 @@ -use serde::{Deserialize, Serialize}; +use serde::Deserialize; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Clone)] pub struct Remote { + pub name: String, pub host: String, pub user: String, pub ssh_port: u16, pub temp_dir: String, } +#[derive(Debug, Deserialize)] +struct OptionRemote { + pub name: Option, + pub host: String, + pub user: String, + pub ssh_port: Option, + pub temp_dir: Option, +} + impl Default for Remote { fn default() -> Self { Self { + name: String::new(), host: String::new(), user: String::new(), ssh_port: 22, @@ -19,25 +30,46 @@ impl Default for Remote { } } +impl From for Remote { + fn from(minimal_remote: OptionRemote) -> Self { + let default = Remote::default(); + let name = minimal_remote.name.unwrap_or(default.name); + let ssh_port = minimal_remote.ssh_port.unwrap_or(default.ssh_port); + let temp_dir = minimal_remote.temp_dir.unwrap_or(default.temp_dir); + Remote { + name, + host: minimal_remote.host, + user: minimal_remote.user, + ssh_port, + temp_dir, + } + } +} + +impl<'de> Deserialize<'de> for Remote { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + OptionRemote::deserialize(deserializer).map(Self::from) + } +} + impl Remote { pub fn user_host(&self) -> String { format!("{}@{}", self.user, self.host) } } -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize)] pub struct Config { - pub remote: Remote, + #[serde(rename = "remote")] + remotes: Option>, } impl Config { pub fn new(project_dir: &std::path::Path) -> Result { - let mut conf = config::Config::default(); - - conf.merge(config::File::from_str( - toml::to_string(&Config::default()).unwrap().as_str(), - config::FileFormat::Toml, - ))?; + let mut conf = config::Config::new(); if let Some(config_file) = xdg::BaseDirectories::with_prefix("cargo-remote") .ok() @@ -53,4 +85,37 @@ impl Config { conf.try_into() } + + pub fn get_remote(&self, opts: &crate::Opts) -> Option { + let remotes: Vec<_> = self.remotes.clone().unwrap_or_default(); + let config_remote = match &opts.remote_name { + Some(remote_name) => remotes + .into_iter() + .find(|remote| remote.name == *remote_name), + None => remotes.into_iter().next(), + }; + + let blueprint_remote = match ( + config_remote, + opts.remote_host.is_some() && opts.remote_user.is_some(), + ) { + (Some(config_remote), _) => config_remote, + (None, true) => Remote::default(), + (None, false) => return None, + }; + + Some(Remote { + name: opts.remote_name.clone().unwrap_or(blueprint_remote.name), + host: opts.remote_host.clone().unwrap_or(blueprint_remote.host), + user: opts.remote_user.clone().unwrap_or(blueprint_remote.user), + ssh_port: opts + .remote_ssh_port + .clone() + .unwrap_or(blueprint_remote.ssh_port), + temp_dir: opts + .remote_temp_dir + .clone() + .unwrap_or(blueprint_remote.temp_dir), + }) + } } diff --git a/src/main.rs b/src/main.rs index 7c0abe4..ff9f8b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,93 +12,91 @@ const PROGRESS_FLAG: &str = "--info=progress2"; #[derive(StructOpt, Debug)] #[structopt(name = "cargo-remote", bin_name = "cargo")] -enum Opts { - #[structopt(name = "remote")] - Remote { - #[structopt(short = "r", long = "remote", help = "Remote ssh build server")] - remote: Option, - - #[structopt( - short = "b", - long = "build-env", - help = "Set remote environment variables. RUST_BACKTRACE, CC, LIB, etc. ", - default_value = "RUST_BACKTRACE=1" - )] - build_env: String, - - #[structopt( - short = "d", - long = "rustup-default", - help = "Rustup default (stable|beta|nightly)", - default_value = "stable" - )] - rustup_default: String, - - #[structopt( - short = "e", - long = "env", - help = "Environment profile. default_value = /etc/profile", - default_value = "/etc/profile" - )] - env: String, - - #[structopt( - short = "c", - long = "copy-back", - help = "Transfer the target folder or specific file from that folder back to the local machine" - )] - copy_back: Option>, - - #[structopt( - long = "no-copy-lock", - help = "don't transfer the Cargo.lock file back to the local machine" - )] - no_copy_lock: bool, - - #[structopt( - long = "manifest-path", - help = "Path to the manifest to execute", - default_value = "Cargo.toml", - parse(from_os_str) - )] - manifest_path: PathBuf, - - #[structopt( - short = "h", - long = "transfer-hidden", - help = "Transfer hidden files and directories to the build server" - )] - hidden: bool, - - #[structopt(help = "cargo command that will be executed remotely")] - command: String, - - #[structopt( - help = "cargo options and flags that will be applied remotely", - name = "remote options" - )] - options: Vec, - }, +pub struct Opts { + #[structopt(short = "r", long = "remote", help = "Remote ssh build server")] + remote_name: Option, + + #[structopt(short, long, help = "")] + remote_host: Option, + + #[structopt(short, long, help = "")] + remote_user: Option, + + #[structopt(short, long, help = "")] + remote_ssh_port: Option, + + #[structopt(short, long, help = "")] + remote_temp_dir: Option, + + #[structopt( + short = "b", + long = "build-env", + help = "Set remote environment variables. RUST_BACKTRACE, CC, LIB, etc. ", + default_value = "RUST_BACKTRACE=1" + )] + build_env: String, + + #[structopt( + short = "d", + long = "rustup-default", + help = "Rustup default (stable|beta|nightly)", + default_value = "stable" + )] + rustup_default: String, + + #[structopt( + short = "e", + long = "env", + help = "Environment profile. default_value = /etc/profile", + default_value = "/etc/profile" + )] + env: String, + + #[structopt( + short = "c", + long = "copy-back", + help = "Transfer the target folder or specific file from that folder back to the local machine" + )] + copy_back: Option>, + + #[structopt( + long = "no-copy-lock", + help = "don't transfer the Cargo.lock file back to the local machine" + )] + no_copy_lock: bool, + + #[structopt( + long = "manifest-path", + help = "Path to the manifest to execute", + default_value = "Cargo.toml", + parse(from_os_str) + )] + manifest_path: PathBuf, + + #[structopt( + short = "h", + long = "transfer-hidden", + help = "Transfer hidden files and directories to the build server" + )] + hidden: bool, + + #[structopt(help = "cargo command that will be executed remotely")] + command: String, + + #[structopt( + help = "cargo options and flags that will be applied remotely", + name = "remote options" + )] + options: Vec, } fn main() { simple_logger::init().unwrap(); - let Opts::Remote { - remote, - build_env, - rustup_default, - env, - copy_back, - no_copy_lock, - manifest_path, - hidden, - command, - options, - } = Opts::from_args(); + let opts = Opts::from_args(); let mut metadata_cmd = cargo_metadata::MetadataCommand::new(); - metadata_cmd.manifest_path(manifest_path).no_deps(); + metadata_cmd.manifest_path(&opts.manifest_path).no_deps(); let project_metadata = metadata_cmd.exec().unwrap(); let project_dir = project_metadata.workspace_root; @@ -112,12 +110,32 @@ fn main() { } }; - let build_server = remote.unwrap_or_else(|| conf.remote.user_host()); + let remote = match conf.get_remote(&opts) { + Some(remote) => remote, + None => { + error!("No remote build server was defined (use config file or the --remote flags)"); + exit(4); + } + }; + + let build_server = remote.user_host(); + + let Opts { + build_env, + rustup_default, + env, + copy_back, + no_copy_lock, + hidden, + command, + options, + .. + } = opts; // generate a unique build path by using the hashed project dir as folder on the remote machine let mut hasher = DefaultHasher::new(); project_dir.hash(&mut hasher); - let build_path = format!("{}/{}/", conf.remote.temp_dir, hasher.finish()); + let build_path = format!("{}/{}/", remote.temp_dir, hasher.finish()); info!("Transferring sources to build server."); // transfer project to build server @@ -126,7 +144,7 @@ fn main() { .arg("-a".to_owned()) .arg("--delete") .arg("--compress") - .arg(format!("-e ssh -p {}", conf.remote.ssh_port)) + .arg(format!("-e ssh -p {}", remote.ssh_port)) .arg("--info=progress2") .arg("--exclude") .arg("target"); @@ -163,7 +181,7 @@ fn main() { info!("Starting build process."); let output = Command::new("ssh") - .arg(format!("-p {}", conf.remote.ssh_port)) + .arg(format!("-p {}", remote.ssh_port)) .arg("-t") .arg(&build_server) .arg(build_command) @@ -183,7 +201,7 @@ fn main() { .arg("-a") .arg("--delete") .arg("--compress") - .arg(format!("-e ssh -p {}", conf.remote.ssh_port)) + .arg(format!("-e ssh -p {}", remote.ssh_port)) .arg("--info=progress2") .arg(format!( "{}:{}/target/{}", @@ -213,7 +231,7 @@ fn main() { .arg("-a") .arg("--delete") .arg("--compress") - .arg(format!("-e ssh -p {}", conf.remote.ssh_port)) + .arg(format!("-e ssh -p {}", remote.ssh_port)) .arg("--info=progress2") .arg(format!("{}:{}/Cargo.lock", build_server, build_path)) .arg(format!("{}/Cargo.lock", project_dir.to_string_lossy()))