Skip to content

Commit

Permalink
Use relative paths by default in uv add (#6686)
Browse files Browse the repository at this point in the history
## Summary

Closes #6684.
  • Loading branch information
charliermarsh authored Aug 27, 2024
1 parent d86075f commit 3f15f2d
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 8 deletions.
21 changes: 15 additions & 6 deletions crates/uv-workspace/src/pyproject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down Expand Up @@ -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 {
Expand All @@ -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 { .. }) {
Expand Down Expand Up @@ -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, ..
Expand Down
6 changes: 5 additions & 1 deletion crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -358,6 +359,7 @@ pub(crate) async fn add(
rev.clone(),
tag.clone(),
branch.clone(),
project.root(),
)?
}
};
Expand Down Expand Up @@ -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,
Expand All @@ -690,6 +693,7 @@ fn resolve_requirement(
rev,
tag,
branch,
root,
);

let source = match result {
Expand Down
107 changes: 106 additions & 1 deletion crates/uv/tests/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit 3f15f2d

Please sign in to comment.