Compare commits

...

49 Commits

Author SHA1 Message Date
Sebastian Geisler a7680240a1 link readme in manifest 3 years ago
Sebastian Geisler c33655c6ee fix warnings and duplicated code 3 years ago
ZSchoen 2a63be383b updated README 3 years ago
ZSchoen 22e98e4555 fixed ssh-port & rsync address + added env to Remote 3 years ago
ZSchoen 9955b75ae8 added structopt informations for RemoteOpts 3 years ago
ZSchoen 3328e485ad removed user + fixed Cli & moved RemoteOpts into own struct + fixed port args 3 years ago
ZSchoen 11019a2799 Option.. => Partial.. 3 years ago
ZSchoen 3c6d182109 updated config format & cli 3 years ago
ZSchoen d6de41410b basic config rework 3 years ago
Bastian Köcher 5a3f93abe1 Update src/main.rs 5 years ago
Bastian Köcher fcf2629d85 Hash project path to generate unique remote build dir
This is useful when you have the same project checked out multiple times
and don't want to share the build dir (because you work on different
stuff).
5 years ago
Sebastian a2739b8917
Merge pull request #9 from paritytech/td-fix
Bring back tty allocation, to clean sub-processes.
5 years ago
Sebastian Geisler 86c5234623 add MacOS instructions to README
fixes #10
5 years ago
Sebastian 083feac84e
Merge pull request #3 from sgeisler/progress
Fix progress on MacOS
5 years ago
Sebastian Geisler aa65cb50bf os dependant rsync progress flag 5 years ago
Tomasz Drwięga 0b8cfa9022
Bring back tty allocation, to clean sub-processes. 5 years ago
Sebastian Geisler aeefaac97e remove leftover helper files 5 years ago
Denis S. Soldatov aka General-Beck 7a6ac0f4cb
Merge pull request #2 from paritytech/fix_dir_issues
Fix dir issues
5 years ago
Denis S. Soldatov aka General-Beck b0668598a1
Update main.rs
error -> info Setting the remote dir name like the local.
5 years ago
Denis S. Soldatov aka General-Beck 75aa64371f
Update README.md
fix git clone url
5 years ago
Denis P cee42131b6
takes root dir name if there's no project name in the manifest 5 years ago
Denis S. Soldatov aka General-Beck b5e18db60c
Merge pull request #1 from paritytech/td-fix
Fix build.
5 years ago
Denis S. Soldatov aka General-Beck e92b5bc51f
Update main.rs
update project name
5 years ago
Tomasz Drwięga 0855793590
Fix build. 5 years ago
Tomasz Drwięga d322c48878
Merge remote-tracking branch 'gbeck/master' 5 years ago
Sebastian 707d34043c
Merge pull request #7 from tomusdrw/master
More practical workflow.
5 years ago
Denis S. Soldatov aka General-Beck 1c0cad2735 project_name = project_dir 5 years ago
Denis S. Soldatov aka General-Beck 21ac116fa5 project dir 5 years ago
Denis S. Soldatov aka General-Beck 060ebe8889 Use project dir name for remote 5 years ago
Denis_P 118c671c6f Update README.md 5 years ago
Denis_P 0b0aa76351 Update README.md 5 years ago
Tomasz Drwięga 700369a5f9
Change to error if code is not present (Unix kill-by-signal). 5 years ago
Tomasz Drwięga deaa457925
Change error code for Cargo.lock 5 years ago
Tomasz Drwięga 09db6b2aa9
Allow transferring only a subset of files. 5 years ago
Tomasz Drwięga bd053415e1
Propagate error code. 5 years ago
Tomasz Drwięga fad8aef22f
Copy back the lock file. 5 years ago
Denis S. Soldatov aka General-Beck 351ce94df3 Merge branch 'env' into 'master'
addition options

See merge request General-Beck/cargo-remote!1
5 years ago
Denis S. Soldatov aka General-Beck feb6d92f4a add rustup version
env default /etc/profile
5 years ago
Denis S. Soldatov aka General-Beck 78aabdd621 source /etc/profile 5 years ago
Denis S. Soldatov aka General-Beck 04ff506dfc update PATH to cargo 5 years ago
Sebastian Geisler 68c9ec563c add CI config 5 years ago
Sebastian Geisler c83b18b6f1
Merge pull request #6 from fisherdarling/update-to-2018
Updated to 2018 edition. Refactored some sections.
5 years ago
Fisher Darling 992e1fcc87 Update 2018, fixed breaking changes. 5 years ago
Fisher Darling ae0a3d8d6e Updated to 2018, changed some sections of code. 5 years ago
Sebastian Geisler 41549dcec1
bump version for publishing 6 years ago
Sebastian Geisler f28ff627d8
Merge pull request #1 from ProgVal/rsync-compress
Use compression when rsyncing target/ back to the client.
6 years ago
Valentin Lorentz 8730bd4d49 Use compression when rsyncing target/ back to the client. 6 years ago
Sebastian Geisler 2d1f21680c bump version 7 years ago
Sebastian Geisler c937f69177 search for config files at multiple places 7 years ago

@ -0,0 +1,4 @@
language: rust
rust:
- stable
- nightly

@ -4,15 +4,18 @@ description = "Cargo subcommand to build rust projects remotely"
keywords = ["remote", "build"]
categories = ["command-line-utilities", "development-tools::build-utils", "development-tools::cargo-plugins"]
maintenance = { status = "experimental" }
version = "0.1.0"
version = "0.2.0"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
license = "MIT"
repository = "https://github.com/sgeisler/cargo-remote"
edition = "2018"
readme = "README.md"
[dependencies]
structopt = "0.1.6"
structopt-derive = "0.1.6"
cargo_metadata = "0.4.1"
structopt = "0.2.18"
cargo_metadata = "0.8.0"
log = "0.4.1"
simple_logger = "0.4.0"
toml = "0.4.5"
simple_logger = "1.3.0"
xdg = "2.1.0"
serde = { version = "1.0", features = ["derive"] }
config = "0.11"

@ -25,46 +25,67 @@ the same rust version and have the same processor architecture. On the client `s
and `rsync` need to be installed.
If you want to pass remote flags you have to end the options/flags section using
`--`. E.g. to build in release mode and copy back the result use:
`--`. E.g. to build in release mode and copy back the result use:
```bash
cargo remote -c -- build --release
```
### Configuration
You can place a file called `.cargo-remote.toml` in the same directory as your
`Cargo.toml`. There you can define a default remote build host and user. It can
be overridden by the `-r` flag.
You can place a config file called `.cargo-remote.toml` in the same directory as your
`Cargo.toml` or at `~/.config/cargo-remote/cargo-remote.toml`. There you can define a
default remote build host and user. It can be overridden by the `-r` flag.
Example `.cargo-remote.toml`:
Example config file:
```toml
remote = "builds@myserver"
[[remote]]
name = "myRemote" # Not needed for a single remote
host = "myUser@myServer" # Could also be a ssh config entry
ssh_port = 42 # defaults to 22
temp_dir = "~/rust" # Default is "~/remote-builds"
env = "~/.profile" # Default is "/etc/profile"
```
### Flags and options
```
cargo-remote
```
USAGE:
cargo remote [FLAGS] [OPTIONS] <command>
cargo remote [FLAGS] [OPTIONS] <command> [remote options]...
FLAGS:
-c, --copy-back transfer the target folder back to the local machine
--help Prints help information
-h, --transfer-hidden transfer hidden files and directories to the build server
-h, --transfer-hidden Transfer hidden files and directories to the build server
--no-copy-lock don't transfer the Cargo.lock file back to the local machine
-V, --version Prints version information
OPTIONS:
--manifest-path <manifest_path> Path to the manifest to execute
-r, --remote <remote> remote ssh build server
-b, --build-env <build_env> Set remote environment variables. RUST_BACKTRACE, CC, LIB, etc. [default:
RUST_BACKTRACE=1]
-c, --copy-back <copy_back> Transfer the target folder or specific file from that folder back to the
local machine
-e, --env <env> Environment profile. default_value = /etc/profile
-H, --remote-host <host> Remote ssh build server with user or the name of the ssh entry
--manifest-path <manifest_path> Path to the manifest to execute [default: Cargo.toml]
-r, --remote <name> The name of the remote specified in the config
-d, --rustup-default <rustup_default> Rustup default (stable|beta|nightly) [default: stable]
-p, --remote-ssh-port <ssh_port> The ssh port to communicate with the build server
-t, --remote-temp-dir <temp_dir> The directory where cargo builds the project
ARGS:
<command> cargo command that will be executed remotely
<command> cargo command that will be executed remotely
<remote options>... cargo options and flags that will be applied remotely
```
## How to install
```bash
git clone https://github.com/sgeisler/cargo-remote.git
cd cargo-remote
cargo install
git clone https://github.com/sgeisler/cargo-remote
cargo install --path cargo-remote/
```
### MacOS Problems
It was reported that the `rsync` version shipped with MacOS doesn't support the progress flag and thus fails when
`cargo-remote` tries to use it. You can install a newer version by running
```bash
brew install rsync
```
See also [#10](https://github.com/sgeisler/cargo-remote/issues/10).

@ -0,0 +1,107 @@
use serde::Deserialize;
#[derive(Debug, Clone)]
pub struct Remote {
pub name: String,
pub host: String,
pub ssh_port: u16,
pub temp_dir: String,
pub env: String,
}
#[derive(Debug, Deserialize)]
struct PartialRemote {
pub name: Option<String>,
pub host: String,
pub ssh_port: Option<u16>,
pub temp_dir: Option<String>,
pub env: Option<String>,
}
impl Default for Remote {
fn default() -> Self {
Self {
name: String::new(),
host: String::new(),
ssh_port: 22,
temp_dir: "~/remote-builds".to_string(),
env: "/etc/profile".to_string(),
}
}
}
impl From<PartialRemote> for Remote {
fn from(minimal_remote: PartialRemote) -> 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);
let env = minimal_remote.env.unwrap_or(default.env);
Remote {
name,
host: minimal_remote.host,
ssh_port,
temp_dir,
env,
}
}
}
impl<'de> Deserialize<'de> for Remote {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
PartialRemote::deserialize(deserializer).map(Self::from)
}
}
#[derive(Debug, Default, Deserialize)]
pub struct Config {
#[serde(rename = "remote")]
remotes: Option<Vec<Remote>>,
}
impl Config {
pub fn new(project_dir: &std::path::Path) -> Result<Self, config::ConfigError> {
let mut conf = config::Config::new();
if let Some(config_file) = xdg::BaseDirectories::with_prefix("cargo-remote")
.ok()
.and_then(|base| base.find_config_file("cargo-remote.toml"))
{
conf.merge(config::File::from(config_file))?;
}
let project_config = project_dir.join(".cargo-remote.toml");
if project_config.is_file() {
conf.merge(config::File::from(project_config))?;
}
conf.try_into()
}
pub fn get_remote(&self, opts: &crate::RemoteOpts) -> Option<Remote> {
let remotes: Vec<_> = self.remotes.clone().unwrap_or_default();
let config_remote = match &opts.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.host.is_some()) {
(Some(config_remote), _) => config_remote,
(None, true) => Remote::default(),
(None, false) => return None,
};
Some(Remote {
name: opts.name.clone().unwrap_or(blueprint_remote.name),
host: opts.host.clone().unwrap_or(blueprint_remote.host),
ssh_port: opts.ssh_port.clone().unwrap_or(blueprint_remote.ssh_port),
temp_dir: opts.temp_dir.clone().unwrap_or(blueprint_remote.temp_dir),
env: opts.env.clone().unwrap_or(blueprint_remote.env),
})
}
}

@ -1,53 +1,90 @@
extern crate structopt;
#[macro_use]
extern crate structopt_derive;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::process::{exit, Command, Stdio};
use structopt::StructOpt;
extern crate cargo_metadata;
use log::{error, info};
#[macro_use] extern crate log;
extern crate simple_logger;
mod config;
extern crate toml;
const PROGRESS_FLAG: &str = "--info=progress2";
use std::process::{exit, Command, Stdio};
use std::path::{Path, PathBuf};
use std::fs::File;
use std::io::Read;
use std::borrow::Borrow;
#[derive(StructOpt, Debug)]
pub struct RemoteOpts {
/// The name of the remote specified in the config
#[structopt(short = "r", long = "remote")]
name: Option<String>,
use structopt::StructOpt;
/// Remote ssh build server with user or the name of the ssh entry
#[structopt(short = "H", long = "remote-host")]
host: Option<String>,
use toml::Value;
/// The ssh port to communicate with the build server
#[structopt(short = "p", long = "remote-ssh-port")]
ssh_port: Option<u16>,
/// The directory where cargo builds the project
#[structopt(short, long = "remote-temp-dir")]
temp_dir: Option<String>,
#[structopt(
short = "e",
long = "env",
help = "Environment profile. default_value = /etc/profile"
)]
env: Option<String>,
}
#[derive(StructOpt, Debug)]
#[structopt(
name = "cargo-remote",
bin_name = "cargo",
)]
#[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<String>,
#[structopt(flatten)]
remote_opts: RemoteOpts,
#[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 = "c",
long = "copy-back",
help = "transfer the target folder back to the local machine"
help = "Transfer the target folder or specific file from that folder back to the local machine"
)]
copy_back: bool,
copy_back: Option<Option<String>>,
#[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: Option<PathBuf>,
manifest_path: PathBuf,
#[structopt(
short = "h",
long = "transfer-hidden",
help = "transfer hidden files and directories to the build server"
help = "Transfer hidden files and directories to the build server"
)]
hidden: bool,
@ -59,76 +96,76 @@ enum Opts {
name = "remote options"
)]
options: Vec<String>,
}
},
}
fn main() {
simple_logger::init().unwrap();
let Opts::Remote{
remote,
let Opts::Remote {
remote_opts,
build_env,
rustup_default,
copy_back,
no_copy_lock,
manifest_path,
hidden,
command,
options
options,
} = Opts::from_args();
let manifest_path = manifest_path.as_ref().map(PathBuf::borrow);
let project_metadata = cargo_metadata::metadata(manifest_path)
.unwrap_or_else(|e| {
error!("Could not read cargo metadata: {}", e);
exit(-1);
});
let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
metadata_cmd.manifest_path(manifest_path).no_deps();
let project_metadata = metadata_cmd.exec().unwrap();
let project_dir = project_metadata.workspace_root;
info!("Project dir: {:?}", project_dir);
// for now, assume that there is only one project and find it's root directory
let (project_dir, project_name) = project_metadata.packages.first().map_or_else(|| {
error!("No project found.");
exit(-2);
}, |project| {
(
Path::new(&project.manifest_path)
.parent()
.expect("Cargo.toml seems to have no parent directory?"),
&project.name
)
});
// TODO: refactor argument and config parsing and merging (related: kbknapp/clap-rs#748)
let build_server = remote.unwrap_or_else(|| {
let config_path = project_dir.join(".cargo-remote.toml");
File::open(config_path).ok().and_then(|mut file| {
let mut config_file_string = "".to_owned();
// ignore the result for now, the whole config/argument parsing needs to
// be refactored
let _ = file.read_to_string(&mut config_file_string);
config_file_string.parse::<Value>().ok()
}).and_then(|value| {
value["remote"].as_str().map(str::to_owned)
}).unwrap_or_else(|| {
error!("No remote build server was defined (use config file or --remote flag)");
let conf = match config::Config::new(&project_dir) {
Ok(conf) => conf,
Err(error) => {
error!("{}", error);
exit(-3);
})
});
}
};
let remote = match conf.get_remote(&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.host;
// 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!("{}/{}/", remote.temp_dir, hasher.finish());
info!("Transferring sources to build server.");
// transfer project to build server
let mut rsync_to = Command::new("rsync");
rsync_to.arg("-a".to_owned())
rsync_to
.arg("-a".to_owned())
.arg("--delete")
.arg("--info=progress2")
.arg("--compress")
.arg("-e")
.arg(format!("ssh -p {}", remote.ssh_port))
.arg(PROGRESS_FLAG)
.arg("--exclude")
.arg("target");
if !hidden {
rsync_to.arg("--exclude")
.arg(".*");
rsync_to.arg("--exclude").arg(".*");
}
rsync_to.arg("--rsync-path")
rsync_to
.arg("--rsync-path")
.arg("mkdir -p remote-builds && rsync")
.arg(format!("{}/", project_dir.to_string_lossy()))
.arg(format!("{}:~/remote-builds/{}/", build_server, project_name))
.arg(format!("{}:{}", build_server, build_path))
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.stdin(Stdio::inherit())
@ -137,19 +174,23 @@ fn main() {
error!("Failed to transfer project to build server (error: {})", e);
exit(-4);
});
info!("Build ENV: {:?}", build_env);
info!("Environment profile: {:?}", remote.env);
info!("Build path: {:?}", build_path);
let build_command = format!(
"cd ~/remote-builds/{}/; $HOME/.cargo/bin/cargo {} {}",
project_name,
"source {}; rustup default {}; cd {}; {} cargo {} {}",
remote.env,
rustup_default,
build_path,
build_env,
command,
options.iter().fold(
String::new(),
|acc, x| format!("{} {}", acc, x)
)
options.join(" ")
);
info!("Starting build process.");
Command::new("ssh")
let output = Command::new("ssh")
.args(&["-p", &remote.ssh_port.to_string()])
.arg("-t")
.arg(&build_server)
.arg(build_command)
.stdout(Stdio::inherit())
@ -161,21 +202,63 @@ fn main() {
exit(-5);
});
if copy_back {
if let Some(file_name) = copy_back {
info!("Transferring artifacts back to client.");
let file_name = file_name.unwrap_or_else(String::new);
Command::new("rsync")
.arg("-a")
.arg("--delete")
.arg("--info=progress2")
.arg(format!("{}:~/remote-builds/{}/target/", build_server, project_name))
.arg(format!("{}/target/", project_dir.to_string_lossy()))
.arg("--compress")
.arg("-e")
.arg(format!("ssh -p {}", remote.ssh_port))
.arg(PROGRESS_FLAG)
.arg(format!(
"{}:{}target/{}",
build_server, build_path, file_name
))
.arg(format!(
"{}/target/{}",
project_dir.to_string_lossy(),
file_name
))
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.stdin(Stdio::inherit())
.output()
.unwrap_or_else(|e| {
error!("Failed to transfer target back to local machine (error: {})", e);
error!(
"Failed to transfer target back to local machine (error: {})",
e
);
exit(-6);
});
}
if !no_copy_lock {
info!("Transferring Cargo.lock file back to client.");
Command::new("rsync")
.arg("-a")
.arg("--delete")
.arg("--compress")
.arg("-e")
.arg(format!("ssh -p {}", remote.ssh_port))
.arg(PROGRESS_FLAG)
.arg(format!("{}:{}Cargo.lock", build_server, build_path))
.arg(format!("{}/Cargo.lock", project_dir.to_string_lossy()))
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.stdin(Stdio::inherit())
.output()
.unwrap_or_else(|e| {
error!(
"Failed to transfer Cargo.lock back to local machine (error: {})",
e
);
exit(-7);
});
}
if !output.status.success() {
exit(output.status.code().unwrap_or(1))
}
}

Loading…
Cancel
Save