Skip to content

Commit 5498e4d

Browse files
Respect -e flags in uv add (#16882)
## Summary Closes #16872.
1 parent e2bda11 commit 5498e4d

2 files changed

Lines changed: 236 additions & 3 deletions

File tree

crates/uv-workspace/src/pyproject.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,9 +1697,25 @@ impl Source {
16971697
return Ok(None);
16981698
}
16991699
}
1700-
RequirementSource::Path { install_path, .. }
1701-
| RequirementSource::Directory { install_path, .. } => Self::Path {
1702-
editable,
1700+
RequirementSource::Path { install_path, .. } => Self::Path {
1701+
editable: None,
1702+
package: None,
1703+
path: PortablePathBuf::from(
1704+
relative_to(&install_path, root)
1705+
.or_else(|_| std::path::absolute(&install_path))
1706+
.map_err(SourceError::Absolute)?
1707+
.into_boxed_path(),
1708+
),
1709+
marker: MarkerTree::TRUE,
1710+
extra: None,
1711+
group: None,
1712+
},
1713+
RequirementSource::Directory {
1714+
install_path,
1715+
editable: is_editable,
1716+
..
1717+
} => Self::Path {
1718+
editable: editable.or(is_editable),
17031719
package: None,
17041720
path: PortablePathBuf::from(
17051721
relative_to(&install_path, root)

crates/uv/tests/it/edit.rs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5379,6 +5379,223 @@ fn add_requirements_file() -> Result<()> {
53795379
Ok(())
53805380
}
53815381

5382+
/// Add a path dependency from a requirements file, respecting the lack of a `-e` flag.
5383+
#[test]
5384+
fn add_requirements_file_non_editable() -> Result<()> {
5385+
let context = TestContext::new("3.12");
5386+
5387+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
5388+
pyproject_toml.write_str(indoc! {r#"
5389+
[project]
5390+
name = "project"
5391+
version = "0.1.0"
5392+
requires-python = ">=3.12"
5393+
dependencies = []
5394+
"#})?;
5395+
5396+
// Create a peer package.
5397+
let child = context.temp_dir.child("packages").child("child");
5398+
child.child("pyproject.toml").write_str(indoc! {r#"
5399+
[project]
5400+
name = "child"
5401+
version = "0.1.0"
5402+
requires-python = ">=3.12"
5403+
5404+
[build-system]
5405+
requires = ["hatchling"]
5406+
build-backend = "hatchling.build"
5407+
"#})?;
5408+
child
5409+
.child("src")
5410+
.child("child")
5411+
.child("__init__.py")
5412+
.touch()?;
5413+
5414+
// Without `-e`, the package should not be listed as editable.
5415+
let requirements_txt = context.temp_dir.child("requirements.txt");
5416+
requirements_txt.write_str("./packages/child")?;
5417+
5418+
uv_snapshot!(context.filters(), context.add().arg("-r").arg("requirements.txt").arg("--no-workspace"), @r"
5419+
success: true
5420+
exit_code: 0
5421+
----- stdout -----
5422+
5423+
----- stderr -----
5424+
Resolved 2 packages in [TIME]
5425+
Prepared 1 package in [TIME]
5426+
Installed 1 package in [TIME]
5427+
+ child==0.1.0 (from file://[TEMP_DIR]/packages/child)
5428+
");
5429+
5430+
let pyproject_toml_content = context.read("pyproject.toml");
5431+
5432+
insta::with_settings!({
5433+
filters => context.filters(),
5434+
}, {
5435+
assert_snapshot!(
5436+
pyproject_toml_content, @r#"
5437+
[project]
5438+
name = "project"
5439+
version = "0.1.0"
5440+
requires-python = ">=3.12"
5441+
dependencies = [
5442+
"child",
5443+
]
5444+
5445+
[tool.uv.sources]
5446+
child = { path = "packages/child" }
5447+
"#
5448+
);
5449+
});
5450+
5451+
Ok(())
5452+
}
5453+
5454+
/// Add a path dependency from a requirements file, respecting `-e` for editable.
5455+
#[test]
5456+
fn add_requirements_file_editable() -> Result<()> {
5457+
let context = TestContext::new("3.12");
5458+
5459+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
5460+
pyproject_toml.write_str(indoc! {r#"
5461+
[project]
5462+
name = "project"
5463+
version = "0.1.0"
5464+
requires-python = ">=3.12"
5465+
dependencies = []
5466+
"#})?;
5467+
5468+
// Create a peer package.
5469+
let child = context.temp_dir.child("packages").child("child");
5470+
child.child("pyproject.toml").write_str(indoc! {r#"
5471+
[project]
5472+
name = "child"
5473+
version = "0.1.0"
5474+
requires-python = ">=3.12"
5475+
5476+
[build-system]
5477+
requires = ["hatchling"]
5478+
build-backend = "hatchling.build"
5479+
"#})?;
5480+
child
5481+
.child("src")
5482+
.child("child")
5483+
.child("__init__.py")
5484+
.touch()?;
5485+
5486+
// With `-e`, the package should be listed as editable.
5487+
let requirements_txt = context.temp_dir.child("requirements.txt");
5488+
requirements_txt.write_str("-e ./packages/child")?;
5489+
5490+
uv_snapshot!(context.filters(), context.add().arg("-r").arg("requirements.txt").arg("--no-workspace"), @r"
5491+
success: true
5492+
exit_code: 0
5493+
----- stdout -----
5494+
5495+
----- stderr -----
5496+
Resolved 2 packages in [TIME]
5497+
Prepared 1 package in [TIME]
5498+
Installed 1 package in [TIME]
5499+
+ child==0.1.0 (from file://[TEMP_DIR]/packages/child)
5500+
");
5501+
5502+
let pyproject_toml_content = context.read("pyproject.toml");
5503+
5504+
insta::with_settings!({
5505+
filters => context.filters(),
5506+
}, {
5507+
assert_snapshot!(
5508+
pyproject_toml_content, @r#"
5509+
[project]
5510+
name = "project"
5511+
version = "0.1.0"
5512+
requires-python = ">=3.12"
5513+
dependencies = [
5514+
"child",
5515+
]
5516+
5517+
[tool.uv.sources]
5518+
child = { path = "packages/child", editable = true }
5519+
"#
5520+
);
5521+
});
5522+
5523+
Ok(())
5524+
}
5525+
5526+
/// Add a path dependency from a requirements file, overriding the `-e` flag.
5527+
#[test]
5528+
fn add_requirements_file_editable_override() -> Result<()> {
5529+
let context = TestContext::new("3.12");
5530+
5531+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
5532+
pyproject_toml.write_str(indoc! {r#"
5533+
[project]
5534+
name = "project"
5535+
version = "0.1.0"
5536+
requires-python = ">=3.12"
5537+
dependencies = []
5538+
"#})?;
5539+
5540+
// Create a peer package.
5541+
let child = context.temp_dir.child("packages").child("child");
5542+
child.child("pyproject.toml").write_str(indoc! {r#"
5543+
[project]
5544+
name = "child"
5545+
version = "0.1.0"
5546+
requires-python = ">=3.12"
5547+
5548+
[build-system]
5549+
requires = ["hatchling"]
5550+
build-backend = "hatchling.build"
5551+
"#})?;
5552+
child
5553+
.child("src")
5554+
.child("child")
5555+
.child("__init__.py")
5556+
.touch()?;
5557+
5558+
// With `-e`, the package should be listed as editable, but the `--no-editable` flag should
5559+
// override it.
5560+
let requirements_txt = context.temp_dir.child("requirements.txt");
5561+
requirements_txt.write_str("-e ./packages/child")?;
5562+
5563+
uv_snapshot!(context.filters(), context.add().arg("-r").arg("requirements.txt").arg("--no-workspace").arg("--no-editable"), @r"
5564+
success: true
5565+
exit_code: 0
5566+
----- stdout -----
5567+
5568+
----- stderr -----
5569+
Resolved 2 packages in [TIME]
5570+
Prepared 1 package in [TIME]
5571+
Installed 1 package in [TIME]
5572+
+ child==0.1.0 (from file://[TEMP_DIR]/packages/child)
5573+
");
5574+
5575+
let pyproject_toml_content = context.read("pyproject.toml");
5576+
5577+
insta::with_settings!({
5578+
filters => context.filters(),
5579+
}, {
5580+
assert_snapshot!(
5581+
pyproject_toml_content, @r#"
5582+
[project]
5583+
name = "project"
5584+
version = "0.1.0"
5585+
requires-python = ">=3.12"
5586+
dependencies = [
5587+
"child",
5588+
]
5589+
5590+
[tool.uv.sources]
5591+
child = { path = "packages/child", editable = false }
5592+
"#
5593+
);
5594+
});
5595+
5596+
Ok(())
5597+
}
5598+
53825599
/// Add requirements from a file with a marker flag.
53835600
///
53845601
/// We test that:

0 commit comments

Comments
 (0)