Skip to main content

uv_cli/
version.rs

1//! Code for representing uv's release version number.
2// See also <https://github.com/astral-sh/ruff/blob/8118d29419055b779719cc96cdf3dacb29ac47c9/crates/ruff/src/version.rs>
3use std::fmt;
4
5use serde::Serialize;
6
7use uv_normalize::PackageName;
8use uv_pep508::uv_pep440::Version;
9
10/// Information about the git repository where uv was built from.
11#[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/// Version information for uv itself (e.g., in `uv self version`).
21#[derive(Serialize)]
22pub struct SelfVersionInfo {
23    /// Name of the package (always "uv").
24    package_name: String,
25    /// Version, such as "0.5.1".
26    version: String,
27    /// Information about the git commit we may have been built from.
28    ///
29    /// `None` if not built from a git repo or if retrieval failed.
30    commit_info: Option<CommitInfo>,
31    /// The target triple for which uv was built (e.g., `x86_64-unknown-linux-gnu`).
32    target_triple: String,
33}
34
35/// Version information for a project (`uv version`).
36#[derive(Serialize)]
37pub struct ProjectVersionInfo {
38    /// Name of the package.
39    pub package_name: Option<String>,
40    /// Version, such as "0.5.1".
41    version: String,
42    /// Information about the git commit uv was built from.
43    ///
44    /// Always `null` for project versions, kept for backwards compatibility.
45    // TODO(zanieb): Remove this field in a breaking release.
46    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    /// Returns just the version string (e.g., "0.5.1"), without commit info or target triple.
61    pub fn version(&self) -> &str {
62        &self.version
63    }
64}
65
66impl fmt::Display for SelfVersionInfo {
67    /// Formatted version information: "<version>[+<commits>] ([<commit> <date> ]<target>)"
68    ///
69    /// This is intended for consumption by `clap` to provide `uv --version`,
70    /// and intentionally omits the name of the package.
71    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
111/// Returns information about uv's version.
112pub fn uv_self_version() -> SelfVersionInfo {
113    // Environment variables are only read at compile-time
114    macro_rules! option_env_str {
115        ($name:expr) => {
116            option_env!($name).map(|s| s.to_string())
117        };
118    }
119
120    // This version is pulled from Cargo.toml and set by Cargo
121    let version = uv_version::version().to_string();
122
123    // Commit info is pulled from git and set by `build.rs`
124    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    // Set by `uv-cli/build.rs`
135    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}