mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25: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,
|
||||
)]
|
||||
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)]
|
||||
|
@ -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`.
|
||||
///
|
||||
/// Allowed: 1, 2, 3 or 4.
|
||||
|
|
|
@ -56,13 +56,19 @@ pub(crate) async fn upgrade_project_dependencies(
|
|||
allow if !allow.is_empty() => allow,
|
||||
_ => vec![1, 2, 3, 4],
|
||||
};
|
||||
let tomls = match args
|
||||
.recursive
|
||||
.then(|| search_pyproject_tomls(Path::new(".")))
|
||||
{
|
||||
None => vec![".".to_string()],
|
||||
Some(Ok(tomls)) => tomls,
|
||||
Some(Err(err)) => return Err(err),
|
||||
|
||||
let only_packages = !args.requirements.is_empty();
|
||||
let tomls = if only_packages {
|
||||
vec![String::new()]
|
||||
} else {
|
||||
match args
|
||||
.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;
|
||||
|
@ -94,11 +100,32 @@ pub(crate) async fn upgrade_project_dependencies(
|
|||
.and_then(|v| RequiresPython::from_str(&v).ok());
|
||||
let mut all_versioned = FxHashMap::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 {
|
||||
let pyproject_toml = Path::new(toml_dir).join("pyproject.toml");
|
||||
let toml = match read_pyproject_toml(&pyproject_toml).await {
|
||||
Ok(value) => value,
|
||||
Err(value) => return value,
|
||||
let toml = if only_packages {
|
||||
if packages.is_empty() {
|
||||
warn_user!("No versioned dependencies found in packages");
|
||||
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();
|
||||
if versioned.is_empty() {
|
||||
|
@ -133,7 +160,7 @@ pub(crate) async fn upgrade_project_dependencies(
|
|||
|
||||
for (toml_dir, toml) in &mut toml_contents {
|
||||
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()
|
||||
} else {
|
||||
format!("{}/", &toml_dir[2..])
|
||||
|
@ -161,7 +188,8 @@ pub(crate) async fn upgrade_project_dependencies(
|
|||
if !skipped.is_empty() {
|
||||
writeln!(
|
||||
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
|
||||
|
@ -174,8 +202,12 @@ pub(crate) async fn upgrade_project_dependencies(
|
|||
} else {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{info} No upgrades found for {found} dependencies in {subpath}, check manually if not committed yet{}",
|
||||
skipped.format(" (skipped ", &format!(" of {count_skipped} upgrades)"))
|
||||
"{info} No upgrades found for {} in {subpath}, check manually if not committed yet{}",
|
||||
plural(found, "dependency"),
|
||||
skipped.format(
|
||||
" (skipped ",
|
||||
&format!(" of {})", plural(count_skipped, "upgrade"))
|
||||
)
|
||||
)?;
|
||||
}
|
||||
continue;
|
||||
|
@ -220,30 +252,41 @@ pub(crate) async fn upgrade_project_dependencies(
|
|||
);
|
||||
});
|
||||
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 {
|
||||
return Err(err.into());
|
||||
}
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{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() {
|
||||
writeln!(
|
||||
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 {
|
||||
item_written = true;
|
||||
}
|
||||
}
|
||||
let files = format!(
|
||||
"{} file{}",
|
||||
tomls.len(),
|
||||
if tomls.len() == 1 { "" } else { "s" }
|
||||
);
|
||||
let files = plural(tomls.len(), "file");
|
||||
if args.recursive && files_bumped != 1 {
|
||||
if tomls.is_empty() {
|
||||
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() {
|
||||
writeln!(
|
||||
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)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
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() {
|
||||
writeln!(
|
||||
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)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{info} Upgraded {all_bumped}/{all_found} dependencies in {files} 🚀 Check manually, update {uv_sync} and run tests{}",
|
||||
all_skipped.format(" (skipped ", &format!(" of {all_count_skipped} upgrades)"))
|
||||
"{info} Total: Upgraded {all_bumped}/{} in {files} 🚀 Check manually, update {uv_sync} and run tests{}",
|
||||
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)
|
||||
}
|
||||
|
||||
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> {
|
||||
toml.get_requires_python()
|
||||
.map(RequiresPython::from_str)
|
||||
|
@ -309,24 +369,29 @@ fn format_allow(allow: &[usize]) -> String {
|
|||
.join(",")
|
||||
}
|
||||
|
||||
async fn read_pyproject_toml(
|
||||
pyproject_toml: &Path,
|
||||
) -> Result<PyProjectTomlMut, Result<ExitStatus>> {
|
||||
async fn read_pyproject_toml(pyproject_toml: &Path) -> Result<PyProjectTomlMut, anyhow::Error> {
|
||||
let content = match fs_err::tokio::read_to_string(pyproject_toml.to_path_buf()).await {
|
||||
Ok(content) => content,
|
||||
Err(err) => {
|
||||
if err.kind() == ErrorKind::NotFound {
|
||||
warn_user!("No pyproject.toml found in current directory");
|
||||
return Err(Ok(ExitStatus::Error));
|
||||
warn_user!(
|
||||
"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) {
|
||||
Ok(toml) => toml,
|
||||
Err(err) => {
|
||||
warn_user!("Couldn't read pyproject.toml: {}", err);
|
||||
return Err(Ok(ExitStatus::Error));
|
||||
warn_user!("Could not parse pyproject.toml: {}", err);
|
||||
return Err(anyhow::Error::from(err));
|
||||
}
|
||||
};
|
||||
Ok(toml)
|
||||
|
|
|
@ -942,9 +942,14 @@ Upgrade the project's dependency constraints
|
|||
<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>
|
||||
|
||||
<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