1use std::fmt;
4
5use serde::Serialize;
6
7use uv_normalize::PackageName;
8use uv_pep508::uv_pep440::Version;
9
10#[derive(Serialize)]
12pub(crate) struct CommitInfo {
13 short_commit_hash: String,
14 commit_hash: String,
15 commit_date: String,
16 last_tag: Option<String>,
17 commits_since_last_tag: u32,
18}
19
20#[derive(Serialize)]
22pub struct SelfVersionInfo {
23 package_name: String,
25 version: String,
27 commit_info: Option<CommitInfo>,
31 target_triple: String,
33}
34
35#[derive(Serialize)]
37pub struct ProjectVersionInfo {
38 pub package_name: Option<String>,
40 version: String,
42 commit_info: Option<CommitInfo>,
47}
48
49impl ProjectVersionInfo {
50 pub fn new(package_name: Option<&PackageName>, version: &Version) -> Self {
51 Self {
52 package_name: package_name.map(ToString::to_string),
53 version: version.to_string(),
54 commit_info: None,
55 }
56 }
57}
58
59impl SelfVersionInfo {
60 pub fn version(&self) -> &str {
62 &self.version
63 }
64}
65
66impl fmt::Display for SelfVersionInfo {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "{}", self.version)?;
73 if let Some(ci) = &self.commit_info {
74 if ci.commits_since_last_tag > 0 {
75 write!(f, "+{}", ci.commits_since_last_tag)?;
76 }
77 write!(
78 f,
79 " ({} {} {})",
80 ci.short_commit_hash, ci.commit_date, self.target_triple
81 )?;
82 } else {
83 write!(f, " ({})", self.target_triple)?;
84 }
85 Ok(())
86 }
87}
88
89impl fmt::Display for ProjectVersionInfo {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(f, "{}", self.version)
92 }
93}
94
95impl fmt::Display for CommitInfo {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 if self.commits_since_last_tag > 0 {
98 write!(f, "+{}", self.commits_since_last_tag)?;
99 }
100 write!(f, " ({} {})", self.short_commit_hash, self.commit_date)?;
101 Ok(())
102 }
103}
104
105impl From<SelfVersionInfo> for clap::builder::Str {
106 fn from(val: SelfVersionInfo) -> Self {
107 val.to_string().into()
108 }
109}
110
111pub fn uv_self_version() -> SelfVersionInfo {
113 macro_rules! option_env_str {
115 ($name:expr) => {
116 option_env!($name).map(|s| s.to_string())
117 };
118 }
119
120 let version = uv_version::version().to_string();
122
123 let commit_info = option_env_str!("UV_COMMIT_HASH").map(|commit_hash| CommitInfo {
125 short_commit_hash: option_env_str!("UV_COMMIT_SHORT_HASH").unwrap(),
126 commit_hash,
127 commit_date: option_env_str!("UV_COMMIT_DATE").unwrap(),
128 last_tag: option_env_str!("UV_LAST_TAG"),
129 commits_since_last_tag: option_env_str!("UV_LAST_TAG_DISTANCE")
130 .as_deref()
131 .map_or(0, |value| value.parse::<u32>().unwrap_or(0)),
132 });
133
134 let target_triple = env!("RUST_HOST_TARGET").to_string();
136
137 SelfVersionInfo {
138 package_name: "uv".to_owned(),
139 version,
140 commit_info,
141 target_triple,
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use insta::{assert_json_snapshot, assert_snapshot};
148
149 use super::{CommitInfo, SelfVersionInfo};
150
151 #[test]
152 fn version_formatting() {
153 let version = SelfVersionInfo {
154 package_name: "uv".to_string(),
155 version: "0.0.0".to_string(),
156 commit_info: None,
157 target_triple: "x86_64-unknown-linux-gnu".to_string(),
158 };
159 assert_snapshot!(version, @"0.0.0 (x86_64-unknown-linux-gnu)");
160 }
161
162 #[test]
163 fn version_formatting_with_commit_info() {
164 let version = SelfVersionInfo {
165 package_name: "uv".to_string(),
166 version: "0.0.0".to_string(),
167 commit_info: Some(CommitInfo {
168 short_commit_hash: "53b0f5d92".to_string(),
169 commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
170 last_tag: Some("v0.0.1".to_string()),
171 commit_date: "2023-10-19".to_string(),
172 commits_since_last_tag: 0,
173 }),
174 target_triple: "x86_64-unknown-linux-gnu".to_string(),
175 };
176 assert_snapshot!(version, @"0.0.0 (53b0f5d92 2023-10-19 x86_64-unknown-linux-gnu)");
177 }
178
179 #[test]
180 fn version_formatting_with_commits_since_last_tag() {
181 let version = SelfVersionInfo {
182 package_name: "uv".to_string(),
183 version: "0.0.0".to_string(),
184 commit_info: Some(CommitInfo {
185 short_commit_hash: "53b0f5d92".to_string(),
186 commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
187 last_tag: Some("v0.0.1".to_string()),
188 commit_date: "2023-10-19".to_string(),
189 commits_since_last_tag: 24,
190 }),
191 target_triple: "x86_64-unknown-linux-gnu".to_string(),
192 };
193 assert_snapshot!(version, @"0.0.0+24 (53b0f5d92 2023-10-19 x86_64-unknown-linux-gnu)");
194 }
195
196 #[test]
197 fn version_serializable() {
198 let version = SelfVersionInfo {
199 package_name: "uv".to_string(),
200 version: "0.0.0".to_string(),
201 commit_info: Some(CommitInfo {
202 short_commit_hash: "53b0f5d92".to_string(),
203 commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
204 last_tag: Some("v0.0.1".to_string()),
205 commit_date: "2023-10-19".to_string(),
206 commits_since_last_tag: 0,
207 }),
208 target_triple: "x86_64-unknown-linux-gnu".to_string(),
209 };
210 assert_json_snapshot!(version, @r#"
211 {
212 "package_name": "uv",
213 "version": "0.0.0",
214 "commit_info": {
215 "short_commit_hash": "53b0f5d92",
216 "commit_hash": "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7",
217 "commit_date": "2023-10-19",
218 "last_tag": "v0.0.1",
219 "commits_since_last_tag": 0
220 },
221 "target_triple": "x86_64-unknown-linux-gnu"
222 }
223 "#);
224 }
225}