From 9210008283b37a6a08f625bb330aa9c08875045e Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Mon, 25 Mar 2024 10:03:49 +0100 Subject: [PATCH] feat: add error for unsupported pypi dependencies --- src/lock_file/outdated.rs | 1 + src/lock_file/update.rs | 58 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/lock_file/outdated.rs b/src/lock_file/outdated.rs index df7c977d1..92bc48a50 100644 --- a/src/lock_file/outdated.rs +++ b/src/lock_file/outdated.rs @@ -10,6 +10,7 @@ use std::collections::{HashMap, HashSet}; /// /// Use the [`OutdatedEnvironments::from_project_and_lock_file`] to create an instance of this /// struct by examining the project and lock-file and finding any mismatches. +#[derive(Debug)] pub struct OutdatedEnvironments<'p> { /// The conda environments that are considered out of date with the lock-file. pub conda: HashMap, HashSet>, diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 1d4c152a0..9e09f6bb4 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -22,7 +22,7 @@ use futures::{future::Either, stream::FuturesUnordered, FutureExt, StreamExt, Tr use indexmap::{IndexMap, IndexSet}; use indicatif::ProgressBar; use itertools::Itertools; -use miette::{IntoDiagnostic, WrapErr}; +use miette::{IntoDiagnostic, LabeledSpan, MietteDiagnostic, WrapErr}; use rattler::package_cache::PackageCache; use rattler_conda_types::{Channel, MatchSpec, PackageName, Platform, RepoDataRecord}; use rattler_lock::{LockFile, PypiPackageData, PypiPackageEnvironmentData}; @@ -732,7 +732,7 @@ pub async fn ensure_up_to_date_lock_file( // platform for this group. let records_future = context .get_latest_group_repodata_records(&group, current_platform) - .expect("conda records should be available now or in the future"); + .ok_or_else(|| make_unsupported_pypi_platform_error(environment, current_platform))?; // Spawn a task to instantiate the environment let environment_name = environment.name().clone(); @@ -1073,6 +1073,60 @@ pub async fn ensure_up_to_date_lock_file( }) } +/// Constructs an error that indicates that the current platform cannot solve pypi dependencies because there is no python interpreter available for the current platform. +fn make_unsupported_pypi_platform_error( + environment: &Environment<'_>, + current_platform: Platform, +) -> miette::Report { + let grouped_environment = GroupedEnvironment::from(environment.clone()); + + // Construct a diagnostic that explains that the current platform is not supported. + let mut diag = MietteDiagnostic::new(format!("Unable to solve pypi dependencies for the {} {} because no compatible python interpreter can be installed for the current platform", grouped_environment.name().fancy_display(), match &grouped_environment { + GroupedEnvironment::Group(_) => "solve group", + GroupedEnvironment::Environment(_) => "environment" + })); + + let mut labels = Vec::new(); + + // Add a reference to the set of platforms that are supported by the project. + let project_platforms = &environment.project().manifest.parsed.project.platforms; + if let Some(span) = project_platforms.span.clone() { + labels.push(LabeledSpan::at( + span, + format!("even though the projects does include support for '{current_platform}'"), + )); + } + + // Find all the features that are excluding the current platform. + let features_without_platform = grouped_environment.features().filter_map(|feature| { + let platforms = feature.platforms.as_ref()?; + if !platforms.value.contains(¤t_platform) { + Some((feature, platforms)) + } else { + None + } + }); + + for (feature, platforms) in features_without_platform { + let Some(span) = platforms.span.as_ref() else { + continue; + }; + + labels.push(LabeledSpan::at( + span.clone(), + format!( + "feature '{}' does not support '{current_platform}'", + feature.name + ), + )); + } + + diag.labels = Some(labels); + diag.help = Some("Try converting your [pypi-dependencies] to conda [dependencies]".to_string()); + + miette::Report::new(diag).with_source_code(environment.project().manifest.contents.clone()) +} + /// Represents data that is sent back from a task. This is used to communicate the result of a task /// back to the main task which will forward the information to other tasks waiting for results. enum TaskResult {