mirror of https://github.com/astral-sh/uv
Refactor adding bounds to dependencies in `pyproject.toml` (#12945)
Preparation work for making the kind of bound added configurable. No functional changes, just moving some methods around.
This commit is contained in:
parent
77e0fbee4f
commit
2b96dbdd49
|
|
@ -518,21 +518,25 @@ impl PyProjectTomlMut {
|
|||
Ok(added)
|
||||
}
|
||||
|
||||
/// Set the minimum version for an existing dependency in `project.dependencies`.
|
||||
/// Set the minimum version for an existing dependency.
|
||||
pub fn set_dependency_minimum_version(
|
||||
&mut self,
|
||||
dependency_type: &DependencyType,
|
||||
index: usize,
|
||||
version: Version,
|
||||
) -> Result<(), Error> {
|
||||
// Get or create `project.dependencies`.
|
||||
let dependencies = self
|
||||
.project()?
|
||||
.entry("dependencies")
|
||||
.or_insert(Item::Value(Value::Array(Array::new())))
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
let group = match dependency_type {
|
||||
DependencyType::Production => self.set_project_dependency_minimum_version()?,
|
||||
DependencyType::Dev => self.set_dev_dependency_minimum_version()?,
|
||||
DependencyType::Optional(ref extra) => {
|
||||
self.set_optional_dependency_minimum_version(extra)?
|
||||
}
|
||||
DependencyType::Group(ref group) => {
|
||||
self.set_dependency_group_requirement_minimum_version(group)?
|
||||
}
|
||||
};
|
||||
|
||||
let Some(req) = dependencies.get(index) else {
|
||||
let Some(req) = group.get(index) else {
|
||||
return Err(Error::MissingDependency(index));
|
||||
};
|
||||
|
||||
|
|
@ -543,17 +547,26 @@ impl PyProjectTomlMut {
|
|||
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
|
||||
VersionSpecifier::greater_than_equal_version(version),
|
||||
)));
|
||||
dependencies.replace(index, req.to_string());
|
||||
group.replace(index, req.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the minimum version for an existing dependency in `project.dependencies`.
|
||||
fn set_project_dependency_minimum_version(&mut self) -> Result<&mut Array, Error> {
|
||||
// Get or create `project.dependencies`.
|
||||
let dependencies = self
|
||||
.project()?
|
||||
.entry("dependencies")
|
||||
.or_insert(Item::Value(Value::Array(Array::new())))
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
Ok(dependencies)
|
||||
}
|
||||
|
||||
/// Set the minimum version for an existing dependency in `tool.uv.dev-dependencies`.
|
||||
pub fn set_dev_dependency_minimum_version(
|
||||
&mut self,
|
||||
index: usize,
|
||||
version: Version,
|
||||
) -> Result<(), Error> {
|
||||
fn set_dev_dependency_minimum_version(&mut self) -> Result<&mut Array, Error> {
|
||||
// Get or create `tool.uv.dev-dependencies`.
|
||||
let dev_dependencies = self
|
||||
.doc
|
||||
|
|
@ -570,29 +583,14 @@ impl PyProjectTomlMut {
|
|||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
let Some(req) = dev_dependencies.get(index) else {
|
||||
return Err(Error::MissingDependency(index));
|
||||
};
|
||||
|
||||
let mut req = req
|
||||
.as_str()
|
||||
.and_then(try_parse_requirement)
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
|
||||
VersionSpecifier::greater_than_equal_version(version),
|
||||
)));
|
||||
dev_dependencies.replace(index, req.to_string());
|
||||
|
||||
Ok(())
|
||||
Ok(dev_dependencies)
|
||||
}
|
||||
|
||||
/// Set the minimum version for an existing dependency in `project.optional-dependencies`.
|
||||
pub fn set_optional_dependency_minimum_version(
|
||||
fn set_optional_dependency_minimum_version(
|
||||
&mut self,
|
||||
group: &ExtraName,
|
||||
index: usize,
|
||||
version: Version,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<&mut Array, Error> {
|
||||
// Get or create `project.optional-dependencies`.
|
||||
let optional_dependencies = self
|
||||
.project()?
|
||||
|
|
@ -601,48 +599,30 @@ impl PyProjectTomlMut {
|
|||
.as_table_like_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
// Try to find the existing group.
|
||||
let existing_group = optional_dependencies.iter_mut().find_map(|(key, value)| {
|
||||
if ExtraName::from_str(key.get()).is_ok_and(|g| g == *group) {
|
||||
Some(value)
|
||||
// Try to find the existing extra.
|
||||
let existing_key = optional_dependencies.iter().find_map(|(key, _value)| {
|
||||
if ExtraName::from_str(key).is_ok_and(|g| g == *group) {
|
||||
Some(key.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// If the group doesn't exist, create it.
|
||||
let group = match existing_group {
|
||||
Some(value) => value,
|
||||
None => optional_dependencies
|
||||
.entry(group.as_ref())
|
||||
.or_insert(Item::Value(Value::Array(Array::new()))),
|
||||
}
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
let Some(req) = group.get(index) else {
|
||||
return Err(Error::MissingDependency(index));
|
||||
};
|
||||
|
||||
let mut req = req
|
||||
.as_str()
|
||||
.and_then(try_parse_requirement)
|
||||
let group = optional_dependencies
|
||||
.entry(existing_key.as_deref().unwrap_or(group.as_ref()))
|
||||
.or_insert(Item::Value(Value::Array(Array::new())))
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
|
||||
VersionSpecifier::greater_than_equal_version(version),
|
||||
)));
|
||||
group.replace(index, req.to_string());
|
||||
|
||||
Ok(())
|
||||
Ok(group)
|
||||
}
|
||||
|
||||
/// Set the minimum version for an existing dependency in `dependency-groups`.
|
||||
pub fn set_dependency_group_requirement_minimum_version(
|
||||
fn set_dependency_group_requirement_minimum_version(
|
||||
&mut self,
|
||||
group: &GroupName,
|
||||
index: usize,
|
||||
version: Version,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<&mut Array, Error> {
|
||||
// Get or create `dependency-groups`.
|
||||
let dependency_groups = self
|
||||
.doc
|
||||
|
|
@ -652,38 +632,22 @@ impl PyProjectTomlMut {
|
|||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
// Try to find the existing group.
|
||||
let existing_group = dependency_groups.iter_mut().find_map(|(key, value)| {
|
||||
if GroupName::from_str(key.get()).is_ok_and(|g| g == *group) {
|
||||
Some(value)
|
||||
let existing_key = dependency_groups.iter().find_map(|(key, _value)| {
|
||||
if GroupName::from_str(key).is_ok_and(|g| g == *group) {
|
||||
Some(key.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// If the group doesn't exist, create it.
|
||||
let group = match existing_group {
|
||||
Some(value) => value,
|
||||
None => dependency_groups
|
||||
.entry(group.as_ref())
|
||||
.or_insert(Item::Value(Value::Array(Array::new()))),
|
||||
}
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
let Some(req) = group.get(index) else {
|
||||
return Err(Error::MissingDependency(index));
|
||||
};
|
||||
|
||||
let mut req = req
|
||||
.as_str()
|
||||
.and_then(try_parse_requirement)
|
||||
let group = dependency_groups
|
||||
.entry(existing_key.as_deref().unwrap_or(group.as_ref()))
|
||||
.or_insert(Item::Value(Value::Array(Array::new())))
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
|
||||
VersionSpecifier::greater_than_equal_version(version),
|
||||
)));
|
||||
group.replace(index, req.to_string());
|
||||
|
||||
Ok(())
|
||||
Ok(group)
|
||||
}
|
||||
|
||||
/// Adds a source to `tool.uv.sources`.
|
||||
|
|
|
|||
|
|
@ -438,6 +438,126 @@ pub(crate) async fn add(
|
|||
DependencyTarget::PyProjectToml,
|
||||
),
|
||||
}?;
|
||||
let edits = edits(
|
||||
requirements,
|
||||
&target,
|
||||
editable,
|
||||
&dependency_type,
|
||||
raw_sources,
|
||||
rev.as_deref(),
|
||||
tag.as_deref(),
|
||||
branch.as_deref(),
|
||||
&extras,
|
||||
index,
|
||||
&mut toml,
|
||||
)?;
|
||||
|
||||
// Add any indexes that were provided on the command-line, in priority order.
|
||||
if !raw_sources {
|
||||
let urls = IndexUrls::from_indexes(indexes);
|
||||
for index in urls.defined_indexes() {
|
||||
toml.add_index(index)?;
|
||||
}
|
||||
}
|
||||
|
||||
let content = toml.to_string();
|
||||
|
||||
// Save the modified `pyproject.toml` or script.
|
||||
let modified = target.write(&content)?;
|
||||
|
||||
// If `--frozen`, exit early. There's no reason to lock and sync, since we don't need a `uv.lock`
|
||||
// to exist at all.
|
||||
if frozen {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
// If we're modifying a script, and lockfile doesn't exist, don't create it.
|
||||
if let AddTarget::Script(ref script, _) = target {
|
||||
if !LockTarget::from(script).lock_path().is_file() {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"Updated `{}`",
|
||||
script.path.user_display().cyan()
|
||||
)?;
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the content prior to any modifications.
|
||||
let snapshot = target.snapshot().await?;
|
||||
|
||||
// Update the `pypackage.toml` in-memory.
|
||||
let target = target.update(&content)?;
|
||||
|
||||
// Set the Ctrl-C handler to revert changes on exit.
|
||||
let _ = ctrlc::set_handler({
|
||||
let snapshot = snapshot.clone();
|
||||
move || {
|
||||
if modified {
|
||||
let _ = snapshot.revert();
|
||||
}
|
||||
|
||||
#[allow(clippy::exit, clippy::cast_possible_wrap)]
|
||||
std::process::exit(if cfg!(windows) {
|
||||
0xC000_013A_u32 as i32
|
||||
} else {
|
||||
130
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Use separate state for locking and syncing.
|
||||
let lock_state = state.fork();
|
||||
let sync_state = state;
|
||||
|
||||
match Box::pin(lock_and_sync(
|
||||
target,
|
||||
&mut toml,
|
||||
&edits,
|
||||
lock_state,
|
||||
sync_state,
|
||||
locked,
|
||||
&dependency_type,
|
||||
raw_sources,
|
||||
constraints,
|
||||
&settings,
|
||||
&network_settings,
|
||||
installer_metadata,
|
||||
concurrency,
|
||||
cache,
|
||||
printer,
|
||||
preview,
|
||||
))
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(ExitStatus::Success),
|
||||
Err(err) => {
|
||||
if modified {
|
||||
let _ = snapshot.revert();
|
||||
}
|
||||
match err {
|
||||
ProjectError::Operation(err) => diagnostics::OperationDiagnostic::native_tls(network_settings.native_tls).with_hint(format!("If you want to add the package regardless of the failed resolution, provide the `{}` flag to skip locking and syncing.", "--frozen".green()))
|
||||
.report(err)
|
||||
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into())),
|
||||
err => Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn edits(
|
||||
requirements: Vec<Requirement>,
|
||||
target: &AddTarget,
|
||||
editable: Option<bool>,
|
||||
dependency_type: &DependencyType,
|
||||
raw_sources: bool,
|
||||
rev: Option<&str>,
|
||||
tag: Option<&str>,
|
||||
branch: Option<&str>,
|
||||
extras: &[ExtraName],
|
||||
index: Option<&IndexName>,
|
||||
toml: &mut PyProjectTomlMut,
|
||||
) -> Result<Vec<DependencyEdit>> {
|
||||
let mut edits = Vec::<DependencyEdit>::with_capacity(requirements.len());
|
||||
for mut requirement in requirements {
|
||||
// Add the specified extras.
|
||||
|
|
@ -461,9 +581,9 @@ pub(crate) async fn add(
|
|||
false,
|
||||
editable,
|
||||
index.cloned(),
|
||||
rev.clone(),
|
||||
tag.clone(),
|
||||
branch.clone(),
|
||||
rev.map(ToString::to_string),
|
||||
tag.map(ToString::to_string),
|
||||
branch.map(ToString::to_string),
|
||||
script_dir,
|
||||
existing_sources,
|
||||
)?
|
||||
|
|
@ -485,9 +605,9 @@ pub(crate) async fn add(
|
|||
workspace,
|
||||
editable,
|
||||
index.cloned(),
|
||||
rev.clone(),
|
||||
tag.clone(),
|
||||
branch.clone(),
|
||||
rev.map(ToString::to_string),
|
||||
tag.map(ToString::to_string),
|
||||
branch.map(ToString::to_string),
|
||||
project.root(),
|
||||
existing_sources,
|
||||
)?
|
||||
|
|
@ -609,98 +729,7 @@ pub(crate) async fn add(
|
|||
edit,
|
||||
});
|
||||
}
|
||||
|
||||
// Add any indexes that were provided on the command-line, in priority order.
|
||||
if !raw_sources {
|
||||
let urls = IndexUrls::from_indexes(indexes);
|
||||
for index in urls.defined_indexes() {
|
||||
toml.add_index(index)?;
|
||||
}
|
||||
}
|
||||
|
||||
let content = toml.to_string();
|
||||
|
||||
// Save the modified `pyproject.toml` or script.
|
||||
let modified = target.write(&content)?;
|
||||
|
||||
// If `--frozen`, exit early. There's no reason to lock and sync, since we don't need a `uv.lock`
|
||||
// to exist at all.
|
||||
if frozen {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
// If we're modifying a script, and lockfile doesn't exist, don't create it.
|
||||
if let AddTarget::Script(ref script, _) = target {
|
||||
if !LockTarget::from(script).lock_path().is_file() {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"Updated `{}`",
|
||||
script.path.user_display().cyan()
|
||||
)?;
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the content prior to any modifications.
|
||||
let snapshot = target.snapshot().await?;
|
||||
|
||||
// Update the `pypackage.toml` in-memory.
|
||||
let target = target.update(&content)?;
|
||||
|
||||
// Set the Ctrl-C handler to revert changes on exit.
|
||||
let _ = ctrlc::set_handler({
|
||||
let snapshot = snapshot.clone();
|
||||
move || {
|
||||
if modified {
|
||||
let _ = snapshot.revert();
|
||||
}
|
||||
|
||||
#[allow(clippy::exit, clippy::cast_possible_wrap)]
|
||||
std::process::exit(if cfg!(windows) {
|
||||
0xC000_013A_u32 as i32
|
||||
} else {
|
||||
130
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Use separate state for locking and syncing.
|
||||
let lock_state = state.fork();
|
||||
let sync_state = state;
|
||||
|
||||
match Box::pin(lock_and_sync(
|
||||
target,
|
||||
&mut toml,
|
||||
&edits,
|
||||
lock_state,
|
||||
sync_state,
|
||||
locked,
|
||||
&dependency_type,
|
||||
raw_sources,
|
||||
constraints,
|
||||
&settings,
|
||||
&network_settings,
|
||||
installer_metadata,
|
||||
concurrency,
|
||||
cache,
|
||||
printer,
|
||||
preview,
|
||||
))
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(ExitStatus::Success),
|
||||
Err(err) => {
|
||||
if modified {
|
||||
let _ = snapshot.revert();
|
||||
}
|
||||
match err {
|
||||
ProjectError::Operation(err) => diagnostics::OperationDiagnostic::native_tls(network_settings.native_tls).with_hint(format!("If you want to add the package regardless of the failed resolution, provide the `{}` flag to skip locking and syncing.", "--frozen".green()))
|
||||
.report(err)
|
||||
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into())),
|
||||
err => Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(edits)
|
||||
}
|
||||
|
||||
/// Re-lock and re-sync the project after a series of edits.
|
||||
|
|
@ -801,20 +830,7 @@ async fn lock_and_sync(
|
|||
// For example, convert `1.2.3+local` to `1.2.3`.
|
||||
let minimum = (*minimum).clone().without_local();
|
||||
|
||||
match edit.dependency_type {
|
||||
DependencyType::Production => {
|
||||
toml.set_dependency_minimum_version(*index, minimum)?;
|
||||
}
|
||||
DependencyType::Dev => {
|
||||
toml.set_dev_dependency_minimum_version(*index, minimum)?;
|
||||
}
|
||||
DependencyType::Optional(ref extra) => {
|
||||
toml.set_optional_dependency_minimum_version(extra, *index, minimum)?;
|
||||
}
|
||||
DependencyType::Group(ref group) => {
|
||||
toml.set_dependency_group_requirement_minimum_version(group, *index, minimum)?;
|
||||
}
|
||||
}
|
||||
toml.set_dependency_minimum_version(&edit.dependency_type, *index, minimum)?;
|
||||
|
||||
modified = true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue