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)
|
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(
|
pub fn set_dependency_minimum_version(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
dependency_type: &DependencyType,
|
||||||
index: usize,
|
index: usize,
|
||||||
version: Version,
|
version: Version,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Get or create `project.dependencies`.
|
let group = match dependency_type {
|
||||||
let dependencies = self
|
DependencyType::Production => self.set_project_dependency_minimum_version()?,
|
||||||
.project()?
|
DependencyType::Dev => self.set_dev_dependency_minimum_version()?,
|
||||||
.entry("dependencies")
|
DependencyType::Optional(ref extra) => {
|
||||||
.or_insert(Item::Value(Value::Array(Array::new())))
|
self.set_optional_dependency_minimum_version(extra)?
|
||||||
.as_array_mut()
|
}
|
||||||
.ok_or(Error::MalformedDependencies)?;
|
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));
|
return Err(Error::MissingDependency(index));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -543,17 +547,26 @@ impl PyProjectTomlMut {
|
||||||
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
|
req.version_or_url = Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from(
|
||||||
VersionSpecifier::greater_than_equal_version(version),
|
VersionSpecifier::greater_than_equal_version(version),
|
||||||
)));
|
)));
|
||||||
dependencies.replace(index, req.to_string());
|
group.replace(index, req.to_string());
|
||||||
|
|
||||||
Ok(())
|
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`.
|
/// Set the minimum version for an existing dependency in `tool.uv.dev-dependencies`.
|
||||||
pub fn set_dev_dependency_minimum_version(
|
fn set_dev_dependency_minimum_version(&mut self) -> Result<&mut Array, Error> {
|
||||||
&mut self,
|
|
||||||
index: usize,
|
|
||||||
version: Version,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Get or create `tool.uv.dev-dependencies`.
|
// Get or create `tool.uv.dev-dependencies`.
|
||||||
let dev_dependencies = self
|
let dev_dependencies = self
|
||||||
.doc
|
.doc
|
||||||
|
|
@ -570,29 +583,14 @@ impl PyProjectTomlMut {
|
||||||
.as_array_mut()
|
.as_array_mut()
|
||||||
.ok_or(Error::MalformedDependencies)?;
|
.ok_or(Error::MalformedDependencies)?;
|
||||||
|
|
||||||
let Some(req) = dev_dependencies.get(index) else {
|
Ok(dev_dependencies)
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the minimum version for an existing dependency in `project.optional-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,
|
&mut self,
|
||||||
group: &ExtraName,
|
group: &ExtraName,
|
||||||
index: usize,
|
) -> Result<&mut Array, Error> {
|
||||||
version: Version,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Get or create `project.optional-dependencies`.
|
// Get or create `project.optional-dependencies`.
|
||||||
let optional_dependencies = self
|
let optional_dependencies = self
|
||||||
.project()?
|
.project()?
|
||||||
|
|
@ -601,48 +599,30 @@ impl PyProjectTomlMut {
|
||||||
.as_table_like_mut()
|
.as_table_like_mut()
|
||||||
.ok_or(Error::MalformedDependencies)?;
|
.ok_or(Error::MalformedDependencies)?;
|
||||||
|
|
||||||
// Try to find the existing group.
|
// Try to find the existing extra.
|
||||||
let existing_group = optional_dependencies.iter_mut().find_map(|(key, value)| {
|
let existing_key = optional_dependencies.iter().find_map(|(key, _value)| {
|
||||||
if ExtraName::from_str(key.get()).is_ok_and(|g| g == *group) {
|
if ExtraName::from_str(key).is_ok_and(|g| g == *group) {
|
||||||
Some(value)
|
Some(key.to_string())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the group doesn't exist, create it.
|
// If the group doesn't exist, create it.
|
||||||
let group = match existing_group {
|
let group = optional_dependencies
|
||||||
Some(value) => value,
|
.entry(existing_key.as_deref().unwrap_or(group.as_ref()))
|
||||||
None => optional_dependencies
|
.or_insert(Item::Value(Value::Array(Array::new())))
|
||||||
.entry(group.as_ref())
|
|
||||||
.or_insert(Item::Value(Value::Array(Array::new()))),
|
|
||||||
}
|
|
||||||
.as_array_mut()
|
.as_array_mut()
|
||||||
.ok_or(Error::MalformedDependencies)?;
|
.ok_or(Error::MalformedDependencies)?;
|
||||||
|
|
||||||
let Some(req) = group.get(index) else {
|
Ok(group)
|
||||||
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),
|
|
||||||
)));
|
|
||||||
group.replace(index, req.to_string());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the minimum version for an existing dependency in `dependency-groups`.
|
/// 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,
|
&mut self,
|
||||||
group: &GroupName,
|
group: &GroupName,
|
||||||
index: usize,
|
) -> Result<&mut Array, Error> {
|
||||||
version: Version,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Get or create `dependency-groups`.
|
// Get or create `dependency-groups`.
|
||||||
let dependency_groups = self
|
let dependency_groups = self
|
||||||
.doc
|
.doc
|
||||||
|
|
@ -652,38 +632,22 @@ impl PyProjectTomlMut {
|
||||||
.ok_or(Error::MalformedDependencies)?;
|
.ok_or(Error::MalformedDependencies)?;
|
||||||
|
|
||||||
// Try to find the existing group.
|
// Try to find the existing group.
|
||||||
let existing_group = dependency_groups.iter_mut().find_map(|(key, value)| {
|
let existing_key = dependency_groups.iter().find_map(|(key, _value)| {
|
||||||
if GroupName::from_str(key.get()).is_ok_and(|g| g == *group) {
|
if GroupName::from_str(key).is_ok_and(|g| g == *group) {
|
||||||
Some(value)
|
Some(key.to_string())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the group doesn't exist, create it.
|
// If the group doesn't exist, create it.
|
||||||
let group = match existing_group {
|
let group = dependency_groups
|
||||||
Some(value) => value,
|
.entry(existing_key.as_deref().unwrap_or(group.as_ref()))
|
||||||
None => dependency_groups
|
.or_insert(Item::Value(Value::Array(Array::new())))
|
||||||
.entry(group.as_ref())
|
|
||||||
.or_insert(Item::Value(Value::Array(Array::new()))),
|
|
||||||
}
|
|
||||||
.as_array_mut()
|
.as_array_mut()
|
||||||
.ok_or(Error::MalformedDependencies)?;
|
.ok_or(Error::MalformedDependencies)?;
|
||||||
|
|
||||||
let Some(req) = group.get(index) else {
|
Ok(group)
|
||||||
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),
|
|
||||||
)));
|
|
||||||
group.replace(index, req.to_string());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a source to `tool.uv.sources`.
|
/// Adds a source to `tool.uv.sources`.
|
||||||
|
|
|
||||||
|
|
@ -438,6 +438,126 @@ pub(crate) async fn add(
|
||||||
DependencyTarget::PyProjectToml,
|
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());
|
let mut edits = Vec::<DependencyEdit>::with_capacity(requirements.len());
|
||||||
for mut requirement in requirements {
|
for mut requirement in requirements {
|
||||||
// Add the specified extras.
|
// Add the specified extras.
|
||||||
|
|
@ -461,9 +581,9 @@ pub(crate) async fn add(
|
||||||
false,
|
false,
|
||||||
editable,
|
editable,
|
||||||
index.cloned(),
|
index.cloned(),
|
||||||
rev.clone(),
|
rev.map(ToString::to_string),
|
||||||
tag.clone(),
|
tag.map(ToString::to_string),
|
||||||
branch.clone(),
|
branch.map(ToString::to_string),
|
||||||
script_dir,
|
script_dir,
|
||||||
existing_sources,
|
existing_sources,
|
||||||
)?
|
)?
|
||||||
|
|
@ -485,9 +605,9 @@ pub(crate) async fn add(
|
||||||
workspace,
|
workspace,
|
||||||
editable,
|
editable,
|
||||||
index.cloned(),
|
index.cloned(),
|
||||||
rev.clone(),
|
rev.map(ToString::to_string),
|
||||||
tag.clone(),
|
tag.map(ToString::to_string),
|
||||||
branch.clone(),
|
branch.map(ToString::to_string),
|
||||||
project.root(),
|
project.root(),
|
||||||
existing_sources,
|
existing_sources,
|
||||||
)?
|
)?
|
||||||
|
|
@ -609,98 +729,7 @@ pub(crate) async fn add(
|
||||||
edit,
|
edit,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Ok(edits)
|
||||||
// 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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-lock and re-sync the project after a series of 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`.
|
// For example, convert `1.2.3+local` to `1.2.3`.
|
||||||
let minimum = (*minimum).clone().without_local();
|
let minimum = (*minimum).clone().without_local();
|
||||||
|
|
||||||
match edit.dependency_type {
|
toml.set_dependency_minimum_version(&edit.dependency_type, *index, minimum)?;
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue