mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
--packages-only
This commit is contained in:
parent
0a037894c8
commit
c315a3c6b7
3 changed files with 123 additions and 37 deletions
|
@ -658,6 +658,10 @@ pub struct UpgradeProjectArgs {
|
||||||
value_parser = parse_maybe_string,
|
value_parser = parse_maybe_string,
|
||||||
)]
|
)]
|
||||||
pub python: Option<Maybe<String>>,
|
pub python: Option<Maybe<String>>,
|
||||||
|
|
||||||
|
/// Upgrade only the given requirements (i.e. `uv<0.5`) instead of pyproject.toml files.
|
||||||
|
#[arg(required = false, value_parser = parse_requirement)]
|
||||||
|
pub requirements: Vec<Maybe<Requirement>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, clap::ValueEnum)]
|
#[derive(Debug, Copy, Clone, PartialEq, clap::ValueEnum)]
|
||||||
|
@ -1119,6 +1123,18 @@ fn parse_dependency_type(input: &str) -> Result<Maybe<DependencyType>, String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a string like `uv<0.5` into an [`Requirement`], mapping the empty string to `None`.
|
||||||
|
fn parse_requirement(input: &str) -> Result<Maybe<Requirement>, String> {
|
||||||
|
if input.is_empty() {
|
||||||
|
Ok(Maybe::None)
|
||||||
|
} else {
|
||||||
|
match Requirement::from_str(input) {
|
||||||
|
Ok(table) => Ok(Maybe::Some(table)),
|
||||||
|
Err(err) => Err(err.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a string into an [`usize`], mapping the empty string or unknown digits to `None`.
|
/// Parse a string into an [`usize`], mapping the empty string or unknown digits to `None`.
|
||||||
///
|
///
|
||||||
/// Allowed: 1, 2, 3 or 4.
|
/// Allowed: 1, 2, 3 or 4.
|
||||||
|
|
|
@ -56,13 +56,19 @@ pub(crate) async fn upgrade_project_dependencies(
|
||||||
allow if !allow.is_empty() => allow,
|
allow if !allow.is_empty() => allow,
|
||||||
_ => vec![1, 2, 3, 4],
|
_ => vec![1, 2, 3, 4],
|
||||||
};
|
};
|
||||||
let tomls = match args
|
|
||||||
.recursive
|
let only_packages = !args.requirements.is_empty();
|
||||||
.then(|| search_pyproject_tomls(Path::new(".")))
|
let tomls = if only_packages {
|
||||||
{
|
vec![String::new()]
|
||||||
None => vec![".".to_string()],
|
} else {
|
||||||
Some(Ok(tomls)) => tomls,
|
match args
|
||||||
Some(Err(err)) => return Err(err),
|
.recursive
|
||||||
|
.then(|| search_pyproject_tomls(Path::new(".")))
|
||||||
|
{
|
||||||
|
None => vec![String::new()], // recursive=false or no pyproject.toml files found
|
||||||
|
Some(Ok(tomls)) => tomls,
|
||||||
|
Some(Err(err)) => return Err(err), // error searching pyproject.toml files
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let printer = Printer::Default;
|
let printer = Printer::Default;
|
||||||
|
@ -94,11 +100,32 @@ pub(crate) async fn upgrade_project_dependencies(
|
||||||
.and_then(|v| RequiresPython::from_str(&v).ok());
|
.and_then(|v| RequiresPython::from_str(&v).ok());
|
||||||
let mut all_versioned = FxHashMap::default();
|
let mut all_versioned = FxHashMap::default();
|
||||||
let mut toml_contents = BTreeMap::default();
|
let mut toml_contents = BTreeMap::default();
|
||||||
|
let packages: Vec<_> = args
|
||||||
|
.requirements
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| {
|
||||||
|
let requirement = r.clone().into_option().expect("no req");
|
||||||
|
requirement.version_or_url.as_ref()?; // Skip unversioned requirements
|
||||||
|
Some(format!("\"{requirement}\""))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
for toml_dir in &tomls {
|
for toml_dir in &tomls {
|
||||||
let pyproject_toml = Path::new(toml_dir).join("pyproject.toml");
|
let toml = if only_packages {
|
||||||
let toml = match read_pyproject_toml(&pyproject_toml).await {
|
if packages.is_empty() {
|
||||||
Ok(value) => value,
|
warn_user!("No versioned dependencies found in packages");
|
||||||
Err(value) => return value,
|
return Ok(ExitStatus::Error);
|
||||||
|
}
|
||||||
|
let content = format!("[project]\ndependencies = [\n{}\n]", packages.join(",\n"));
|
||||||
|
match PyProjectTomlMut::from_toml(&content, DependencyTarget::PyProjectToml) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(err) => {
|
||||||
|
warn_user!("Couldn't parse packages: {}", err.to_string());
|
||||||
|
return Ok(ExitStatus::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let pyproject_toml = Path::new(toml_dir).join("pyproject.toml");
|
||||||
|
read_pyproject_toml(&pyproject_toml).await?
|
||||||
};
|
};
|
||||||
let versioned = toml.find_versioned_dependencies();
|
let versioned = toml.find_versioned_dependencies();
|
||||||
if versioned.is_empty() {
|
if versioned.is_empty() {
|
||||||
|
@ -133,7 +160,7 @@ pub(crate) async fn upgrade_project_dependencies(
|
||||||
|
|
||||||
for (toml_dir, toml) in &mut toml_contents {
|
for (toml_dir, toml) in &mut toml_contents {
|
||||||
let pyproject_toml = Path::new(*toml_dir).join("pyproject.toml");
|
let pyproject_toml = Path::new(*toml_dir).join("pyproject.toml");
|
||||||
let relative = if *toml_dir == "." {
|
let relative = if toml_dir.is_empty() || *toml_dir == "." {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
format!("{}/", &toml_dir[2..])
|
format!("{}/", &toml_dir[2..])
|
||||||
|
@ -161,7 +188,8 @@ pub(crate) async fn upgrade_project_dependencies(
|
||||||
if !skipped.is_empty() {
|
if !skipped.is_empty() {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{info} Skipped {skipped} ({count_skipped} upgrades) of {found} dependencies in {subpath}"
|
"{info} Skipped {skipped} ({count_skipped} upgrades) of {} in {subpath}",
|
||||||
|
plural(found, "dependency"),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
continue; // Skip intermediate messages if nothing was changed
|
continue; // Skip intermediate messages if nothing was changed
|
||||||
|
@ -174,8 +202,12 @@ pub(crate) async fn upgrade_project_dependencies(
|
||||||
} else {
|
} else {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{info} No upgrades found for {found} dependencies in {subpath}, check manually if not committed yet{}",
|
"{info} No upgrades found for {} in {subpath}, check manually if not committed yet{}",
|
||||||
skipped.format(" (skipped ", &format!(" of {count_skipped} upgrades)"))
|
plural(found, "dependency"),
|
||||||
|
skipped.format(
|
||||||
|
" (skipped ",
|
||||||
|
&format!(" of {})", plural(count_skipped, "upgrade"))
|
||||||
|
)
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
@ -220,30 +252,41 @@ pub(crate) async fn upgrade_project_dependencies(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
table.printstd();
|
table.printstd();
|
||||||
if !args.dry_run {
|
if only_packages {
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"{info} Upgraded {bumped} of {} 🚀{}",
|
||||||
|
plural(found, "package"),
|
||||||
|
skipped.format(
|
||||||
|
" (skipped ",
|
||||||
|
&format!(" of {})", plural(count_skipped, "upgrade"))
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
} else if !args.dry_run {
|
||||||
if let Err(err) = fs_err::tokio::write(pyproject_toml, toml.to_string()).await {
|
if let Err(err) = fs_err::tokio::write(pyproject_toml, toml.to_string()).await {
|
||||||
return Err(err.into());
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{info} Upgraded {bumped}/{found} in {subpath} 🚀 Check manually, update {uv_sync} and run tests{}",
|
"{info} Upgraded {bumped}/{found} in {subpath} 🚀 Check manually, update {uv_sync} and run tests{}",
|
||||||
skipped.format(" (skipped ", &format!(" of {count_skipped} upgrades)"))
|
skipped.format(
|
||||||
|
" (skipped ",
|
||||||
|
&format!(" of {})", plural(count_skipped, "upgrade"))
|
||||||
|
)
|
||||||
)?;
|
)?;
|
||||||
} else if !skipped.is_empty() {
|
} else if !skipped.is_empty() {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{info} Skipped {skipped} ({count_skipped} upgrades), upgraded {bumped} of {found} dependencies in {subpath}"
|
"{info} Skipped {skipped} ({}), upgraded {bumped} of {} in {subpath}",
|
||||||
|
plural(count_skipped, "upgrade"),
|
||||||
|
plural(found, "dependency"),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if !item_written {
|
if !item_written {
|
||||||
item_written = true;
|
item_written = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let files = format!(
|
let files = plural(tomls.len(), "file");
|
||||||
"{} file{}",
|
|
||||||
tomls.len(),
|
|
||||||
if tomls.len() == 1 { "" } else { "s" }
|
|
||||||
);
|
|
||||||
if args.recursive && files_bumped != 1 {
|
if args.recursive && files_bumped != 1 {
|
||||||
if tomls.is_empty() {
|
if tomls.is_empty() {
|
||||||
warn_user!("No pyproject.toml files found recursively");
|
warn_user!("No pyproject.toml files found recursively");
|
||||||
|
@ -257,26 +300,35 @@ pub(crate) async fn upgrade_project_dependencies(
|
||||||
} else if !all_skipped.is_empty() {
|
} else if !all_skipped.is_empty() {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{info} Skipped {all_skipped} ({all_count_skipped} upgrades), {all_found} dependencies in {files} not upgraded for --allow={}",
|
"{info} Skipped {all_skipped} ({}), {} in {files} not upgraded for --allow={}",
|
||||||
|
plural(all_count_skipped, "upgrade"),
|
||||||
|
plural(all_found, "dependency"),
|
||||||
format_allow(&allow)
|
format_allow(&allow)
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{info} No upgrades in {all_found} dependencies and {files} found, check manually if not committed yet"
|
"{info} No upgrades in {} and {files} found, check manually if not committed yet",
|
||||||
|
plural(all_found, "dependency"),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
} else if !all_skipped.is_empty() {
|
} else if !all_skipped.is_empty() {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{info} Total: Skipped {all_skipped} ({all_count_skipped} upgrades), upgraded {all_bumped} of {all_found} dependencies for --allow={}",
|
"{info} Total: Skipped {all_skipped} ({}), upgraded {all_bumped} of {} for --allow={}",
|
||||||
|
plural(all_count_skipped, "upgrade"),
|
||||||
|
plural(all_found, "dependency"),
|
||||||
format_allow(&allow)
|
format_allow(&allow)
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{info} Upgraded {all_bumped}/{all_found} dependencies in {files} 🚀 Check manually, update {uv_sync} and run tests{}",
|
"{info} Total: Upgraded {all_bumped}/{} in {files} 🚀 Check manually, update {uv_sync} and run tests{}",
|
||||||
all_skipped.format(" (skipped ", &format!(" of {all_count_skipped} upgrades)"))
|
plural(all_found, "dependency"),
|
||||||
|
all_skipped.format(
|
||||||
|
" (skipped ",
|
||||||
|
&format!(" of {})", plural(all_count_skipped, "upgrade"))
|
||||||
|
)
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -284,6 +336,14 @@ pub(crate) async fn upgrade_project_dependencies(
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn plural(count: usize, word: &str) -> String {
|
||||||
|
if count != 1 && word.ends_with('y') {
|
||||||
|
format!("{count} {}ies", &word[..word.len() - 1])
|
||||||
|
} else {
|
||||||
|
format!("{count} {word}{}", if count == 1 { "" } else { "s" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_requires_python(toml: &PyProjectTomlMut) -> Option<RequiresPython> {
|
fn get_requires_python(toml: &PyProjectTomlMut) -> Option<RequiresPython> {
|
||||||
toml.get_requires_python()
|
toml.get_requires_python()
|
||||||
.map(RequiresPython::from_str)
|
.map(RequiresPython::from_str)
|
||||||
|
@ -309,24 +369,29 @@ fn format_allow(allow: &[usize]) -> String {
|
||||||
.join(",")
|
.join(",")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_pyproject_toml(
|
async fn read_pyproject_toml(pyproject_toml: &Path) -> Result<PyProjectTomlMut, anyhow::Error> {
|
||||||
pyproject_toml: &Path,
|
|
||||||
) -> Result<PyProjectTomlMut, Result<ExitStatus>> {
|
|
||||||
let content = match fs_err::tokio::read_to_string(pyproject_toml.to_path_buf()).await {
|
let content = match fs_err::tokio::read_to_string(pyproject_toml.to_path_buf()).await {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.kind() == ErrorKind::NotFound {
|
if err.kind() == ErrorKind::NotFound {
|
||||||
warn_user!("No pyproject.toml found in current directory");
|
warn_user!(
|
||||||
return Err(Ok(ExitStatus::Error));
|
"Could not find {}",
|
||||||
|
pyproject_toml.to_str().expect("path not UTF-8")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
warn_user!(
|
||||||
|
"Could not read {}",
|
||||||
|
pyproject_toml.to_str().expect("path not UTF-8")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Err(Err(err.into()));
|
return Err(anyhow::Error::from(err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let toml = match PyProjectTomlMut::from_toml(&content, DependencyTarget::PyProjectToml) {
|
let toml = match PyProjectTomlMut::from_toml(&content, DependencyTarget::PyProjectToml) {
|
||||||
Ok(toml) => toml,
|
Ok(toml) => toml,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn_user!("Couldn't read pyproject.toml: {}", err);
|
warn_user!("Could not parse pyproject.toml: {}", err);
|
||||||
return Err(Ok(ExitStatus::Error));
|
return Err(anyhow::Error::from(err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(toml)
|
Ok(toml)
|
||||||
|
|
|
@ -942,9 +942,14 @@ Upgrade the project's dependency constraints
|
||||||
<h3 class="cli-reference">Usage</h3>
|
<h3 class="cli-reference">Usage</h3>
|
||||||
|
|
||||||
```
|
```
|
||||||
uv upgrade [OPTIONS]
|
uv upgrade [OPTIONS] [REQUIREMENTS]...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<h3 class="cli-reference">Arguments</h3>
|
||||||
|
|
||||||
|
<dl class="cli-reference"><dt id="uv-upgrade--requirements"><a href="#uv-upgrade--requirements"<code>REQUIREMENTS</code></a></dt><dd><p>Upgrade only the given requirements (i.e. <code>uv<0.5</code>) instead of pyproject.toml files</p>
|
||||||
|
</dd></dl>
|
||||||
|
|
||||||
<h3 class="cli-reference">Options</h3>
|
<h3 class="cli-reference">Options</h3>
|
||||||
|
|
||||||
<dl class="cli-reference"><dt id="uv-upgrade--allow"><a href="#uv-upgrade--allow"><code>--allow</code></a> <i>allow</i></dt><dd><p>Allow only some version digits to change, others will be skipped: <code>1,2,3,4</code> (major, minor, patch, build number)</p>
|
<dl class="cli-reference"><dt id="uv-upgrade--allow"><a href="#uv-upgrade--allow"><code>--allow</code></a> <i>allow</i></dt><dd><p>Allow only some version digits to change, others will be skipped: <code>1,2,3,4</code> (major, minor, patch, build number)</p>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue