mirror of https://github.com/astral-sh/uv
Add long-form version output (#1930)
Similar to https://github.com/astral-sh/ruff/pull/8034 Adds more version information so it's clear what revision the user is on ``` ❯ cargo run -q -- --version uv 0.1.10 (daa8565a72024-02-23) ❯ cargo run -q -- -V uv 0.1.10 ❯ cargo run -q -- version uv 0.1.10 (daa8565a72024-02-23) ❯ cargo run -q -- version --output-format json { "version": "0.1.10", "commit_info": { "short_commit_hash": "daa8565a7", "commit_hash": "daa8565a75249305821fdc34ace085060c082ba3", "commit_date": "2024-02-23", "last_tag": null, "commits_since_last_tag": 0 } } ```
This commit is contained in:
parent
ba9c788680
commit
af39bbde75
|
|
@ -1572,6 +1572,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"linked-hash-map",
|
||||
"regex",
|
||||
"serde",
|
||||
"similar",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
|
@ -4168,6 +4169,8 @@ dependencies = [
|
|||
"requirements-txt",
|
||||
"reqwest",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"textwrap",
|
||||
"thiserror",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ anstream = { workspace = true }
|
|||
anyhow = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
clap = { workspace = true, features = ["derive", "string"] }
|
||||
clap_complete_command = { workspace = true }
|
||||
console = { workspace = true }
|
||||
ctrlc = { workspace = true }
|
||||
|
|
@ -56,6 +56,8 @@ owo-colors = { workspace = true }
|
|||
pubgrub = { workspace = true }
|
||||
pyproject-toml = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
textwrap = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
|
@ -79,7 +81,7 @@ assert_cmd = { version = "2.0.12" }
|
|||
assert_fs = { version = "1.1.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
indoc = { version = "2.0.4" }
|
||||
insta = { version = "1.34.0", features = ["filters"] }
|
||||
insta = { version = "1.34.0", features = ["filters", "json"] }
|
||||
predicates = { version = "3.0.4" }
|
||||
regex = { version = "1.10.3" }
|
||||
reqwest = { version = "0.11.23", features = ["blocking"], default-features = false }
|
||||
|
|
@ -94,4 +96,3 @@ pypi = []
|
|||
git = []
|
||||
# Introduces a dependency on Maturin.
|
||||
maturin = []
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
use std::{fs, path::Path, process::Command};
|
||||
|
||||
fn main() {
|
||||
// The workspace root directory is not available without walking up the tree
|
||||
// https://github.com/rust-lang/cargo/issues/3946
|
||||
let workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||
.join("..")
|
||||
.join("..");
|
||||
|
||||
commit_info(&workspace_root);
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let target = std::env::var("TARGET").unwrap();
|
||||
println!("cargo:rustc-env=RUST_HOST_TARGET={target}");
|
||||
}
|
||||
|
||||
fn commit_info(workspace_root: &Path) {
|
||||
// If not in a git repository, do not attempt to retrieve commit information
|
||||
let git_dir = workspace_root.join(".git");
|
||||
if !git_dir.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let git_head_path = git_dir.join("HEAD");
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
git_head_path.as_path().display()
|
||||
);
|
||||
|
||||
let git_head_contents = fs::read_to_string(git_head_path);
|
||||
if let Ok(git_head_contents) = git_head_contents {
|
||||
// The contents are either a commit or a reference in the following formats
|
||||
// - "<commit>" when the head is detached
|
||||
// - "ref <ref>" when working on a branch
|
||||
// If a commit, checking if the HEAD file has changed is sufficient
|
||||
// If a ref, we need to add the head file for that ref to rebuild on commit
|
||||
let mut git_ref_parts = git_head_contents.split_whitespace();
|
||||
git_ref_parts.next();
|
||||
if let Some(git_ref) = git_ref_parts.next() {
|
||||
let git_ref_path = git_dir.join(git_ref);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
git_ref_path.as_path().display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let output = match Command::new("git")
|
||||
.arg("log")
|
||||
.arg("-1")
|
||||
.arg("--date=short")
|
||||
.arg("--abbrev=9")
|
||||
.arg("--format=%H %h %cd %(describe)")
|
||||
.output()
|
||||
{
|
||||
Ok(output) if output.status.success() => output,
|
||||
_ => return,
|
||||
};
|
||||
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||
let mut parts = stdout.split_whitespace();
|
||||
let mut next = || parts.next().unwrap();
|
||||
println!("cargo:rustc-env=UV_COMMIT_HASH={}", next());
|
||||
println!("cargo:rustc-env=UV_COMMIT_SHORT_HASH={}", next());
|
||||
println!("cargo:rustc-env=UV_COMMIT_DATE={}", next());
|
||||
|
||||
// Describe can fail for some commits
|
||||
// https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt-emdescribeoptionsem
|
||||
if let Some(describe) = parts.next() {
|
||||
let mut describe_parts = describe.split('-');
|
||||
println!(
|
||||
"cargo:rustc-env=UV_LAST_TAG={}",
|
||||
describe_parts.next().unwrap()
|
||||
);
|
||||
// If this is the tagged commit, this component will be missing
|
||||
println!(
|
||||
"cargo:rustc-env=UV_LAST_TAG_DISTANCE={}",
|
||||
describe_parts.next().unwrap_or("0")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ pub(crate) use pip_install::pip_install;
|
|||
pub(crate) use pip_sync::pip_sync;
|
||||
pub(crate) use pip_uninstall::pip_uninstall;
|
||||
pub(crate) use venv::venv;
|
||||
pub(crate) use version::version;
|
||||
|
||||
mod cache_clean;
|
||||
mod cache_dir;
|
||||
|
|
@ -20,6 +21,7 @@ mod pip_sync;
|
|||
mod pip_uninstall;
|
||||
mod reporters;
|
||||
mod venv;
|
||||
mod version;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum ExitStatus {
|
||||
|
|
@ -72,3 +74,9 @@ pub(super) struct ChangeEvent<T: InstalledMetadata> {
|
|||
dist: T,
|
||||
kind: ChangeEventKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||
pub(crate) enum VersionFormat {
|
||||
Text,
|
||||
Json,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use crate::commands::VersionFormat;
|
||||
|
||||
/// Display version information
|
||||
pub(crate) fn version(output_format: VersionFormat, buffer: &mut dyn std::io::Write) -> Result<()> {
|
||||
let version_info = crate::version::version();
|
||||
|
||||
match output_format {
|
||||
VersionFormat::Text => {
|
||||
writeln!(buffer, "uv {}", &version_info)?;
|
||||
}
|
||||
VersionFormat::Json => {
|
||||
serde_json::to_writer_pretty(&mut *buffer, &version_info)?;
|
||||
// Add a trailing newline
|
||||
writeln!(buffer)?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ use uv_traits::{
|
|||
ConfigSettingEntry, ConfigSettings, NoBuild, PackageNameSpecifier, SetupPyStrategy,
|
||||
};
|
||||
|
||||
use crate::commands::{extra_name_with_clap_error, ExitStatus, Upgrade};
|
||||
use crate::commands::{extra_name_with_clap_error, ExitStatus, Upgrade, VersionFormat};
|
||||
use crate::compat::CompatArgs;
|
||||
use crate::requirements::RequirementsSource;
|
||||
|
||||
|
|
@ -50,11 +50,12 @@ mod confirm;
|
|||
mod logging;
|
||||
mod printer;
|
||||
mod requirements;
|
||||
mod version;
|
||||
|
||||
const DEFAULT_VENV_NAME: &str = ".venv";
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about)]
|
||||
#[command(author, version, long_version = crate::version::version(), about)]
|
||||
#[command(propagate_version = true)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
struct Cli {
|
||||
|
|
@ -122,6 +123,11 @@ enum Commands {
|
|||
/// Remove all items from the cache.
|
||||
#[clap(hide = true)]
|
||||
Clean(CleanArgs),
|
||||
/// Display uv's version
|
||||
Version {
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
output_format: VersionFormat,
|
||||
},
|
||||
/// Generate shell completion
|
||||
#[clap(alias = "--generate-shell-completion", hide = true)]
|
||||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||
|
|
@ -1126,6 +1132,10 @@ async fn run() -> Result<ExitStatus> {
|
|||
)
|
||||
.await
|
||||
}
|
||||
Commands::Version { output_format } => {
|
||||
commands::version(output_format, &mut stdout())?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Commands::GenerateShellCompletion { shell } => {
|
||||
shell.generate(&mut Cli::command(), &mut stdout());
|
||||
Ok(ExitStatus::Success)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
//! Code for representing uv's release version number.
|
||||
// See also <https://github.com/astral-sh/ruff/blob/8118d29419055b779719cc96cdf3dacb29ac47c9/crates/ruff/src/version.rs>
|
||||
use serde::Serialize;
|
||||
use std::fmt;
|
||||
|
||||
/// Information about the git repository where uv was built from.
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CommitInfo {
|
||||
short_commit_hash: String,
|
||||
commit_hash: String,
|
||||
commit_date: String,
|
||||
last_tag: Option<String>,
|
||||
commits_since_last_tag: u32,
|
||||
}
|
||||
|
||||
/// uv's version.
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct VersionInfo {
|
||||
/// uv's version, such as "0.5.1"
|
||||
version: String,
|
||||
/// Information about the git commit we may have been built from.
|
||||
///
|
||||
/// `None` if not built from a git repo or if retrieval failed.
|
||||
commit_info: Option<CommitInfo>,
|
||||
}
|
||||
impl fmt::Display for VersionInfo {
|
||||
/// Formatted version information: "<version>[+<commits>] (<commit> <date>)"
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.version)?;
|
||||
|
||||
if let Some(ref ci) = self.commit_info {
|
||||
if ci.commits_since_last_tag > 0 {
|
||||
write!(f, "+{}", ci.commits_since_last_tag)?;
|
||||
}
|
||||
write!(f, " ({} {})", ci.short_commit_hash, ci.commit_date)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VersionInfo> for clap::builder::Str {
|
||||
fn from(val: VersionInfo) -> Self {
|
||||
val.to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns information about uv's version.
|
||||
pub(crate) fn version() -> VersionInfo {
|
||||
// Environment variables are only read at compile-time
|
||||
macro_rules! option_env_str {
|
||||
($name:expr) => {
|
||||
option_env!($name).map(|s| s.to_string())
|
||||
};
|
||||
}
|
||||
|
||||
// This version is pulled from Cargo.toml and set by Cargo
|
||||
let version = option_env_str!("CARGO_PKG_VERSION").unwrap();
|
||||
|
||||
// Commit info is pulled from git and set by `build.rs`
|
||||
let commit_info = option_env_str!("UV_COMMIT_HASH").map(|commit_hash| CommitInfo {
|
||||
short_commit_hash: option_env_str!("UV_COMMIT_SHORT_HASH").unwrap(),
|
||||
commit_hash,
|
||||
commit_date: option_env_str!("UV_COMMIT_DATE").unwrap(),
|
||||
last_tag: option_env_str!("UV_LAST_TAG"),
|
||||
commits_since_last_tag: option_env_str!("UV_LAST_TAG_DISTANCE")
|
||||
.as_deref()
|
||||
.map_or(0, |value| value.parse::<u32>().unwrap_or(0)),
|
||||
});
|
||||
|
||||
VersionInfo {
|
||||
version,
|
||||
commit_info,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::{assert_display_snapshot, assert_json_snapshot};
|
||||
|
||||
use super::{CommitInfo, VersionInfo};
|
||||
|
||||
#[test]
|
||||
fn version_formatting() {
|
||||
let version = VersionInfo {
|
||||
version: "0.0.0".to_string(),
|
||||
commit_info: None,
|
||||
};
|
||||
assert_display_snapshot!(version, @"0.0.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_formatting_with_commit_info() {
|
||||
let version = VersionInfo {
|
||||
version: "0.0.0".to_string(),
|
||||
commit_info: Some(CommitInfo {
|
||||
short_commit_hash: "53b0f5d92".to_string(),
|
||||
commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
|
||||
last_tag: Some("v0.0.1".to_string()),
|
||||
commit_date: "2023-10-19".to_string(),
|
||||
commits_since_last_tag: 0,
|
||||
}),
|
||||
};
|
||||
assert_display_snapshot!(version, @"0.0.0 (53b0f5d92 2023-10-19)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_formatting_with_commits_since_last_tag() {
|
||||
let version = VersionInfo {
|
||||
version: "0.0.0".to_string(),
|
||||
commit_info: Some(CommitInfo {
|
||||
short_commit_hash: "53b0f5d92".to_string(),
|
||||
commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
|
||||
last_tag: Some("v0.0.1".to_string()),
|
||||
commit_date: "2023-10-19".to_string(),
|
||||
commits_since_last_tag: 24,
|
||||
}),
|
||||
};
|
||||
assert_display_snapshot!(version, @"0.0.0+24 (53b0f5d92 2023-10-19)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_serializable() {
|
||||
let version = VersionInfo {
|
||||
version: "0.0.0".to_string(),
|
||||
commit_info: Some(CommitInfo {
|
||||
short_commit_hash: "53b0f5d92".to_string(),
|
||||
commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
|
||||
last_tag: Some("v0.0.1".to_string()),
|
||||
commit_date: "2023-10-19".to_string(),
|
||||
commits_since_last_tag: 0,
|
||||
}),
|
||||
};
|
||||
assert_json_snapshot!(version, @r###"
|
||||
{
|
||||
"version": "0.0.0",
|
||||
"commit_info": {
|
||||
"short_commit_hash": "53b0f5d92",
|
||||
"commit_hash": "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7",
|
||||
"commit_date": "2023-10-19",
|
||||
"last_tag": "v0.0.1",
|
||||
"commits_since_last_tag": 0
|
||||
}
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue