mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Add --workspace
option to uv add
(#4362)
## Summary Implements `uv add foo --workspace`, which adds `foo` as a workspace dependency with the corresponding `tool.uv.sources` entry. Part of https://github.com/astral-sh/uv/issues/3959.
This commit is contained in:
parent
96bfba9435
commit
eefc8c6d3c
7 changed files with 462 additions and 279 deletions
|
@ -2,12 +2,12 @@ use std::fmt;
|
|||
use std::str::FromStr;
|
||||
|
||||
use thiserror::Error;
|
||||
use toml_edit::{Array, DocumentMut, Item, RawString, Table, TomlError, Value};
|
||||
use toml_edit::{Array, DocumentMut, InlineTable, Item, RawString, Table, TomlError, Value};
|
||||
|
||||
use pep508_rs::{PackageName, Requirement};
|
||||
use pypi_types::VerbatimParsedUrl;
|
||||
|
||||
use crate::pyproject::PyProjectToml;
|
||||
use crate::pyproject::{PyProjectToml, Source};
|
||||
|
||||
/// Raw and mutable representation of a `pyproject.toml`.
|
||||
///
|
||||
|
@ -23,6 +23,8 @@ pub enum Error {
|
|||
Parse(#[from] Box<TomlError>),
|
||||
#[error("Dependencies in `pyproject.toml` are malformed")]
|
||||
MalformedDependencies,
|
||||
#[error("Sources in `pyproject.toml` are malformed")]
|
||||
MalformedSources,
|
||||
}
|
||||
|
||||
impl PyProjectTomlMut {
|
||||
|
@ -34,47 +36,165 @@ impl PyProjectTomlMut {
|
|||
}
|
||||
|
||||
/// Adds a dependency to `project.dependencies`.
|
||||
pub fn add_dependency(&mut self, req: &Requirement) -> Result<(), Error> {
|
||||
add_dependency(req, &mut self.doc["project"]["dependencies"])
|
||||
pub fn add_dependency(
|
||||
&mut self,
|
||||
req: &Requirement,
|
||||
source: Option<&Source>,
|
||||
) -> Result<(), Error> {
|
||||
// Get or create `project.dependencies`.
|
||||
let dependencies = self
|
||||
.doc
|
||||
.entry("project")
|
||||
.or_insert(Item::Table(Table::new()))
|
||||
.as_table_mut()
|
||||
.ok_or(Error::MalformedDependencies)?
|
||||
.entry("dependencies")
|
||||
.or_insert(Item::Value(Value::Array(Array::new())))
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
add_dependency(req, dependencies);
|
||||
|
||||
if let Some(source) = source {
|
||||
// Get or create `tool.uv.sources`.
|
||||
let sources = self
|
||||
.doc
|
||||
.entry("tool")
|
||||
.or_insert(implicit())
|
||||
.as_table_mut()
|
||||
.ok_or(Error::MalformedSources)?
|
||||
.entry("uv")
|
||||
.or_insert(implicit())
|
||||
.as_table_mut()
|
||||
.ok_or(Error::MalformedSources)?
|
||||
.entry("sources")
|
||||
.or_insert(Item::Table(Table::new()))
|
||||
.as_table_mut()
|
||||
.ok_or(Error::MalformedSources)?;
|
||||
|
||||
add_source(req, source, sources);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a development dependency to `tool.uv.dev-dependencies`.
|
||||
pub fn add_dev_dependency(&mut self, req: &Requirement) -> Result<(), Error> {
|
||||
let tool = self.doc["tool"].or_insert({
|
||||
let mut tool = Table::new();
|
||||
tool.set_implicit(true);
|
||||
Item::Table(tool)
|
||||
});
|
||||
let tool_uv = tool["uv"].or_insert(Item::Table(Table::new()));
|
||||
pub fn add_dev_dependency(
|
||||
&mut self,
|
||||
req: &Requirement,
|
||||
source: Option<&Source>,
|
||||
) -> Result<(), Error> {
|
||||
// Get or create `tool.uv`.
|
||||
let tool_uv = self
|
||||
.doc
|
||||
.entry("tool")
|
||||
.or_insert(implicit())
|
||||
.as_table_mut()
|
||||
.ok_or(Error::MalformedSources)?
|
||||
.entry("uv")
|
||||
.or_insert(Item::Table(Table::new()))
|
||||
.as_table_mut()
|
||||
.ok_or(Error::MalformedSources)?;
|
||||
|
||||
add_dependency(req, &mut tool_uv["dev-dependencies"])
|
||||
// Get or create the `tool.uv.dev-dependencies` array.
|
||||
let dev_dependencies = tool_uv
|
||||
.entry("dev-dependencies")
|
||||
.or_insert(Item::Value(Value::Array(Array::new())))
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
add_dependency(req, dev_dependencies);
|
||||
|
||||
if let Some(source) = source {
|
||||
// Get or create `tool.uv.sources`.
|
||||
let sources = tool_uv
|
||||
.entry("sources")
|
||||
.or_insert(Item::Table(Table::new()))
|
||||
.as_table_mut()
|
||||
.ok_or(Error::MalformedSources)?;
|
||||
|
||||
add_source(req, source, sources);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes all occurrences of dependencies with the given name.
|
||||
pub fn remove_dependency(&mut self, req: &PackageName) -> Result<Vec<Requirement>, Error> {
|
||||
remove_dependency(req, &mut self.doc["project"]["dependencies"])
|
||||
// Try to get `project.dependencies`.
|
||||
let Some(dependencies) = self
|
||||
.doc
|
||||
.get_mut("project")
|
||||
.and_then(Item::as_table_mut)
|
||||
.and_then(|project| project.get_mut("dependencies"))
|
||||
else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let dependencies = dependencies
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
let requirements = remove_dependency(req, dependencies);
|
||||
|
||||
// Remove a matching source from `tool.uv.sources`, if it exists.
|
||||
if let Some(sources) = self
|
||||
.doc
|
||||
.get_mut("tool")
|
||||
.and_then(Item::as_table_mut)
|
||||
.and_then(|tool| tool.get_mut("uv"))
|
||||
.and_then(Item::as_table_mut)
|
||||
.and_then(|tool_uv| tool_uv.get_mut("sources"))
|
||||
{
|
||||
let sources = sources.as_table_mut().ok_or(Error::MalformedSources)?;
|
||||
sources.remove(req.as_ref());
|
||||
}
|
||||
|
||||
Ok(requirements)
|
||||
}
|
||||
|
||||
/// Removes all occurrences of development dependencies with the given name.
|
||||
pub fn remove_dev_dependency(&mut self, req: &PackageName) -> Result<Vec<Requirement>, Error> {
|
||||
let Some(tool_uv) = self.doc.get_mut("tool").and_then(|tool| tool.get_mut("uv")) else {
|
||||
let Some(tool_uv) = self
|
||||
.doc
|
||||
.get_mut("tool")
|
||||
.and_then(Item::as_table_mut)
|
||||
.and_then(|tool| tool.get_mut("uv"))
|
||||
.and_then(Item::as_table_mut)
|
||||
else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
remove_dependency(req, &mut tool_uv["dev-dependencies"])
|
||||
// Try to get `tool.uv.dev-dependencies`.
|
||||
let Some(dev_dependencies) = tool_uv.get_mut("dev-dependencies") else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let dev_dependencies = dev_dependencies
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
let requirements = remove_dependency(req, dev_dependencies);
|
||||
|
||||
// Remove a matching source from `tool.uv.sources`, if it exists.
|
||||
if let Some(sources) = tool_uv.get_mut("sources") {
|
||||
let sources = sources.as_table_mut().ok_or(Error::MalformedSources)?;
|
||||
sources.remove(req.as_ref());
|
||||
};
|
||||
|
||||
Ok(requirements)
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a dependency to the given `deps` array.
|
||||
pub fn add_dependency(req: &Requirement, deps: &mut Item) -> Result<(), Error> {
|
||||
let deps = deps
|
||||
.or_insert(Item::Value(Value::Array(Array::new())))
|
||||
.as_array_mut()
|
||||
.ok_or(Error::MalformedDependencies)?;
|
||||
/// Returns an implicit table.
|
||||
fn implicit() -> Item {
|
||||
let mut table = Table::new();
|
||||
table.set_implicit(true);
|
||||
Item::Table(table)
|
||||
}
|
||||
|
||||
/// Adds a dependency to the given `deps` array.
|
||||
pub fn add_dependency(req: &Requirement, deps: &mut Array) {
|
||||
// Find matching dependencies.
|
||||
let to_replace = find_dependencies(&req.name, deps);
|
||||
|
||||
if to_replace.is_empty() {
|
||||
deps.push(req.to_string());
|
||||
} else {
|
||||
|
@ -84,19 +204,11 @@ pub fn add_dependency(req: &Requirement, deps: &mut Item) -> Result<(), Error> {
|
|||
deps.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
reformat_array_multiline(deps);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes all occurrences of dependencies with the given name from the given `deps` array.
|
||||
fn remove_dependency(req: &PackageName, deps: &mut Item) -> Result<Vec<Requirement>, Error> {
|
||||
if deps.is_none() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let deps = deps.as_array_mut().ok_or(Error::MalformedDependencies)?;
|
||||
|
||||
fn remove_dependency(req: &PackageName, deps: &mut Array) -> Vec<Requirement> {
|
||||
// Remove matching dependencies.
|
||||
let removed = find_dependencies(req, deps)
|
||||
.into_iter()
|
||||
|
@ -112,7 +224,7 @@ fn remove_dependency(req: &PackageName, deps: &mut Item) -> Result<Vec<Requireme
|
|||
reformat_array_multiline(deps);
|
||||
}
|
||||
|
||||
Ok(removed)
|
||||
removed
|
||||
}
|
||||
|
||||
// Returns a `Vec` containing the indices of all dependencies with the given name.
|
||||
|
@ -131,6 +243,24 @@ fn find_dependencies(name: &PackageName, deps: &Array) -> Vec<usize> {
|
|||
to_replace
|
||||
}
|
||||
|
||||
// Add a source to `tool.uv.sources`.
|
||||
fn add_source(req: &Requirement, source: &Source, sources: &mut Table) {
|
||||
match source {
|
||||
Source::Workspace {
|
||||
workspace,
|
||||
editable,
|
||||
} => {
|
||||
let mut value = InlineTable::new();
|
||||
value.insert("workspace", Value::from(*workspace));
|
||||
if let Some(editable) = editable {
|
||||
value.insert("editable", Value::from(*editable));
|
||||
}
|
||||
sources.insert(req.name.as_ref(), Item::Value(Value::InlineTable(value)));
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PyProjectTomlMut {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.doc.fmt(f)
|
||||
|
|
|
@ -1611,6 +1611,10 @@ pub(crate) struct AddArgs {
|
|||
#[arg(long)]
|
||||
pub(crate) dev: bool,
|
||||
|
||||
/// Add the requirements as workspace dependencies.
|
||||
#[arg(long)]
|
||||
pub(crate) workspace: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
pub(crate) installer: ResolverInstallerArgs,
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_distribution::pyproject::Source;
|
||||
use uv_distribution::pyproject_mut::PyProjectTomlMut;
|
||||
use uv_git::GitResolver;
|
||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
|
||||
|
@ -24,6 +25,7 @@ use crate::settings::ResolverInstallerSettings;
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn add(
|
||||
requirements: Vec<RequirementsSource>,
|
||||
workspace: bool,
|
||||
dev: bool,
|
||||
python: Option<String>,
|
||||
settings: ResolverInstallerSettings,
|
||||
|
@ -134,10 +136,19 @@ pub(crate) async fn add(
|
|||
// Add the requirements to the `pyproject.toml`.
|
||||
let mut pyproject = PyProjectTomlMut::from_toml(project.current_project().pyproject_toml())?;
|
||||
for req in requirements.into_iter().map(pep508_rs::Requirement::from) {
|
||||
if dev {
|
||||
pyproject.add_dev_dependency(&req)?;
|
||||
let source = if workspace {
|
||||
Some(Source::Workspace {
|
||||
workspace: true,
|
||||
editable: None,
|
||||
})
|
||||
} else {
|
||||
pyproject.add_dependency(&req)?;
|
||||
None
|
||||
};
|
||||
|
||||
if dev {
|
||||
pyproject.add_dev_dependency(&req, source.as_ref())?;
|
||||
} else {
|
||||
pyproject.add_dependency(&req, source.as_ref())?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ pub(crate) async fn remove(
|
|||
.filter(|deps| !deps.is_empty())
|
||||
.is_some()
|
||||
{
|
||||
uv_warnings::warn_user!("`{req}` is not a development dependency; try calling `uv add` without the `--dev` flag");
|
||||
uv_warnings::warn_user!("`{req}` is not a development dependency; try calling `uv remove` without the `--dev` flag");
|
||||
}
|
||||
|
||||
anyhow::bail!("The dependency `{req}` could not be found in `dev-dependencies`");
|
||||
|
@ -65,7 +65,7 @@ pub(crate) async fn remove(
|
|||
.is_some()
|
||||
{
|
||||
uv_warnings::warn_user!(
|
||||
"`{req}` is a development dependency; try calling `uv add --dev`"
|
||||
"`{req}` is a development dependency; try calling `uv remove --dev`"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -686,14 +686,9 @@ async fn run() -> Result<ExitStatus> {
|
|||
// Initialize the cache.
|
||||
let cache = cache.init()?.with_refresh(args.refresh);
|
||||
|
||||
let requirements = args
|
||||
.requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Package)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
commands::add(
|
||||
requirements,
|
||||
args.requirements,
|
||||
args.workspace,
|
||||
args.dev,
|
||||
args.python,
|
||||
args.settings,
|
||||
|
|
|
@ -17,6 +17,7 @@ use uv_configuration::{
|
|||
Upgrade,
|
||||
};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_requirements::RequirementsSource;
|
||||
use uv_resolver::{AnnotationStyle, DependencyMode, ExcludeNewer, PreReleaseMode, ResolutionMode};
|
||||
use uv_settings::{
|
||||
Combine, FilesystemOptions, InstallerOptions, Options, PipOptions, ResolverInstallerOptions,
|
||||
|
@ -373,7 +374,8 @@ impl LockSettings {
|
|||
#[allow(clippy::struct_excessive_bools, dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct AddSettings {
|
||||
pub(crate) requirements: Vec<String>,
|
||||
pub(crate) requirements: Vec<RequirementsSource>,
|
||||
pub(crate) workspace: bool,
|
||||
pub(crate) dev: bool,
|
||||
pub(crate) python: Option<String>,
|
||||
pub(crate) refresh: Refresh,
|
||||
|
@ -387,14 +389,21 @@ impl AddSettings {
|
|||
let AddArgs {
|
||||
requirements,
|
||||
dev,
|
||||
workspace,
|
||||
installer,
|
||||
build,
|
||||
refresh,
|
||||
python,
|
||||
} = args;
|
||||
|
||||
let requirements = requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Package)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Self {
|
||||
requirements,
|
||||
workspace,
|
||||
dev,
|
||||
python,
|
||||
refresh: Refresh::from(refresh),
|
||||
|
|
|
@ -374,9 +374,9 @@ fn add_unnamed() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a development dependency.
|
||||
/// Add and remove a development dependency.
|
||||
#[test]
|
||||
fn add_dev() -> Result<()> {
|
||||
fn add_remove_dev() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
|
@ -494,6 +494,272 @@ fn add_dev() -> Result<()> {
|
|||
Audited 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
// This should fail without --dev.
|
||||
uv_snapshot!(context.filters(), context.remove(&["anyio"]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv remove` is experimental and may change without warning.
|
||||
warning: `anyio` is a development dependency; try calling `uv remove --dev`
|
||||
error: The dependency `anyio` could not be found in `dependencies`
|
||||
"###);
|
||||
|
||||
// Remove the dependency.
|
||||
uv_snapshot!(context.filters(), context.remove(&["anyio"]).arg("--dev"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv remove` is experimental and may change without warning.
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Uninstalled 4 packages in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- anyio==3.7.0
|
||||
- idna==3.7
|
||||
- project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
- sniffio==1.3.1
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Audited 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add and remove a workspace dependency.
|
||||
#[test]
|
||||
fn add_remove_workspace() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let workspace = context.temp_dir.child("pyproject.toml");
|
||||
workspace.write_str(indoc! {r#"
|
||||
[tool.uv.workspace]
|
||||
members = ["child1", "child2"]
|
||||
"#})?;
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("child1/pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "child1"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"#})?;
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("child2/pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "child2"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"#})?;
|
||||
|
||||
let child1 = context.temp_dir.join("child1");
|
||||
let mut add_cmd = context.add(&["child2"]);
|
||||
add_cmd
|
||||
.arg("--preview")
|
||||
.arg("--workspace")
|
||||
.current_dir(&child1);
|
||||
|
||||
uv_snapshot!(context.filters(), add_cmd, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Downloaded 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ child1==0.1.0 (from file://[TEMP_DIR]/child1)
|
||||
+ child2==0.1.0 (from file://[TEMP_DIR]/child2)
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(child1.join("pyproject.toml"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
[project]
|
||||
name = "child1"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"child2",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
child2 = { workspace = true }
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// `uv add` implies a full lock and sync, including development dependencies.
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[distribution]]
|
||||
name = "child1"
|
||||
version = "0.1.0"
|
||||
source = "editable+child1"
|
||||
sdist = { path = "child1" }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "child2"
|
||||
version = "0.1.0"
|
||||
source = "editable+child2"
|
||||
|
||||
[[distribution]]
|
||||
name = "child2"
|
||||
version = "0.1.0"
|
||||
source = "editable+child2"
|
||||
sdist = { path = "child2" }
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().current_dir(&child1), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Audited 2 packages in [TIME]
|
||||
"###);
|
||||
|
||||
// Remove the dependency.
|
||||
uv_snapshot!(context.filters(), context.remove(&["child2"]).current_dir(&child1), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv remove` is experimental and may change without warning.
|
||||
Resolved 2 packages in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Uninstalled 2 packages in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- child1==0.1.0 (from file://[TEMP_DIR]/child1)
|
||||
+ child1==0.1.0 (from file://[TEMP_DIR]/child1)
|
||||
- child2==0.1.0 (from file://[TEMP_DIR]/child2)
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(child1.join("pyproject.toml"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
[project]
|
||||
name = "child1"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv.sources]
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[distribution]]
|
||||
name = "child1"
|
||||
version = "0.1.0"
|
||||
source = "editable+child1"
|
||||
sdist = { path = "child1" }
|
||||
|
||||
[[distribution]]
|
||||
name = "child2"
|
||||
version = "0.1.0"
|
||||
source = "editable+child2"
|
||||
sdist = { path = "child2" }
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().current_dir(&child1), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Audited 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -891,235 +1157,3 @@ fn remove_registry() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a development dependency.
|
||||
#[test]
|
||||
fn remove_dev() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = ["anyio==3.7.0"]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv lock` is experimental and may change without warning.
|
||||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Downloaded 4 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ idna==3.6
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ sniffio==1.3.1
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.remove(&["anyio"]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv remove` is experimental and may change without warning.
|
||||
warning: `anyio` is a development dependency; try calling `uv add --dev`
|
||||
error: The dependency `anyio` could not be found in `dependencies`
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.remove(&["anyio"]).arg("--dev"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv remove` is experimental and may change without warning.
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Uninstalled 4 packages in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- anyio==3.7.0
|
||||
- idna==3.6
|
||||
- project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
- sniffio==1.3.1
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Audited 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a PyPI requirement that occurs multiple times.
|
||||
#[test]
|
||||
fn remove_all_registry() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"anyio == 3.7.0 ; python_version >= '3.12'",
|
||||
"anyio < 3.7.0 ; python_version < '3.12'",
|
||||
]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv lock` is experimental and may change without warning.
|
||||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Downloaded 4 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ idna==3.6
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ sniffio==1.3.1
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.remove(&["anyio"]), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv remove` is experimental and may change without warning.
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Uninstalled 4 packages in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- anyio==3.7.0
|
||||
- idna==3.6
|
||||
- project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
- sniffio==1.3.1
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Audited 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue