diff --git a/Cargo.lock b/Cargo.lock index 0b66386b7..74c00448a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -598,6 +598,32 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const_fn" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" + +[[package]] +name = "const_format" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +dependencies = [ + "proc-macro2 1.0.63", + "quote 1.0.29", + "unicode-xid 0.2.4", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -894,6 +920,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "document-features" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e493c573fce17f00dcab13b6ac057994f3ce17d1af4dc39bfd482b83c6eb6157" +dependencies = [ + "litrs", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -1251,6 +1286,19 @@ version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +[[package]] +name = "git2" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044" +dependencies = [ + "bitflags 1.3.2", + "libc", + "libgit2-sys", + "log", + "url", +] + [[package]] name = "h2" version = "0.3.20" @@ -1493,6 +1541,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "is_debug" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" + [[package]] name = "itertools" version = "0.10.5" @@ -1556,18 +1610,48 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libgit2-sys" +version = "0.15.2+1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "libz-sys" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +[[package]] +name = "litrs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa" + [[package]] name = "lock_api" version = "0.4.10" @@ -1762,6 +1846,7 @@ dependencies = [ "serde_repr", "serde_stacker", "serde_yaml", + "shadow-rs", "strum 0.25.0", "strum_macros 0.25.0", "tinytemplate", @@ -1922,6 +2007,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.31.1" @@ -2710,6 +2804,20 @@ dependencies = [ "digest", ] +[[package]] +name = "shadow-rs" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970538704756fd0bb4ec8cb89f80674afb661e7c0fe716f9ba5be57717742300" +dependencies = [ + "const_format", + "document-features", + "git2", + "is_debug", + "time", + "tzdb", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2959,8 +3067,12 @@ version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ + "itoa", + "libc", + "num_threads", "serde", "time-core", + "time-macros", ] [[package]] @@ -2969,6 +3081,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -3143,6 +3264,25 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "tz-rs" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4" +dependencies = [ + "const_fn", +] + +[[package]] +name = "tzdb" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec758958f2fb5069cd7fae385be95cc8eceb8cdfd270c7d14de6034f0108d99e" +dependencies = [ + "iana-time-zone", + "tz-rs", +] + [[package]] name = "ucd-trie" version = "0.1.5" @@ -3272,6 +3412,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs b/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs index 5a16a16c1..d141089a1 100644 --- a/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs +++ b/packages_rs/nextclade-cli/src/cli/nextalign_cli.rs @@ -9,10 +9,12 @@ use itertools::Itertools; use nextclade::align::params::AlignPairwiseParamsOptional; use nextclade::io::fs::add_extension; use nextclade::make_error; +use nextclade::utils::build_info::get_build_info; use nextclade::utils::global_init::setup_logger; use std::fmt::Debug; use std::io; use std::path::PathBuf; +use std::process::exit; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -31,11 +33,23 @@ use strum_macros::EnumIter; /// Please read short help with `nextalign -h` and extended help with `nextalign --help`. Each subcommand has its own help, for example: `nextclade run --help`. pub struct NextalignArgs { #[clap(subcommand)] - pub command: NextalignCommands, + pub command: Option, /// Make output more quiet or more verbose #[clap(flatten, next_help_heading = " Verbosity")] pub verbosity: Verbosity, + + /// Detailed version information + #[clap(long, short = 'W', global = true)] + pub version_detailed: bool, + + /// Full version information + #[clap(long, short = 'X', global = true)] + pub version_full: bool, + + /// Full version information in JSON format + #[clap(long, short = 'Y', global = true)] + pub version_json: bool, } #[derive(Subcommand, Debug)] @@ -446,19 +460,34 @@ pub fn nextalign_check_removed_args(run_args: &mut NextalignRunArgs) -> Result<( Ok(()) } +#[allow(clippy::exit)] pub fn nextalign_handle_cli_args() -> Result<(), Report> { let args = NextalignArgs::parse(); setup_logger(args.verbosity.get_filter_level()); + if args.version_detailed { + println!("{}", get_build_info().detailed()?); + exit(0); + } else if args.version_full { + println!("{}", get_build_info().full()?); + exit(0); + } else if args.version_json { + println!("{}", get_build_info().json()?); + exit(0); + } + match args.command { - NextalignCommands::Completions { shell } => { + Some(NextalignCommands::Completions { shell }) => { generate_completions(&shell).wrap_err_with(|| format!("When generating completions for shell '{shell}'")) } - NextalignCommands::Run(mut run_args) => { + Some(NextalignCommands::Run(mut run_args)) => { nextalign_check_removed_args(&mut run_args)?; nextalign_get_output_filenames(&mut run_args).wrap_err("When deducing output filenames")?; nextalign_run(*run_args) } + _ => { + make_error!("Nextalign requires a command as the first argument. Please type `nextalign --help` for more info.") + } } } diff --git a/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs b/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs index 06384a8ef..4ced4ca64 100644 --- a/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs +++ b/packages_rs/nextclade-cli/src/cli/nextclade_cli.rs @@ -12,11 +12,13 @@ use lazy_static::lazy_static; use nextclade::align::params::AlignPairwiseParamsOptional; use nextclade::io::fs::add_extension; use nextclade::tree::params::TreeBuilderParamsOptional; +use nextclade::utils::build_info::get_build_info; use nextclade::utils::global_init::setup_logger; use nextclade::{getenv, make_error}; use std::fmt::Debug; use std::io; use std::path::PathBuf; +use std::process::exit; use std::str::FromStr; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -50,11 +52,23 @@ pub fn check_shells(value: &str) -> Result { /// For short help type: `nextclade -h`, for extended help type: `nextclade --help`. Each subcommand has its own help, for example: `nextclade run --help`. pub struct NextcladeArgs { #[clap(subcommand)] - pub command: NextcladeCommands, + pub command: Option, /// Make output more quiet or more verbose #[clap(flatten, next_help_heading = " Verbosity")] pub verbosity: Verbosity, + + /// Detailed version information + #[clap(long, short = 'W', global = true)] + pub version_detailed: bool, + + /// Full version information + #[clap(long, short = 'X', global = true)] + pub version_full: bool, + + /// Full version information in JSON format + #[clap(long, short = 'Y', global = true)] + pub version_json: bool, } #[derive(Subcommand, Debug)] @@ -824,24 +838,39 @@ pub fn nextclade_check_column_config_args(run_args: &NextcladeRunArgs) -> Result Ok(()) } +#[allow(clippy::exit)] pub fn nextclade_parse_cli_args() -> Result<(), Report> { let args = NextcladeArgs::parse(); setup_logger(args.verbosity.get_filter_level()); + if args.version_detailed { + println!("{}", get_build_info().detailed()?); + exit(0); + } else if args.version_full { + println!("{}", get_build_info().full()?); + exit(0); + } else if args.version_json { + println!("{}", get_build_info().json()?); + exit(0); + } + match args.command { - NextcladeCommands::Completions { shell } => { + Some(NextcladeCommands::Completions { shell }) => { generate_completions(&shell).wrap_err_with(|| format!("When generating completions for shell '{shell}'")) } - NextcladeCommands::Run(mut run_args) => { + Some(NextcladeCommands::Run(mut run_args)) => { nextclade_check_removed_args(&run_args)?; nextclade_check_column_config_args(&run_args)?; nextclade_get_output_filenames(&mut run_args).wrap_err("When deducing output filenames")?; nextclade_run(*run_args) } - NextcladeCommands::Dataset(dataset_command) => match dataset_command.command { + Some(NextcladeCommands::Dataset(dataset_command)) => match dataset_command.command { NextcladeDatasetCommands::List(dataset_list_args) => nextclade_dataset_list(dataset_list_args), NextcladeDatasetCommands::Get(dataset_get_args) => nextclade_dataset_get(&dataset_get_args), }, + _ => { + make_error!("Nextclade requires a command as the first argument. Please type `nextclade --help` for more info.") + } } } diff --git a/packages_rs/nextclade/Cargo.toml b/packages_rs/nextclade/Cargo.toml index 7c1ed07dd..2462f2a89 100644 --- a/packages_rs/nextclade/Cargo.toml +++ b/packages_rs/nextclade/Cargo.toml @@ -8,6 +8,7 @@ homepage = "https://clades.nextstrain.org/" edition = "2021" license = "MIT" publish = false +build = "build.rs" [lib] crate-type = ["lib"] @@ -49,10 +50,11 @@ regex = "=1.8.4" schemars = { version = "=0.8.12", features = ["chrono", "either", "enumset", "indexmap"] } semver = "=1.0.17" serde = { version = "=1.0.164", features = ["derive"] } -serde_repr = "=0.1.12" serde_json = { version = "=1.0.99", features = ["preserve_order", "indexmap", "unbounded_depth"] } +serde_repr = "=0.1.12" serde_stacker = { version = "=0.1.8" } serde_yaml = "=0.9.22" +shadow-rs = { version = "=0.23.0", features = ["git2", "tzdb", "document-features"] } strum = "=0.25.0" strum_macros = "=0.25.0" tinytemplate = "=1.2.1" @@ -67,13 +69,15 @@ bzip2 = "=0.4.4" xz2 = "=0.1.7" zstd = { version = "=0.12.3", features = ["zstdmt"] } +[build-dependencies] +shadow-rs = { version = "=0.23.0", features = ["git2", "tzdb", "document-features"] } + [dev-dependencies] assert2 = "=0.3.11" criterion = { version = "=0.5.1", features = ["html_reports"] } rstest = "=0.17.0" rstest_reuse = "=0.5.0" - [[bench]] name = "bench_create_stripes" harness = false diff --git a/packages_rs/nextclade/build.rs b/packages_rs/nextclade/build.rs new file mode 100644 index 000000000..36b2683a5 --- /dev/null +++ b/packages_rs/nextclade/build.rs @@ -0,0 +1,3 @@ +fn main() -> shadow_rs::SdResult<()> { + shadow_rs::new() +} diff --git a/packages_rs/nextclade/src/utils/build_info.rs b/packages_rs/nextclade/src/utils/build_info.rs new file mode 100644 index 000000000..1ec5dda03 --- /dev/null +++ b/packages_rs/nextclade/src/utils/build_info.rs @@ -0,0 +1,109 @@ +use crate::io::json::json_stringify; +use eyre::Report; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use shadow_rs::{shadow, Format}; +use std::fmt::{Display, Formatter, Write}; + +shadow!(build); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BuildInfo { + version: &'static str, + is_debug: bool, + git_is_clean: bool, + git_branch: String, + git_tag: String, + git_commit: &'static str, + git_commit_date: &'static str, + build_time: &'static str, + build_os: &'static str, + rust_version: &'static str, + rust_channel: &'static str, +} + +impl BuildInfo { + pub fn detailed(&self) -> Result { + let mut f = String::new(); + + write!(f, "{}", self.version)?; + write!(f, " (")?; + + let mut version_ext = vec![]; + + if self.is_debug { + version_ext.push("Debug".to_owned()); + } + + if !self.git_is_clean { + version_ext.push("Dirty".to_owned()); + } + + if !self.git_commit.is_empty() { + version_ext.push(format!("Commit {}", self.git_commit)); + } + + if !self.git_branch.is_empty() { + version_ext.push(format!("Branch {}", self.git_branch)); + } + + if !self.git_tag.is_empty() { + version_ext.push(format!("Tag {}", self.git_tag)); + } + + if !self.git_commit_date.is_empty() { + version_ext.push(format!("Committed on {}", self.git_commit_date)); + } + + if !self.build_time.is_empty() { + version_ext.push(format!("Built on {}", self.build_time)); + } + + write!(f, "{}", version_ext.join("; "))?; + + write!(f, ")")?; + + Ok(f) + } + + pub fn full(&self) -> Result { + Ok(self.to_string()) + } + + pub fn json(&self) -> Result { + json_stringify(&self) + } +} + +impl Display for BuildInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Version {}", self.version)?; + writeln!(f, "Debug {}", self.is_debug)?; + writeln!(f, "Dirty {}", !self.git_is_clean)?; + writeln!(f, "Branch {}", self.git_branch)?; + writeln!(f, "Tag {}", self.git_tag)?; + writeln!(f, "Commit {}", self.git_commit)?; + writeln!(f, "Committed on {}", self.git_commit_date)?; + writeln!(f, "Built on {}", self.build_time)?; + writeln!(f, "Build OS {}", self.build_os)?; + writeln!(f, "Rust version {}", self.rust_version)?; + writeln!(f, "Rust channel {}", self.rust_channel)?; + Ok(()) + } +} + +pub fn get_build_info() -> BuildInfo { + BuildInfo { + version: build::PKG_VERSION, + git_branch: shadow_rs::branch(), + git_tag: shadow_rs::tag(), + is_debug: shadow_rs::is_debug(), + git_commit: build::SHORT_COMMIT, + git_commit_date: build::COMMIT_DATE, + build_os: build::BUILD_OS, + rust_version: build::RUST_VERSION, + rust_channel: build::RUST_CHANNEL, + build_time: build::BUILD_TIME, + git_is_clean: build::GIT_CLEAN, + } +} diff --git a/packages_rs/nextclade/src/utils/mod.rs b/packages_rs/nextclade/src/utils/mod.rs index 754e5e19c..797825511 100644 --- a/packages_rs/nextclade/src/utils/mod.rs +++ b/packages_rs/nextclade/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod build_info; pub mod collections; pub mod datetime; pub mod error;