Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions crates/pixi_core/src/lock_file/resolve/build_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,12 @@ pub struct LazyBuildDispatchDependencies {
extra_build_requires: OnceCell<ExtraBuildRequires>,
/// Package-specific configuration settings
package_config_settings: OnceCell<PackageConfigSettings>,
/// The last initialization error that occurred
last_error: OnceCell<LazyBuildDispatchError>,
}

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
enum LazyBuildDispatchError {
pub enum LazyBuildDispatchError {
#[error(
"installation of conda environment is required to solve PyPI source dependencies but `--no-install` flag has been set"
)]
Expand Down Expand Up @@ -268,6 +270,11 @@ impl IsBuildBackendError for LazyBuildDispatchError {
}

impl<'a> LazyBuildDispatch<'a> {
/// Get the last initialization error if available
pub fn last_initialization_error(&self) -> Option<&LazyBuildDispatchError> {
self.lazy_deps.last_error.get()
}

/// Create a new `PixiBuildDispatch` instance.
#[allow(clippy::too_many_arguments)]
pub fn new(
Expand Down Expand Up @@ -414,11 +421,14 @@ impl BuildContext for LazyBuildDispatch<'_> {
//
// Even though initialize does not initialize twice, we check it beforehand
// because the initialization takes time
self.get_or_try_init()
.await
.expect("could not initialize build dispatch correctly")
.interpreter()
.await
match self.get_or_try_init().await {
Ok(dispatch) => dispatch.interpreter().await,
Err(e) => {
// Store the error for later retrieval
let _ = self.lazy_deps.last_error.set(e);
panic!("could not initialize build dispatch correctly")
}
}
}

fn cache(&self) -> &uv_cache::Cache {
Expand Down
95 changes: 71 additions & 24 deletions crates/pixi_core/src/lock_file/resolve/pypi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ use std::{
collections::{HashMap, HashSet},
iter::once,
ops::Deref,
panic,
path::{Path, PathBuf},
rc::Rc,
str::FromStr,
sync::Arc,
};

use futures::FutureExt;

use chrono::{DateTime, Utc};
use indexmap::{IndexMap, IndexSet};
use indicatif::ProgressBar;
Expand Down Expand Up @@ -212,6 +215,10 @@ pub enum SolveError {
},
#[error("failed to resolve pypi dependencies")]
Other(#[from] ResolveError),
#[error("build dispatch initialization failed: {message}")]
BuildDispatchPanic { message: String },
#[error("unexpected panic during PyPI resolution: {message}")]
GeneralPanic { message: String },
}

/// Creates a custom `SolveError` from a `ResolveError`.
Expand Down Expand Up @@ -626,30 +633,70 @@ pub async fn resolve_pypi(
// We need a new in-memory index for the resolver so that it does not conflict
// with the build dispatch one. As we have noted in the comment above.
let resolver_in_memory_index = InMemoryIndex::default();
let resolution = Resolver::new_custom_io(
manifest,
options,
&context.hash_strategy,
resolver_env,
&marker_environment,
Some(tags),
&PythonRequirement::from_marker_environment(&marker_environment, requires_python.clone()),
Conflicts::default(),
&resolver_in_memory_index,
context.shared_state.git(),
&context.capabilities,
&index_locations,
provider,
EmptyInstalledPackages,
)
.into_diagnostic()
.context("failed to resolve pypi dependencies")?
.with_reporter(UvReporter::new_arc(
UvReporterOptions::new().with_existing(pb.clone()),
))
.resolve()
.await
.map_err(|e| create_solve_error(e, &conda_python_packages))?;

// Wrap the resolution in panic catching to handle conda prefix initialization failures
let resolution_future = panic::AssertUnwindSafe(async {
let resolver = Resolver::new_custom_io(
manifest,
options,
&context.hash_strategy,
resolver_env,
&marker_environment,
Some(tags),
&PythonRequirement::from_marker_environment(
&marker_environment,
requires_python.clone(),
),
Conflicts::default(),
&resolver_in_memory_index,
context.shared_state.git(),
&context.capabilities,
&index_locations,
provider,
EmptyInstalledPackages,
)
.into_diagnostic()
.context("failed to resolve pypi dependencies")
.map_err(|e| SolveError::GeneralPanic {
message: format!("Failed to create resolver: {}", e),
})?
.with_reporter(UvReporter::new_arc(
UvReporterOptions::new().with_existing(pb.clone()),
));

resolver
.resolve()
.await
.map_err(|e| create_solve_error(e, &conda_python_packages))
});

// We try to distinguis between build dispatch panics and any other panics that occur
let resolution = match resolution_future.catch_unwind().await {
Ok(result) => result?,
Err(panic_payload) => {
// Try to get the stored initialization error from the lazy build dispatch
if let Some(stored_error) = lazy_build_dispatch.last_initialization_error() {
return Err(SolveError::BuildDispatchPanic {
message: format!("{}", stored_error),
}
.into());
} else {
// Use the original panic message for general panics
let panic_message = if let Some(s) = panic_payload.downcast_ref::<String>() {
s.clone()
} else if let Some(&s) = panic_payload.downcast_ref::<&str>() {
s.to_string()
} else {
"unknown panic occurred during PyPI resolution".to_string()
};

return Err(SolveError::GeneralPanic {
message: panic_message,
}
.into());
}
}
};
let resolution = Resolution::from(resolution);

// Print the overridden package requests
Expand Down
Loading