use std::{
ffi::OsString,
path::{Path, PathBuf},
process::Command,
str::FromStr,
};
use anyhow::{bail, Context};
use pico_args::Arguments;
use tempfile::TempDir;
use crate::{install::InstallOpt, util, Parcel};
#[derive(Debug, Clone)]
pub struct Options {
prefix_skip: usize,
prefix: PathBuf,
root: PathBuf,
config: Parcel,
strip: bool,
target: Option<String>,
output: Output,
tar_command: String,
}
#[derive(Debug, Clone)]
enum Output {
Path(String),
Stdout,
}
impl FromStr for Output {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "-" {
Ok(Output::Stdout)
} else {
Ok(Output::Path(s.into()))
}
}
}
impl Options {
pub fn from_args(mut args: Arguments, config: &Parcel) -> anyhow::Result<Self> {
let prefix = util::get_prefix_opt(&mut args)?;
let opt = Options {
prefix_skip: prefix.components().count(),
prefix,
root: args.opt_value_from_str("--root")?.unwrap_or_else(|| {
format!("{}-{}", config.pkg_name(), config.pkg_version()).into()
}),
config: config.clone(),
strip: !args.contains("--no-strip"),
target: args.opt_value_from_str("--target")?,
output: args.opt_value_from_str("-o")?.unwrap_or_else(|| {
Output::Path(format!(
"{}-{}.tar.gz",
config.pkg_name(),
config.pkg_version()
))
}),
tar_command: args
.opt_value_from_str("--tar")?
.unwrap_or_else(|| "tar".to_owned()),
};
let remainder = args.finish();
if !remainder.is_empty() {
bail!("unconsumed arguments: {}", util::DisplayArgs(remainder.iter()));
}
Ok(opt)
}
}
static TAR_COMPRESSION_FLAGS: &[(&str, Option<&str>)] = &[
(".tar", None),
(".tar.gz", Some("-z")),
(".tar.bz2", Some("-j")),
(".tar.xz", Some("--xz")),
(".tar.zstd", Some("--zstd")),
];
pub fn create(opt: &Options) -> anyhow::Result<()> {
let tmp_dir = TempDir::new()?;
InstallOpt::new(opt.config.clone(), &opt.prefix)
.dest_dir(tmp_dir.path())
.strip_binaries(opt.strip)
.cargo_target(opt.target.as_ref().map(String::as_str))
.install()?;
use std::path::Component;
let skip_prefix: PathBuf = opt
.prefix
.components()
.skip_while(|c| {
if let Component::Normal(_) = c {
false
} else {
true
}
})
.take(opt.prefix_skip)
.collect();
let mut transform = OsString::new();
transform.push("--transform=s,^.,./");
transform.push(&opt.root);
transform.push(",");
let out_args = match &opt.output {
Output::Path(o) => {
let mut out_args = vec!["-f", o];
if let Some(name) = Path::new(o).file_name().and_then(|f| f.to_str()) {
let compression_flag = TAR_COMPRESSION_FLAGS
.iter()
.find_map(|(suffix, flag)| if name.ends_with(suffix) { Some(flag) } else { None })
.unwrap_or_else(|| {
eprintln!("warning: output file with unknown extension, creating an uncompressed tar archive");
&None
});
out_args.extend(compression_flag.iter());
} else {
eprintln!(
"warning: not looking at extension in non-UTF8 output file name {}",
o
);
}
out_args
}
Output::Stdout => vec![],
};
let tar_status = Command::new(&opt.tar_command)
.arg("-C")
.arg(tmp_dir.path().join(&skip_prefix))
.args(&["-c", "."])
.arg(&transform)
.args(out_args)
.status()
.with_context(|| "running `tar` to create archive failed")?;
if !tar_status.success() {
bail!(
"running `tar` to create archive failed with exit status {}",
tar_status
);
}
Ok(())
}