//! 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);