Reject `--editable` flag on non-directory requirements (#10994)

## Summary

Closes https://github.com/astral-sh/uv/issues/10992.
This commit is contained in:
Charlie Marsh 2025-01-27 14:37:23 -05:00 committed by GitHub
parent 71f0798536
commit a00f6f5d3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 67 additions and 0 deletions

View File

@ -1312,6 +1312,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("`{0}` did not resolve to a local directory, but the `--editable` flag was provided. Editable installs are only supported for local directories.")]
UnusedEditable(String),
#[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.")]
UnusedNoEditable(String),
#[error("Failed to resolve absolute path")]
Absolute(#[from] std::io::Error),
#[error("Path contains invalid characters: `{}`", _0.display())]
@ -1349,6 +1353,20 @@ impl Source {
}
}
if workspace {
// If a workspace source is added with `--no-editable`, error.
if editable == Some(false) {
return Err(SourceError::UnusedNoEditable(name.to_string()));
}
} else {
// If we resolved a non-path source, and user specified an `--editable` flag, error.
if !matches!(source, RequirementSource::Directory { .. }) {
if editable == Some(true) {
return Err(SourceError::UnusedEditable(name.to_string()));
}
}
}
// If the source is a workspace package, error if the user tried to specify a source.
if workspace {
return match source {

View File

@ -875,6 +875,37 @@ fn add_raw_error() -> Result<()> {
Ok(())
}
#[test]
#[cfg(feature = "git")]
fn add_editable_error() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#})?;
// Provide `--editable` with a non-source tree.
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###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: `flask` did not resolve to a local directory, but the `--editable` flag was provided. Editable installs are only supported for local directories.
"###);
Ok(())
}
/// Add an unnamed requirement.
#[test]
#[cfg(feature = "git")]
@ -2186,6 +2217,24 @@ fn add_workspace_editable() -> Result<()> {
"#})?;
let child1 = context.temp_dir.join("child1");
// `--no-editable` should error.
let mut add_cmd = context.add();
add_cmd
.arg("child2")
.arg("--no-editable")
.current_dir(&child1);
uv_snapshot!(context.filters(), add_cmd, @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
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.
"###);
// `--editable` should not.
let mut add_cmd = context.add();
add_cmd.arg("child2").arg("--editable").current_dir(&child1);