use config::SshDeviceConfiguration;
use errors::*;
use device::make_remote_app;
use platform::regular_platform::RegularPlatform;
use project::Project;
use std::fmt;
use std::fmt::{ Debug, Display };
use std::fmt::Formatter;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use utils::path_to_str;
use Build;
use Device;
use DeviceCompatibility;
use BuildBundle;
use Runnable;
pub struct SshDevice {
pub id: String,
pub conf: SshDeviceConfiguration,
}
impl SshDevice {
fn install_app(&self, project: &Project, build: &Build, runnable: &Runnable) -> Result<(BuildBundle, BuildBundle)> {
debug!("make_remote_app {}", runnable.id);
let build_bundle = make_remote_app(project, build, runnable)?;
trace!("make_remote_app {} done", runnable.id);
let remote_bundle = self.to_remote_bundle(&build_bundle)?;
trace!("Create remote dir: {:?}", remote_bundle.bundle_dir);
let _ = self.ssh_command()?
.arg("mkdir").arg("-p").arg(&remote_bundle.bundle_dir)
.status();
info!("Install {} to {}", runnable.id, self.id);
self.sync(&build_bundle.bundle_dir, &remote_bundle.bundle_dir)?;
self.sync(&build_bundle.lib_dir, &remote_bundle.lib_dir)?;
Ok((build_bundle, remote_bundle))
}
fn ssh_command(&self) -> Result<Command> {
let mut command = Command::new("ssh");
command.arg(format!("{}@{}", self.conf.username, self.conf.hostname));
if let Some(port) = self.conf.port {
command.arg("-p").arg(&format!("{}", port));
}
if atty::is(atty::Stream::Stdout) {
command.arg("-t").arg("-o").arg("LogLevel=QUIET");
}
Ok(command)
}
fn sync<FP: AsRef<Path>, TP: AsRef<Path>>(&self, from_path: FP, to_path: TP) -> Result<()> {
let mut command = Command::new("/usr/bin/rsync");
command.arg("-a").arg("-v");
if let Some(port) = self.conf.port {
command.arg(&*format!("ssh -p {}", port));
};
if !log_enabled!(::log::Level::Debug) {
command.stdout(::std::process::Stdio::null());
command.stderr(::std::process::Stdio::null());
}
command
.arg(&format!("{}/", path_to_str(&from_path.as_ref())?))
.arg(&format!("{}@{}:{}/", self.conf.username, self.conf.hostname, path_to_str(&to_path.as_ref())?));
debug!("Running {:?}", command);
if !command.status()?.success() {
bail!("Error syncing ssh directory ({:?})", command)
} else {
Ok(())
}
}
fn to_remote_bundle(&self, build_bundle: &BuildBundle) -> Result<BuildBundle> {
let remote_prefix = PathBuf::from(self.conf.path.clone()
.unwrap_or("/tmp".into()))
.join("dinghy");
build_bundle.replace_prefix_with(remote_prefix)
}
}
impl DeviceCompatibility for SshDevice {
fn is_compatible_with_regular_platform(&self, platform: &RegularPlatform) -> bool {
self.conf.platform.as_ref().map_or(false, |it| *it == platform.id)
}
}
impl Device for SshDevice {
fn clean_app(&self, build_bundle: &BuildBundle) -> Result<()> {
let status = self.ssh_command()?
.arg(&format!("rm -rf {}", path_to_str(&build_bundle.bundle_exe)?))
.status()?;
if !status.success() {
Err("test fail.")?
}
Ok(())
}
fn debug_app(&self, _project: &Project, _build: &Build, _args: &[&str], _envs: &[&str]) -> Result<BuildBundle> {
unimplemented!()
}
fn id(&self) -> &str {
&self.id
}
fn name(&self) -> &str {
&self.id
}
fn run_app(&self, project: &Project, build: &Build, args: &[&str], envs: &[&str]) -> Result<Vec<BuildBundle>> {
let mut build_bundles = vec![];
let args:Vec<String> = args.iter().map(|&a| ::shell_escape::escape(a.into()).to_string()).collect();
for runnable in &build.runnables {
info!("Install {:?}", runnable.id);
let (build_bundle, remote_bundle) = self.install_app(&project, &build, &runnable)?;
debug!("Installed {:?}", runnable.id);
let command = format!(
"cd '{}' ; {} RUST_BACKTRACE=1 DINGHY=1 LD_LIBRARY_PATH=\"{}:$LD_LIBRARY_PATH\" {} {} {}",
path_to_str(&remote_bundle.bundle_dir)?,
envs.join(" "),
path_to_str(&remote_bundle.lib_dir)?,
path_to_str(&remote_bundle.bundle_exe)?,
if build.build_args.compile_mode == ::cargo::core::compiler::CompileMode::Bench { "--bench" } else { "" },
args.join(" ")
);
trace!("Ssh command: {}", command);
info!("Run {} on {} ({:?})", runnable.id, self.id, build.build_args.compile_mode);
let status = self.ssh_command()?
.arg(&command)
.status()?;
if !status.success() {
Err("Test failed 🐛")?
}
build_bundles.push(build_bundle);
}
Ok(build_bundles)
}
fn start_remote_lldb(&self) -> Result<String> {
unimplemented!()
}
}
impl Debug for SshDevice {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
Ok(fmt.write_str(format!("Ssh {{ \"id\": \"{}\", \"hostname\": \"{}\", \"username\": \"{}\", \"port\": \"{}\" }}",
self.id,
self.conf.hostname,
self.conf.username,
self.conf.port.as_ref().map_or("none".to_string(), |it| it.to_string())).as_str())?)
}
}
impl Display for SshDevice {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(fmt, "{}", self.conf.hostname)
}
}