diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index 399525cbd58be..7944ec3f55cd4 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -480,33 +480,35 @@ impl<'a> BaseClientBuilder<'a> { // Checks for the presence of `SSL_CERT_FILE`. // Certificate loading support is delegated to `rustls-native-certs`. // See https://github.com/rustls/rustls-native-certs/blob/813790a297ad4399efe70a8e5264ca1b420acbec/src/lib.rs#L118-L125 - let ssl_cert_file_exists = env::var_os(EnvVars::SSL_CERT_FILE).is_some_and(|path| { - let path = Path::new(&path); - match path.metadata() { - Ok(metadata) if metadata.is_file() => true, - Ok(_) => { - warn_user_once!( - "Ignoring invalid `SSL_CERT_FILE`. Path is not a file: {}.", - path.simplified_display().cyan() - ); - false - } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - warn_user_once!( - "Ignoring invalid `SSL_CERT_FILE`. Path does not exist: {}.", - path.simplified_display().cyan() - ); - false - } - Err(err) => { - warn_user_once!( - "Ignoring invalid `SSL_CERT_FILE`. Path is not accessible: {} ({err}).", - path.simplified_display().cyan() - ); - false + let ssl_cert_file_exists = env::var_os(EnvVars::SSL_CERT_FILE) + .filter(|v| !v.is_empty()) + .is_some_and(|path| { + let path = Path::new(&path); + match path.metadata() { + Ok(metadata) if metadata.is_file() => true, + Ok(_) => { + warn_user_once!( + "Ignoring invalid `SSL_CERT_FILE`. Path is not a file: {}.", + path.simplified_display().cyan() + ); + false + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + warn_user_once!( + "Ignoring invalid `SSL_CERT_FILE`. Path does not exist: {}.", + path.simplified_display().cyan() + ); + false + } + Err(err) => { + warn_user_once!( + "Ignoring invalid `SSL_CERT_FILE`. Path is not accessible: {} ({err}).", + path.simplified_display().cyan() + ); + false + } } - } - }); + }); // Checks for the presence of `SSL_CERT_DIR`. // Certificate loading support is delegated to `rustls-native-certs`. diff --git a/crates/uv-client/tests/it/ssl_certs.rs b/crates/uv-client/tests/it/ssl_certs.rs index 2b4f8df9b303d..3fa252946ca7a 100644 --- a/crates/uv-client/tests/it/ssl_certs.rs +++ b/crates/uv-client/tests/it/ssl_certs.rs @@ -76,6 +76,63 @@ async fn ssl_env_vars() -> Result<()> { ), )?; + // ** Set SSL_CERT_FILE to empty value + // ** Then verify it's treated like unset (i.e. we still fail against a self-signed cert) + + unsafe { + std::env::set_var(EnvVars::SSL_CERT_FILE, ""); + } + let (server_task, addr) = start_https_user_agent_server(&standalone_server_cert).await?; + let url = DisplaySafeUrl::from_str(&format!("https://{addr}"))?; + let cache = Cache::temp()?.init().await?; + let client = + RegistryClientBuilder::new(BaseClientBuilder::default().no_retry_delay(true), cache) + .build(); + let res = client + .cached_client() + .uncached() + .for_host(&url) + .get(Url::from(url)) + .send() + .await; + unsafe { + std::env::remove_var(EnvVars::SSL_CERT_FILE); + } + + // Validate the client error + let Some(reqwest_middleware::Error::Middleware(middleware_error)) = res.err() else { + panic!("expected middleware error"); + }; + let reqwest_error = middleware_error + .chain() + .find_map(|err| { + err.downcast_ref::().map(|err| { + if let reqwest_middleware::Error::Reqwest(inner) = err { + inner + } else { + panic!("expected reqwest error") + } + }) + }) + .expect("expected reqwest error"); + assert!(reqwest_error.is_connect()); + + // Validate the server error + let server_res = server_task.await?; + let expected_err = if let Err(anyhow_err) = server_res + && let Some(io_err) = anyhow_err.downcast_ref::() + && let Some(wrapped_err) = io_err.get_ref() + && let Some(tls_err) = wrapped_err.downcast_ref::() + && matches!( + tls_err, + rustls::Error::AlertReceived(AlertDescription::UnknownCA) + ) { + true + } else { + false + }; + assert!(expected_err); + // ** Set SSL_CERT_FILE to non-existent location // ** Then verify our request fails to establish a connection diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 645e50943ce2f..cc0c16b7135bd 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -147,8 +147,10 @@ async fn run(mut cli: Cli) -> Result { // If found, this file is combined with the user configuration file. // 3. The nearest configuration file (`uv.toml` or `pyproject.toml`) in the directory tree, // starting from the current directory. + let config_file = cli.top_level.config_file.clone(); + let workspace_cache = WorkspaceCache::default(); - let filesystem = if let Some(config_file) = cli.top_level.config_file.as_ref() { + let filesystem = if let Some(config_file) = config_file.as_ref() { if config_file .file_name() .is_some_and(|file_name| file_name == "pyproject.toml") @@ -362,6 +364,12 @@ async fn run(mut cli: Cli) -> Result { )?; debug!("uv {}", uv_cli::version::uv_self_version()); + if let Some(config_file) = config_file.as_ref() { + debug!( + "Using `--config-file` / `UV_CONFIG_FILE` at `{}`, ignoring discovered project, user, and system configuration files", + config_file.user_display() + ); + } if globals.preview.all_enabled() { debug!("All preview features are enabled"); } else if globals.preview.any_enabled() { diff --git a/crates/uv/tests/it/cache_clean.rs b/crates/uv/tests/it/cache_clean.rs index 4cef825a09ec5..695b7a0dcc644 100644 --- a/crates/uv/tests/it/cache_clean.rs +++ b/crates/uv/tests/it/cache_clean.rs @@ -36,6 +36,31 @@ fn clean_all() -> Result<()> { Ok(()) } +#[test] +fn clean_all_with_config_file_logs_override() -> Result<()> { + let context = uv_test::test_context!("3.12").with_filtered_counts(); + + let config = context.temp_dir.child("uv.toml"); + config.write_str("")?; + + uv_snapshot!(context.filters(), context.clean() + .arg("--verbose") + .arg("--config-file") + .arg(config.path()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + DEBUG uv [VERSION] ([COMMIT] DATE) + DEBUG Using `--config-file` / `UV_CONFIG_FILE` at `uv.toml`, ignoring discovered project, user, and system configuration files + Clearing cache at: [CACHE_DIR]/ + Removed [N] files ([SIZE]) + "); + + Ok(()) +} + #[tokio::test] async fn clean_force() -> Result<()> { let context = uv_test::test_context!("3.12").with_filtered_counts();