From 93ff59ae250a3f7532cd07114779072f8aecf260 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 11 Mar 2024 17:36:31 +0100 Subject: [PATCH] cli: Add `rad version --json` flag This will be used to automatically populated metadata around releases for the homepage. --- build.rs | 17 +++++ radicle-cli/src/main.rs | 42 +++++++++--- radicle-node/src/main.rs | 13 ++-- radicle-remote-helper/src/git-remote-rad.rs | 13 ++-- radicle/src/version.rs | 71 +++++++++++++++------ 5 files changed, 117 insertions(+), 39 deletions(-) diff --git a/build.rs b/build.rs index e9c72969..af4ecdef 100644 --- a/build.rs +++ b/build.rs @@ -19,6 +19,23 @@ fn main() { }) .unwrap_or(env::var("GIT_HEAD").unwrap_or("unknown".into())); + // Set a build-time `GIT_COMMIT_TIME` env var which includes the commit time. + let commit_time = Command::new("git") + .arg("show") + .arg("--format=%ct") + .arg("HEAD") + .output() + .ok() + .and_then(|output| { + if output.status.success() { + String::from_utf8(output.stdout).ok() + } else { + None + } + }) + .unwrap_or(0.to_string()); + + println!("cargo:rustc-env=GIT_COMMIT_TIME={commit_time}"); println!("cargo:rustc-env=GIT_HEAD={hash}"); println!("cargo:rustc-rerun-if-changed=.git/HEAD"); } diff --git a/radicle-cli/src/main.rs b/radicle-cli/src/main.rs index 0a30c230..ec843acc 100644 --- a/radicle-cli/src/main.rs +++ b/radicle-cli/src/main.rs @@ -1,23 +1,30 @@ use std::ffi::OsString; -use std::io; +use std::io::{self, Write}; use std::{io::ErrorKind, iter, process}; use anyhow::anyhow; -use radicle::version; +use radicle::version::Version; use radicle_cli::commands::*; use radicle_cli::terminal as term; pub const NAME: &str = "rad"; -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const DESCRIPTION: &str = "Radicle command line interface"; pub const GIT_HEAD: &str = env!("GIT_HEAD"); +pub const TIMESTAMP: &str = env!("GIT_COMMIT_TIME"); +pub const VERSION: Version = Version { + name: NAME, + version: PKG_VERSION, + commit: GIT_HEAD, + timestamp: TIMESTAMP, +}; #[derive(Debug)] enum Command { Other(Vec), Help, - Version, + Version { json: bool }, } fn main() { @@ -43,18 +50,24 @@ fn parse_args() -> anyhow::Result { let mut parser = lexopt::Parser::from_env(); let mut command = None; + let mut json = false; while let Some(arg) = parser.next()? { match arg { + Long("json") => { + json = true; + } Long("help") | Short('h') => { command = Some(Command::Help); } Long("version") => { - command = Some(Command::Version); + command = Some(Command::Version { json: false }); } Value(val) if command.is_none() => { if val == *"." { command = Some(Command::Other(vec![OsString::from("inspect")])); + } else if val == "version" { + command = Some(Command::Version { json: false }); } else { let args = iter::once(val) .chain(iter::from_fn(|| parser.value().ok())) @@ -66,12 +79,14 @@ fn parse_args() -> anyhow::Result { _ => return Err(anyhow::anyhow!(arg.unexpected())), } } - + if let Some(Command::Version { json: j }) = &mut command { + *j = json; + } Ok(command.unwrap_or_else(|| Command::Other(vec![]))) } fn print_help() -> anyhow::Result<()> { - version::print(&mut io::stdout(), NAME, VERSION, GIT_HEAD)?; + VERSION.write(&mut io::stdout())?; println!("{DESCRIPTION}"); println!(); @@ -80,9 +95,16 @@ fn print_help() -> anyhow::Result<()> { fn run(command: Command) -> Result<(), Option> { match command { - Command::Version => { - version::print(&mut io::stdout(), NAME, VERSION, GIT_HEAD) - .map_err(|e| Some(e.into()))?; + Command::Version { json } => { + let mut stdout = io::stdout(); + if json { + VERSION + .write_json(&mut stdout) + .map_err(|e| Some(e.into()))?; + writeln!(&mut stdout).ok(); + } else { + VERSION.write(&mut stdout).map_err(|e| Some(e.into()))?; + } } Command::Help => { print_help()?; diff --git a/radicle-node/src/main.rs b/radicle-node/src/main.rs index 010d98b9..2d5ba7d5 100644 --- a/radicle-node/src/main.rs +++ b/radicle-node/src/main.rs @@ -7,14 +7,17 @@ use crossbeam_channel as chan; use radicle::logger; use radicle::prelude::Signer; use radicle::profile; -use radicle::version; +use radicle::version::Version; use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner}; use radicle_node::signals; use radicle_node::Runtime; -pub const NAME: &str = "radicle-node"; -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const GIT_HEAD: &str = env!("GIT_HEAD"); +pub const VERSION: Version = Version { + name: env!("CARGO_PKG_NAME"), + commit: env!("GIT_HEAD"), + version: env!("CARGO_PKG_VERSION"), + timestamp: env!("GIT_COMMIT_TIME"), +}; pub const HELP_MSG: &str = r#" Usage @@ -68,7 +71,7 @@ impl Options { process::exit(0); } Long("version") => { - version::print(&mut io::stdout(), NAME, VERSION, GIT_HEAD)?; + VERSION.write(&mut io::stdout())?; process::exit(0); } _ => anyhow::bail!(arg.unexpected()), diff --git a/radicle-remote-helper/src/git-remote-rad.rs b/radicle-remote-helper/src/git-remote-rad.rs index 0ec0d833..843ddc9b 100644 --- a/radicle-remote-helper/src/git-remote-rad.rs +++ b/radicle-remote-helper/src/git-remote-rad.rs @@ -1,11 +1,14 @@ use std::env; use std::process; -use radicle::version; +use radicle::version::Version; -pub const NAME: &str = "git-remote-rad"; -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const GIT_HEAD: &str = env!("GIT_HEAD"); +pub const VERSION: Version = Version { + name: "git-remote-rad", + commit: env!("GIT_HEAD"), + version: env!("CARGO_PKG_VERSION"), + timestamp: env!("GIT_COMMIT_TIME"), +}; fn main() { let mut args = env::args(); @@ -14,7 +17,7 @@ fn main() { radicle::logger::set(radicle::logger::StderrLogger::new(lvl), lvl).ok(); } if args.nth(1).as_deref() == Some("--version") { - if let Err(e) = version::print(std::io::stdout(), NAME, VERSION, GIT_HEAD) { + if let Err(e) = VERSION.write(std::io::stdout()) { eprintln!("error: {e}"); process::exit(1); }; diff --git a/radicle/src/version.rs b/radicle/src/version.rs index 638e0d18..eb79d70b 100644 --- a/radicle/src/version.rs +++ b/radicle/src/version.rs @@ -1,22 +1,41 @@ +use serde::{Deserialize, Serialize}; use std::io; -/// Print program version. -/// -/// The program version follows [semantic versioning](https://semver.org). -/// -/// Adjust with caution, third party applications parse the string for version info. -pub fn print( - mut w: impl std::io::Write, - name: &str, - version: &str, - git_head: &str, -) -> Result<(), io::Error> { - if version.ends_with("-dev") { - writeln!(w, "{name} {version}+{git_head}")?; - } else { - writeln!(w, "{name} {version} ({git_head})")?; - }; - Ok(()) +/// Program version metadata. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Version<'a> { + pub name: &'a str, + pub version: &'a str, + pub commit: &'a str, + pub timestamp: &'a str, +} + +impl<'a> Version<'a> { + /// Write program version as string. + /// + /// The program version follows [semantic versioning](https://semver.org). + /// + /// Adjust with caution, third party applications parse the string for version info. + pub fn write(&self, mut w: impl std::io::Write) -> Result<(), io::Error> { + let Version { + name, + version, + commit, + .. + } = self; + + if version.ends_with("-dev") { + writeln!(w, "{name} {version}+{commit}")?; + } else { + writeln!(w, "{name} {version} ({commit})")?; + }; + Ok(()) + } + + /// Write the program version metadata as a JSON value. + pub fn write_json(&self, w: impl std::io::Write) -> Result<(), serde_json::Error> { + serde_json::to_writer(w, self) + } } #[cfg(test)] @@ -26,12 +45,26 @@ mod test { #[test] fn test_version() { let mut buffer = Vec::new(); - print(&mut buffer, "rad", "1.2.3", "28b341d").unwrap(); + Version { + name: "rad", + version: "1.2.3", + commit: "28b341d", + timestamp: "", + } + .write(&mut buffer) + .unwrap(); let res = std::str::from_utf8(&buffer).unwrap(); assert_eq!("rad 1.2.3 (28b341d)\n", res); let mut buffer = Vec::new(); - print(&mut buffer, "rad", "1.2.3-dev", "28b341d").unwrap(); + Version { + name: "rad", + version: "1.2.3-dev", + commit: "28b341d", + timestamp: "", + } + .write(&mut buffer) + .unwrap(); let res = std::str::from_utf8(&buffer).unwrap(); assert_eq!("rad 1.2.3-dev+28b341d\n", res); }