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",
|
"lazy_static",
|
||||||
"linked-hash-map",
|
"linked-hash-map",
|
||||||
"regex",
|
"regex",
|
||||||
|
"serde",
|
||||||
"similar",
|
"similar",
|
||||||
"yaml-rust",
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
@ -4168,6 +4169,8 @@ dependencies = [
|
||||||
"requirements-txt",
|
"requirements-txt",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ anstream = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
clap = { workspace = true, features = ["derive"] }
|
clap = { workspace = true, features = ["derive", "string"] }
|
||||||
clap_complete_command = { workspace = true }
|
clap_complete_command = { workspace = true }
|
||||||
console = { workspace = true }
|
console = { workspace = true }
|
||||||
ctrlc = { workspace = true }
|
ctrlc = { workspace = true }
|
||||||
|
|
@ -56,6 +56,8 @@ owo-colors = { workspace = true }
|
||||||
pubgrub = { workspace = true }
|
pubgrub = { workspace = true }
|
||||||
pyproject-toml = { workspace = true }
|
pyproject-toml = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
textwrap = { workspace = true }
|
textwrap = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
@ -79,7 +81,7 @@ assert_cmd = { version = "2.0.12" }
|
||||||
assert_fs = { version = "1.1.0" }
|
assert_fs = { version = "1.1.0" }
|
||||||
filetime = { version = "0.2.23" }
|
filetime = { version = "0.2.23" }
|
||||||
indoc = { version = "2.0.4" }
|
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" }
|
predicates = { version = "3.0.4" }
|
||||||
regex = { version = "1.10.3" }
|
regex = { version = "1.10.3" }
|
||||||
reqwest = { version = "0.11.23", features = ["blocking"], default-features = false }
|
reqwest = { version = "0.11.23", features = ["blocking"], default-features = false }
|
||||||
|
|
@ -94,4 +96,3 @@ pypi = []
|
||||||
git = []
|
git = []
|
||||||
# Introduces a dependency on Maturin.
|
# Introduces a dependency on Maturin.
|
||||||
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_sync::pip_sync;
|
||||||
pub(crate) use pip_uninstall::pip_uninstall;
|
pub(crate) use pip_uninstall::pip_uninstall;
|
||||||
pub(crate) use venv::venv;
|
pub(crate) use venv::venv;
|
||||||
|
pub(crate) use version::version;
|
||||||
|
|
||||||
mod cache_clean;
|
mod cache_clean;
|
||||||
mod cache_dir;
|
mod cache_dir;
|
||||||
|
|
@ -20,6 +21,7 @@ mod pip_sync;
|
||||||
mod pip_uninstall;
|
mod pip_uninstall;
|
||||||
mod reporters;
|
mod reporters;
|
||||||
mod venv;
|
mod venv;
|
||||||
|
mod version;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) enum ExitStatus {
|
pub(crate) enum ExitStatus {
|
||||||
|
|
@ -72,3 +74,9 @@ pub(super) struct ChangeEvent<T: InstalledMetadata> {
|
||||||
dist: T,
|
dist: T,
|
||||||
kind: ChangeEventKind,
|
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,
|
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::compat::CompatArgs;
|
||||||
use crate::requirements::RequirementsSource;
|
use crate::requirements::RequirementsSource;
|
||||||
|
|
||||||
|
|
@ -50,11 +50,12 @@ mod confirm;
|
||||||
mod logging;
|
mod logging;
|
||||||
mod printer;
|
mod printer;
|
||||||
mod requirements;
|
mod requirements;
|
||||||
|
mod version;
|
||||||
|
|
||||||
const DEFAULT_VENV_NAME: &str = ".venv";
|
const DEFAULT_VENV_NAME: &str = ".venv";
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, long_version = crate::version::version(), about)]
|
||||||
#[command(propagate_version = true)]
|
#[command(propagate_version = true)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
|
@ -122,6 +123,11 @@ enum Commands {
|
||||||
/// Remove all items from the cache.
|
/// Remove all items from the cache.
|
||||||
#[clap(hide = true)]
|
#[clap(hide = true)]
|
||||||
Clean(CleanArgs),
|
Clean(CleanArgs),
|
||||||
|
/// Display uv's version
|
||||||
|
Version {
|
||||||
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
|
output_format: VersionFormat,
|
||||||
|
},
|
||||||
/// Generate shell completion
|
/// Generate shell completion
|
||||||
#[clap(alias = "--generate-shell-completion", hide = true)]
|
#[clap(alias = "--generate-shell-completion", hide = true)]
|
||||||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||||
|
|
@ -1126,6 +1132,10 @@ async fn run() -> Result<ExitStatus> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
Commands::Version { output_format } => {
|
||||||
|
commands::version(output_format, &mut stdout())?;
|
||||||
|
Ok(ExitStatus::Success)
|
||||||
|
}
|
||||||
Commands::GenerateShellCompletion { shell } => {
|
Commands::GenerateShellCompletion { shell } => {
|
||||||
shell.generate(&mut Cli::command(), &mut stdout());
|
shell.generate(&mut Cli::command(), &mut stdout());
|
||||||
Ok(ExitStatus::Success)
|
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