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
7/// Information about the git repository where uv was built from.
8#[derive(Serialize)]
9pub(crate) struct CommitInfo {
10    short_commit_hash: String,
11    commit_hash: String,
12    commit_date: String,
13    last_tag: Option<String>,
14    commits_since_last_tag: u32,
15}
16
17/// Version information for uv itself (e.g., in `uv self version`).
18#[derive(Serialize)]
19pub struct SelfVersionInfo {
20    /// Name of the package (always "uv").
21    package_name: String,
22    /// Version, such as "0.5.1".
23    version: String,
24    /// Information about the git commit we may have been built from.
25    ///
26    /// `None` if Git metadata was disabled, unavailable, or retrieval failed.
27    commit_info: Option<CommitInfo>,
28    /// The target triple for which uv was built (e.g., `x86_64-unknown-linux-gnu`).
29    target_triple: String,
30}
31
32impl SelfVersionInfo {
33    /// Returns just the version string (e.g., "0.5.1"), without commit info or target triple.
34    pub fn version(&self) -> &str {
35        &self.version
36    }
37}
38
39impl fmt::Display for SelfVersionInfo {
40    /// Formatted version information: `<version>[+<commits>] ([<commit> <date> ]<target>)`.
41    ///
42    /// This is intended for consumption by `clap` to provide `uv --version`,
43    /// and intentionally omits the name of the package.
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "{}", self.version)?;
46        if let Some(ci) = &self.commit_info {
47            if ci.commits_since_last_tag > 0 {
48                write!(f, "+{}", ci.commits_since_last_tag)?;
49            }
50            write!(
51                f,
52                " ({} {} {})",
53                ci.short_commit_hash, ci.commit_date, self.target_triple
54            )?;
55        } else {
56            write!(f, " ({})", self.target_triple)?;
57        }
58        Ok(())
59    }
60}
61
62impl fmt::Display for CommitInfo {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        if self.commits_since_last_tag > 0 {
65            write!(f, "+{}", self.commits_since_last_tag)?;
66        }
67        write!(f, " ({} {})", self.short_commit_hash, self.commit_date)?;
68        Ok(())
69    }
70}
71
72impl From<SelfVersionInfo> for clap::builder::Str {
73    fn from(val: SelfVersionInfo) -> Self {
74        val.to_string().into()
75    }
76}
77
78/// Returns information about uv's version.
79pub fn uv_self_version() -> SelfVersionInfo {
80    // Environment variables are only read at compile-time
81    macro_rules! option_env_str {
82        ($name:expr) => {
83            option_env!($name).map(|s| s.to_string())
84        };
85    }
86
87    // This version is pulled from Cargo.toml and set by Cargo
88    let version = uv_version::version().to_string();
89
90    // Commit info is pulled from git and set by `build.rs`
91    let commit_info = option_env_str!("UV_COMMIT_HASH").map(|commit_hash| CommitInfo {
92        short_commit_hash: option_env_str!("UV_COMMIT_SHORT_HASH").unwrap(),
93        commit_hash,
94        commit_date: option_env_str!("UV_COMMIT_DATE").unwrap(),
95        last_tag: option_env_str!("UV_LAST_TAG"),
96        commits_since_last_tag: option_env_str!("UV_LAST_TAG_DISTANCE")
97            .as_deref()
98            .map_or(0, |value| value.parse::<u32>().unwrap_or(0)),
99    });
100
101    // Set by `uv-cli/build.rs`
102    let target_triple = env!("RUST_HOST_TARGET").to_string();
103
104    SelfVersionInfo {
105        package_name: "uv".to_owned(),
106        version,
107        commit_info,
108        target_triple,
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use insta::{assert_json_snapshot, assert_snapshot};
115
116    use super::{CommitInfo, SelfVersionInfo};
117
118    #[test]
119    fn version_formatting() {
120        let version = SelfVersionInfo {
121            package_name: "uv".to_string(),
122            version: "0.0.0".to_string(),
123            commit_info: None,
124            target_triple: "x86_64-unknown-linux-gnu".to_string(),
125        };
126        assert_snapshot!(version, @"0.0.0 (x86_64-unknown-linux-gnu)");
127    }
128
129    #[test]
130    fn version_formatting_with_commit_info() {
131        let version = SelfVersionInfo {
132            package_name: "uv".to_string(),
133            version: "0.0.0".to_string(),
134            commit_info: Some(CommitInfo {
135                short_commit_hash: "53b0f5d92".to_string(),
136                commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
137                last_tag: Some("v0.0.1".to_string()),
138                commit_date: "2023-10-19".to_string(),
139                commits_since_last_tag: 0,
140            }),
141            target_triple: "x86_64-unknown-linux-gnu".to_string(),
142        };
143        assert_snapshot!(version, @"0.0.0 (53b0f5d92 2023-10-19 x86_64-unknown-linux-gnu)");
144    }
145
146    #[test]
147    fn version_formatting_with_commits_since_last_tag() {
148        let version = SelfVersionInfo {
149            package_name: "uv".to_string(),
150            version: "0.0.0".to_string(),
151            commit_info: Some(CommitInfo {
152                short_commit_hash: "53b0f5d92".to_string(),
153                commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
154                last_tag: Some("v0.0.1".to_string()),
155                commit_date: "2023-10-19".to_string(),
156                commits_since_last_tag: 24,
157            }),
158            target_triple: "x86_64-unknown-linux-gnu".to_string(),
159        };
160        assert_snapshot!(version, @"0.0.0+24 (53b0f5d92 2023-10-19 x86_64-unknown-linux-gnu)");
161    }
162
163    #[test]
164    fn version_serializable() {
165        let version = SelfVersionInfo {
166            package_name: "uv".to_string(),
167            version: "0.0.0".to_string(),
168            commit_info: Some(CommitInfo {
169                short_commit_hash: "53b0f5d92".to_string(),
170                commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
171                last_tag: Some("v0.0.1".to_string()),
172                commit_date: "2023-10-19".to_string(),
173                commits_since_last_tag: 0,
174            }),
175            target_triple: "x86_64-unknown-linux-gnu".to_string(),
176        };
177        assert_json_snapshot!(version, @r#"
178        {
179          "package_name": "uv",
180          "version": "0.0.0",
181          "commit_info": {
182            "short_commit_hash": "53b0f5d92",
183            "commit_hash": "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7",
184            "commit_date": "2023-10-19",
185            "last_tag": "v0.0.1",
186            "commits_since_last_tag": 0
187          },
188          "target_triple": "x86_64-unknown-linux-gnu"
189        }
190        "#);
191    }
192}