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.
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{collections::BTreeMap, mem};
|
||||
|
||||
use glob::Pattern;
|
||||
|
|
@ -16,6 +17,7 @@ use url::Url;
|
|||
|
||||
use pep440_rs::VersionSpecifiers;
|
||||
use pypi_types::{RequirementSource, SupportedEnvironments, VerbatimParsedUrl};
|
||||
use uv_fs::relative_to;
|
||||
use uv_git::GitReference;
|
||||
use uv_macros::OptionsMetadata;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
|
|
@ -341,6 +343,10 @@ pub enum SourceError {
|
|||
UnusedTag(String, String),
|
||||
#[error("`{0}` did not resolve to a Git repository, but a Git reference (`--branch {1}`) was provided.")]
|
||||
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 {
|
||||
|
|
@ -352,6 +358,7 @@ impl Source {
|
|||
rev: Option<String>,
|
||||
tag: Option<String>,
|
||||
branch: Option<String>,
|
||||
root: &Path,
|
||||
) -> Result<Option<Source>, SourceError> {
|
||||
// If we resolved to a non-Git source, and the user specified a Git reference, error.
|
||||
if !matches!(source, RequirementSource::Git { .. }) {
|
||||
|
|
@ -386,13 +393,15 @@ impl Source {
|
|||
|
||||
let source = match source {
|
||||
RequirementSource::Registry { .. } => return Ok(None),
|
||||
RequirementSource::Path { install_path, .. } => Source::Path {
|
||||
RequirementSource::Path { install_path, .. }
|
||||
| RequirementSource::Directory { install_path, .. } => Source::Path {
|
||||
editable,
|
||||
path: install_path.to_string_lossy().into_owned(),
|
||||
},
|
||||
RequirementSource::Directory { install_path, .. } => Source::Path {
|
||||
editable,
|
||||
path: install_path.to_string_lossy().into_owned(),
|
||||
path: relative_to(&install_path, root)
|
||||
.or_else(|_| std::path::absolute(&install_path))
|
||||
.map_err(SourceError::Absolute)?
|
||||
.to_str()
|
||||
.ok_or_else(|| SourceError::NonUtf8Path(install_path))?
|
||||
.to_string(),
|
||||
},
|
||||
RequirementSource::Url {
|
||||
subdirectory, url, ..
|
||||
|
|
|
|||
|
|
@ -338,13 +338,14 @@ pub(crate) async fn add(
|
|||
Target::Script(_, _) | Target::Project(_, _) if raw_sources => {
|
||||
(pep508_rs::Requirement::from(requirement), None)
|
||||
}
|
||||
Target::Script(_, _) => resolve_requirement(
|
||||
Target::Script(ref script, _) => resolve_requirement(
|
||||
requirement,
|
||||
false,
|
||||
editable,
|
||||
rev.clone(),
|
||||
tag.clone(),
|
||||
branch.clone(),
|
||||
&script.path,
|
||||
)?,
|
||||
Target::Project(ref project, _) => {
|
||||
let workspace = project
|
||||
|
|
@ -358,6 +359,7 @@ pub(crate) async fn add(
|
|||
rev.clone(),
|
||||
tag.clone(),
|
||||
branch.clone(),
|
||||
project.root(),
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
|
@ -681,6 +683,7 @@ fn resolve_requirement(
|
|||
rev: Option<String>,
|
||||
tag: Option<String>,
|
||||
branch: Option<String>,
|
||||
root: &Path,
|
||||
) -> Result<(Requirement, Option<Source>), anyhow::Error> {
|
||||
let result = Source::from_requirement(
|
||||
&requirement.name,
|
||||
|
|
@ -690,6 +693,7 @@ fn resolve_requirement(
|
|||
rev,
|
||||
tag,
|
||||
branch,
|
||||
root,
|
||||
);
|
||||
|
||||
let source = match result {
|
||||
|
|
|
|||
|
|
@ -1626,6 +1626,111 @@ fn add_workspace_editable() -> Result<()> {
|
|||
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.
|
||||
#[test]
|
||||
#[cfg(feature = "git")]
|
||||
|
|
@ -3868,7 +3973,7 @@ fn add_git_to_script() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// Revert changes to pyproject.toml if add fails
|
||||
/// Revert changes to a `pyproject.toml` the `add` fails.
|
||||
#[test]
|
||||
fn fail_to_add_revert_project() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue