//! See .
//!
//! This binary defines various auxiliary build commands, which are not
//! expressible with just `cargo`.
//!
//! This binary is integrated into the `cargo` command line by using an alias in
//! `.cargo/config`.
use std::path::{Path, PathBuf};
use std::{fs::File, process::Command};
use anyhow::{anyhow, bail, ensure, Context, Result};
use build_info::BuildInfo;
use clap::{Parser, Subcommand};
use flate2::{write::GzEncoder, Compression};
use once_cell::sync::Lazy;
use tar::Builder;
mod release;
const PKG_NAME: &str = "projectr";
const PKG_VER: &str = env!("CARGO_PKG_VERSION");
const PKG_INCLUDE: &[&str] = &[
"bin/tmux-projectr",
"CONTRIBUTING.md",
"README.md",
"LICENSE",
];
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::OutDir => println!("{}", out_dir()?.display()),
Commands::Dist { check } => {
let version = match version(cli.pre_release) {
Ok(_) if check => std::process::exit(0),
Err(_) if check => std::process::exit(1),
res => res?,
};
let targz = generate_tar_gz(version)?;
println!("{}", targz.display());
}
Commands::Release(release) => release.run()?,
};
Ok(())
}
#[derive(Debug, Clone, Parser)]
#[command(author, version, about)]
struct Cli {
/// Disable version/git tag check and appends `-dev` to the version
#[arg(short, long, global = true, required = false)]
pre_release: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Clone, Subcommand)]
enum Commands {
/// Print the default value of OUT_DIR used by cargo when building the package.
OutDir,
/// Generate distributable package
Dist {
/// Validate a git tag matching the package version exists and exit.
#[arg(short, long, required = false)]
check: bool,
},
Release(release::Release),
}
fn version(pre_release: bool) -> Result {
use build_info::VersionControl::Git;
let BuildInfo {
version_control: Some(Git(git)),
..
} = build_info() else {
bail!("Failed to get version control info.");
};
if pre_release {
Ok(format!("{PKG_VER}-dev"))
} else if git.tags.contains(&format!("v{PKG_VER}")) {
Ok(PKG_VER.to_owned())
} else {
Err(anyhow!("Failed to find git tag matching package version."))
}
}
fn out_dir() -> Result {
RELEASE_DIR
.join("build")
.read_dir()
.context("Failed to read build directory.")?
.flatten()
.filter_map(|d| {
d.file_name()
.to_str()?
.starts_with(PKG_NAME)
.then(|| d.path().join("invoked.timestamp"))
.filter(|p| p.exists())
})
.reduce(|acc, path_buf| {
std::cmp::max_by_key(path_buf, acc, |p| {
p.metadata()
.and_then(|m| m.modified())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH)
})
})
.map(|p| p.with_file_name("out"))
.filter(|o| o.exists())
.context("Failed to find `out` directory for latest build")
}
fn generate_tar_gz(version: String) -> Result {
let target = build_info::format!("{}", $.target.triple);
let dist_pkg = DIST_DIR.join(format!("{PKG_NAME}-v{version}-{target}.tar.gz"));
let binary = build_binary()?;
ensure!(binary.exists(), "Failed to find package binary",);
let _ = std::fs::remove_dir_all(&*DIST_DIR);
std::fs::create_dir_all(&*DIST_DIR)?;
let tar_gz = File::create(&dist_pkg)?;
let enc = GzEncoder::new(tar_gz, Compression::default());
let mut tar = Builder::new(enc);
tar.append_path_with_name(binary, PathBuf::from("bin").join(PKG_NAME))?;
tar.append_dir_all(".", out_dir()?)?;
PKG_INCLUDE.iter().try_for_each(|p| tar.append_path(p))?;
tar.into_inner()?.finish()?;
Ok(dist_pkg)
}
fn build_binary() -> Result {
let status = Command::new("cargo")
.arg("build")
.arg("--release")
.arg(format!("--package={PKG_NAME}"))
.status()
.context("Failed to invoke `cargo build`")?;
anyhow::ensure!(status.success(), "Cargo returned an error");
let mut binary = RELEASE_DIR.join(PKG_NAME);
if cfg!(windows) {
binary.set_extension("exe");
};
if let Err(e) = Command::new("strip").arg(&binary).status() {
eprintln!("Failed to strip the binary: {}", e)
}
Ok(binary)
}
static PROJECT_ROOT: Lazy = Lazy::new(|| {
let dir = std::env::current_dir().unwrap_or_else(|_| {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.to_path_buf()
});
dir.ancestors()
.find(|p| p.join(".git").is_dir())
.unwrap_or(&dir)
.to_path_buf()
});
static DIST_DIR: Lazy = Lazy::new(|| PROJECT_ROOT.join("target").join("dist"));
static RELEASE_DIR: Lazy = Lazy::new(|| PROJECT_ROOT.join("target").join("release"));
build_info::build_info!(fn build_info);