diff options
Diffstat (limited to 'xtask/src')
-rw-r--r-- | xtask/src/main.rs | 166 |
1 files changed, 138 insertions, 28 deletions
diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 7454bf0..1de4147 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -6,26 +6,40 @@ //! This binary is integrated into the `cargo` command line by using an alias in //! `.cargo/config`. -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::{fs::File, process::Command}; -use anyhow::Result; +use anyhow::{anyhow, bail, ensure, Context, Result}; +use build_info::BuildInfo; use clap::{Parser, Subcommand}; -use xtask::{dist, release}; +use flate2::{write::GzEncoder, Compression}; +use once_cell::sync::Lazy; +use tar::Builder; +use xtask::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(); - std::env::set_current_dir(cli.directory)?; - match cli.command { - Commands::Dist { tag } => { - let targz = dist::generate_tar_gz(&cli.profile, tag.as_deref())?; + 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::OutDir => { - let out_dir = dist::find_out_dir(&cli.profile)?; - println!("{}", out_dir.display()); - } Commands::Release(release) => release.run()?, }; @@ -35,16 +49,12 @@ fn main() -> Result<()> { #[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, - - /// Cargo profile to use. - #[arg(short, long, default_value = "release")] - profile: String, - - /// Change to DIRECTORY before doing anything - #[arg(short='C', long, default_value_os_t = get_package_dir())] - directory: PathBuf, } #[derive(Debug, Clone, Subcommand)] @@ -52,19 +62,119 @@ enum Commands { /// Print the default value of OUT_DIR used by cargo when building the package. OutDir, - /// Generate distributable package and print it's path + /// Generate distributable package Dist { - /// Verify the package version matches the provided tag. - #[arg(short, long)] - tag: Option<String>, + /// Validate a git tag matching the package version exists and exit. + #[arg(short, long, required = false)] + check: bool, }, Release(release::Release), } -fn get_package_dir() -> PathBuf { - std::path::Path::new(&env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .to_path_buf() +fn version(pre_release: bool) -> Result<String> { + 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<PathBuf> { + 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<PathBuf> { + 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<PathBuf> { + 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<PathBuf> = 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<PathBuf> = Lazy::new(|| PROJECT_ROOT.join("target").join("dist")); +static RELEASE_DIR: Lazy<PathBuf> = Lazy::new(|| PROJECT_ROOT.join("target").join("release")); + +build_info::build_info!(fn build_info); |