Skip to content

Commit 161769f

Browse files
Gankrazanieb
authored andcommitted
change uv version to be an interface for project version reads and edits (#12349)
This is a reimplementation of #7248 with a new CLI interface. The old `uv version` is now `uv self version` (also it has gained a `--short` flag for parity). The new `uv version` is now an interface for getting/setting the project version. To give a modicum of support for migration, if `uv version` is run and we fail to find/read a `pyproject.toml` we will fallback to `uv self version`. `uv version --project .` prevents this fallback from being allowed. The new API of `uv version` is as follows: * pass nothing to read the project version * pass a version to set the project version * `--bump major|minor|patch` to semver-bump the project version * `--dry-run` to show the result but not apply it * `--short` to have the final printout contain only the final version * `--output-format json` to get the final printout as json ``` $ uv version myfast 0.1.0 $ uv version --bump major --dry-run myfast 0.1.0 => 1.0.0 $ uv version 1.2.3 --dry-run myfast 0.1.0 => 1.2.3 $ uv version 1.2.3 myfast 0.1.0 => 1.2.3 $ uv version --short 1.2.3 $ uv version --output-format json { "package_name": "myfast", "version": "1.2.3", "commit_info": null } ``` Fixes #6298
1 parent 5b7167d commit 161769f

File tree

11 files changed

+1738
-45
lines changed

11 files changed

+1738
-45
lines changed

crates/uv-cli/src/lib.rs

+46-6
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const STYLES: Styles = Styles::styled()
7474
.placeholder(AnsiColor::Cyan.on_default());
7575

7676
#[derive(Parser)]
77-
#[command(name = "uv", author, long_version = crate::version::version())]
77+
#[command(name = "uv", author, long_version = crate::version::uv_self_version())]
7878
#[command(about = "An extremely fast Python package manager.")]
7979
#[command(propagate_version = true)]
8080
#[command(
@@ -494,11 +494,8 @@ pub enum Commands {
494494
/// Clear the cache, removing all entries or those linked to specific packages.
495495
#[command(hide = true)]
496496
Clean(CleanArgs),
497-
/// Display uv's version
498-
Version {
499-
#[arg(long, value_enum, default_value = "text")]
500-
output_format: VersionFormat,
501-
},
497+
/// Read or update the project's version.
498+
Version(VersionArgs),
502499
/// Generate shell completion
503500
#[command(alias = "--generate-shell-completion", hide = true)]
504501
GenerateShellCompletion(GenerateShellCompletionArgs),
@@ -529,6 +526,41 @@ pub struct HelpArgs {
529526
pub command: Option<Vec<String>>,
530527
}
531528

529+
#[derive(Args, Debug)]
530+
#[command(group = clap::ArgGroup::new("operation"))]
531+
pub struct VersionArgs {
532+
/// Set the project version to this value
533+
///
534+
/// To update the project using semantic versioning components instead, use `--bump`.
535+
#[arg(group = "operation")]
536+
pub value: Option<String>,
537+
/// Update the project version using the given semantics
538+
#[arg(group = "operation", long)]
539+
pub bump: Option<VersionBump>,
540+
/// Don't write a new version to the `pyproject.toml`
541+
///
542+
/// Instead, the version will be displayed.
543+
#[arg(long)]
544+
pub dry_run: bool,
545+
/// Only show the version
546+
///
547+
/// By default, uv will show the project name before the version.
548+
#[arg(long)]
549+
pub short: bool,
550+
#[arg(long, value_enum, default_value = "text")]
551+
pub output_format: VersionFormat,
552+
}
553+
554+
#[derive(Debug, Copy, Clone, PartialEq, clap::ValueEnum)]
555+
pub enum VersionBump {
556+
/// Increase the major version (1.2.3 => 2.0.0)
557+
Major,
558+
/// Increase the minor version (1.2.3 => 1.3.0)
559+
Minor,
560+
/// Increase the patch version (1.2.3 => 1.2.4)
561+
Patch,
562+
}
563+
532564
#[derive(Args)]
533565
pub struct SelfNamespace {
534566
#[command(subcommand)]
@@ -539,6 +571,14 @@ pub struct SelfNamespace {
539571
pub enum SelfCommand {
540572
/// Update uv.
541573
Update(SelfUpdateArgs),
574+
/// Display uv's version
575+
Version {
576+
/// Only print the version
577+
#[arg(long)]
578+
short: bool,
579+
#[arg(long, value_enum, default_value = "text")]
580+
output_format: VersionFormat,
581+
},
542582
}
543583

544584
#[derive(Args, Debug)]

crates/uv-cli/src/version.rs

+35-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::fmt;
44

55
use serde::Serialize;
6+
use uv_pep508::{uv_pep440::Version, PackageName};
67

78
/// Information about the git repository where uv was built from.
89
#[derive(Serialize)]
@@ -17,26 +18,46 @@ pub(crate) struct CommitInfo {
1718
/// uv's version.
1819
#[derive(Serialize)]
1920
pub struct VersionInfo {
20-
/// uv's version, such as "0.5.1"
21+
/// Name of the package (or "uv" if printing uv's own version)
22+
pub package_name: Option<String>,
23+
/// version, such as "0.5.1"
2124
version: String,
2225
/// Information about the git commit we may have been built from.
2326
///
2427
/// `None` if not built from a git repo or if retrieval failed.
2528
commit_info: Option<CommitInfo>,
2629
}
2730

31+
impl VersionInfo {
32+
pub fn new(package_name: Option<&PackageName>, version: &Version) -> Self {
33+
Self {
34+
package_name: package_name.map(ToString::to_string),
35+
version: version.to_string(),
36+
commit_info: None,
37+
}
38+
}
39+
}
40+
2841
impl fmt::Display for VersionInfo {
2942
/// Formatted version information: "<version>[+<commits>] (<commit> <date>)"
43+
///
44+
/// This is intended for consumption by `clap` to provide `uv --version`,
45+
/// and intentionally omits the name of the package
3046
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3147
write!(f, "{}", self.version)?;
32-
33-
if let Some(ref ci) = self.commit_info {
34-
if ci.commits_since_last_tag > 0 {
35-
write!(f, "+{}", ci.commits_since_last_tag)?;
36-
}
37-
write!(f, " ({} {})", ci.short_commit_hash, ci.commit_date)?;
48+
if let Some(ci) = &self.commit_info {
49+
write!(f, "{ci}")?;
3850
}
51+
Ok(())
52+
}
53+
}
3954

55+
impl fmt::Display for CommitInfo {
56+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57+
if self.commits_since_last_tag > 0 {
58+
write!(f, "+{}", self.commits_since_last_tag)?;
59+
}
60+
write!(f, " ({} {})", self.short_commit_hash, self.commit_date)?;
4061
Ok(())
4162
}
4263
}
@@ -48,7 +69,7 @@ impl From<VersionInfo> for clap::builder::Str {
4869
}
4970

5071
/// Returns information about uv's version.
51-
pub fn version() -> VersionInfo {
72+
pub fn uv_self_version() -> VersionInfo {
5273
// Environment variables are only read at compile-time
5374
macro_rules! option_env_str {
5475
($name:expr) => {
@@ -71,6 +92,7 @@ pub fn version() -> VersionInfo {
7192
});
7293

7394
VersionInfo {
95+
package_name: Some("uv".to_owned()),
7496
version,
7597
commit_info,
7698
}
@@ -85,6 +107,7 @@ mod tests {
85107
#[test]
86108
fn version_formatting() {
87109
let version = VersionInfo {
110+
package_name: Some("uv".to_string()),
88111
version: "0.0.0".to_string(),
89112
commit_info: None,
90113
};
@@ -94,6 +117,7 @@ mod tests {
94117
#[test]
95118
fn version_formatting_with_commit_info() {
96119
let version = VersionInfo {
120+
package_name: Some("uv".to_string()),
97121
version: "0.0.0".to_string(),
98122
commit_info: Some(CommitInfo {
99123
short_commit_hash: "53b0f5d92".to_string(),
@@ -109,6 +133,7 @@ mod tests {
109133
#[test]
110134
fn version_formatting_with_commits_since_last_tag() {
111135
let version = VersionInfo {
136+
package_name: Some("uv".to_string()),
112137
version: "0.0.0".to_string(),
113138
commit_info: Some(CommitInfo {
114139
short_commit_hash: "53b0f5d92".to_string(),
@@ -124,6 +149,7 @@ mod tests {
124149
#[test]
125150
fn version_serializable() {
126151
let version = VersionInfo {
152+
package_name: Some("uv".to_string()),
127153
version: "0.0.0".to_string(),
128154
commit_info: Some(CommitInfo {
129155
short_commit_hash: "53b0f5d92".to_string(),
@@ -135,6 +161,7 @@ mod tests {
135161
};
136162
assert_json_snapshot!(version, @r#"
137163
{
164+
"package_name": "uv",
138165
"version": "0.0.0",
139166
"commit_info": {
140167
"short_commit_hash": "53b0f5d92",

crates/uv-workspace/src/pyproject_mut.rs

+43-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use uv_cache_key::CanonicalUrl;
1414
use uv_distribution_types::Index;
1515
use uv_fs::PortablePath;
1616
use uv_normalize::GroupName;
17-
use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers};
17+
use uv_pep440::{Version, VersionParseError, VersionSpecifier, VersionSpecifiers};
1818
use uv_pep508::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl};
1919

2020
use crate::pyproject::{DependencyType, Source};
@@ -44,6 +44,8 @@ pub enum Error {
4444
MalformedWorkspace,
4545
#[error("Expected a dependency at index {0}")]
4646
MissingDependency(usize),
47+
#[error("Failed to parse `version` field of `pyproject.toml`")]
48+
VersionParse(#[from] VersionParseError),
4749
#[error("Cannot perform ambiguous update; found multiple entries for `{}`:\n{}", package_name, requirements.iter().map(|requirement| format!("- `{requirement}`")).join("\n"))]
4850
Ambiguous {
4951
package_name: PackageName,
@@ -942,6 +944,46 @@ impl PyProjectTomlMut {
942944

943945
types
944946
}
947+
948+
pub fn version(&mut self) -> Result<Version, Error> {
949+
let version = self
950+
.doc
951+
.get("project")
952+
.and_then(Item::as_table)
953+
.and_then(|project| project.get("version"))
954+
.and_then(Item::as_str)
955+
.ok_or(Error::MalformedWorkspace)?;
956+
957+
Ok(Version::from_str(version)?)
958+
}
959+
960+
pub fn has_dynamic_version(&mut self) -> bool {
961+
let Some(dynamic) = self
962+
.doc
963+
.get("project")
964+
.and_then(Item::as_table)
965+
.and_then(|project| project.get("dynamic"))
966+
.and_then(Item::as_array)
967+
else {
968+
return false;
969+
};
970+
971+
dynamic.iter().any(|val| val.as_str() == Some("version"))
972+
}
973+
974+
pub fn set_version(&mut self, version: &Version) -> Result<(), Error> {
975+
let project = self
976+
.doc
977+
.get_mut("project")
978+
.and_then(Item::as_table_mut)
979+
.ok_or(Error::MalformedWorkspace)?;
980+
project.insert(
981+
"version",
982+
Item::Value(Value::String(Formatted::new(version.to_string()))),
983+
);
984+
985+
Ok(())
986+
}
945987
}
946988

947989
/// Returns an implicit table.

crates/uv/src/commands/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use uv_normalize::PackageName;
5656
use uv_python::PythonEnvironment;
5757
use uv_scripts::Pep723Script;
5858
pub(crate) use venv::venv;
59-
pub(crate) use version::version;
59+
pub(crate) use version::{project_version, self_version};
6060

6161
use crate::printer::Printer;
6262

0 commit comments

Comments
 (0)