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 <vincenzopalazzodev@gmail.com>
This commit is contained in:
Alexis Sellier 2023-07-25 11:35:38 +02:00
parent f24590afe2
commit 3119294367
No known key found for this signature in database
6 changed files with 76 additions and 97 deletions

View File

@ -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<Command> {
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<anyhow::Error>> {
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<anyhow::Error>>
}
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::<u32>().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"
);
}
}

View File

@ -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> 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()),
}
}

View File

@ -4,6 +4,7 @@ license = "MIT OR Apache-2.0"
version = "0.2.0"
authors = ["Alexis Sellier <alexis@radicle.xyz>"]
edition = "2021"
build = "../build.rs"
[dependencies]
thiserror = "1"

View File

@ -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);
}
}

View File

@ -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;

38
radicle/src/version.rs Normal file
View File

@ -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);
}
}