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 fmt::Display for SelfVersionInfo {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{}", self.version)?;
66 if let Some(ci) = &self.commit_info {
67 if ci.commits_since_last_tag > 0 {
68 write!(f, "+{}", ci.commits_since_last_tag)?;
69 }
70 write!(
71 f,
72 " ({} {} {})",
73 ci.short_commit_hash, ci.commit_date, self.target_triple
74 )?;
75 } else {
76 write!(f, " ({})", self.target_triple)?;
77 }
78 Ok(())
79 }
80}
81
82impl fmt::Display for ProjectVersionInfo {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "{}", self.version)
85 }
86}
87
88impl fmt::Display for CommitInfo {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 if self.commits_since_last_tag > 0 {
91 write!(f, "+{}", self.commits_since_last_tag)?;
92 }
93 write!(f, " ({} {})", self.short_commit_hash, self.commit_date)?;
94 Ok(())
95 }
96}
97
98impl From<SelfVersionInfo> for clap::builder::Str {
99 fn from(val: SelfVersionInfo) -> Self {
100 val.to_string().into()
101 }
102}
103
104pub fn uv_self_version() -> SelfVersionInfo {
106 macro_rules! option_env_str {
108 ($name:expr) => {
109 option_env!($name).map(|s| s.to_string())
110 };
111 }
112
113 let version = uv_version::version().to_string();
115
116 let commit_info = option_env_str!("UV_COMMIT_HASH").map(|commit_hash| CommitInfo {
118 short_commit_hash: option_env_str!("UV_COMMIT_SHORT_HASH").unwrap(),
119 commit_hash,
120 commit_date: option_env_str!("UV_COMMIT_DATE").unwrap(),
121 last_tag: option_env_str!("UV_LAST_TAG"),
122 commits_since_last_tag: option_env_str!("UV_LAST_TAG_DISTANCE")
123 .as_deref()
124 .map_or(0, |value| value.parse::<u32>().unwrap_or(0)),
125 });
126
127 let target_triple = env!("RUST_HOST_TARGET").to_string();
129
130 SelfVersionInfo {
131 package_name: "uv".to_owned(),
132 version,
133 commit_info,
134 target_triple,
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use insta::{assert_json_snapshot, assert_snapshot};
141
142 use super::{CommitInfo, SelfVersionInfo};
143
144 #[test]
145 fn version_formatting() {
146 let version = SelfVersionInfo {
147 package_name: "uv".to_string(),
148 version: "0.0.0".to_string(),
149 commit_info: None,
150 target_triple: "x86_64-unknown-linux-gnu".to_string(),
151 };
152 assert_snapshot!(version, @"0.0.0 (x86_64-unknown-linux-gnu)");
153 }
154
155 #[test]
156 fn version_formatting_with_commit_info() {
157 let version = SelfVersionInfo {
158 package_name: "uv".to_string(),
159 version: "0.0.0".to_string(),
160 commit_info: Some(CommitInfo {
161 short_commit_hash: "53b0f5d92".to_string(),
162 commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
163 last_tag: Some("v0.0.1".to_string()),
164 commit_date: "2023-10-19".to_string(),
165 commits_since_last_tag: 0,
166 }),
167 target_triple: "x86_64-unknown-linux-gnu".to_string(),
168 };
169 assert_snapshot!(version, @"0.0.0 (53b0f5d92 2023-10-19 x86_64-unknown-linux-gnu)");
170 }
171
172 #[test]
173 fn version_formatting_with_commits_since_last_tag() {
174 let version = SelfVersionInfo {
175 package_name: "uv".to_string(),
176 version: "0.0.0".to_string(),
177 commit_info: Some(CommitInfo {
178 short_commit_hash: "53b0f5d92".to_string(),
179 commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
180 last_tag: Some("v0.0.1".to_string()),
181 commit_date: "2023-10-19".to_string(),
182 commits_since_last_tag: 24,
183 }),
184 target_triple: "x86_64-unknown-linux-gnu".to_string(),
185 };
186 assert_snapshot!(version, @"0.0.0+24 (53b0f5d92 2023-10-19 x86_64-unknown-linux-gnu)");
187 }
188
189 #[test]
190 fn version_serializable() {
191 let version = SelfVersionInfo {
192 package_name: "uv".to_string(),
193 version: "0.0.0".to_string(),
194 commit_info: Some(CommitInfo {
195 short_commit_hash: "53b0f5d92".to_string(),
196 commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
197 last_tag: Some("v0.0.1".to_string()),
198 commit_date: "2023-10-19".to_string(),
199 commits_since_last_tag: 0,
200 }),
201 target_triple: "x86_64-unknown-linux-gnu".to_string(),
202 };
203 assert_json_snapshot!(version, @r#"
204 {
205 "package_name": "uv",
206 "version": "0.0.0",
207 "commit_info": {
208 "short_commit_hash": "53b0f5d92",
209 "commit_hash": "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7",
210 "commit_date": "2023-10-19",
211 "last_tag": "v0.0.1",
212 "commits_since_last_tag": 0
213 },
214 "target_triple": "x86_64-unknown-linux-gnu"
215 }
216 "#);
217 }
218}