From ef17e7d0f4b6424d8c0d5d6104f2034f7b15e844 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 15 Sep 2025 09:54:38 -0400 Subject: [PATCH] Load credentials for explicit members when lowering (#15844) ## Summary If the target for `uv pip compile` is a `pyproject.toml` in a subdirectory, we won't have loaded the credentials when we go to lower (since it won't be loaded as part of "configuration discovery"). We now add those indexes just-in-time. Closes https://github.com/astral-sh/uv/issues/15362. --- Cargo.lock | 1 + crates/uv-distribution/Cargo.toml | 1 + .../uv-distribution/src/metadata/lowering.rs | 33 +++++----- crates/uv/tests/it/pip_compile.rs | 66 +++++++++++++++++++ 4 files changed, 85 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0bc2dff6a..3ea7ea3d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5624,6 +5624,7 @@ dependencies = [ "toml", "tracing", "url", + "uv-auth", "uv-cache", "uv-cache-info", "uv-client", diff --git a/crates/uv-distribution/Cargo.toml b/crates/uv-distribution/Cargo.toml index 5209f5ccf..80859f18b 100644 --- a/crates/uv-distribution/Cargo.toml +++ b/crates/uv-distribution/Cargo.toml @@ -16,6 +16,7 @@ doctest = false workspace = true [dependencies] +uv-auth = { workspace = true } uv-cache = { workspace = true } uv-cache-info = { workspace = true } uv-client = { workspace = true } diff --git a/crates/uv-distribution/src/metadata/lowering.rs b/crates/uv-distribution/src/metadata/lowering.rs index 666175b0d..d86cafa49 100644 --- a/crates/uv-distribution/src/metadata/lowering.rs +++ b/crates/uv-distribution/src/metadata/lowering.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::io; use std::path::{Path, PathBuf}; +use std::sync::Arc; use either::Either; use thiserror::Error; @@ -222,20 +223,20 @@ impl LoweredRequirement { .find(|Index { name, .. }| { name.as_ref().is_some_and(|name| *name == index) }) - .map( - |Index { - url, format: kind, .. - }| IndexMetadata { - url: url.clone(), - format: *kind, - }, - ) else { return Err(LoweringError::MissingIndex( requirement.name.clone(), index, )); }; + if let Some(credentials) = index.credentials() { + let credentials = Arc::new(credentials); + uv_auth::store_credentials(index.raw_url(), credentials); + } + let index = IndexMetadata { + url: index.url.clone(), + format: index.format, + }; let conflict = project_name.and_then(|project_name| { if let Some(extra) = extra { Some(ConflictItem::from((project_name.clone(), extra))) @@ -456,20 +457,20 @@ impl LoweredRequirement { .find(|Index { name, .. }| { name.as_ref().is_some_and(|name| *name == index) }) - .map( - |Index { - url, format: kind, .. - }| IndexMetadata { - url: url.clone(), - format: *kind, - }, - ) else { return Err(LoweringError::MissingIndex( requirement.name.clone(), index, )); }; + if let Some(credentials) = index.credentials() { + let credentials = Arc::new(credentials); + uv_auth::store_credentials(index.raw_url(), credentials); + } + let index = IndexMetadata { + url: index.url.clone(), + format: index.format, + }; let conflict = None; let source = registry_source(&requirement, index, conflict); (source, marker) diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 0643973c5..8a7b9f8fb 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -17784,3 +17784,69 @@ fn omit_python_patch_universal() -> Result<()> { Ok(()) } + +#[test] +fn credentials_from_subdirectory() -> Result<()> { + let context = TestContext::new("3.12"); + + // Create a local dependency in a subdirectory. + let pyproject_toml = context.temp_dir.child("foo").child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "foo" + version = "1.0.0" + dependencies = ["iniconfig"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + + [tool.uv.sources] + iniconfig = { index = "internal" } + + [[tool.uv.index]] + name = "internal" + url = "https://pypi-proxy.fly.dev/basic-auth/simple/" + explicit = true + "#, + )?; + context + .temp_dir + .child("foo") + .child("src") + .child("foo") + .child("__init__.py") + .touch()?; + + uv_snapshot!(context.filters(), context + .pip_compile() + .arg("foo/pyproject.toml"), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because iniconfig was not found in the package registry and foo depends on iniconfig, we can conclude that your requirements are unsatisfiable. + "); + + uv_snapshot!(context.filters(), context + .pip_compile() + .arg("foo/pyproject.toml") + .env("UV_INDEX_INTERNAL_USERNAME", "public") + .env("UV_INDEX_INTERNAL_PASSWORD", "heron"), @r" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] foo/pyproject.toml + iniconfig==2.0.0 + # via foo (foo/pyproject.toml) + + ----- stderr ----- + Resolved 1 package in [TIME] + "); + + Ok(()) +}