diff --git a/Cargo.toml b/Cargo.toml index aaadbf9..6e797ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "echoircd" -version = "0.1.0" +version = "5.0.0" edition = "2021" description = "A small, dependency-light IRC daemon in Rust with a modular command/mode/module API." license = "MIT" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..cc3a3ce --- /dev/null +++ b/build.rs @@ -0,0 +1,42 @@ +use std::process::Command; + +// Capture build provenance (git rev/date, toolchain, target) into env vars the +// crate reads via env! for the VERSION reply. Always emits every var, with an +// "unknown" fallback, so a non-git build (e.g. a source tarball) still compiles. +fn main() { + println!("cargo:rerun-if-changed=.git/HEAD"); + println!("cargo:rerun-if-changed=.git/index"); + + let git = |args: &[&str]| { + Command::new("git") + .args(args) + .output() + .ok() + .filter(|o| o.status.success()) + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .filter(|s| !s.is_empty()) + }; + + let hash = git(&["rev-parse", "--short=7", "HEAD"]).unwrap_or_else(|| "unknown".into()); + let dirty = match git(&["status", "--porcelain", "--untracked-files=no"]) { + Some(s) if !s.is_empty() => "-dirty", + _ => "", + }; + let date = git(&["log", "-1", "--format=%cd", "--date=short"]).unwrap_or_else(|| "unknown".into()); + + let rustc = Command::new(std::env::var("RUSTC").unwrap_or_else(|_| "rustc".into())) + .arg("--version") + .output() + .ok() + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .and_then(|s| s.split_whitespace().nth(1).map(str::to_string)) + .unwrap_or_else(|| "unknown".into()); + + let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown".into()); + + println!("cargo:rustc-env=ECHOIRCD_GIT_HASH={hash}"); + println!("cargo:rustc-env=ECHOIRCD_GIT_DIRTY={dirty}"); + println!("cargo:rustc-env=ECHOIRCD_COMMIT_DATE={date}"); + println!("cargo:rustc-env=ECHOIRCD_RUSTC={rustc}"); + println!("cargo:rustc-env=ECHOIRCD_TARGET={target}"); +} diff --git a/src/coremods/core_info.rs b/src/coremods/core_info.rs index 3b3afc6..07325e0 100644 --- a/src/coremods/core_info.rs +++ b/src/coremods/core_info.rs @@ -2,7 +2,7 @@ use crate::command::{CmdResult, Command}; use crate::numeric::*; -use crate::server::{Server, VERSION}; +use crate::server::{version_comment, Server, RELEASE}; use crate::users::User; use crate::Uid; @@ -677,10 +677,8 @@ impl Command for VersionCmd { true } fn handle(&self, s: &mut Server, uid: Uid, _params: &[String]) -> CmdResult { - s.send( - uid, - format!(":{} 351 * echoircd-{VERSION} {} :", s.name, s.name), - ); + let line = format!("echoircd-{RELEASE} {} :{}", s.name, version_comment()); + s.numeric(uid, RPL_VERSION, &line); CmdResult::Ok } } diff --git a/src/numeric.rs b/src/numeric.rs index 38eb2eb..d81ca43 100644 --- a/src/numeric.rs +++ b/src/numeric.rs @@ -78,6 +78,7 @@ pub const RPL_WELCOME: u16 = 1; pub const RPL_YOURHOST: u16 = 2; pub const RPL_CREATED: u16 = 3; pub const RPL_MYINFO: u16 = 4; +pub const RPL_VERSION: u16 = 351; pub const RPL_ISUPPORT: u16 = 5; pub const RPL_UMODEIS: u16 = 221; diff --git a/src/server.rs b/src/server.rs index b02c4b3..7bd5810 100644 --- a/src/server.rs +++ b/src/server.rs @@ -28,6 +28,19 @@ use crate::xline::XLine; use crate::Uid; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +/// The wire version token, major only (e.g. `echoircd-5`), for 002/004/351. +pub const RELEASE: &str = env!("CARGO_PKG_VERSION_MAJOR"); +// Build provenance, captured by build.rs. +pub const GIT_HASH: &str = env!("ECHOIRCD_GIT_HASH"); +pub const GIT_DIRTY: &str = env!("ECHOIRCD_GIT_DIRTY"); +pub const COMMIT_DATE: &str = env!("ECHOIRCD_COMMIT_DATE"); +pub const RUSTC: &str = env!("ECHOIRCD_RUSTC"); +pub const TARGET: &str = env!("ECHOIRCD_TARGET"); + +/// The `RPL_VERSION` (351) comment: full version, build, and toolchain. +pub fn version_comment() -> String { + format!("echoircd {VERSION} · {GIT_HASH}{GIT_DIRTY} · built {COMMIT_DATE} · rustc {RUSTC} · {TARGET}") +} /// Background timer cadence + idle/ping timeouts, in seconds. pub const TICK_SECS: u64 = 15; @@ -1507,6 +1520,14 @@ mod tests { use crate::users::{valid_nick, User, UserFlags}; use std::sync::mpsc::{self, Receiver}; + #[test] + fn version_token_and_comment() { + assert_eq!(RELEASE, "5"); + let c = version_comment(); + assert!(c.starts_with("echoircd 5.0.0 \u{b7}"), "unexpected: {c}"); + assert!(c.contains("rustc ") && c.contains("built ")); + } + /// Insert a registered user with an output channel readable in the test. fn add_user(s: &mut Server, uid: Uid, nick: &str) -> Receiver { let (tx, rx) = mpsc::channel(); diff --git a/src/users.rs b/src/users.rs index ae95865..3a6f092 100644 --- a/src/users.rs +++ b/src/users.rs @@ -7,7 +7,7 @@ use std::net::{SocketAddr, TcpStream}; use crate::extensible::Extensible; use crate::module::Hook; use crate::numeric::*; -use crate::server::{Server, VERSION}; +use crate::server::{Server, RELEASE}; use crate::socketengine::OutSink; use crate::Uid; @@ -464,7 +464,7 @@ impl Server { self.numeric( uid, RPL_YOURHOST, - &format!(":Your host is {}, running echoircd-{VERSION}", self.name), + &format!(":Your host is {}, running echoircd-{RELEASE}", self.name), ); self.numeric( uid, @@ -475,7 +475,7 @@ impl Server { uid, RPL_MYINFO, &format!( - "{} echoircd-{VERSION} iowxsgBkDIHrRzWhc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUdKXwD", + "{} echoircd-{RELEASE} iowxsgBkDIHrRzWhc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUdKXwD", self.name ), );