mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-29 11:07:59 +00:00
Add --with-editable support to uv run (#6262)
Closes https://github.com/astral-sh/uv/issues/6254
This commit is contained in:
parent
c3c05c4602
commit
f10ccc488e
6 changed files with 166 additions and 2 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue