cli: Add `rad version --json` flag

This will be used to automatically populated metadata around releases
for the homepage.
This commit is contained in:
cloudhead 2024-03-11 17:36:31 +01:00
parent fa4b929a06
commit 93ff59ae25
No known key found for this signature in database
5 changed files with 117 additions and 39 deletions

View File

@ -19,6 +19,23 @@ fn main() {
}) })
.unwrap_or(env::var("GIT_HEAD").unwrap_or("unknown".into())); .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-env=GIT_HEAD={hash}");
println!("cargo:rustc-rerun-if-changed=.git/HEAD"); println!("cargo:rustc-rerun-if-changed=.git/HEAD");
} }

View File

@ -1,23 +1,30 @@
use std::ffi::OsString; use std::ffi::OsString;
use std::io; use std::io::{self, Write};
use std::{io::ErrorKind, iter, process}; use std::{io::ErrorKind, iter, process};
use anyhow::anyhow; use anyhow::anyhow;
use radicle::version; use radicle::version::Version;
use radicle_cli::commands::*; use radicle_cli::commands::*;
use radicle_cli::terminal as term; use radicle_cli::terminal as term;
pub const NAME: &str = "rad"; 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 DESCRIPTION: &str = "Radicle command line interface";
pub const GIT_HEAD: &str = env!("GIT_HEAD"); 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)] #[derive(Debug)]
enum Command { enum Command {
Other(Vec<OsString>), Other(Vec<OsString>),
Help, Help,
Version, Version { json: bool },
} }
fn main() { fn main() {
@ -43,18 +50,24 @@ fn parse_args() -> anyhow::Result<Command> {
let mut parser = lexopt::Parser::from_env(); let mut parser = lexopt::Parser::from_env();
let mut command = None; let mut command = None;
let mut json = false;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
Long("json") => {
json = true;
}
Long("help") | Short('h') => { Long("help") | Short('h') => {
command = Some(Command::Help); command = Some(Command::Help);
} }
Long("version") => { Long("version") => {
command = Some(Command::Version); command = Some(Command::Version { json: false });
} }
Value(val) if command.is_none() => { Value(val) if command.is_none() => {
if val == *"." { if val == *"." {
command = Some(Command::Other(vec![OsString::from("inspect")])); command = Some(Command::Other(vec![OsString::from("inspect")]));
} else if val == "version" {
command = Some(Command::Version { json: false });
} else { } else {
let args = iter::once(val) let args = iter::once(val)
.chain(iter::from_fn(|| parser.value().ok())) .chain(iter::from_fn(|| parser.value().ok()))
@ -66,12 +79,14 @@ fn parse_args() -> anyhow::Result<Command> {
_ => return Err(anyhow::anyhow!(arg.unexpected())), _ => 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![]))) Ok(command.unwrap_or_else(|| Command::Other(vec![])))
} }
fn print_help() -> anyhow::Result<()> { fn print_help() -> anyhow::Result<()> {
version::print(&mut io::stdout(), NAME, VERSION, GIT_HEAD)?; VERSION.write(&mut io::stdout())?;
println!("{DESCRIPTION}"); println!("{DESCRIPTION}");
println!(); println!();
@ -80,9 +95,16 @@ fn print_help() -> anyhow::Result<()> {
fn run(command: Command) -> Result<(), Option<anyhow::Error>> { fn run(command: Command) -> Result<(), Option<anyhow::Error>> {
match command { match command {
Command::Version => { Command::Version { json } => {
version::print(&mut io::stdout(), NAME, VERSION, GIT_HEAD) let mut stdout = io::stdout();
.map_err(|e| Some(e.into()))?; 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 => { Command::Help => {
print_help()?; print_help()?;

View File

@ -7,14 +7,17 @@ use crossbeam_channel as chan;
use radicle::logger; use radicle::logger;
use radicle::prelude::Signer; use radicle::prelude::Signer;
use radicle::profile; use radicle::profile;
use radicle::version; use radicle::version::Version;
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner}; use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
use radicle_node::signals; use radicle_node::signals;
use radicle_node::Runtime; use radicle_node::Runtime;
pub const NAME: &str = "radicle-node"; pub const VERSION: Version = Version {
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); name: env!("CARGO_PKG_NAME"),
pub const GIT_HEAD: &str = env!("GIT_HEAD"); commit: env!("GIT_HEAD"),
version: env!("CARGO_PKG_VERSION"),
timestamp: env!("GIT_COMMIT_TIME"),
};
pub const HELP_MSG: &str = r#" pub const HELP_MSG: &str = r#"
Usage Usage
@ -68,7 +71,7 @@ impl Options {
process::exit(0); process::exit(0);
} }
Long("version") => { Long("version") => {
version::print(&mut io::stdout(), NAME, VERSION, GIT_HEAD)?; VERSION.write(&mut io::stdout())?;
process::exit(0); process::exit(0);
} }
_ => anyhow::bail!(arg.unexpected()), _ => anyhow::bail!(arg.unexpected()),

View File

@ -1,11 +1,14 @@
use std::env; use std::env;
use std::process; use std::process;
use radicle::version; use radicle::version::Version;
pub const NAME: &str = "git-remote-rad"; pub const VERSION: Version = Version {
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); name: "git-remote-rad",
pub const GIT_HEAD: &str = env!("GIT_HEAD"); commit: env!("GIT_HEAD"),
version: env!("CARGO_PKG_VERSION"),
timestamp: env!("GIT_COMMIT_TIME"),
};
fn main() { fn main() {
let mut args = env::args(); let mut args = env::args();
@ -14,7 +17,7 @@ fn main() {
radicle::logger::set(radicle::logger::StderrLogger::new(lvl), lvl).ok(); radicle::logger::set(radicle::logger::StderrLogger::new(lvl), lvl).ok();
} }
if args.nth(1).as_deref() == Some("--version") { 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}"); eprintln!("error: {e}");
process::exit(1); process::exit(1);
}; };

View File

@ -1,22 +1,41 @@
use serde::{Deserialize, Serialize};
use std::io; use std::io;
/// Print program version. /// Program version metadata.
/// #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
/// The program version follows [semantic versioning](https://semver.org). pub struct Version<'a> {
/// pub name: &'a str,
/// Adjust with caution, third party applications parse the string for version info. pub version: &'a str,
pub fn print( pub commit: &'a str,
mut w: impl std::io::Write, pub timestamp: &'a str,
name: &str, }
version: &str,
git_head: &str, impl<'a> Version<'a> {
) -> Result<(), io::Error> { /// Write program version as string.
if version.ends_with("-dev") { ///
writeln!(w, "{name} {version}+{git_head}")?; /// The program version follows [semantic versioning](https://semver.org).
} else { ///
writeln!(w, "{name} {version} ({git_head})")?; /// 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> {
Ok(()) 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)] #[cfg(test)]
@ -26,12 +45,26 @@ mod test {
#[test] #[test]
fn test_version() { fn test_version() {
let mut buffer = Vec::new(); 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(); let res = std::str::from_utf8(&buffer).unwrap();
assert_eq!("rad 1.2.3 (28b341d)\n", res); assert_eq!("rad 1.2.3 (28b341d)\n", res);
let mut buffer = Vec::new(); 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(); let res = std::str::from_utf8(&buffer).unwrap();
assert_eq!("rad 1.2.3-dev+28b341d\n", res); assert_eq!("rad 1.2.3-dev+28b341d\n", res);
} }