Add --with-editable support to uv run (#6262)

Closes https://github.com/astral-sh/uv/issues/6254
This commit is contained in:
Zanie Blue 2024-08-20 14:04:46 -05:00 committed by GitHub
parent c3c05c4602
commit f10ccc488e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 166 additions and 2 deletions

View file

@ -2140,6 +2140,14 @@ pub struct RunArgs {
#[arg(long)]
pub with: Vec<String>,
/// Run with the given packages installed as editables
///
/// When used in a project, these dependencies will be layered on top of
/// the project environment in a separate, ephemeral environment. These
/// dependencies are allowed to conflict with those specified by the project.
#[arg(long)]
pub with_editable: Vec<String>,
/// Run with all packages listed in the given `requirements.txt` files.
///
/// The same environment semantics as `--with` apply.

View file

@ -554,7 +554,16 @@ pub(crate) async fn run(
eprint!("{report:?}");
return Ok(ExitStatus::Failure);
}
Err(err) => return Err(err.into()),
Err(ProjectError::Operation(operations::Error::Named(err))) => {
let err = miette::Report::msg(format!("{err}"))
.context("Invalid `--with` requirement");
eprint!("{err:?}");
return Ok(ExitStatus::Failure);
}
Err(err) => {
return Err(err.into());
}
};
environment.into()

View file

@ -1011,6 +1011,11 @@ async fn run_project(
.with
.into_iter()
.map(RequirementsSource::from_package)
.chain(
args.with_editable
.into_iter()
.map(RequirementsSource::Editable),
)
.chain(
args.with_requirements
.into_iter()

View file

@ -193,6 +193,7 @@ pub(crate) struct RunSettings {
pub(crate) dev: bool,
pub(crate) command: ExternalCommand,
pub(crate) with: Vec<String>,
pub(crate) with_editable: Vec<String>,
pub(crate) with_requirements: Vec<PathBuf>,
pub(crate) isolated: bool,
pub(crate) show_resolution: bool,
@ -215,6 +216,7 @@ impl RunSettings {
no_dev,
command,
with,
with_editable,
with_requirements,
isolated,
locked,
@ -238,6 +240,7 @@ impl RunSettings {
dev: flag(dev, no_dev).unwrap_or(true),
command,
with,
with_editable,
with_requirements: with_requirements
.into_iter()
.filter_map(Maybe::into_option)

View file

@ -7,7 +7,7 @@ use indoc::indoc;
use uv_python::PYTHON_VERSION_FILENAME;
use common::{uv_snapshot, TestContext};
use common::{copy_dir_all, uv_snapshot, TestContext};
mod common;
@ -538,6 +538,141 @@ fn run_with() -> Result<()> {
Ok(())
}
#[test]
fn run_with_editable() -> Result<()> {
let context = TestContext::new("3.12");
let anyio_local = context.temp_dir.child("src").child("anyio_local");
copy_dir_all(
context.workspace_root.join("scripts/packages/anyio_local"),
&anyio_local,
)?;
let black_editable = context.temp_dir.child("src").child("black_editable");
copy_dir_all(
context
.workspace_root
.join("scripts/packages/black_editable"),
&black_editable,
)?;
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio", "sniffio==1.3.1"]
"#
})?;
let test_script = context.temp_dir.child("main.py");
test_script.write_str(indoc! { r"
import sniffio
"
})?;
// Requesting an editable requirement should install it in a layer.
uv_snapshot!(context.filters(), context.run().arg("--with-editable").arg("./src/black_editable").arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Prepared 4 packages in [TIME]
Installed 4 packages in [TIME]
+ anyio==4.3.0
+ foo==1.0.0 (from file://[TEMP_DIR]/)
+ idna==3.6
+ sniffio==1.3.1
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ black==0.1.0 (from file://[TEMP_DIR]/src/black_editable)
"###);
// Requesting an editable requirement should install it in a layer, even if it satisfied
uv_snapshot!(context.filters(), context.run().arg("--with-editable").arg("./src/anyio_local").arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Audited 4 packages in [TIME]
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ anyio==4.3.0+foo (from file://[TEMP_DIR]/src/anyio_local)
"###);
// Requesting the project itself should use the base environment.
uv_snapshot!(context.filters(), context.run().arg("--with-editable").arg(".").arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 6 packages in [TIME]
Audited 4 packages in [TIME]
"###);
// Similarly, an already editable requirement does not require a layer
pyproject_toml.write_str(indoc! { r#"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio", "sniffio==1.3.1"]
[tool.uv.sources]
anyio = { path = "./src/anyio_local", editable = true }
"#
})?;
uv_snapshot!(context.filters(), context.sync(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 1 package in [TIME]
Uninstalled 3 packages in [TIME]
Installed 2 packages in [TIME]
- anyio==4.3.0
+ anyio==4.3.0+foo (from file://[TEMP_DIR]/src/anyio_local)
~ foo==1.0.0 (from file://[TEMP_DIR]/)
- idna==3.6
"###);
uv_snapshot!(context.filters(), context.run().arg("--with-editable").arg("./src/anyio_local").arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Audited 3 packages in [TIME]
"###);
// If invalid, we should reference `--with-editable`.
uv_snapshot!(context.filters(), context.run().arg("--with").arg("./foo").arg("main.py"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Audited 3 packages in [TIME]
× Invalid `--with` requirement
Distribution not found at: file://[TEMP_DIR]/foo
"###);
Ok(())
}
#[test]
fn run_locked() -> Result<()> {
let context = TestContext::new("3.12");