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 fmt::Display for SelfVersionInfo {
60    /// Formatted version information: "<version>[+<commits>] ([<commit> <date> ]<target>)"
61    ///
62    /// This is intended for consumption by `clap` to provide `uv --version`,
63    /// and intentionally omits the name of the package.
64    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
104/// Returns information about uv's version.
105pub fn uv_self_version() -> SelfVersionInfo {
106    // Environment variables are only read at compile-time
107    macro_rules! option_env_str {
108        ($name:expr) => {
109            option_env!($name).map(|s| s.to_string())
110        };
111    }
112
113    // This version is pulled from Cargo.toml and set by Cargo
114    let version = uv_version::version().to_string();
115
116    // Commit info is pulled from git and set by `build.rs`
117    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    // Set by `uv-cli/build.rs`
128    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}