From c6831914081b12cbdb06bce1622ba1596d7130ca Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 14 Oct 2024 21:04:30 -0400 Subject: [PATCH] Don't recommend `--prerelease=allow` for source dist builds (#8192) ## Summary Closes https://github.com/astral-sh/uv/issues/3686. --- crates/uv-dispatch/src/lib.rs | 5 +-- crates/uv-resolver/src/error.rs | 5 +++ crates/uv-resolver/src/lib.rs | 2 +- crates/uv-resolver/src/options.rs | 24 ++++++++++++- crates/uv-resolver/src/pubgrub/report.rs | 44 ++++++++++++++---------- crates/uv-resolver/src/resolver/mod.rs | 1 + crates/uv/src/lib.rs | 4 +-- crates/uv/tests/it/pip_install.rs | 37 ++++++++++++++++++++ 8 files changed, 98 insertions(+), 24 deletions(-) diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 69e894c0b..daccf6902 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -28,8 +28,8 @@ use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages}; use uv_pypi_types::Requirement; use uv_python::{Interpreter, PythonEnvironment}; use uv_resolver::{ - ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver, - ResolverMarkers, + ExcludeNewer, FlatIndex, Flexibility, InMemoryIndex, Manifest, OptionsBuilder, + PythonRequirement, Resolver, ResolverMarkers, }; use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; @@ -170,6 +170,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { OptionsBuilder::new() .exclude_newer(self.exclude_newer) .index_strategy(self.index_strategy) + .flexibility(Flexibility::Fixed) .build(), &python_requirement, ResolverMarkers::specific_environment(markers), diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index d63ae0813..e16caabcd 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -15,6 +15,7 @@ use crate::pubgrub::{ use crate::python_requirement::PythonRequirement; use crate::resolution::ConflictingDistributionError; use crate::resolver::{IncompletePackage, ResolverMarkers, UnavailablePackage, UnavailableReason}; +use crate::Options; use tracing::trace; use uv_distribution_types::{BuiltDist, IndexLocations, IndexUrl, InstalledDist, SourceDist}; use uv_normalize::PackageName; @@ -117,6 +118,7 @@ pub struct NoSolutionError { fork_urls: ForkUrls, markers: ResolverMarkers, workspace_members: BTreeSet, + options: Options, } impl NoSolutionError { @@ -133,6 +135,7 @@ impl NoSolutionError { fork_urls: ForkUrls, markers: ResolverMarkers, workspace_members: BTreeSet, + options: Options, ) -> Self { Self { error, @@ -146,6 +149,7 @@ impl NoSolutionError { fork_urls, markers, workspace_members, + options, } } @@ -250,6 +254,7 @@ impl std::fmt::Display for NoSolutionError { &self.fork_urls, &self.markers, &self.workspace_members, + self.options, &mut additional_hints, ); for hint in additional_hints { diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index 9c83275b7..91f4a025d 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -7,7 +7,7 @@ pub use lock::{ Lock, LockError, RequirementsTxtExport, ResolverManifest, SatisfiesResult, TreeDisplay, }; pub use manifest::Manifest; -pub use options::{Options, OptionsBuilder}; +pub use options::{Flexibility, Options, OptionsBuilder}; pub use preferences::{Preference, PreferenceError, Preferences}; pub use prerelease::PrereleaseMode; pub use pubgrub::{PubGrubSpecifier, PubGrubSpecifierError}; diff --git a/crates/uv-resolver/src/options.rs b/crates/uv-resolver/src/options.rs index 8351eaef9..7f2c1010e 100644 --- a/crates/uv-resolver/src/options.rs +++ b/crates/uv-resolver/src/options.rs @@ -3,13 +3,14 @@ use uv_configuration::IndexStrategy; use crate::{DependencyMode, ExcludeNewer, PrereleaseMode, ResolutionMode}; /// Options for resolving a manifest. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Options { pub resolution_mode: ResolutionMode, pub prerelease_mode: PrereleaseMode, pub dependency_mode: DependencyMode, pub exclude_newer: Option, pub index_strategy: IndexStrategy, + pub flexibility: Flexibility, } /// Builder for [`Options`]. @@ -20,6 +21,7 @@ pub struct OptionsBuilder { dependency_mode: DependencyMode, exclude_newer: Option, index_strategy: IndexStrategy, + flexibility: Flexibility, } impl OptionsBuilder { @@ -63,6 +65,13 @@ impl OptionsBuilder { self } + /// Sets the [`Flexibility`]. + #[must_use] + pub fn flexibility(mut self, flexibility: Flexibility) -> Self { + self.flexibility = flexibility; + self + } + /// Builds the options. pub fn build(self) -> Options { Options { @@ -71,6 +80,19 @@ impl OptionsBuilder { dependency_mode: self.dependency_mode, exclude_newer: self.exclude_newer, index_strategy: self.index_strategy, + flexibility: self.flexibility, } } } + +/// Whether the [`Options`] are configurable or fixed. +/// +/// Applies to the [`ResolutionMode`], [`PrereleaseMode`], and [`DependencyMode`] fields. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum Flexibility { + /// The setting is configurable. + #[default] + Configurable, + /// The setting is fixed. + Fixed, +} diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index decf99087..688206318 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -19,7 +19,7 @@ use crate::fork_urls::ForkUrls; use crate::prerelease::AllowPrerelease; use crate::python_requirement::{PythonRequirement, PythonRequirementSource}; use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason}; -use crate::{RequiresPython, ResolverMarkers}; +use crate::{Flexibility, Options, RequiresPython, ResolverMarkers}; use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython}; @@ -511,6 +511,7 @@ impl PubGrubReportFormatter<'_> { fork_urls: &ForkUrls, markers: &ResolverMarkers, workspace_members: &BTreeSet, + options: Options, output_hints: &mut IndexSet, ) { match derivation_tree { @@ -519,15 +520,17 @@ impl PubGrubReportFormatter<'_> { ) => { if let PubGrubPackageInner::Package { name, .. } = &**package { // Check for no versions due to pre-release options. - if !fork_urls.contains_key(name) { - self.prerelease_available_hint( - package, - name, - set, - selector, - markers, - output_hints, - ); + if options.flexibility == Flexibility::Configurable { + if !fork_urls.contains_key(name) { + self.prerelease_available_hint( + package, + name, + set, + selector, + markers, + output_hints, + ); + } } // Check for no versions due to no `--find-links` flat index. @@ -592,6 +595,7 @@ impl PubGrubReportFormatter<'_> { fork_urls, markers, workspace_members, + options, output_hints, ); self.generate_hints( @@ -604,6 +608,7 @@ impl PubGrubReportFormatter<'_> { fork_urls, markers, workspace_members, + options, output_hints, ); } @@ -995,29 +1000,32 @@ impl std::fmt::Display for PubGrubHint { Self::PrereleaseAvailable { package, version } => { write!( f, - "{}{} Pre-releases are available for {} in the requested range (e.g., {}), but pre-releases weren't enabled (try: `--prerelease=allow`)", + "{}{} Pre-releases are available for {} in the requested range (e.g., {}), but pre-releases weren't enabled (try: `{}`)", "hint".bold().cyan(), ":".bold(), package.bold(), - version.bold() + version.bold(), + "--prerelease=allow".green(), ) } Self::PrereleaseRequested { package, range } => { write!( f, - "{}{} {} was requested with a pre-release marker (e.g., {}), but pre-releases weren't enabled (try: `--prerelease=allow`)", + "{}{} {} was requested with a pre-release marker (e.g., {}), but pre-releases weren't enabled (try: `{}`)", "hint".bold().cyan(), ":".bold(), package.bold(), - PackageRange::compatibility(package, range, None).bold() + PackageRange::compatibility(package, range, None).bold(), + "--prerelease=allow".green(), ) } Self::NoIndex => { write!( f, - "{}{} Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `)", + "{}{} Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `{}`)", "hint".bold().cyan(), ":".bold(), + "--find-links ".green(), ) } Self::Offline => { @@ -1183,8 +1191,8 @@ impl std::fmt::Display for PubGrubHint { "{}{} The package `{}` depends on the package `{}` but the name is shadowed by {your_project}. Consider changing the name of {the_project}.", "hint".bold().cyan(), ":".bold(), - package, - dependency, + package.bold(), + dependency.bold(), ) } Self::UncheckedIndex { @@ -1198,7 +1206,7 @@ impl std::fmt::Display for PubGrubHint { "{}{} `{}` was found on {}, but not at the requested version ({}). A compatible version may be available on a subsequent index (e.g., {}). By default, uv will only consider versions that are published on the first index that contains a given package, to avoid dependency confusion attacks. If all indexes are equally trusted, use `{}` to consider all versions from all indexes, regardless of the order in which they were defined.", "hint".bold().cyan(), ":".bold(), - package, + package.bold(), found_index.cyan(), PackageRange::compatibility(package, range, None).cyan(), next_index.cyan(), diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 16a6ea914..9050d2b9a 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -1989,6 +1989,7 @@ impl ResolverState Result { // Initialize the cache. let cache = cache.init()?.with_refresh(Refresh::All(Timestamp::now())); - commands::tool_upgrade( + Box::pin(commands::tool_upgrade( args.name, args.python, globals.connectivity, @@ -978,7 +978,7 @@ async fn run(mut cli: Cli) -> Result { globals.native_tls, &cache, printer, - ) + )) .await } Commands::Tool(ToolNamespace { diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index fc7bcffd7..77e3d9a74 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -2243,6 +2243,43 @@ fn only_binary_editable_setup_py() { ); } +/// We should not recommend `--prerelease=allow` in source distribution build failures, since we +/// don't propagate the `--prerelease` flag to the source distribution build regardless. +#[test] +fn no_prerelease_hint_source_builds() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["setuptools>=40.8.0"] + build-backend = "setuptools.build_meta" + "#})?; + + uv_snapshot!(context.filters(), context.pip_install().arg(".").env("UV_EXCLUDE_NEWER", "2018-10-08"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + error: Failed to prepare distributions + Caused by: Failed to fetch wheel: project @ file://[TEMP_DIR]/ + Caused by: Failed to resolve requirements from `setup.py` build + Caused by: No solution found when resolving: `setuptools>=40.8.0` + Caused by: Because only setuptools<40.8.0 is available and you require setuptools>=40.8.0, we can conclude that your requirements are unsatisfiable. + "### + ); + + Ok(()) +} + #[test] fn cache_priority() { let context = TestContext::new("3.12");