Skip to content

Commit a00f6f5

Browse files
Reject --editable flag on non-directory requirements (#10994)
## Summary Closes #10992.
1 parent 71f0798 commit a00f6f5

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

crates/uv-workspace/src/pyproject.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,10 @@ pub enum SourceError {
13121312
UnusedTag(String, String),
13131313
#[error("`{0}` did not resolve to a Git repository, but a Git reference (`--branch {1}`) was provided.")]
13141314
UnusedBranch(String, String),
1315+
#[error("`{0}` did not resolve to a local directory, but the `--editable` flag was provided. Editable installs are only supported for local directories.")]
1316+
UnusedEditable(String),
1317+
#[error("Workspace dependency `{0}` was marked as `--no-editable`, but workspace dependencies are always added in editable mode. Pass `--no-editable` to `uv sync` or `uv run` to install workspace dependencies in non-editable mode.")]
1318+
UnusedNoEditable(String),
13151319
#[error("Failed to resolve absolute path")]
13161320
Absolute(#[from] std::io::Error),
13171321
#[error("Path contains invalid characters: `{}`", _0.display())]
@@ -1349,6 +1353,20 @@ impl Source {
13491353
}
13501354
}
13511355

1356+
if workspace {
1357+
// If a workspace source is added with `--no-editable`, error.
1358+
if editable == Some(false) {
1359+
return Err(SourceError::UnusedNoEditable(name.to_string()));
1360+
}
1361+
} else {
1362+
// If we resolved a non-path source, and user specified an `--editable` flag, error.
1363+
if !matches!(source, RequirementSource::Directory { .. }) {
1364+
if editable == Some(true) {
1365+
return Err(SourceError::UnusedEditable(name.to_string()));
1366+
}
1367+
}
1368+
}
1369+
13521370
// If the source is a workspace package, error if the user tried to specify a source.
13531371
if workspace {
13541372
return match source {

crates/uv/tests/it/edit.rs

+49
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,37 @@ fn add_raw_error() -> Result<()> {
875875
Ok(())
876876
}
877877

878+
#[test]
879+
#[cfg(feature = "git")]
880+
fn add_editable_error() -> Result<()> {
881+
let context = TestContext::new("3.12");
882+
883+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
884+
pyproject_toml.write_str(indoc! {r#"
885+
[project]
886+
name = "project"
887+
version = "0.1.0"
888+
requires-python = ">=3.12"
889+
dependencies = []
890+
891+
[build-system]
892+
requires = ["setuptools>=42"]
893+
build-backend = "setuptools.build_meta"
894+
"#})?;
895+
896+
// Provide `--editable` with a non-source tree.
897+
uv_snapshot!(context.filters(), context.add().arg("flask @ https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl").arg("--editable"), @r###"
898+
success: false
899+
exit_code: 2
900+
----- stdout -----
901+
902+
----- stderr -----
903+
error: `flask` did not resolve to a local directory, but the `--editable` flag was provided. Editable installs are only supported for local directories.
904+
"###);
905+
906+
Ok(())
907+
}
908+
878909
/// Add an unnamed requirement.
879910
#[test]
880911
#[cfg(feature = "git")]
@@ -2186,6 +2217,24 @@ fn add_workspace_editable() -> Result<()> {
21862217
"#})?;
21872218

21882219
let child1 = context.temp_dir.join("child1");
2220+
2221+
// `--no-editable` should error.
2222+
let mut add_cmd = context.add();
2223+
add_cmd
2224+
.arg("child2")
2225+
.arg("--no-editable")
2226+
.current_dir(&child1);
2227+
2228+
uv_snapshot!(context.filters(), add_cmd, @r###"
2229+
success: false
2230+
exit_code: 2
2231+
----- stdout -----
2232+
2233+
----- stderr -----
2234+
error: Workspace dependency `child2` was marked as `--no-editable`, but workspace dependencies are always added in editable mode. Pass `--no-editable` to `uv sync` or `uv run` to install workspace dependencies in non-editable mode.
2235+
"###);
2236+
2237+
// `--editable` should not.
21892238
let mut add_cmd = context.add();
21902239
add_cmd.arg("child2").arg("--editable").current_dir(&child1);
21912240

0 commit comments

Comments
 (0)