use argh::FromArgs;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::{exit, Command, ExitStatus};
use std::{env, fs};
static DEFAULT_REPO: &str = "https://github.com/softprops/lambda-rust";
static DEFAULT_REV: &str = "master";
static DEFAULT_CONTAINER_CMD: &str = "docker";
fn abort(s: &str) -> ! {
eprintln!("{}", s);
exit(1);
}
fn cmd_str(cmd: &Command) -> String {
format!("{:?}", cmd).replace('"', "")
}
fn run_cmd_no_check(cmd: &mut Command) -> ExitStatus {
let cmd_str = cmd_str(cmd);
println!("{}", cmd_str);
let status = match cmd.status() {
Ok(status) => status,
Err(err) => {
abort(&format!("failed to run {}: {}", cmd_str, err));
}
};
status
}
fn run_cmd(cmd: &mut Command) {
let cmd_str = cmd_str(cmd);
let status = run_cmd_no_check(cmd);
if !status.success() {
abort(&format!("command {} failed: {}", cmd_str, status));
}
}
fn git_cmd_in(repo_path: &Path) -> Command {
let mut cmd = Command::new("git");
cmd.arg("-C").arg(repo_path);
cmd
}
#[derive(FromArgs)]
struct Opt {
#[argh(option, default = "DEFAULT_REPO.into()")]
repo: String,
#[argh(option, default = "DEFAULT_REV.into()")]
rev: String,
#[argh(option, default = "DEFAULT_CONTAINER_CMD.into()")]
cmd: String,
#[argh(positional, default = "env::current_dir().unwrap()")]
project: PathBuf,
}
fn main() {
let opt: Opt = argh::from_env();
let repo_url = &opt.repo;
let home: PathBuf = if let Some(home) = env::var_os("HOME") {
Path::new(&home).into()
} else {
abort("HOME is not set");
};
let cache: PathBuf = if let Some(cache) = env::var_os("XDG_CACHE_HOME") {
Path::new(&cache).into()
} else {
home.join(".cache")
};
let repo_path = cache.join("lambda-build/repo");
let _ = fs::create_dir_all(&repo_path);
if !repo_path.join(".git").exists() {
run_cmd(
Command::new("git")
.args(&["clone", repo_url])
.arg(&repo_path),
);
} else {
run_cmd(
git_cmd_in(&repo_path)
.args(&["remote", "set-url", "origin"])
.arg(repo_url),
);
run_cmd(git_cmd_in(&repo_path).arg("fetch"));
};
let status = run_cmd_no_check(
git_cmd_in(&repo_path)
.args(&["checkout", &format!("origin/{}", opt.rev)]),
);
if !status.success() {
run_cmd(git_cmd_in(&repo_path).args(&["checkout", &opt.rev]));
}
let image_tag = "rust-lambda-build";
run_cmd(
Command::new(&opt.cmd)
.current_dir(&repo_path)
.args(&["build", "--tag", image_tag, "."]),
);
let volume = |src: &Path, dst: &Path| {
let mut s = OsString::new();
s.push(src);
s.push(":");
s.push(dst);
s
};
let volume_read_only = |src, dst| {
let mut s = volume(src, dst);
s.push(":ro");
s
};
let output_dir = opt.project.join("lambda-target");
let _ = fs::create_dir(&output_dir);
let registry_dir = output_dir.join("cargo-registry");
let _ = fs::create_dir(®istry_dir);
let git_dir = output_dir.join("cargo-git");
let _ = fs::create_dir(&git_dir);
run_cmd(
Command::new(&opt.cmd)
.args(&["run", "--rm", "--init"])
.arg("-u")
.arg(format!(
"{}:{}",
users::get_current_uid(),
users::get_current_gid()
))
.arg("-v")
.arg(volume_read_only(&opt.project, Path::new("/code")))
.arg("-v")
.arg(volume(®istry_dir, Path::new("/cargo/registry")))
.arg("-v")
.arg(volume(&git_dir, Path::new("/cargo/git")))
.arg("-v")
.arg(volume(&output_dir, Path::new("/code/target")))
.arg(image_tag),
);
}