Use explicit credentials cache instead of global static (#16768)

Fixes https://github.com/astral-sh/uv/issues/16447

Passing this around explicitly uncovers some behaviors where we pass
e.g. the credentials store to reading the lockfile. The changes in this
PR should preserve the existing behavior for now, they only make the
locations we read from more explicit.

Labeling this PR as "Enhancement" instead of "Internal" in case this
changes behavior when it shouldn't have.
This commit is contained in:
konsti 2025-12-03 14:51:25 +01:00 committed by GitHub
parent e00cc8c35f
commit 05fa19c440
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 301 additions and 131 deletions

1
Cargo.lock generated
View File

@ -5666,6 +5666,7 @@ dependencies = [
"tokio",
"toml_edit 0.23.7",
"tracing",
"uv-auth",
"uv-cache-key",
"uv-configuration",
"uv-distribution",

View File

@ -11,8 +11,8 @@ use url::Url;
use uv_once_map::OnceMap;
use uv_redacted::DisplaySafeUrl;
use crate::Realm;
use crate::credentials::{Authentication, Username};
use crate::{Credentials, Realm};
type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;
@ -33,6 +33,7 @@ impl Display for FetchUrl {
}
}
#[derive(Debug)] // All internal types are redacted.
pub struct CredentialsCache {
/// A cache per realm and username
realms: RwLock<FxHashMap<(Realm, Username), Arc<Authentication>>>,
@ -58,6 +59,27 @@ impl CredentialsCache {
}
}
/// Populate the global authentication store with credentials on a URL, if there are any.
///
/// Returns `true` if the store was updated.
pub fn store_credentials_from_url(&self, url: &DisplaySafeUrl) -> bool {
if let Some(credentials) = Credentials::from_url(url) {
trace!("Caching credentials for {url}");
self.insert(url, Arc::new(Authentication::from(credentials)));
true
} else {
false
}
}
/// Populate the global authentication store with credentials on a URL, if there are any.
///
/// Returns `true` if the store was updated.
pub fn store_credentials(&self, url: &DisplaySafeUrl, credentials: Credentials) {
trace!("Caching credentials for {url}");
self.insert(url, Arc::new(Authentication::from(credentials)));
}
/// Return the credentials that should be used for a realm and username, if any.
pub(crate) fn get_realm(
&self,

View File

@ -1,12 +1,5 @@
use std::sync::{Arc, LazyLock};
use tracing::trace;
use uv_redacted::DisplaySafeUrl;
use crate::credentials::Authentication;
pub use access_token::AccessToken;
use cache::CredentialsCache;
pub use cache::CredentialsCache;
pub use credentials::{Credentials, Username};
pub use index::{AuthPolicy, Index, Indexes};
pub use keyring::KeyringProvider;
@ -29,32 +22,3 @@ mod pyx;
mod realm;
mod service;
mod store;
// TODO(zanieb): Consider passing a cache explicitly throughout
/// Global authentication cache for a uv invocation
///
/// This is used to share credentials across uv clients.
pub(crate) static CREDENTIALS_CACHE: LazyLock<CredentialsCache> =
LazyLock::new(CredentialsCache::default);
/// Populate the global authentication store with credentials on a URL, if there are any.
///
/// Returns `true` if the store was updated.
pub fn store_credentials_from_url(url: &DisplaySafeUrl) -> bool {
if let Some(credentials) = Credentials::from_url(url) {
trace!("Caching credentials for {url}");
CREDENTIALS_CACHE.insert(url, Arc::new(Authentication::from(credentials)));
true
} else {
false
}
}
/// Populate the global authentication store with credentials on a URL, if there are any.
///
/// Returns `true` if the store was updated.
pub fn store_credentials(url: &DisplaySafeUrl, credentials: Credentials) {
trace!("Caching credentials for {url}");
CREDENTIALS_CACHE.insert(url, Arc::new(Authentication::from(credentials)));
}

View File

@ -17,7 +17,7 @@ use crate::credentials::Authentication;
use crate::providers::{HuggingFaceProvider, S3EndpointProvider};
use crate::pyx::{DEFAULT_TOLERANCE_SECS, PyxTokenStore};
use crate::{
AccessToken, CREDENTIALS_CACHE, CredentialsCache, KeyringProvider,
AccessToken, CredentialsCache, KeyringProvider,
cache::FetchUrl,
credentials::{Credentials, Username},
index::{AuthPolicy, Indexes},
@ -131,7 +131,8 @@ pub struct AuthMiddleware {
netrc: NetrcMode,
text_store: TextStoreMode,
keyring: Option<KeyringProvider>,
cache: Option<CredentialsCache>,
/// Global authentication cache for a uv invocation to share credentials across uv clients.
cache: Arc<CredentialsCache>,
/// Auth policies for specific URLs.
indexes: Indexes,
/// Set all endpoints as needing authentication. We never try to send an
@ -146,13 +147,20 @@ pub struct AuthMiddleware {
preview: Preview,
}
impl Default for AuthMiddleware {
fn default() -> Self {
Self::new()
}
}
impl AuthMiddleware {
pub fn new() -> Self {
Self {
netrc: NetrcMode::default(),
text_store: TextStoreMode::default(),
keyring: None,
cache: None,
// TODO(konsti): There shouldn't be a credential cache without that in the initializer.
cache: Arc::new(CredentialsCache::default()),
indexes: Indexes::new(),
only_authenticated: false,
base_client: None,
@ -205,7 +213,14 @@ impl AuthMiddleware {
/// Configure the [`CredentialsCache`] to use.
#[must_use]
pub fn with_cache(mut self, cache: CredentialsCache) -> Self {
self.cache = Some(cache);
self.cache = Arc::new(cache);
self
}
/// Configure the [`CredentialsCache`] to use from an existing [`Arc`].
#[must_use]
pub fn with_cache_arc(mut self, cache: Arc<CredentialsCache>) -> Self {
self.cache = cache;
self
}
@ -238,17 +253,9 @@ impl AuthMiddleware {
self
}
/// Get the configured authentication store.
///
/// If not set, the global store is used.
/// Global authentication cache for a uv invocation to share credentials across uv clients.
fn cache(&self) -> &CredentialsCache {
self.cache.as_ref().unwrap_or(&CREDENTIALS_CACHE)
}
}
impl Default for AuthMiddleware {
fn default() -> Self {
Self::new()
&self.cache
}
}

View File

@ -16,6 +16,7 @@ doctest = false
workspace = true
[dependencies]
uv-auth = { workspace = true }
uv-cache-key = { workspace = true }
uv-configuration = { workspace = true }
uv-distribution = { workspace = true }

View File

@ -28,7 +28,7 @@ use tokio::io::AsyncBufReadExt;
use tokio::process::Command;
use tokio::sync::{Mutex, Semaphore};
use tracing::{Instrument, debug, info_span, instrument, warn};
use uv_auth::CredentialsCache;
use uv_cache_key::cache_digest;
use uv_configuration::{BuildKind, BuildOutput, SourceStrategy};
use uv_distribution::BuildRequires;
@ -292,6 +292,7 @@ impl SourceBuild {
mut environment_variables: FxHashMap<OsString, OsString>,
level: BuildOutput,
concurrent_builds: usize,
credentials_cache: &CredentialsCache,
preview: Preview,
) -> Result<Self, Error> {
let temp_dir = build_context.cache().venv_dir()?;
@ -310,6 +311,7 @@ impl SourceBuild {
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
.await
.map_err(|err| *err)?;
@ -452,6 +454,7 @@ impl SourceBuild {
&environment_variables,
&modified_path,
&temp_dir,
credentials_cache,
)
.await?;
}
@ -556,6 +559,7 @@ impl SourceBuild {
locations: &IndexLocations,
source_strategy: SourceStrategy,
workspace_cache: &WorkspaceCache,
credentials_cache: &CredentialsCache,
) -> Result<(Pep517Backend, Option<Project>), Box<Error>> {
match fs::read_to_string(source_tree.join("pyproject.toml")) {
Ok(toml) => {
@ -584,6 +588,7 @@ impl SourceBuild {
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
.await
.map_err(Error::Lowering)?;
@ -956,6 +961,7 @@ async fn create_pep517_build_environment(
environment_variables: &FxHashMap<OsString, OsString>,
modified_path: &OsString,
temp_dir: &TempDir,
credentials_cache: &CredentialsCache,
) -> Result<(), Error> {
// Write the hook output to a file so that we can read it back reliably.
let outfile = temp_dir
@ -1050,6 +1056,7 @@ async fn create_pep517_build_environment(
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
.await
.map_err(Error::Lowering)?;

View File

@ -28,7 +28,7 @@ use tracing::{debug, trace};
use url::ParseError;
use url::Url;
use uv_auth::{AuthMiddleware, Credentials, Indexes, PyxTokenStore};
use uv_auth::{AuthMiddleware, Credentials, CredentialsCache, Indexes, PyxTokenStore};
use uv_configuration::{KeyringProviderType, TrustedHost};
use uv_fs::Simplified;
use uv_pep508::MarkerEnvironment;
@ -78,6 +78,8 @@ pub struct BaseClientBuilder<'a> {
markers: Option<&'a MarkerEnvironment>,
platform: Option<&'a Platform>,
auth_integration: AuthIntegration,
/// Global authentication cache for a uv invocation to share credentials across uv clients.
credentials_cache: Arc<CredentialsCache>,
indexes: Indexes,
timeout: Duration,
extra_middleware: Option<ExtraMiddleware>,
@ -138,6 +140,7 @@ impl Default for BaseClientBuilder<'_> {
markers: None,
platform: None,
auth_integration: AuthIntegration::default(),
credentials_cache: Arc::new(CredentialsCache::default()),
indexes: Indexes::new(),
timeout: Duration::from_secs(30),
extra_middleware: None,
@ -150,7 +153,7 @@ impl Default for BaseClientBuilder<'_> {
}
}
impl BaseClientBuilder<'_> {
impl<'a> BaseClientBuilder<'a> {
pub fn new(
connectivity: Connectivity,
native_tls: bool,
@ -169,9 +172,7 @@ impl BaseClientBuilder<'_> {
..Self::default()
}
}
}
impl<'a> BaseClientBuilder<'a> {
/// Use a custom reqwest client instead of creating a new one.
///
/// This allows you to provide your own reqwest client with custom configuration.
@ -285,6 +286,20 @@ impl<'a> BaseClientBuilder<'a> {
self
}
pub fn credentials_cache(&self) -> &CredentialsCache {
&self.credentials_cache
}
/// See [`CredentialsCache::store_credentials_from_url`].
pub fn store_credentials_from_url(&self, url: &DisplaySafeUrl) -> bool {
self.credentials_cache.store_credentials_from_url(url)
}
/// See [`CredentialsCache::store_credentials`].
pub fn store_credentials(&self, url: &DisplaySafeUrl, credentials: Credentials) {
self.credentials_cache.store_credentials(url, credentials);
}
pub fn is_native_tls(&self) -> bool {
self.native_tls
}
@ -333,6 +348,7 @@ impl<'a> BaseClientBuilder<'a> {
dangerous_client,
raw_dangerous_client,
timeout,
credentials_cache: self.credentials_cache.clone(),
}
}
@ -359,6 +375,7 @@ impl<'a> BaseClientBuilder<'a> {
raw_client: existing.raw_client.clone(),
raw_dangerous_client: existing.raw_dangerous_client.clone(),
timeout: existing.timeout,
credentials_cache: existing.credentials_cache.clone(),
}
}
@ -563,6 +580,7 @@ impl<'a> BaseClientBuilder<'a> {
match self.auth_integration {
AuthIntegration::Default => {
let mut auth_middleware = AuthMiddleware::new()
.with_cache_arc(self.credentials_cache.clone())
.with_base_client(base_client)
.with_indexes(self.indexes.clone())
.with_keyring(self.keyring.to_provider())
@ -574,6 +592,7 @@ impl<'a> BaseClientBuilder<'a> {
}
AuthIntegration::OnlyAuthenticated => {
let mut auth_middleware = AuthMiddleware::new()
.with_cache_arc(self.credentials_cache.clone())
.with_base_client(base_client)
.with_indexes(self.indexes.clone())
.with_keyring(self.keyring.to_provider())
@ -617,6 +636,8 @@ pub struct BaseClient {
allow_insecure_host: Vec<TrustedHost>,
/// The number of retries to attempt on transient errors.
retries: u32,
/// Global authentication cache for a uv invocation to share credentials across uv clients.
credentials_cache: Arc<CredentialsCache>,
}
#[derive(Debug, Clone, Copy)]
@ -668,6 +689,10 @@ impl BaseClient {
}
builder.build_with_max_retries(self.retries)
}
pub fn credentials_cache(&self) -> &CredentialsCache {
&self.credentials_cache
}
}
/// Wrapper around [`ClientWithMiddleware`] that manages redirects.

View File

@ -15,7 +15,7 @@ use tokio::sync::{Mutex, Semaphore};
use tracing::{Instrument, debug, info_span, instrument, trace, warn};
use url::Url;
use uv_auth::{Indexes, PyxTokenStore};
use uv_auth::{CredentialsCache, Indexes, PyxTokenStore};
use uv_cache::{Cache, CacheBucket, CacheEntry, WheelCache};
use uv_configuration::IndexStrategy;
use uv_configuration::KeyringProviderType;
@ -148,8 +148,30 @@ impl<'a> RegistryClientBuilder<'a> {
self
}
pub fn build(self) -> RegistryClient {
self.index_locations.cache_index_credentials();
/// Add all authenticated sources to the cache.
pub fn cache_index_credentials(&mut self) {
for index in self.index_locations.known_indexes() {
if let Some(credentials) = index.credentials() {
trace!(
"Read credentials for index {}",
index
.name
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| index.url.to_string())
);
if let Some(root_url) = index.root_url() {
self.base_client_builder
.store_credentials(&root_url, credentials.clone());
}
self.base_client_builder
.store_credentials(index.raw_url(), credentials);
}
}
}
pub fn build(mut self) -> RegistryClient {
self.cache_index_credentials();
let index_urls = self.index_locations.index_urls();
// Build a base client
@ -180,8 +202,8 @@ impl<'a> RegistryClientBuilder<'a> {
}
/// Share the underlying client between two different middleware configurations.
pub fn wrap_existing(self, existing: &BaseClient) -> RegistryClient {
self.index_locations.cache_index_credentials();
pub fn wrap_existing(mut self, existing: &BaseClient) -> RegistryClient {
self.cache_index_credentials();
let index_urls = self.index_locations.index_urls();
// Wrap in any relevant middleware and handle connectivity.
@ -269,6 +291,10 @@ impl RegistryClient {
self.timeout
}
pub fn credentials_cache(&self) -> &CredentialsCache {
self.client.uncached().credentials_cache()
}
/// Return the appropriate index URLs for the given [`PackageName`].
fn index_urls_for(
&self,

View File

@ -492,6 +492,7 @@ impl BuildContext for BuildDispatch<'_> {
environment_variables,
build_output,
self.concurrency.builds,
self.client.credentials_cache(),
self.preview,
)
.boxed_local()

View File

@ -8,7 +8,6 @@ use std::sync::{Arc, LazyLock, RwLock};
use itertools::Either;
use rustc_hash::{FxHashMap, FxHashSet};
use thiserror::Error;
use tracing::trace;
use url::{ParseError, Url};
use uv_auth::RealmRef;
use uv_cache_key::CanonicalUrl;
@ -440,26 +439,6 @@ impl<'a> IndexLocations {
}
}
/// Add all authenticated sources to the cache.
pub fn cache_index_credentials(&self) {
for index in self.known_indexes() {
if let Some(credentials) = index.credentials() {
trace!(
"Read credentials for index {}",
index
.name
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| index.url.to_string())
);
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
uv_auth::store_credentials(index.raw_url(), credentials);
}
}
}
/// Return the Simple API cache control header for an [`IndexUrl`], if configured.
pub fn simple_api_cache_control_for(&self, url: &IndexUrl) -> Option<&str> {
for index in &self.indexes {

View File

@ -554,7 +554,11 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
pyproject_toml: &PyProjectToml,
) -> Result<Option<RequiresDist>, Error> {
self.builder
.source_tree_requires_dist(path, pyproject_toml)
.source_tree_requires_dist(
path,
pyproject_toml,
self.client.unmanaged.credentials_cache(),
)
.await
}

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use std::path::Path;
use uv_auth::CredentialsCache;
use uv_configuration::SourceStrategy;
use uv_distribution_types::{
ExtraBuildRequirement, ExtraBuildRequires, IndexLocations, Requirement,
@ -42,6 +42,7 @@ impl BuildRequires {
locations: &IndexLocations,
sources: SourceStrategy,
cache: &WorkspaceCache,
credentials_cache: &CredentialsCache,
) -> Result<Self, MetadataError> {
let discovery = match sources {
SourceStrategy::Enabled => DiscoveryOptions::default(),
@ -56,7 +57,13 @@ impl BuildRequires {
return Ok(Self::from_metadata23(metadata));
};
Self::from_project_workspace(metadata, &project_workspace, locations, sources)
Self::from_project_workspace(
metadata,
&project_workspace,
locations,
sources,
credentials_cache,
)
}
/// Lower the `build-system.requires` field from a `pyproject.toml` file.
@ -65,6 +72,7 @@ impl BuildRequires {
project_workspace: &ProjectWorkspace,
locations: &IndexLocations,
source_strategy: SourceStrategy,
credentials_cache: &CredentialsCache,
) -> Result<Self, MetadataError> {
// Collect any `tool.uv.index` entries.
let empty = vec![];
@ -114,6 +122,7 @@ impl BuildRequires {
locations,
project_workspace.workspace(),
None,
credentials_cache,
)
.map(move |requirement| match requirement {
Ok(requirement) => Ok(requirement.into_inner()),
@ -139,6 +148,7 @@ impl BuildRequires {
workspace: &Workspace,
locations: &IndexLocations,
source_strategy: SourceStrategy,
credentials_cache: &CredentialsCache,
) -> Result<Self, MetadataError> {
// Collect any `tool.uv.index` entries.
let empty = vec![];
@ -186,6 +196,7 @@ impl BuildRequires {
locations,
workspace,
None,
credentials_cache,
)
.map(move |requirement| match requirement {
Ok(requirement) => Ok(requirement.into_inner()),
@ -225,6 +236,7 @@ impl LoweredExtraBuildDependencies {
workspace: &Workspace,
index_locations: &IndexLocations,
source_strategy: SourceStrategy,
credentials_cache: &CredentialsCache,
) -> Result<Self, MetadataError> {
match source_strategy {
SourceStrategy::Enabled => {
@ -271,6 +283,7 @@ impl LoweredExtraBuildDependencies {
index_locations,
workspace,
None,
credentials_cache,
)
.map(move |requirement| {
match requirement {

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use uv_auth::CredentialsCache;
use uv_configuration::SourceStrategy;
use uv_distribution_types::{IndexLocations, Requirement};
use uv_normalize::{GroupName, PackageName};
@ -57,6 +57,7 @@ impl SourcedDependencyGroups {
locations: &IndexLocations,
source_strategy: SourceStrategy,
cache: &WorkspaceCache,
credentials_cache: &CredentialsCache,
) -> Result<Self, MetadataError> {
// If the `pyproject.toml` doesn't exist, fail early.
if !pyproject_path.is_file() {
@ -156,6 +157,7 @@ impl SourcedDependencyGroups {
locations,
project.workspace(),
git_member,
credentials_cache,
)
.map(move |requirement| match requirement {
Ok(requirement) => Ok(requirement.into_inner()),

View File

@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use either::Either;
use thiserror::Error;
use uv_auth::CredentialsCache;
use uv_distribution_filename::DistExtension;
use uv_distribution_types::{
Index, IndexLocations, IndexMetadata, IndexName, Origin, Requirement, RequirementSource,
@ -44,6 +44,7 @@ impl LoweredRequirement {
locations: &'data IndexLocations,
workspace: &'data Workspace,
git_member: Option<&'data GitWorkspaceMember<'data>>,
credentials_cache: &'data CredentialsCache,
) -> impl Iterator<Item = Result<Self, LoweringError>> + use<'data> + 'data {
// Identify the source from the `tool.uv.sources` table.
let (sources, origin) = if let Some(source) = project_sources.get(&requirement.name) {
@ -231,7 +232,7 @@ impl LoweredRequirement {
));
};
if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials);
credentials_cache.store_credentials(index.raw_url(), credentials);
}
let index = IndexMetadata {
url: index.url.clone(),
@ -358,6 +359,7 @@ impl LoweredRequirement {
sources: &'data BTreeMap<PackageName, Sources>,
indexes: &'data [Index],
locations: &'data IndexLocations,
credentials_cache: &'data CredentialsCache,
) -> impl Iterator<Item = Result<Self, LoweringError>> + 'data {
let source = sources.get(&requirement.name).cloned();
@ -466,7 +468,7 @@ impl LoweredRequirement {
));
};
if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials);
credentials_cache.store_credentials(index.raw_url(), credentials);
}
let index = IndexMetadata {
url: index.url.clone(),

View File

@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use thiserror::Error;
use uv_auth::CredentialsCache;
use uv_configuration::SourceStrategy;
use uv_distribution_types::{GitSourceUrl, IndexLocations, Requirement};
use uv_normalize::{ExtraName, GroupName, PackageName};
@ -91,6 +91,7 @@ impl Metadata {
locations: &IndexLocations,
sources: SourceStrategy,
cache: &WorkspaceCache,
credentials_cache: &CredentialsCache,
) -> Result<Self, MetadataError> {
// Lower the requirements.
let requires_dist = uv_pypi_types::RequiresDist {
@ -112,6 +113,7 @@ impl Metadata {
locations,
sources,
cache,
credentials_cache,
)
.await?;

View File

@ -3,7 +3,7 @@ use std::path::Path;
use std::slice;
use rustc_hash::FxHashSet;
use uv_auth::CredentialsCache;
use uv_configuration::SourceStrategy;
use uv_distribution_types::{IndexLocations, Requirement};
use uv_normalize::{ExtraName, GroupName, PackageName};
@ -48,6 +48,7 @@ impl RequiresDist {
locations: &IndexLocations,
sources: SourceStrategy,
cache: &WorkspaceCache,
credentials_cache: &CredentialsCache,
) -> Result<Self, MetadataError> {
let discovery = DiscoveryOptions {
stop_discovery_at: git_member.map(|git_member| {
@ -69,7 +70,14 @@ impl RequiresDist {
return Ok(Self::from_metadata23(metadata));
};
Self::from_project_workspace(metadata, &project_workspace, git_member, locations, sources)
Self::from_project_workspace(
metadata,
&project_workspace,
git_member,
locations,
sources,
credentials_cache,
)
}
fn from_project_workspace(
@ -78,6 +86,7 @@ impl RequiresDist {
git_member: Option<&GitWorkspaceMember<'_>>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
credentials_cache: &CredentialsCache,
) -> Result<Self, MetadataError> {
// Collect any `tool.uv.index` entries.
let empty = vec![];
@ -140,6 +149,7 @@ impl RequiresDist {
locations,
project_workspace.workspace(),
git_member,
credentials_cache,
)
.map(
move |requirement| match requirement {
@ -182,6 +192,7 @@ impl RequiresDist {
locations,
project_workspace.workspace(),
git_member,
credentials_cache,
)
.map(move |requirement| match requirement {
Ok(requirement) => Ok(requirement.into_inner()),
@ -432,7 +443,7 @@ mod test {
use anyhow::Context;
use indoc::indoc;
use insta::assert_snapshot;
use uv_auth::CredentialsCache;
use uv_configuration::SourceStrategy;
use uv_distribution_types::IndexLocations;
use uv_normalize::PackageName;
@ -468,6 +479,7 @@ mod test {
None,
&IndexLocations::default(),
SourceStrategy::default(),
&CredentialsCache::new(),
)?)
}

View File

@ -20,9 +20,9 @@ use reqwest::{Response, StatusCode};
use tokio_util::compat::FuturesAsyncReadCompatExt;
use tracing::{Instrument, debug, info_span, instrument, warn};
use url::Url;
use uv_redacted::DisplaySafeUrl;
use zip::ZipArchive;
use uv_auth::CredentialsCache;
use uv_cache::{Cache, CacheBucket, CacheEntry, CacheShard, Removal, WheelCache};
use uv_cache_info::CacheInfo;
use uv_client::{
@ -44,6 +44,7 @@ use uv_normalize::PackageName;
use uv_pep440::{Version, release_specifiers_to_ranges};
use uv_platform_tags::Tags;
use uv_pypi_types::{HashAlgorithm, HashDigest, HashDigests, PyProjectToml, ResolutionMetadata};
use uv_redacted::DisplaySafeUrl;
use uv_types::{BuildContext, BuildKey, BuildStack, SourceBuildTrait};
use uv_workspace::pyproject::ToolUvSources;
@ -326,12 +327,23 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.await?
}
BuildableSource::Dist(SourceDist::Git(dist)) => {
self.git_metadata(source, &GitSourceUrl::from(dist), hashes, client)
self.git_metadata(
source,
&GitSourceUrl::from(dist),
hashes,
client,
client.unmanaged.credentials_cache(),
)
.boxed_local()
.await?
}
BuildableSource::Dist(SourceDist::Directory(dist)) => {
self.source_tree_metadata(source, &DirectorySourceUrl::from(dist), hashes)
self.source_tree_metadata(
source,
&DirectorySourceUrl::from(dist),
hashes,
client.unmanaged.credentials_cache(),
)
.boxed_local()
.await?
}
@ -365,12 +377,23 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.await?
}
BuildableSource::Url(SourceUrl::Git(resource)) => {
self.git_metadata(source, resource, hashes, client)
self.git_metadata(
source,
resource,
hashes,
client,
client.unmanaged.credentials_cache(),
)
.boxed_local()
.await?
}
BuildableSource::Url(SourceUrl::Directory(resource)) => {
self.source_tree_metadata(source, resource, hashes)
self.source_tree_metadata(
source,
resource,
hashes,
client.unmanaged.credentials_cache(),
)
.boxed_local()
.await?
}
@ -1249,6 +1272,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
source: &BuildableSource<'_>,
resource: &DirectorySourceUrl<'_>,
hashes: HashPolicy<'_>,
credentials_cache: &CredentialsCache,
) -> Result<ArchiveMetadata, Error> {
// Before running the build, check that the hashes match.
if hashes.is_validate() {
@ -1266,6 +1290,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
self.build_context.locations(),
self.build_context.sources(),
self.build_context.workspace_cache(),
credentials_cache,
)
.await?,
));
@ -1319,6 +1344,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
self.build_context.locations(),
self.build_context.sources(),
self.build_context.workspace_cache(),
credentials_cache,
)
.await?,
));
@ -1368,6 +1394,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
self.build_context.locations(),
self.build_context.sources(),
self.build_context.workspace_cache(),
credentials_cache,
)
.await?,
));
@ -1429,6 +1456,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
self.build_context.locations(),
self.build_context.sources(),
self.build_context.workspace_cache(),
credentials_cache,
)
.await?,
))
@ -1491,6 +1519,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
&self,
path: &Path,
pyproject_toml: &PyProjectToml,
credentials_cache: &CredentialsCache,
) -> Result<Option<RequiresDist>, Error> {
// Attempt to read static metadata from the `pyproject.toml`.
match uv_pypi_types::RequiresDist::from_pyproject_toml(pyproject_toml.clone()) {
@ -1503,6 +1532,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
self.build_context.locations(),
self.build_context.sources(),
self.build_context.workspace_cache(),
credentials_cache,
)
.await?;
Ok(Some(requires_dist))
@ -1662,6 +1692,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
resource: &GitSourceUrl<'_>,
hashes: HashPolicy<'_>,
client: &ManagedClient<'_>,
credentials_cache: &CredentialsCache,
) -> Result<ArchiveMetadata, Error> {
// Before running the build, check that the hashes match.
if hashes.is_validate() {
@ -1823,6 +1854,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
self.build_context.locations(),
self.build_context.sources(),
self.build_context.workspace_cache(),
credentials_cache,
)
.await?,
));
@ -1856,6 +1888,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
self.build_context.locations(),
self.build_context.sources(),
self.build_context.workspace_cache(),
credentials_cache,
)
.await?,
));
@ -1908,6 +1941,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
self.build_context.locations(),
self.build_context.sources(),
self.build_context.workspace_cache(),
credentials_cache,
)
.await?,
));
@ -1969,6 +2003,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
self.build_context.locations(),
self.build_context.sources(),
self.build_context.workspace_cache(),
credentials_cache,
)
.await?,
))

View File

@ -1,4 +1,5 @@
use std::borrow::Borrow;
use std::fmt::{Debug, Formatter};
use std::hash::{BuildHasher, Hash, RandomState};
use std::pin::pin;
use std::sync::Arc;
@ -19,6 +20,12 @@ pub struct OnceMap<K, V, S = RandomState> {
items: DashMap<K, Value<V>, S>,
}
impl<K: Eq + Hash + Debug, V: Debug, S: BuildHasher + Clone> Debug for OnceMap<K, V, S> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.items, f)
}
}
impl<K: Eq + Hash, V: Clone, H: BuildHasher + Clone> OnceMap<K, V, H> {
/// Create a [`OnceMap`] with the specified hasher.
pub fn with_hasher(hasher: H) -> Self {
@ -142,6 +149,7 @@ where
}
}
#[derive(Debug)]
enum Value<V> {
Waiting(Arc<Notify>),
Filled(V),

View File

@ -1445,7 +1445,7 @@ impl Lock {
Ok(SatisfiesResult::Satisfied)
}
/// Convert the [`Lock`] to a [`Resolution`] using the given marker environment, tags, and root.
/// Check whether the lock matches the project structure, requirements and configuration.
pub async fn satisfies<Context: BuildContext>(
&self,
root: &Path,

View File

@ -217,6 +217,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
build_dispatch.locations(),
build_dispatch.sources(),
build_dispatch.workspace_cache(),
client.credentials_cache(),
)
.await
.with_context(|| {

View File

@ -442,6 +442,7 @@ pub(crate) async fn add(
project.workspace(),
&settings.resolver.index_locations,
settings.resolver.sources,
client.credentials_cache(),
)?
} else {
LoweredExtraBuildDependencies::from_non_lowered(

View File

@ -494,14 +494,39 @@ async fn do_lock(
let source_trees = vec![];
// If necessary, lower the overrides and constraints.
let requirements = target.lower(requirements, index_locations, *sources)?;
let overrides = target.lower(overrides, index_locations, *sources)?;
let constraints = target.lower(constraints, index_locations, *sources)?;
let build_constraints = target.lower(build_constraints, index_locations, *sources)?;
let requirements = target.lower(
requirements,
index_locations,
*sources,
client_builder.credentials_cache(),
)?;
let overrides = target.lower(
overrides,
index_locations,
*sources,
client_builder.credentials_cache(),
)?;
let constraints = target.lower(
constraints,
index_locations,
*sources,
client_builder.credentials_cache(),
)?;
let build_constraints = target.lower(
build_constraints,
index_locations,
*sources,
client_builder.credentials_cache(),
)?;
let dependency_groups = dependency_groups
.into_iter()
.map(|(name, group)| {
let requirements = target.lower(group.requirements, index_locations, *sources)?;
let requirements = target.lower(
group.requirements,
index_locations,
*sources,
client_builder.credentials_cache(),
)?;
Ok((name, requirements))
})
.collect::<Result<BTreeMap<_, _>, ProjectError>>()?;
@ -655,9 +680,9 @@ async fn do_lock(
for index in target.indexes() {
if let Some(credentials) = index.credentials() {
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
client_builder.store_credentials(&root_url, credentials.clone());
}
uv_auth::store_credentials(index.raw_url(), credentials);
client_builder.store_credentials(index.raw_url(), credentials);
}
}
@ -716,10 +741,11 @@ async fn do_lock(
workspace,
index_locations,
*sources,
client.credentials_cache(),
)?,
LockTarget::Script(script) => {
// Try to get extra build dependencies from the script metadata
script_extra_build_requires((*script).into(), settings)?
script_extra_build_requires((*script).into(), settings, client.credentials_cache())?
}
}
.into_inner();

View File

@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use itertools::Either;
use uv_auth::CredentialsCache;
use uv_configuration::{DependencyGroupsWithDefaults, SourceStrategy};
use uv_distribution::LoweredRequirement;
use uv_distribution_types::{Index, IndexLocations, Requirement, RequiresPython};
@ -343,6 +343,7 @@ impl<'lock> LockTarget<'lock> {
requirements: Vec<uv_pep508::Requirement<VerbatimParsedUrl>>,
locations: &IndexLocations,
sources: SourceStrategy,
credentials_cache: &CredentialsCache,
) -> Result<Vec<Requirement>, uv_distribution::MetadataError> {
match self {
Self::Workspace(workspace) => {
@ -362,6 +363,7 @@ impl<'lock> LockTarget<'lock> {
workspace,
locations,
sources,
credentials_cache,
)?;
Ok(metadata
@ -407,6 +409,7 @@ impl<'lock> LockTarget<'lock> {
sources,
indexes,
locations,
credentials_cache,
)
.map(move |requirement| match requirement {
Ok(requirement) => Ok(requirement.into_inner()),

View File

@ -7,7 +7,7 @@ use std::sync::Arc;
use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::{debug, trace, warn};
use uv_auth::CredentialsCache;
use uv_cache::{Cache, CacheBucket};
use uv_cache_key::cache_digest;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
@ -2569,6 +2569,7 @@ pub(crate) fn detect_conflicts(
pub(crate) fn script_specification(
script: Pep723ItemRef<'_>,
settings: &ResolverSettings,
credentials_cache: &CredentialsCache,
) -> Result<Option<RequirementsSpecification>, ProjectError> {
let Some(dependencies) = script.metadata().dependencies.as_ref() else {
return Ok(None);
@ -2588,6 +2589,7 @@ pub(crate) fn script_specification(
script_sources,
script_indexes,
&settings.index_locations,
credentials_cache,
)
.map_ok(LoweredRequirement::into_inner)
})
@ -2608,6 +2610,7 @@ pub(crate) fn script_specification(
script_sources,
script_indexes,
&settings.index_locations,
credentials_cache,
)
.map_ok(LoweredRequirement::into_inner)
})
@ -2628,6 +2631,7 @@ pub(crate) fn script_specification(
script_sources,
script_indexes,
&settings.index_locations,
credentials_cache,
)
.map_ok(LoweredRequirement::into_inner)
})
@ -2645,6 +2649,7 @@ pub(crate) fn script_specification(
pub(crate) fn script_extra_build_requires(
script: Pep723ItemRef<'_>,
settings: &ResolverSettings,
credentials_cache: &CredentialsCache,
) -> Result<LoweredExtraBuildDependencies, ProjectError> {
let script_dir = script.directory()?;
let script_indexes = script.indexes(settings.sources);
@ -2677,6 +2682,7 @@ pub(crate) fn script_extra_build_requires(
script_sources,
script_indexes,
&settings.index_locations,
credentials_cache,
)
.map_ok(move |requirement| ExtraBuildRequirement {
requirement: requirement.into_inner(),

View File

@ -372,9 +372,17 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
}
// Install the script requirements, if necessary. Otherwise, use an isolated environment.
if let Some(spec) = script_specification((&script).into(), &settings.resolver)? {
let script_extra_build_requires =
script_extra_build_requires((&script).into(), &settings.resolver)?.into_inner();
if let Some(spec) = script_specification(
(&script).into(),
&settings.resolver,
client_builder.credentials_cache(),
)? {
let script_extra_build_requires = script_extra_build_requires(
(&script).into(),
&settings.resolver,
client_builder.credentials_cache(),
)?
.into_inner();
let environment = ScriptEnvironment::get_or_init(
(&script).into(),
python.as_deref().map(PythonRequest::parse),

View File

@ -239,9 +239,18 @@ pub(crate) async fn sync(
}
// Parse the requirements from the script.
let spec = script_specification(script.into(), &settings.resolver)?.unwrap_or_default();
let script_extra_build_requires =
script_extra_build_requires(script.into(), &settings.resolver)?.into_inner();
let spec = script_specification(
script.into(),
&settings.resolver,
client_builder.credentials_cache(),
)?
.unwrap_or_default();
let script_extra_build_requires = script_extra_build_requires(
script.into(),
&settings.resolver,
client_builder.credentials_cache(),
)?
.into_inner();
// Parse the build constraints from the script.
let build_constraints = script
@ -645,6 +654,7 @@ pub(super) async fn do_sync(
workspace,
index_locations,
sources,
client_builder.credentials_cache(),
)?
}
InstallTarget::Script { script, .. } => {
@ -668,7 +678,11 @@ pub(super) async fn do_sync(
sources,
upgrade: Upgrade::default(),
};
script_extra_build_requires((*script).into(), &resolver_settings)?
script_extra_build_requires(
(*script).into(),
&resolver_settings,
client_builder.credentials_cache(),
)?
}
}
.into_inner();
@ -744,7 +758,7 @@ pub(super) async fn do_sync(
let extra_build_requires = extra_build_requires.match_runtime(&resolution)?;
// Populate credentials from the target.
store_credentials_from_target(target);
store_credentials_from_target(target, &client_builder);
// Initialize the registry client.
let client = RegistryClientBuilder::new(client_builder, cache.clone())
@ -928,14 +942,14 @@ fn apply_editable_mode(resolution: Resolution, editable: Option<EditableMode>) -
///
/// These credentials can come from any of `tool.uv.sources`, `tool.uv.dev-dependencies`,
/// `project.dependencies`, and `project.optional-dependencies`.
fn store_credentials_from_target(target: InstallTarget<'_>) {
fn store_credentials_from_target(target: InstallTarget<'_>, client_builder: &BaseClientBuilder) {
// Iterate over any indexes in the target.
for index in target.indexes() {
if let Some(credentials) = index.credentials() {
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
client_builder.store_credentials(&root_url, credentials.clone());
}
uv_auth::store_credentials(index.raw_url(), credentials);
client_builder.store_credentials(index.raw_url(), credentials);
}
}
@ -946,7 +960,7 @@ fn store_credentials_from_target(target: InstallTarget<'_>) {
uv_git::store_credentials_from_url(git);
}
Source::Url { url, .. } => {
uv_auth::store_credentials_from_url(url);
client_builder.store_credentials_from_url(url);
}
_ => {}
}
@ -962,7 +976,7 @@ fn store_credentials_from_target(target: InstallTarget<'_>) {
uv_git::store_credentials_from_url(url.repository());
}
ParsedUrl::Archive(ParsedArchiveUrl { url, .. }) => {
uv_auth::store_credentials_from_url(url);
client_builder.store_credentials_from_url(url);
}
_ => {}
}