mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-02 12:59:45 +00:00
Error when editables don't match Requires-Python (#2194)
## Summary Closes https://github.com/astral-sh/uv/issues/2192. ## Test Plan (Needs tests.)
This commit is contained in:
parent
2a53e789b0
commit
0bc047866d
7 changed files with 217 additions and 16 deletions
|
|
@ -22,12 +22,12 @@ impl PythonRequirement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the installed version of Python.
|
/// Return the installed version of Python.
|
||||||
pub(crate) fn installed(&self) -> &Version {
|
pub fn installed(&self) -> &Version {
|
||||||
&self.installed
|
&self.installed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the target version of Python.
|
/// Return the target version of Python.
|
||||||
pub(crate) fn target(&self) -> &Version {
|
pub fn target(&self) -> &Version {
|
||||||
&self.target
|
&self.target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ use uv_interpreter::{Interpreter, PythonVersion};
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
use uv_resolver::{
|
use uv_resolver::{
|
||||||
AnnotationStyle, DependencyMode, DisplayResolutionGraph, InMemoryIndex, Manifest,
|
AnnotationStyle, DependencyMode, DisplayResolutionGraph, InMemoryIndex, Manifest,
|
||||||
OptionsBuilder, PreReleaseMode, ResolutionMode, Resolver,
|
OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode, Resolver,
|
||||||
};
|
};
|
||||||
use uv_traits::{ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
use uv_traits::{ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
|
|
@ -244,8 +244,9 @@ pub(crate) async fn pip_compile(
|
||||||
let downloader = Downloader::new(&cache, &tags, &client, &build_dispatch)
|
let downloader = Downloader::new(&cache, &tags, &client, &build_dispatch)
|
||||||
.with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64));
|
.with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64));
|
||||||
|
|
||||||
|
// Build all editables.
|
||||||
let editable_wheel_dir = tempdir_in(cache.root())?;
|
let editable_wheel_dir = tempdir_in(cache.root())?;
|
||||||
let editable_metadata: Vec<_> = downloader
|
let editables: Vec<_> = downloader
|
||||||
.build_editables(editables, editable_wheel_dir.path())
|
.build_editables(editables, editable_wheel_dir.path())
|
||||||
.await
|
.await
|
||||||
.context("Failed to build editables")?
|
.context("Failed to build editables")?
|
||||||
|
|
@ -253,22 +254,33 @@ pub(crate) async fn pip_compile(
|
||||||
.map(|built_editable| (built_editable.editable, built_editable.metadata))
|
.map(|built_editable| (built_editable.editable, built_editable.metadata))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let s = if editable_metadata.len() == 1 {
|
// Validate that the editables are compatible with the target Python version.
|
||||||
""
|
let requirement = PythonRequirement::new(&interpreter, &markers);
|
||||||
} else {
|
for (.., metadata) in &editables {
|
||||||
"s"
|
if let Some(python_requires) = metadata.requires_python.as_ref() {
|
||||||
};
|
if !python_requires.contains(requirement.target()) {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Editable `{}` requires Python {}, but resolution targets Python {}",
|
||||||
|
metadata.name,
|
||||||
|
python_requires,
|
||||||
|
requirement.target()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = if editables.len() == 1 { "" } else { "s" };
|
||||||
writeln!(
|
writeln!(
|
||||||
printer,
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
format!(
|
format!(
|
||||||
"Built {} in {}",
|
"Built {} in {}",
|
||||||
format!("{} editable{}", editable_metadata.len(), s).bold(),
|
format!("{} editable{}", editables.len(), s).bold(),
|
||||||
elapsed(start.elapsed())
|
elapsed(start.elapsed())
|
||||||
)
|
)
|
||||||
.dimmed()
|
.dimmed()
|
||||||
)?;
|
)?;
|
||||||
editable_metadata
|
editables
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a manifest of the requirements.
|
// Create a manifest of the requirements.
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@ pub(crate) async fn pip_install(
|
||||||
&editables,
|
&editables,
|
||||||
editable_wheel_dir.path(),
|
editable_wheel_dir.path(),
|
||||||
&cache,
|
&cache,
|
||||||
|
&interpreter,
|
||||||
tags,
|
tags,
|
||||||
&client,
|
&client,
|
||||||
&resolve_dispatch,
|
&resolve_dispatch,
|
||||||
|
|
@ -356,10 +357,12 @@ fn specification(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a set of editable distributions.
|
/// Build a set of editable distributions.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn build_editables(
|
async fn build_editables(
|
||||||
editables: &[EditableRequirement],
|
editables: &[EditableRequirement],
|
||||||
editable_wheel_dir: &Path,
|
editable_wheel_dir: &Path,
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
|
interpreter: &Interpreter,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
client: &RegistryClient,
|
client: &RegistryClient,
|
||||||
build_dispatch: &BuildDispatch<'_>,
|
build_dispatch: &BuildDispatch<'_>,
|
||||||
|
|
@ -389,6 +392,21 @@ async fn build_editables(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Validate that the editables are compatible with the target Python version.
|
||||||
|
for editable in &editables {
|
||||||
|
if let Some(python_requires) = editable.metadata.requires_python.as_ref() {
|
||||||
|
if !python_requires.contains(interpreter.python_version()) {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Editable `{}` requires Python {}, but {} is installed",
|
||||||
|
editable.metadata.name,
|
||||||
|
python_requires,
|
||||||
|
interpreter.python_version()
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let s = if editables.len() == 1 { "" } else { "s" };
|
let s = if editables.len() == 1 { "" } else { "s" };
|
||||||
writeln!(
|
writeln!(
|
||||||
printer,
|
printer,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
@ -18,7 +18,7 @@ use uv_fs::Simplified;
|
||||||
use uv_installer::{
|
use uv_installer::{
|
||||||
is_dynamic, Downloader, NoBinary, Plan, Planner, Reinstall, ResolvedEditable, SitePackages,
|
is_dynamic, Downloader, NoBinary, Plan, Planner, Reinstall, ResolvedEditable, SitePackages,
|
||||||
};
|
};
|
||||||
use uv_interpreter::PythonEnvironment;
|
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||||
use uv_resolver::InMemoryIndex;
|
use uv_resolver::InMemoryIndex;
|
||||||
use uv_traits::{ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
use uv_traits::{ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||||
|
|
||||||
|
|
@ -151,7 +151,7 @@ pub(crate) async fn pip_sync(
|
||||||
editables,
|
editables,
|
||||||
&site_packages,
|
&site_packages,
|
||||||
reinstall,
|
reinstall,
|
||||||
&venv,
|
venv.interpreter(),
|
||||||
tags,
|
tags,
|
||||||
&cache,
|
&cache,
|
||||||
&client,
|
&client,
|
||||||
|
|
@ -413,7 +413,7 @@ async fn resolve_editables(
|
||||||
editables: Vec<EditableRequirement>,
|
editables: Vec<EditableRequirement>,
|
||||||
site_packages: &SitePackages<'_>,
|
site_packages: &SitePackages<'_>,
|
||||||
reinstall: &Reinstall,
|
reinstall: &Reinstall,
|
||||||
venv: &PythonEnvironment,
|
interpreter: &Interpreter,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
client: &RegistryClient,
|
client: &RegistryClient,
|
||||||
|
|
@ -479,7 +479,7 @@ async fn resolve_editables(
|
||||||
} else {
|
} else {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
let temp_dir = tempfile::tempdir_in(venv.root())?;
|
let temp_dir = tempfile::tempdir_in(cache.root())?;
|
||||||
|
|
||||||
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
||||||
.with_reporter(DownloadReporter::from(printer).with_length(uninstalled.len() as u64));
|
.with_reporter(DownloadReporter::from(printer).with_length(uninstalled.len() as u64));
|
||||||
|
|
@ -503,6 +503,20 @@ async fn resolve_editables(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Validate that the editables are compatible with the target Python version.
|
||||||
|
for editable in &built_editables {
|
||||||
|
if let Some(python_requires) = editable.metadata.requires_python.as_ref() {
|
||||||
|
if !python_requires.contains(interpreter.python_version()) {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Editable `{}` requires Python {}, but {} is installed",
|
||||||
|
editable.metadata.name,
|
||||||
|
python_requires,
|
||||||
|
interpreter.python_version()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let s = if built_editables.len() == 1 { "" } else { "s" };
|
let s = if built_editables.len() == 1 { "" } else { "s" };
|
||||||
writeln!(
|
writeln!(
|
||||||
printer,
|
printer,
|
||||||
|
|
|
||||||
|
|
@ -4670,3 +4670,89 @@ fn expand_env_var_requirements_txt() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Raise an error when an editable's `Requires-Python` constraint is not met.
|
||||||
|
#[test]
|
||||||
|
fn requires_python_editable() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// Create an editable package with a `Requires-Python` constraint that is not met.
|
||||||
|
let editable_dir = TempDir::new()?;
|
||||||
|
let pyproject_toml = editable_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"[project]
|
||||||
|
name = "example"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyio==4.0.0"
|
||||||
|
]
|
||||||
|
requires-python = "<=3.8"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Write to a requirements file.
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.compile()
|
||||||
|
.arg("requirements.in"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: Editable `example` requires Python <=3.8, but resolution targets Python 3.12.1
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Raise an error when an editable's `Requires-Python` constraint is not met.
|
||||||
|
#[test]
|
||||||
|
fn requires_python_editable_target_version() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// Create an editable package with a `Requires-Python` constraint that is not met.
|
||||||
|
let editable_dir = TempDir::new()?;
|
||||||
|
let pyproject_toml = editable_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"[project]
|
||||||
|
name = "example"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyio==4.0.0"
|
||||||
|
]
|
||||||
|
requires-python = "<=3.8"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Write to a requirements file.
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?;
|
||||||
|
|
||||||
|
let filters: Vec<_> = [
|
||||||
|
// 3.11 may not be installed
|
||||||
|
(
|
||||||
|
"warning: The requested Python version 3.11 is not available; .* will be used to build dependencies instead.\n",
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.chain(INSTA_FILTERS.to_vec())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
uv_snapshot!(filters, context.compile()
|
||||||
|
.arg("requirements.in")
|
||||||
|
.arg("--python-version=3.11"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: Editable `example` requires Python <=3.8, but resolution targets Python 3.11
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2247,3 +2247,37 @@ requires-python = ">=3.11,<3.13"
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Raise an error when an editable's `Requires-Python` constraint is not met.
|
||||||
|
#[test]
|
||||||
|
fn requires_python_editable() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// Create an editable package with a `Requires-Python` constraint that is not met.
|
||||||
|
let editable_dir = assert_fs::TempDir::new()?;
|
||||||
|
let pyproject_toml = editable_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"[project]
|
||||||
|
name = "example"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyio==4.0.0"
|
||||||
|
]
|
||||||
|
requires-python = "<=3.8"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("--editable")
|
||||||
|
.arg(editable_dir.path()), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: Editable `example` requires Python <=3.8, but 3.12.1 is installed
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2956,3 +2956,40 @@ fn compile() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Raise an error when an editable's `Requires-Python` constraint is not met.
|
||||||
|
#[test]
|
||||||
|
fn requires_python_editable() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// Create an editable package with a `Requires-Python` constraint that is not met.
|
||||||
|
let editable_dir = assert_fs::TempDir::new()?;
|
||||||
|
let pyproject_toml = editable_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"[project]
|
||||||
|
name = "example"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyio==4.0.0"
|
||||||
|
]
|
||||||
|
requires-python = "<=3.5"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Write to a requirements file.
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str(&format!("-e {}", editable_dir.path().display()))?;
|
||||||
|
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("requirements.in"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: Editable `example` requires Python <=3.5, but 3.12.1 is installed
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue