From 3119294367d8382868babb9e02e5a2f5a4212590 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Tue, 25 Jul 2023 11:35:38 +0200 Subject: [PATCH] Add `--version` flag to binaries Help users confirm all their radicle binary versions by providing the '--version' flag. Move the print_version function to the radicle crate for use by the node and remote-rad binaries, and remove tests to confirm the output format as it is deemed redundant. Co-Developed-by: Vincenzo Palazzo --- radicle-cli/src/main.rs | 99 +-------------------- radicle-node/src/main.rs | 11 +++ radicle-remote-helper/Cargo.toml | 1 + radicle-remote-helper/src/git-remote-rad.rs | 23 ++++- radicle/src/lib.rs | 1 + radicle/src/version.rs | 38 ++++++++ 6 files changed, 76 insertions(+), 97 deletions(-) create mode 100644 radicle/src/version.rs diff --git a/radicle-cli/src/main.rs b/radicle-cli/src/main.rs index 9fb70fc4..9d72923c 100644 --- a/radicle-cli/src/main.rs +++ b/radicle-cli/src/main.rs @@ -4,6 +4,7 @@ use std::{io::ErrorKind, iter, process}; use anyhow::anyhow; +use radicle::version; use radicle_cli::commands::*; use radicle_cli::terminal as term; @@ -63,20 +64,8 @@ fn parse_args() -> anyhow::Result { Ok(command.unwrap_or_else(|| Command::Other(vec![]))) } -/// Print the Radicle CLI's version. -/// -/// Third party applications use it to parse Radicle Cli's version. -fn print_version(mut w: impl std::io::Write) -> anyhow::Result<()> { - if VERSION.contains("-dev") { - writeln!(w, "{NAME} {VERSION}+{GIT_HEAD}")?; - } else { - writeln!(w, "{NAME} {VERSION} ({GIT_HEAD})")?; - } - Ok(()) -} - fn print_help() -> anyhow::Result<()> { - print_version(&mut io::stdout())?; + version::print(&mut io::stdout(), NAME, VERSION, GIT_HEAD)?; println!("{DESCRIPTION}"); println!(); @@ -86,7 +75,8 @@ fn print_help() -> anyhow::Result<()> { fn run(command: Command) -> Result<(), Option> { match command { Command::Version => { - print_version(&mut io::stdout())?; + version::print(&mut io::stdout(), NAME, VERSION, GIT_HEAD) + .map_err(|e| Some(e.into()))?; } Command::Help => { print_help()?; @@ -349,84 +339,3 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option> } Ok(()) } - -#[cfg(test)] -mod test { - use super::*; - - fn is_dot_separated_identifier(s: &str) -> bool { - let vs: Vec<_> = s.split('.').collect(); - - if Some(&"") == vs.first() || Some(&"") == vs.last() { - return false; - } - for v in vs { - if v.is_empty() || v.contains(|c: char| !(c.is_ascii_alphanumeric() || c == '-')) { - return false; - } - } - true - } - - /// https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions - fn is_semantic_version(s: &str) -> bool { - let (s, build) = s.split_once('+').unwrap_or((s, "")); - let (version_core, pre_release) = s.split_once('-').unwrap_or((s, "")); - - let versions: Vec<_> = version_core.split('.').collect(); - if versions.len() != 3 { - return false; - } - for v in versions { - if v != "0" && (v.get(0..1) == Some("0") || v.parse::().is_err()) { - return false; - } - } - - (pre_release.is_empty() || is_dot_separated_identifier(pre_release)) - && (build.is_empty() || is_dot_separated_identifier(build)) - } - - #[test] - fn test_is_semantic_version() { - assert!(is_semantic_version("0.0.1")); - assert!(is_semantic_version("1.0.0-alpha.1")); - assert!(is_semantic_version("1.0.0-0.3.7")); - assert!(is_semantic_version("1.0.0-x.7.z.92")); - assert!(is_semantic_version("1.0.0-alpha+001")); - assert!(is_semantic_version("1.0.0+20130313144700")); - assert!(is_semantic_version("1.0.0-beta+exp.sha.5114f85")); - assert!(is_semantic_version("1.0.0+21AF26D3----117B344092BD")); - - assert!(!is_semantic_version(""), "empty"); - assert!(!is_semantic_version("1.0"), "too little versions"); - assert!(!is_semantic_version("1.0.01"), "no leading zeroes"); - assert!( - !is_semantic_version("1.0.0-beta+exp..sha.5114f85"), - "dot separated value must be non-empty" - ); - assert!( - !is_semantic_version("1.0.0-beta+exp.sha.5114f85."), - "dot separated value must be non-empty" - ); - assert!( - !is_semantic_version("1.0.0-alpha+001+002"), - "only one '+' allowed" - ); - } - - /// Ensure version output is consistent for consumption by third parties. - #[test] - fn test_version() { - let mut buffer = Vec::new(); - print_version(&mut buffer).unwrap(); - let str = std::str::from_utf8(&buffer).unwrap(); - - let mut strs = str.split(' '); - assert_eq!("rad", strs.next().unwrap_or_default(), "program name"); - assert!( - is_semantic_version(strs.next().unwrap_or_default()), - "semantic version" - ); - } -} diff --git a/radicle-node/src/main.rs b/radicle-node/src/main.rs index 1e50f73f..0e7679d0 100644 --- a/radicle-node/src/main.rs +++ b/radicle-node/src/main.rs @@ -1,3 +1,4 @@ +use std::io; use std::{env, fs, net, process}; use anyhow::anyhow; @@ -8,11 +9,16 @@ use localtime::LocalDuration; use radicle::node; use radicle::prelude::Signer; use radicle::profile; +use radicle::version; use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner}; use radicle_node::prelude::{Address, NodeId}; use radicle_node::Runtime; use radicle_node::{logger, service, signals}; +pub const NAME: &str = "radicle-node"; +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const GIT_HEAD: &str = env!("GIT_HEAD"); + pub const HELP_MSG: &str = r#" Usage @@ -28,6 +34,7 @@ Options --force Force start even if an existing control socket is found --help Print help --listen
Address to listen on + --version Print program version "#; #[derive(Debug)] @@ -95,6 +102,10 @@ impl Options { println!("{HELP_MSG}"); process::exit(0); } + Long("version") => { + version::print(&mut io::stdout(), NAME, VERSION, GIT_HEAD)?; + process::exit(0); + } _ => anyhow::bail!(arg.unexpected()), } } diff --git a/radicle-remote-helper/Cargo.toml b/radicle-remote-helper/Cargo.toml index 17d81dc5..ed1b29fa 100644 --- a/radicle-remote-helper/Cargo.toml +++ b/radicle-remote-helper/Cargo.toml @@ -4,6 +4,7 @@ license = "MIT OR Apache-2.0" version = "0.2.0" authors = ["Alexis Sellier "] edition = "2021" +build = "../build.rs" [dependencies] thiserror = "1" diff --git a/radicle-remote-helper/src/git-remote-rad.rs b/radicle-remote-helper/src/git-remote-rad.rs index d8c99828..ca7829d4 100644 --- a/radicle-remote-helper/src/git-remote-rad.rs +++ b/radicle-remote-helper/src/git-remote-rad.rs @@ -1,14 +1,33 @@ +use std::env; +use std::process; + +use radicle::version; + +pub const NAME: &str = "git-remote-rad"; +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const GIT_HEAD: &str = env!("GIT_HEAD"); + fn main() { + let mut args = env::args(); + + if args.nth(1).as_deref() == Some("--version") { + if let Err(e) = version::print(std::io::stdout(), NAME, VERSION, GIT_HEAD) { + eprintln!("fatal: {e}"); + process::exit(1); + }; + process::exit(0); + } + let profile = match radicle::Profile::load() { Ok(profile) => profile, Err(err) => { eprintln!("fatal: couldn't load profile: {err}"); - std::process::exit(1); + process::exit(1); } }; if let Err(err) = radicle_remote_helper::run(profile) { eprintln!("fatal: {err}"); - std::process::exit(1); + process::exit(1); } } diff --git a/radicle/src/lib.rs b/radicle/src/lib.rs index 5bfcdefd..a740ed0e 100644 --- a/radicle/src/lib.rs +++ b/radicle/src/lib.rs @@ -21,6 +21,7 @@ pub mod sql; pub mod storage; #[cfg(any(test, feature = "test"))] pub mod test; +pub mod version; pub use node::Node; pub use profile::Profile; diff --git a/radicle/src/version.rs b/radicle/src/version.rs new file mode 100644 index 00000000..638e0d18 --- /dev/null +++ b/radicle/src/version.rs @@ -0,0 +1,38 @@ +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(()) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_version() { + let mut buffer = Vec::new(); + print(&mut buffer, "rad", "1.2.3", "28b341d").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(); + let res = std::str::from_utf8(&buffer).unwrap(); + assert_eq!("rad 1.2.3-dev+28b341d\n", res); + } +}