mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Check if relative URL is valid directory before treating as index (#13917)
As per #13874, passing a relative URL like `test` to `--index` for `uv add` causes unexpected behavior if the directory does not exist. The non-existent index is effectively ignored and uv falls back to PyPI. If a package is found there, the spurious index is then written to `pyproject.toml`. This doesn't happen for `--default-index` since resolution will fail without fallback to PyPI. This PR adds a validation step for indexes provided on the command line. If a directory does not exist, uv will fail with an error. Closes #13874
This commit is contained in:
parent
619a0eafa1
commit
2a66349e96
3 changed files with 112 additions and 6 deletions
|
@ -106,10 +106,8 @@ impl VerbatimUrl {
|
|||
let (path, fragment) = split_fragment(&path);
|
||||
|
||||
// Convert to a URL.
|
||||
let mut url = DisplaySafeUrl::from(
|
||||
Url::from_file_path(path.clone())
|
||||
.unwrap_or_else(|()| panic!("path is absolute: {}", path.display())),
|
||||
);
|
||||
let mut url = DisplaySafeUrl::from_file_path(path.clone())
|
||||
.unwrap_or_else(|()| panic!("path is absolute: {}", path.display()));
|
||||
|
||||
// Set the fragment, if it exists.
|
||||
if let Some(fragment) = fragment {
|
||||
|
|
|
@ -23,8 +23,8 @@ use uv_configuration::{
|
|||
use uv_dispatch::BuildDispatch;
|
||||
use uv_distribution::DistributionDatabase;
|
||||
use uv_distribution_types::{
|
||||
Index, IndexName, IndexUrls, NameRequirementSpecification, Requirement, RequirementSource,
|
||||
UnresolvedRequirement, VersionId,
|
||||
Index, IndexName, IndexUrl, IndexUrls, NameRequirementSpecification, Requirement,
|
||||
RequirementSource, UnresolvedRequirement, VersionId,
|
||||
};
|
||||
use uv_fs::{LockedFile, Simplified};
|
||||
use uv_git::GIT_STORE;
|
||||
|
@ -473,6 +473,19 @@ pub(crate) async fn add(
|
|||
&mut toml,
|
||||
)?;
|
||||
|
||||
// Validate any indexes that were provided on the command-line to ensure
|
||||
// they point to existing directories when using path URLs.
|
||||
for index in &indexes {
|
||||
if let IndexUrl::Path(url) = &index.url {
|
||||
let path = url
|
||||
.to_file_path()
|
||||
.map_err(|()| anyhow::anyhow!("Invalid file path in index URL: {url}"))?;
|
||||
if !path.is_dir() {
|
||||
bail!("Directory not found for index: {url}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any indexes that were provided on the command-line, in priority order.
|
||||
if !raw {
|
||||
let urls = IndexUrls::from_indexes(indexes);
|
||||
|
|
|
@ -9303,6 +9303,101 @@ fn add_index_without_trailing_slash() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Add an index with an existing relative path.
|
||||
#[test]
|
||||
fn add_index_with_existing_relative_path_index() -> 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 = []
|
||||
"#})?;
|
||||
|
||||
// Create test-index/ subdirectory and copy our "offline" tqdm wheel there
|
||||
let packages = context.temp_dir.child("test-index");
|
||||
packages.create_dir_all()?;
|
||||
|
||||
let wheel_src = context
|
||||
.workspace_root
|
||||
.join("scripts/links/ok-1.0.0-py3-none-any.whl");
|
||||
let wheel_dst = packages.child("ok-1.0.0-py3-none-any.whl");
|
||||
fs_err::copy(&wheel_src, &wheel_dst)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("test-index"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add an index with a non-existent relative path.
|
||||
#[test]
|
||||
fn add_index_with_non_existent_relative_path() -> 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 = []
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("test-index"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Directory not found for index: file://[TEMP_DIR]/test-index
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add an index with a non-existent relative path with the same name as a defined index.
|
||||
#[test]
|
||||
fn add_index_with_non_existent_relative_path_with_same_name_as_index() -> 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 = []
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "test-index"
|
||||
url = "https://pypi-proxy.fly.dev/simple"
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--index").arg("test-index"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Directory not found for index: file://[TEMP_DIR]/test-index
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a PyPI requirement.
|
||||
#[test]
|
||||
fn add_group_comment() -> Result<()> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue