mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-18 03:13:48 +00:00
Use relative paths by default in uv add (#6686)
## Summary Closes https://github.com/astral-sh/uv/issues/6684.
This commit is contained in:
parent
d86075fc1e
commit
3f15f2d922
3 changed files with 126 additions and 8 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
//! Then lowers them into a dependency specification.
|
//! Then lowers them into a dependency specification.
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::{collections::BTreeMap, mem};
|
use std::{collections::BTreeMap, mem};
|
||||||
|
|
||||||
use glob::Pattern;
|
use glob::Pattern;
|
||||||
|
|
@ -16,6 +17,7 @@ use url::Url;
|
||||||
|
|
||||||
use pep440_rs::VersionSpecifiers;
|
use pep440_rs::VersionSpecifiers;
|
||||||
use pypi_types::{RequirementSource, SupportedEnvironments, VerbatimParsedUrl};
|
use pypi_types::{RequirementSource, SupportedEnvironments, VerbatimParsedUrl};
|
||||||
|
use uv_fs::relative_to;
|
||||||
use uv_git::GitReference;
|
use uv_git::GitReference;
|
||||||
use uv_macros::OptionsMetadata;
|
use uv_macros::OptionsMetadata;
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
|
|
@ -341,6 +343,10 @@ pub enum SourceError {
|
||||||
UnusedTag(String, String),
|
UnusedTag(String, String),
|
||||||
#[error("`{0}` did not resolve to a Git repository, but a Git reference (`--branch {1}`) was provided.")]
|
#[error("`{0}` did not resolve to a Git repository, but a Git reference (`--branch {1}`) was provided.")]
|
||||||
UnusedBranch(String, String),
|
UnusedBranch(String, String),
|
||||||
|
#[error("Failed to resolve absolute path")]
|
||||||
|
Absolute(#[from] std::io::Error),
|
||||||
|
#[error("Path contains invalid characters: `{}`", _0.display())]
|
||||||
|
NonUtf8Path(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source {
|
impl Source {
|
||||||
|
|
@ -352,6 +358,7 @@ impl Source {
|
||||||
rev: Option<String>,
|
rev: Option<String>,
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
branch: Option<String>,
|
branch: Option<String>,
|
||||||
|
root: &Path,
|
||||||
) -> Result<Option<Source>, SourceError> {
|
) -> Result<Option<Source>, SourceError> {
|
||||||
// If we resolved to a non-Git source, and the user specified a Git reference, error.
|
// If we resolved to a non-Git source, and the user specified a Git reference, error.
|
||||||
if !matches!(source, RequirementSource::Git { .. }) {
|
if !matches!(source, RequirementSource::Git { .. }) {
|
||||||
|
|
@ -386,13 +393,15 @@ impl Source {
|
||||||
|
|
||||||
let source = match source {
|
let source = match source {
|
||||||
RequirementSource::Registry { .. } => return Ok(None),
|
RequirementSource::Registry { .. } => return Ok(None),
|
||||||
RequirementSource::Path { install_path, .. } => Source::Path {
|
RequirementSource::Path { install_path, .. }
|
||||||
|
| RequirementSource::Directory { install_path, .. } => Source::Path {
|
||||||
editable,
|
editable,
|
||||||
path: install_path.to_string_lossy().into_owned(),
|
path: relative_to(&install_path, root)
|
||||||
},
|
.or_else(|_| std::path::absolute(&install_path))
|
||||||
RequirementSource::Directory { install_path, .. } => Source::Path {
|
.map_err(SourceError::Absolute)?
|
||||||
editable,
|
.to_str()
|
||||||
path: install_path.to_string_lossy().into_owned(),
|
.ok_or_else(|| SourceError::NonUtf8Path(install_path))?
|
||||||
|
.to_string(),
|
||||||
},
|
},
|
||||||
RequirementSource::Url {
|
RequirementSource::Url {
|
||||||
subdirectory, url, ..
|
subdirectory, url, ..
|
||||||
|
|
|
||||||
|
|
@ -338,13 +338,14 @@ pub(crate) async fn add(
|
||||||
Target::Script(_, _) | Target::Project(_, _) if raw_sources => {
|
Target::Script(_, _) | Target::Project(_, _) if raw_sources => {
|
||||||
(pep508_rs::Requirement::from(requirement), None)
|
(pep508_rs::Requirement::from(requirement), None)
|
||||||
}
|
}
|
||||||
Target::Script(_, _) => resolve_requirement(
|
Target::Script(ref script, _) => resolve_requirement(
|
||||||
requirement,
|
requirement,
|
||||||
false,
|
false,
|
||||||
editable,
|
editable,
|
||||||
rev.clone(),
|
rev.clone(),
|
||||||
tag.clone(),
|
tag.clone(),
|
||||||
branch.clone(),
|
branch.clone(),
|
||||||
|
&script.path,
|
||||||
)?,
|
)?,
|
||||||
Target::Project(ref project, _) => {
|
Target::Project(ref project, _) => {
|
||||||
let workspace = project
|
let workspace = project
|
||||||
|
|
@ -358,6 +359,7 @@ pub(crate) async fn add(
|
||||||
rev.clone(),
|
rev.clone(),
|
||||||
tag.clone(),
|
tag.clone(),
|
||||||
branch.clone(),
|
branch.clone(),
|
||||||
|
project.root(),
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -681,6 +683,7 @@ fn resolve_requirement(
|
||||||
rev: Option<String>,
|
rev: Option<String>,
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
branch: Option<String>,
|
branch: Option<String>,
|
||||||
|
root: &Path,
|
||||||
) -> Result<(Requirement, Option<Source>), anyhow::Error> {
|
) -> Result<(Requirement, Option<Source>), anyhow::Error> {
|
||||||
let result = Source::from_requirement(
|
let result = Source::from_requirement(
|
||||||
&requirement.name,
|
&requirement.name,
|
||||||
|
|
@ -690,6 +693,7 @@ fn resolve_requirement(
|
||||||
rev,
|
rev,
|
||||||
tag,
|
tag,
|
||||||
branch,
|
branch,
|
||||||
|
root,
|
||||||
);
|
);
|
||||||
|
|
||||||
let source = match result {
|
let source = match result {
|
||||||
|
|
|
||||||
|
|
@ -1626,6 +1626,111 @@ fn add_workspace_editable() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a path dependency.
|
||||||
|
#[test]
|
||||||
|
fn add_path() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let workspace = context.temp_dir.child("workspace");
|
||||||
|
workspace.child("pyproject.toml").write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "parent"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
let child = workspace.child("child");
|
||||||
|
child.child("pyproject.toml").write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "child"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.add(&["./child"]).current_dir(workspace.path()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
|
Creating virtualenv at: .venv
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Prepared 2 packages in [TIME]
|
||||||
|
Installed 2 packages in [TIME]
|
||||||
|
+ child==0.1.0 (from file://[TEMP_DIR]/workspace/child)
|
||||||
|
+ parent==0.1.0 (from file://[TEMP_DIR]/workspace)
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let pyproject_toml = fs_err::read_to_string(workspace.join("pyproject.toml"))?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
assert_snapshot!(
|
||||||
|
pyproject_toml, @r###"
|
||||||
|
[project]
|
||||||
|
name = "parent"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"child",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
child = { path = "child" }
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// `uv add` implies a full lock and sync, including development dependencies.
|
||||||
|
let lock = fs_err::read_to_string(workspace.join("uv.lock"))?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
assert_snapshot!(
|
||||||
|
lock, @r###"
|
||||||
|
version = 1
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[options]
|
||||||
|
exclude-newer = "2024-03-25T00:00:00Z"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "child"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { directory = "child" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parent"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "child" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "child", directory = "child" }]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Install from the lockfile.
|
||||||
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen").current_dir(workspace.path()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Audited 2 packages in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Update a requirement, modifying the source and extras.
|
/// Update a requirement, modifying the source and extras.
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "git")]
|
#[cfg(feature = "git")]
|
||||||
|
|
@ -3868,7 +3973,7 @@ fn add_git_to_script() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revert changes to pyproject.toml if add fails
|
/// Revert changes to a `pyproject.toml` the `add` fails.
|
||||||
#[test]
|
#[test]
|
||||||
fn fail_to_add_revert_project() -> Result<()> {
|
fn fail_to_add_revert_project() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue