Better build error messages (#9660)

Build failures are one of the most common user facing failures that
aren't "obivous" errors (such as typos) or resolver errors. Currently,
they show more technical details than being focussed on this being an
error in a subprocess that is either on the side of the package or -
more likely - in the build environment, e.g. the user needs to install a
dev package or their python version is incompatible.

The new error message clearly delineates the part that's important (this
is a build backend problem) from the internals (we called this hook) and
is consistent about which part of the dist building stage failed. We
have to calibrate the exact wording of the error message some more. Most
of the implementation is working around the orphan rule, (this)error
rules and trait rules, so it came out more of a refactoring than
intended.

Example:


![image](https://github.com/user-attachments/assets/2bc12992-db79-4362-a444-fd0d94594b77)
This commit is contained in:
konsti 2024-12-17 16:44:32 +01:00 committed by GitHub
parent b7df5dbaf3
commit ebc6d20d9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 426 additions and 262 deletions

3
Cargo.lock generated
View file

@ -4585,7 +4585,6 @@ name = "uv-build-frontend"
version = "0.0.1"
dependencies = [
"anstream",
"anyhow",
"fs-err 3.0.0",
"indoc",
"insta",
@ -4834,6 +4833,7 @@ dependencies = [
"futures",
"itertools 0.13.0",
"rustc-hash",
"thiserror 2.0.3",
"tokio",
"tracing",
"uv-build-backend",
@ -4847,6 +4847,7 @@ dependencies = [
"uv-git",
"uv-install-wheel",
"uv-installer",
"uv-platform-tags",
"uv-pypi-types",
"uv-python",
"uv-resolver",

View file

@ -30,7 +30,6 @@ uv-types = { workspace = true }
uv-virtualenv = { workspace = true }
anstream = { workspace = true }
anyhow = { workspace = true }
fs-err = { workspace = true }
indoc = { workspace = true }
itertools = { workspace = true }

View file

@ -5,16 +5,17 @@ use std::path::PathBuf;
use std::process::ExitStatus;
use std::sync::LazyLock;
use crate::PythonRunnerOutput;
use owo_colors::OwoColorize;
use regex::Regex;
use thiserror::Error;
use tracing::error;
use uv_configuration::BuildOutput;
use uv_distribution_types::IsBuildBackendError;
use uv_fs::Simplified;
use uv_pep440::Version;
use uv_pep508::PackageName;
use crate::PythonRunnerOutput;
use uv_types::AnyErrorBuild;
/// e.g. `pygraphviz/graphviz_wrap.c:3020:10: fatal error: graphviz/cgraph.h: No such file or directory`
static MISSING_HEADER_RE_GCC: LazyLock<Regex> = LazyLock::new(|| {
@ -68,19 +69,47 @@ pub enum Error {
#[error("Editable installs with setup.py legacy builds are unsupported, please specify a build backend in pyproject.toml")]
EditableSetupPy,
#[error("Failed to resolve requirements from {0}")]
RequirementsResolve(&'static str, #[source] anyhow::Error),
RequirementsResolve(&'static str, #[source] AnyErrorBuild),
#[error("Failed to install requirements from {0}")]
RequirementsInstall(&'static str, #[source] anyhow::Error),
RequirementsInstall(&'static str, #[source] AnyErrorBuild),
#[error("Failed to create temporary virtualenv")]
Virtualenv(#[from] uv_virtualenv::Error),
// Build backend errors
#[error("Failed to run `{0}`")]
CommandFailed(PathBuf, #[source] io::Error),
#[error(transparent)]
#[error("The build backend returned an error")]
BuildBackend(#[from] BuildBackendError),
#[error(transparent)]
#[error("The build backend returned an error")]
MissingHeader(#[from] MissingHeaderError),
#[error("Failed to build PATH for build script")]
BuildScriptPath(#[source] env::JoinPathsError),
// For the convenience of typing `setup_build` properly.
#[error("Building source distributions for {0} is disabled")]
NoSourceDistBuild(PackageName),
#[error("Building source distributions is disabled")]
NoSourceDistBuilds,
}
impl IsBuildBackendError for Error {
fn is_build_backend_error(&self) -> bool {
match self {
Self::Io(_)
| Self::Lowering(_)
| Self::InvalidSourceDist(_)
| Self::InvalidPyprojectTomlSyntax(_)
| Self::InvalidPyprojectTomlSchema(_)
| Self::EditableSetupPy
| Self::RequirementsResolve(_, _)
| Self::RequirementsInstall(_, _)
| Self::Virtualenv(_)
| Self::NoSourceDistBuild(_)
| Self::NoSourceDistBuilds => false,
Self::CommandFailed(_, _)
| Self::BuildBackend(_)
| Self::MissingHeader(_)
| Self::BuildScriptPath(_) => true,
}
}
}
#[derive(Debug)]
@ -247,6 +276,13 @@ impl Display for BuildBackendError {
writeln!(f)?;
}
write!(
f,
"\n{}{} This usually indicates a problem with the package or the build environment.",
"hint".bold().cyan(),
":".bold()
)?;
Ok(())
}
}
@ -416,7 +452,10 @@ mod test {
assert!(matches!(err, Error::MissingHeader { .. }));
// Unix uses exit status, Windows uses exit code.
let formatted = err.to_string().replace("exit status: ", "exit code: ");
let formatted = std::error::Error::source(&err)
.unwrap()
.to_string()
.replace("exit status: ", "exit code: ");
let formatted = anstream::adapter::strip_str(&formatted);
insta::assert_snapshot!(formatted, @r###"
Failed building wheel through setup.py (exit code: 0)
@ -471,7 +510,10 @@ mod test {
);
assert!(matches!(err, Error::MissingHeader { .. }));
// Unix uses exit status, Windows uses exit code.
let formatted = err.to_string().replace("exit status: ", "exit code: ");
let formatted = std::error::Error::source(&err)
.unwrap()
.to_string()
.replace("exit status: ", "exit code: ");
let formatted = anstream::adapter::strip_str(&formatted);
insta::assert_snapshot!(formatted, @r###"
Failed building wheel through setup.py (exit code: 0)
@ -516,7 +558,10 @@ mod test {
);
assert!(matches!(err, Error::MissingHeader { .. }));
// Unix uses exit status, Windows uses exit code.
let formatted = err.to_string().replace("exit status: ", "exit code: ");
let formatted = std::error::Error::source(&err)
.unwrap()
.to_string()
.replace("exit status: ", "exit code: ");
let formatted = anstream::adapter::strip_str(&formatted);
insta::assert_snapshot!(formatted, @r###"
Failed building wheel through setup.py (exit code: 0)
@ -559,7 +604,10 @@ mod test {
);
assert!(matches!(err, Error::MissingHeader { .. }));
// Unix uses exit status, Windows uses exit code.
let formatted = err.to_string().replace("exit status: ", "exit code: ");
let formatted = std::error::Error::source(&err)
.unwrap()
.to_string()
.replace("exit status: ", "exit code: ");
let formatted = anstream::adapter::strip_str(&formatted);
insta::assert_snapshot!(formatted, @r###"
Failed building wheel through setup.py (exit code: 0)

View file

@ -7,7 +7,6 @@ mod error;
use fs_err as fs;
use indoc::formatdoc;
use itertools::Itertools;
use owo_colors::OwoColorize;
use rustc_hash::FxHashMap;
use serde::de::{value, IntoDeserializer, SeqAccess, Visitor};
use serde::{de, Deserialize, Deserializer};
@ -36,7 +35,7 @@ use uv_pep508::PackageName;
use uv_pypi_types::{Requirement, VerbatimParsedUrl};
use uv_python::{Interpreter, PythonEnvironment};
use uv_static::EnvVars;
use uv_types::{BuildContext, BuildIsolation, SourceBuildTrait};
use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, SourceBuildTrait};
pub use crate::error::{Error, MissingHeaderCause};
@ -325,7 +324,7 @@ impl SourceBuild {
build_context
.install(&resolved_requirements, &venv)
.await
.map_err(|err| Error::RequirementsInstall("`build-system.requires`", err))?;
.map_err(|err| Error::RequirementsInstall("`build-system.requires`", err.into()))?;
} else {
debug!("Proceeding without build isolation");
}
@ -423,7 +422,9 @@ impl SourceBuild {
let resolved_requirements = build_context
.resolve(&default_backend.requirements)
.await
.map_err(|err| Error::RequirementsResolve("`setup.py` build", err))?;
.map_err(|err| {
Error::RequirementsResolve("`setup.py` build", err.into())
})?;
*resolution = Some(resolved_requirements.clone());
resolved_requirements
}
@ -431,7 +432,9 @@ impl SourceBuild {
build_context
.resolve(&pep517_backend.requirements)
.await
.map_err(|err| Error::RequirementsResolve("`build-system.requires`", err))?
.map_err(|err| {
Error::RequirementsResolve("`build-system.requires`", err.into())
})?
},
)
}
@ -622,8 +625,8 @@ impl SourceBuild {
if !output.status.success() {
return Err(Error::from_command_output(
format!(
"Build backend failed to determine metadata through `{}`",
format!("prepare_metadata_for_build_{}", self.build_kind).green()
"Call to `{}.prepare_metadata_for_build_{}` failed",
self.pep517_backend.backend, self.build_kind
),
&output,
self.level,
@ -745,9 +748,8 @@ impl SourceBuild {
if !output.status.success() {
return Err(Error::from_command_output(
format!(
"Build backend failed to build {} through `{}`",
self.build_kind,
format!("build_{}", self.build_kind).green(),
"Call to `{}.build_{}` failed",
pep517_backend.backend, self.build_kind
),
&output,
self.level,
@ -761,8 +763,8 @@ impl SourceBuild {
if !output_dir.join(&distribution_filename).is_file() {
return Err(Error::from_command_output(
format!(
"Build backend failed to produce {} through `{}`: `{distribution_filename}` not found",
self.build_kind, format!("build_{}", self.build_kind).green(),
"Call to `{}.build_{}` failed",
pep517_backend.backend, self.build_kind
),
&output,
self.level,
@ -776,11 +778,11 @@ impl SourceBuild {
}
impl SourceBuildTrait for SourceBuild {
async fn metadata(&mut self) -> anyhow::Result<Option<PathBuf>> {
async fn metadata(&mut self) -> Result<Option<PathBuf>, AnyErrorBuild> {
Ok(self.get_metadata_without_build().await?)
}
async fn wheel<'a>(&'a self, wheel_dir: &'a Path) -> anyhow::Result<String> {
async fn wheel<'a>(&'a self, wheel_dir: &'a Path) -> Result<String, AnyErrorBuild> {
Ok(self.build(wheel_dir).await?)
}
}
@ -858,8 +860,8 @@ async fn create_pep517_build_environment(
if !output.status.success() {
return Err(Error::from_command_output(
format!(
"Build backend failed to determine requirements with `{}`",
format!("build_{build_kind}()").green()
"Call to `{}.build_{}` failed",
pep517_backend.backend, build_kind
),
&output,
level,
@ -869,37 +871,27 @@ async fn create_pep517_build_environment(
));
}
// Read the requirements from the output file.
let contents = fs_err::read(&outfile).map_err(|err| {
Error::from_command_output(
// Read and deserialize the requirements from the output file.
let read_requires_result = fs_err::read(&outfile)
.map_err(|err| err.to_string())
.and_then(|contents| serde_json::from_slice(&contents).map_err(|err| err.to_string()));
let extra_requires: Vec<uv_pep508::Requirement<VerbatimParsedUrl>> = match read_requires_result
{
Ok(extra_requires) => extra_requires,
Err(err) => {
return Err(Error::from_command_output(
format!(
"Build backend failed to read requirements from `{}`: {err}",
format!("get_requires_for_build_{build_kind}").green(),
"Call to `{}.get_requires_for_build_{}` failed: {}",
pep517_backend.backend, build_kind, err
),
&output,
level,
package_name,
package_version,
version_id,
)
})?;
// Deserialize the requirements from the output file.
let extra_requires: Vec<uv_pep508::Requirement<VerbatimParsedUrl>> =
serde_json::from_slice::<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>(&contents)
.map_err(|err| {
Error::from_command_output(
format!(
"Build backend failed to return requirements from `{}`: {err}",
format!("get_requires_for_build_{build_kind}").green(),
),
&output,
level,
package_name,
package_version,
version_id,
)
})?;
))
}
};
// If necessary, lower the requirements.
let extra_requires = match source_strategy {
@ -937,15 +929,16 @@ async fn create_pep517_build_environment(
.cloned()
.chain(extra_requires)
.collect();
let resolution = build_context
.resolve(&requirements)
.await
.map_err(|err| Error::RequirementsResolve("`build-system.requires`", err))?;
let resolution = build_context.resolve(&requirements).await.map_err(|err| {
Error::RequirementsResolve("`build-system.requires`", AnyErrorBuild::from(err))
})?;
build_context
.install(&resolution, venv)
.await
.map_err(|err| Error::RequirementsInstall("`build-system.requires`", err))?;
.map_err(|err| {
Error::RequirementsInstall("`build-system.requires`", AnyErrorBuild::from(err))
})?;
}
Ok(())

View file

@ -28,6 +28,7 @@ uv-distribution-types = { workspace = true }
uv-git = { workspace = true }
uv-install-wheel = { workspace = true }
uv-installer = { workspace = true }
uv-platform-tags = { workspace = true }
uv-pypi-types = { workspace = true }
uv-python = { workspace = true }
uv-resolver = { workspace = true }
@ -38,5 +39,6 @@ anyhow = { workspace = true }
futures = { workspace = true }
itertools = { workspace = true }
rustc-hash = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }

View file

@ -5,10 +5,11 @@
use std::ffi::{OsStr, OsString};
use std::path::Path;
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result};
use futures::FutureExt;
use itertools::Itertools;
use rustc_hash::FxHashMap;
use thiserror::Error;
use tracing::{debug, instrument, trace};
use uv_build_backend::check_direct_build;
use uv_build_frontend::{SourceBuild, SourceBuildContext};
@ -22,8 +23,8 @@ use uv_configuration::{BuildOutput, Concurrency};
use uv_distribution::DistributionDatabase;
use uv_distribution_filename::DistFilename;
use uv_distribution_types::{
CachedDist, DependencyMetadata, IndexCapabilities, IndexLocations, Name, Resolution,
SourceDist, VersionOrUrlRef,
CachedDist, DependencyMetadata, IndexCapabilities, IndexLocations, IsBuildBackendError, Name,
Resolution, SourceDist, VersionOrUrlRef,
};
use uv_git::GitResolver;
use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages};
@ -33,7 +34,43 @@ use uv_resolver::{
ExcludeNewer, FlatIndex, Flexibility, InMemoryIndex, Manifest, OptionsBuilder,
PythonRequirement, Resolver, ResolverEnvironment,
};
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
use uv_types::{
AnyErrorBuild, BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight,
};
#[derive(Debug, Error)]
pub enum BuildDispatchError {
#[error(transparent)]
BuildFrontend(#[from] AnyErrorBuild),
#[error(transparent)]
Tags(#[from] uv_platform_tags::TagsError),
#[error(transparent)]
Resolve(#[from] uv_resolver::ResolveError),
#[error(transparent)]
Join(#[from] tokio::task::JoinError),
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
#[error(transparent)]
Prepare(#[from] uv_installer::PrepareError),
}
impl IsBuildBackendError for BuildDispatchError {
fn is_build_backend_error(&self) -> bool {
match self {
BuildDispatchError::Tags(_)
| BuildDispatchError::Resolve(_)
| BuildDispatchError::Join(_)
| BuildDispatchError::Anyhow(_)
| BuildDispatchError::Prepare(_) => false,
BuildDispatchError::BuildFrontend(err) => err.is_build_backend_error(),
}
}
}
/// The main implementation of [`BuildContext`], used by the CLI, see [`BuildContext`]
/// documentation.
@ -124,6 +161,7 @@ impl<'a> BuildDispatch<'a> {
}
}
#[allow(refining_impl_trait)]
impl<'a> BuildContext for BuildDispatch<'a> {
type SourceDistBuilder = SourceBuild;
@ -167,7 +205,10 @@ impl<'a> BuildContext for BuildDispatch<'a> {
self.index_locations
}
async fn resolve<'data>(&'data self, requirements: &'data [Requirement]) -> Result<Resolution> {
async fn resolve<'data>(
&'data self,
requirements: &'data [Requirement],
) -> Result<Resolution, BuildDispatchError> {
let python_requirement = PythonRequirement::from_interpreter(self.interpreter);
let marker_env = self.interpreter.resolver_marker_environment();
let tags = self.interpreter.tags()?;
@ -215,7 +256,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
&'data self,
resolution: &'data Resolution,
venv: &'data PythonEnvironment,
) -> Result<Vec<CachedDist>> {
) -> Result<Vec<CachedDist>, BuildDispatchError> {
debug!(
"Installing in {} in {}",
resolution
@ -325,7 +366,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
sources: SourceStrategy,
build_kind: BuildKind,
build_output: BuildOutput,
) -> Result<SourceBuild> {
) -> Result<SourceBuild, uv_build_frontend::Error> {
let dist_name = dist.map(uv_distribution_types::Name::name);
let dist_version = dist
.map(uv_distribution_types::DistributionMetadata::version_or_url)
@ -342,13 +383,12 @@ impl<'a> BuildContext for BuildDispatch<'a> {
// We always allow editable builds
&& !matches!(build_kind, BuildKind::Editable)
{
if let Some(dist) = dist {
return Err(anyhow!(
"Building source distributions for {} is disabled",
dist.name()
));
}
return Err(anyhow!("Building source distributions is disabled"));
let err = if let Some(dist) = dist {
uv_build_frontend::Error::NoSourceDistBuild(dist.name().clone())
} else {
uv_build_frontend::Error::NoSourceDistBuilds
};
return Err(err);
}
let builder = SourceBuild::setup(
@ -382,7 +422,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
output_dir: &'data Path,
build_kind: BuildKind,
version_id: Option<&'data str>,
) -> Result<Option<DistFilename>> {
) -> Result<Option<DistFilename>, BuildDispatchError> {
// Direct builds are a preview feature with the uv build backend.
if self.preview.is_disabled() {
trace!("Preview is disabled, not checking for direct build");

View file

@ -1,13 +1,19 @@
use crate::{DistRef, Edge, Name, Node, Resolution, ResolvedDist};
use crate::{BuiltDist, Dist, DistRef, Edge, Name, Node, Resolution, ResolvedDist, SourceDist};
use petgraph::prelude::EdgeRef;
use petgraph::Direction;
use rustc_hash::FxHashSet;
use std::collections::VecDeque;
use std::fmt::{Display, Formatter};
use std::fmt::{Debug, Display, Formatter};
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::Version;
use version_ranges::Ranges;
/// Inspect whether an error type is a build error.
pub trait IsBuildBackendError: std::error::Error + Send + Sync + 'static {
/// Returns whether the build backend failed to build the package, so it's not a uv error.
fn is_build_backend_error(&self) -> bool;
}
/// The operation(s) that failed when reporting an error with a distribution.
#[derive(Debug)]
pub enum DistErrorKind {
@ -18,6 +24,29 @@ pub enum DistErrorKind {
Read,
}
impl DistErrorKind {
pub fn from_dist_and_err(dist: &Dist, err: &impl IsBuildBackendError) -> Self {
if err.is_build_backend_error() {
DistErrorKind::BuildBackend
} else {
match dist {
Dist::Built(BuiltDist::Path(_)) => DistErrorKind::Read,
Dist::Source(SourceDist::Path(_) | SourceDist::Directory(_)) => {
DistErrorKind::Build
}
Dist::Built(_) => DistErrorKind::Download,
Dist::Source(source_dist) => {
if source_dist.is_local() {
DistErrorKind::Build
} else {
DistErrorKind::DownloadAndBuild
}
}
}
}
}
}
impl Display for DistErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {

View file

@ -8,10 +8,12 @@ use zip::result::ZipError;
use crate::metadata::MetadataError;
use uv_client::WrappedReqwestError;
use uv_distribution_filename::WheelFilenameError;
use uv_distribution_types::IsBuildBackendError;
use uv_fs::Simplified;
use uv_normalize::PackageName;
use uv_pep440::{Version, VersionSpecifiers};
use uv_pypi_types::{HashAlgorithm, HashDigest, ParsedUrlError};
use uv_types::AnyErrorBuild;
#[derive(Debug, thiserror::Error)]
pub enum Error {
@ -52,7 +54,7 @@ pub enum Error {
// Build error
#[error(transparent)]
Build(anyhow::Error),
Build(AnyErrorBuild),
#[error("Built wheel has an invalid filename")]
WheelFilename(#[from] WheelFilenameError),
#[error("Package metadata name `{metadata}` does not match given name `{given}`")]
@ -174,6 +176,15 @@ impl From<reqwest_middleware::Error> for Error {
}
}
impl IsBuildBackendError for Error {
fn is_build_backend_error(&self) -> bool {
match self {
Self::Build(err) => err.is_build_backend_error(),
_ => false,
}
}
}
impl Error {
/// Construct a hash mismatch error.
pub fn hash_mismatch(

View file

@ -1879,7 +1879,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
Some(&source.to_string()),
)
.await
.map_err(Error::Build)?
.map_err(|err| Error::Build(err.into()))?
{
// In the uv build backend, the normalized filename and the disk filename are the same.
name.to_string()
@ -1900,7 +1900,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
BuildOutput::Debug,
)
.await
.map_err(Error::Build)?
.map_err(|err| Error::Build(err.into()))?
.wheel(temp_dir.path())
.await
.map_err(Error::Build)?
@ -1976,7 +1976,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
BuildOutput::Debug,
)
.await
.map_err(Error::Build)?;
.map_err(|err| Error::Build(err.into()))?;
// Build the metadata.
let dist_info = builder.metadata().await.map_err(Error::Build)?;

View file

@ -226,20 +226,15 @@ pub enum Error {
impl Error {
/// Create an [`Error`] from a distribution error.
fn from_dist(dist: Dist, cause: uv_distribution::Error, resolution: &Resolution) -> Self {
let kind = match &dist {
Dist::Built(_) => DistErrorKind::Download,
Dist::Source(dist) => {
if dist.is_local() {
DistErrorKind::Build
} else {
DistErrorKind::DownloadAndBuild
}
}
};
fn from_dist(dist: Dist, err: uv_distribution::Error, resolution: &Resolution) -> Self {
let chain =
DerivationChain::from_resolution(resolution, (&dist).into()).unwrap_or_default();
Self::Dist(kind, Box::new(dist), chain, cause)
Self::Dist(
DistErrorKind::from_dist_and_err(&dist, &err),
Box::new(dist),
chain,
err,
)
}
}

View file

@ -34,20 +34,12 @@ pub enum Error {
impl Error {
/// Create an [`Error`] from a distribution error.
pub(crate) fn from_dist(dist: Dist, cause: uv_distribution::Error) -> Self {
match dist {
Dist::Built(dist) => {
Self::Dist(DistErrorKind::Download, Box::new(Dist::Built(dist)), cause)
}
Dist::Source(dist) => {
let kind = if dist.is_local() {
DistErrorKind::Build
} else {
DistErrorKind::DownloadAndBuild
};
Self::Dist(kind, Box::new(Dist::Source(dist)), cause)
}
}
pub(crate) fn from_dist(dist: Dist, err: uv_distribution::Error) -> Self {
Self::Dist(
DistErrorKind::from_dist_and_err(&dist, &err),
Box::new(dist),
err,
)
}
}

View file

@ -1059,36 +1059,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
MetadataResponse::Error(dist, err) => {
// TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't
// critical since we fetch URL dependencies _prior_ to invoking the resolver.
let chain = DerivationChain::default();
let (kind, dist) = match &**dist {
Dist::Built(built_dist @ BuiltDist::Path(_)) => {
(DistErrorKind::Read, Dist::Built(built_dist.clone()))
}
Dist::Source(source_dist @ SourceDist::Path(_)) => {
(DistErrorKind::Build, Dist::Source(source_dist.clone()))
}
Dist::Source(source_dist @ SourceDist::Directory(_)) => {
(DistErrorKind::Build, Dist::Source(source_dist.clone()))
}
Dist::Built(built_dist) => {
(DistErrorKind::Download, Dist::Built(built_dist.clone()))
}
Dist::Source(source_dist) => {
if source_dist.is_local() {
(DistErrorKind::Build, Dist::Source(source_dist.clone()))
} else {
(
DistErrorKind::DownloadAndBuild,
Dist::Source(source_dist.clone()),
)
}
}
};
return Err(ResolveError::Dist(
kind,
Box::new(dist),
chain,
(*err).clone(),
DistErrorKind::from_dist_and_err(dist, &**err),
dist.clone(),
DerivationChain::default(),
err.clone(),
));
}
};
@ -1446,35 +1421,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
MetadataResponse::Error(dist, err) => {
let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
.unwrap_or_default();
let (kind, dist) = match &**dist {
Dist::Built(built_dist @ BuiltDist::Path(_)) => {
(DistErrorKind::Read, Dist::Built(built_dist.clone()))
}
Dist::Source(source_dist @ SourceDist::Path(_)) => {
(DistErrorKind::Build, Dist::Source(source_dist.clone()))
}
Dist::Source(source_dist @ SourceDist::Directory(_)) => {
(DistErrorKind::Build, Dist::Source(source_dist.clone()))
}
Dist::Built(built_dist) => {
(DistErrorKind::Download, Dist::Built(built_dist.clone()))
}
Dist::Source(source_dist) => {
if source_dist.is_local() {
(DistErrorKind::Build, Dist::Source(source_dist.clone()))
} else {
(
DistErrorKind::DownloadAndBuild,
Dist::Source(source_dist.clone()),
)
}
}
};
return Err(ResolveError::Dist(
kind,
Box::new(dist),
DistErrorKind::from_dist_and_err(dist, &**err),
dist.clone(),
chain,
(*err).clone(),
err.clone(),
));
}
};
@ -2353,22 +2304,6 @@ impl ForkState {
// A dependency from the root package or requirements.txt.
debug!("Adding direct dependency: {package}{version}");
let name = package.name_no_root().unwrap();
// Catch cases where we pass a package once by name with extras and then once as
// URL for the specific distribution.
has_url = has_url
|| dependencies
.iter()
.filter(|other_dep| *other_dep != dependency)
.filter(|other_dep| {
other_dep
.package
.name()
.is_some_and(|other_name| other_name == name)
})
.any(|other_dep| other_dep.url.is_some());
// Warn the user if a direct dependency lacks a lower bound in `--lowest` resolution.
let missing_lower_bound = version
.bounding_range()
@ -2383,6 +2318,7 @@ impl ForkState {
"The direct dependency `{name}` is unpinned. \
Consider setting a lower bound when using `--resolution lowest` \
to avoid using outdated versions.",
name = package.name_no_root().unwrap(),
);
}
}

View file

@ -1,4 +1,6 @@
use std::fmt::{Debug, Display, Formatter};
use std::future::Future;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use uv_distribution_filename::DistFilename;
@ -9,8 +11,8 @@ use uv_configuration::{
BuildKind, BuildOptions, BuildOutput, ConfigSettings, LowerBound, SourceStrategy,
};
use uv_distribution_types::{
CachedDist, DependencyMetadata, IndexCapabilities, IndexLocations, InstalledDist, Resolution,
SourceDist,
CachedDist, DependencyMetadata, IndexCapabilities, IndexLocations, InstalledDist,
IsBuildBackendError, Resolution, SourceDist,
};
use uv_git::GitResolver;
use uv_pep508::PackageName;
@ -94,7 +96,7 @@ pub trait BuildContext {
fn resolve<'a>(
&'a self,
requirements: &'a [Requirement],
) -> impl Future<Output = Result<Resolution>> + 'a;
) -> impl Future<Output = Result<Resolution, impl IsBuildBackendError>> + 'a;
/// Install the given set of package versions into the virtual environment. The environment must
/// use the same base Python as [`BuildContext::interpreter`]
@ -102,7 +104,7 @@ pub trait BuildContext {
&'a self,
resolution: &'a Resolution,
venv: &'a PythonEnvironment,
) -> impl Future<Output = Result<Vec<CachedDist>>> + 'a;
) -> impl Future<Output = Result<Vec<CachedDist>, impl IsBuildBackendError>> + 'a;
/// Set up a source distribution build by installing the required dependencies. A wrapper for
/// `uv_build::SourceBuild::setup`.
@ -121,7 +123,7 @@ pub trait BuildContext {
sources: SourceStrategy,
build_kind: BuildKind,
build_output: BuildOutput,
) -> impl Future<Output = Result<Self::SourceDistBuilder>> + 'a;
) -> impl Future<Output = Result<Self::SourceDistBuilder, impl IsBuildBackendError>> + 'a;
/// Build by calling directly into the uv build backend without PEP 517, if possible.
///
@ -136,7 +138,7 @@ pub trait BuildContext {
output_dir: &'a Path,
build_kind: BuildKind,
version_id: Option<&'a str>,
) -> impl Future<Output = Result<Option<DistFilename>>> + 'a;
) -> impl Future<Output = Result<Option<DistFilename>, impl IsBuildBackendError>> + 'a;
}
/// A wrapper for `uv_build::SourceBuild` to avoid cyclical crate dependencies.
@ -150,7 +152,7 @@ pub trait SourceBuildTrait {
///
/// Returns the metadata directory if we're having a PEP 517 build and the
/// `prepare_metadata_for_build_wheel` hook exists
fn metadata(&mut self) -> impl Future<Output = Result<Option<PathBuf>>>;
fn metadata(&mut self) -> impl Future<Output = Result<Option<PathBuf>, AnyErrorBuild>>;
/// A wrapper for `uv_build::SourceBuild::build`.
///
@ -159,7 +161,10 @@ pub trait SourceBuildTrait {
/// Returns the filename of the built wheel inside the given `wheel_dir`. The filename is a
/// string and not a `WheelFilename` because the on disk filename might not be normalized in the
/// same way as uv would.
fn wheel<'a>(&'a self, wheel_dir: &'a Path) -> impl Future<Output = Result<String>> + 'a;
fn wheel<'a>(
&'a self,
wheel_dir: &'a Path,
) -> impl Future<Output = Result<String, AnyErrorBuild>> + 'a;
}
/// A wrapper for [`uv_installer::SitePackages`]
@ -181,3 +186,61 @@ impl InstalledPackagesProvider for EmptyInstalledPackages {
std::iter::empty()
}
}
/// `anyhow::Error`-like wrapper type for [`BuildDispatch`] method return values, that also makes
/// `IsBuildBackendError` work as `thiserror` `#[source]`.
///
/// The errors types have the same problem as [`BuildDispatch`] generally: The `uv-resolver`,
/// `uv-installer` and `uv-build-frontend` error types all reference each other:
/// Resolution and installation may need to build packages, while the build frontend needs to
/// resolve and install for the PEP 517 build environment.
///
/// Usually, `anyhow::Error` is opaque error type of choice. In this case though, we error type
/// that we can inspect on whether it's a build backend error with [`IsBuildBackendError`], and
/// `anyhow::Error` does not allow attaching more traits. The next choice would be
/// `Box<dyn std::error::Error + IsBuildFrontendError + Send + Sync + 'static>`, but `thiserror`
/// complains about the internal `AsDynError` not being implemented when being used as `#[source]`.
/// This struct is an otherwise transparent error wrapper that thiserror recognizes.
pub struct AnyErrorBuild(Box<dyn IsBuildBackendError>);
impl Debug for AnyErrorBuild {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl Display for AnyErrorBuild {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl std::error::Error for AnyErrorBuild {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.source()
}
#[allow(deprecated)]
fn description(&self) -> &str {
self.0.description()
}
#[allow(deprecated)]
fn cause(&self) -> Option<&dyn std::error::Error> {
self.0.cause()
}
}
impl<T: IsBuildBackendError> From<T> for AnyErrorBuild {
fn from(err: T) -> Self {
Self(Box::new(err))
}
}
impl Deref for AnyErrorBuild {
type Target = dyn IsBuildBackendError;
fn deref(&self) -> &Self::Target {
&*self.0
}
}

View file

@ -42,7 +42,7 @@ use uv_python::{
use uv_requirements::RequirementsSource;
use uv_resolver::{ExcludeNewer, FlatIndex, RequiresPython};
use uv_settings::PythonInstallMirrors;
use uv_types::{BuildContext, BuildIsolation, HashStrategy};
use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, HashStrategy};
use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceError};
#[derive(Debug, Error)]
@ -66,7 +66,7 @@ enum Error {
#[error(transparent)]
BuildBackend(#[from] uv_build_backend::Error),
#[error(transparent)]
BuildDispatch(anyhow::Error),
BuildDispatch(AnyErrorBuild),
#[error(transparent)]
BuildFrontend(#[from] uv_build_frontend::Error),
#[error("Failed to write message")]
@ -923,7 +923,7 @@ async fn build_sdist(
build_output,
)
.await
.map_err(Error::BuildDispatch)?;
.map_err(|err| Error::BuildDispatch(err.into()))?;
let filename = builder.build(output_dir).await?;
BuildMessage::Build {
filename: DistFilename::SourceDistFilename(
@ -1020,7 +1020,7 @@ async fn build_wheel(
build_output,
)
.await
.map_err(Error::BuildDispatch)?;
.map_err(|err| Error::BuildDispatch(err.into()))?;
let filename = builder.build(output_dir).await?;
BuildMessage::Build {
filename: DistFilename::WheelFilename(

View file

@ -26,7 +26,7 @@ use uv_python::{
use uv_resolver::{ExcludeNewer, FlatIndex};
use uv_settings::PythonInstallMirrors;
use uv_shell::{shlex_posix, shlex_windows, Shell};
use uv_types::{BuildContext, BuildIsolation, HashStrategy};
use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, HashStrategy};
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceError};
@ -113,7 +113,7 @@ enum VenvError {
#[error("Failed to install seed packages")]
#[diagnostic(code(uv::venv::seed))]
Seed(#[source] anyhow::Error),
Seed(#[source] AnyErrorBuild),
#[error("Failed to extract interpreter tags")]
#[diagnostic(code(uv::venv::tags))]
@ -360,11 +360,11 @@ async fn venv_impl(
let resolution = build_dispatch
.resolve(&requirements)
.await
.map_err(VenvError::Seed)?;
.map_err(|err| VenvError::Seed(err.into()))?;
let installed = build_dispatch
.install(&resolution, &venv)
.await
.map_err(VenvError::Seed)?;
.map_err(|err| VenvError::Seed(err.into()))?;
let changelog = Changelog::from_installed(installed);
DefaultInstallLogger

View file

@ -897,7 +897,9 @@ fn fail() -> Result<()> {
from setuptools import setup
IndentationError: unexpected indent
× Failed to build `[TEMP_DIR]/project`
Build backend failed to determine requirements with `build_sdist()` (exit status: 1)
The build backend returned an error
Call to `setuptools.build_meta.build_sdist` failed (exit status: 1)
hint: This usually indicates a problem with the package or the build environment.
"###);
Ok(())
@ -1345,7 +1347,9 @@ fn build_all_with_failure() -> Result<()> {
Successfully built dist/member_a-0.1.0.tar.gz
Successfully built dist/member_a-0.1.0-py3-none-any.whl
× Failed to build `member-b @ [TEMP_DIR]/project/packages/member_b`
Build backend failed to determine requirements with `build_sdist()` (exit status: 1)
The build backend returned an error
Call to `setuptools.build_meta.build_sdist` failed (exit status: 1)
hint: This usually indicates a problem with the package or the build environment.
Successfully built dist/project-0.1.0.tar.gz
Successfully built dist/project-0.1.0-py3-none-any.whl
"###);

View file

@ -5559,7 +5559,8 @@ fn fail_to_add_revert_project() -> Result<()> {
----- stderr -----
Resolved 3 packages in [TIME]
× Failed to build `child @ file://[TEMP_DIR]/child`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
The build backend returned an error
Call to `setuptools.build_meta.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -5574,6 +5575,7 @@ fn fail_to_add_revert_project() -> Result<()> {
File "<string>", line 1, in <module>
ZeroDivisionError: division by zero
hint: This usually indicates a problem with the package or the build environment.
help: `child` was included because `parent` (v0.1.0) depends on `child`
"###);
@ -5668,7 +5670,8 @@ fn fail_to_edit_revert_project() -> Result<()> {
----- stderr -----
Resolved 3 packages in [TIME]
× Failed to build `child @ file://[TEMP_DIR]/child`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
The build backend returned an error
Call to `setuptools.build_meta.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -5683,6 +5686,7 @@ fn fail_to_edit_revert_project() -> Result<()> {
File "<string>", line 1, in <module>
ZeroDivisionError: division by zero
hint: This usually indicates a problem with the package or the build environment.
help: `child` was included because `parent` (v0.1.0) depends on `child`
"###);

View file

@ -19497,8 +19497,9 @@ fn lock_derivation_chain_prod() -> Result<()> {
----- stdout -----
----- stderr -----
× Failed to download and build `wsgiref==0.1.2`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `wsgiref==0.1.2`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -19518,6 +19519,7 @@ fn lock_derivation_chain_prod() -> Result<()> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
hint: This usually indicates a problem with the package or the build environment.
help: `wsgiref` (v0.1.2) was included because `project` (v0.1.0) depends on `wsgiref==0.1.2`
"###);
@ -19555,8 +19557,9 @@ fn lock_derivation_chain_extra() -> Result<()> {
----- stdout -----
----- stderr -----
× Failed to download and build `wsgiref==0.1.2`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `wsgiref==0.1.2`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -19576,6 +19579,7 @@ fn lock_derivation_chain_extra() -> Result<()> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
hint: This usually indicates a problem with the package or the build environment.
help: `wsgiref` (v0.1.2) was included because `project[wsgi]` (v0.1.0) depends on `wsgiref>=0.1`
"###);
@ -19615,8 +19619,9 @@ fn lock_derivation_chain_group() -> Result<()> {
----- stdout -----
----- stderr -----
× Failed to download and build `wsgiref==0.1.2`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `wsgiref==0.1.2`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -19636,6 +19641,7 @@ fn lock_derivation_chain_group() -> Result<()> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
hint: This usually indicates a problem with the package or the build environment.
help: `wsgiref` (v0.1.2) was included because `project:wsgi` (v0.1.0) depends on `wsgiref`
"###);
@ -19686,8 +19692,9 @@ fn lock_derivation_chain_extended() -> Result<()> {
----- stdout -----
----- stderr -----
× Failed to download and build `wsgiref==0.1.2`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `wsgiref==0.1.2`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -19707,6 +19714,7 @@ fn lock_derivation_chain_extended() -> Result<()> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
hint: This usually indicates a problem with the package or the build environment.
help: `wsgiref` (v0.1.2) was included because `project` (v0.1.0) depends on `child` (v0.1.0) which depends on `wsgiref>=0.1, <0.2`
"###);

View file

@ -13781,8 +13781,9 @@ fn compile_derivation_chain() -> Result<()> {
----- stdout -----
----- stderr -----
× Failed to download and build `wsgiref==0.1.2`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `wsgiref==0.1.2`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -13802,6 +13803,7 @@ fn compile_derivation_chain() -> Result<()> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
hint: This usually indicates a problem with the package or the build environment.
help: `wsgiref` (v0.1.2) was included because `child` (v0.1.0) depends on `wsgiref`
"###
);

View file

@ -303,7 +303,8 @@ dependencies = ["flask==1.0.x"]
----- stderr -----
× Failed to build `project @ file://[TEMP_DIR]/path_dep`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stdout]
configuration error: `project.dependencies[0]` must be pep508
@ -355,6 +356,8 @@ dependencies = ["flask==1.0.x"]
raise ValueError(f"{error}/n{summary}") from None
ValueError: invalid pyproject.toml config: `project.dependencies[0]`.
configuration error: `project.dependencies[0]` must be pep508
hint: This usually indicates a problem with the package or the build environment.
"###
);
@ -4059,13 +4062,16 @@ fn no_build_isolation() -> Result<()> {
----- stdout -----
----- stderr -----
× Failed to download and build `anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz`
Build backend failed to determine metadata through `prepare_metadata_for_build_wheel` (exit status: 1)
× Failed to build `anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz`
The build backend returned an error
Call to `setuptools.build_meta.prepare_metadata_for_build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
File "<string>", line 8, in <module>
ModuleNotFoundError: No module named 'setuptools'
hint: This usually indicates a problem with the package or the build environment.
"###
);
@ -4127,13 +4133,16 @@ fn respect_no_build_isolation_env_var() -> Result<()> {
----- stdout -----
----- stderr -----
× Failed to download and build `anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz`
Build backend failed to determine metadata through `prepare_metadata_for_build_wheel` (exit status: 1)
× Failed to build `anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz`
The build backend returned an error
Call to `setuptools.build_meta.prepare_metadata_for_build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
File "<string>", line 8, in <module>
ModuleNotFoundError: No module named 'setuptools'
hint: This usually indicates a problem with the package or the build environment.
"###
);
@ -7124,13 +7133,16 @@ fn install_build_isolation_package() -> Result<()> {
----- stdout -----
----- stderr -----
× Failed to download and build `iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz`
Build backend failed to determine metadata through `prepare_metadata_for_build_wheel` (exit status: 1)
× Failed to build `iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz`
The build backend returned an error
Call to `hatchling.build.prepare_metadata_for_build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
File "<string>", line 8, in <module>
ModuleNotFoundError: No module named 'hatchling'
hint: This usually indicates a problem with the package or the build environment.
"###
);
@ -7383,8 +7395,9 @@ fn sklearn() {
----- stdout -----
----- stderr -----
× Failed to download and build `sklearn==0.0.post12`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `sklearn==0.0.post12`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
The 'sklearn' PyPI package is deprecated, use 'scikit-learn'
@ -7403,6 +7416,7 @@ fn sklearn() {
More information is available at
https://github.com/scikit-learn/sklearn-pypi-package
hint: This usually indicates a problem with the package or the build environment.
help: `sklearn` is often confused for `scikit-learn` Did you mean to install `scikit-learn` instead?
"###
);
@ -7439,8 +7453,9 @@ fn resolve_derivation_chain() -> Result<()> {
----- stdout -----
----- stderr -----
× Failed to download and build `wsgiref==0.1.2`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `wsgiref==0.1.2`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -7460,6 +7475,7 @@ fn resolve_derivation_chain() -> Result<()> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
hint: This usually indicates a problem with the package or the build environment.
help: `wsgiref` (v0.1.2) was included because `project` (v0.1.0) depends on `wsgiref`
"###
);

View file

@ -4169,7 +4169,7 @@ fn require_hashes_wheel_path_mismatch() -> Result<()> {
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download `tqdm @ file://[WORKSPACE]/scripts/links/tqdm-1000.0.0-py3-none-any.whl`
× Failed to read `tqdm @ file://[WORKSPACE]/scripts/links/tqdm-1000.0.0-py3-none-any.whl`
Hash mismatch for `tqdm @ file://[WORKSPACE]/scripts/links/tqdm-1000.0.0-py3-none-any.whl`
Expected:

View file

@ -761,14 +761,16 @@ fn sync_build_isolation_package() -> Result<()> {
----- stderr -----
Resolved 2 packages in [TIME]
× Failed to download and build `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
Build backend failed to build wheel through `build_wheel` (exit status: 1)
× Failed to build `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
The build backend returned an error
Call to `hatchling.build.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
File "<string>", line 8, in <module>
ModuleNotFoundError: No module named 'hatchling'
hint: This usually indicates a problem with the package or the build environment.
help: `source-distribution` was included because `project` (v0.1.0) depends on `source-distribution`
"###);
@ -852,14 +854,16 @@ fn sync_build_isolation_extra() -> Result<()> {
----- stderr -----
Resolved [N] packages in [TIME]
× Failed to download and build `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
Build backend failed to build wheel through `build_wheel` (exit status: 1)
× Failed to build `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
The build backend returned an error
Call to `hatchling.build.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
File "<string>", line 8, in <module>
ModuleNotFoundError: No module named 'hatchling'
hint: This usually indicates a problem with the package or the build environment.
help: `source-distribution` was included because `project[compile]` (v0.1.0) depends on `source-distribution`
"###);
@ -871,14 +875,16 @@ fn sync_build_isolation_extra() -> Result<()> {
----- stderr -----
Resolved [N] packages in [TIME]
× Failed to download and build `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
Build backend failed to build wheel through `build_wheel` (exit status: 1)
× Failed to build `source-distribution @ https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz`
The build backend returned an error
Call to `hatchling.build.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
File "<string>", line 8, in <module>
ModuleNotFoundError: No module named 'hatchling'
hint: This usually indicates a problem with the package or the build environment.
help: `source-distribution` was included because `project[compile]` (v0.1.0) depends on `source-distribution`
"###);
@ -4953,8 +4959,9 @@ fn sync_derivation_chain() -> Result<()> {
----- stderr -----
Resolved 2 packages in [TIME]
× Failed to download and build `wsgiref==0.1.2`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `wsgiref==0.1.2`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -4974,6 +4981,7 @@ fn sync_derivation_chain() -> Result<()> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
hint: This usually indicates a problem with the package or the build environment.
help: `wsgiref` (v0.1.2) was included because `project` (v0.1.0) depends on `wsgiref`
"###);
@ -5017,8 +5025,9 @@ fn sync_derivation_chain_extra() -> Result<()> {
----- stderr -----
Resolved 2 packages in [TIME]
× Failed to download and build `wsgiref==0.1.2`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `wsgiref==0.1.2`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -5038,6 +5047,7 @@ fn sync_derivation_chain_extra() -> Result<()> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
hint: This usually indicates a problem with the package or the build environment.
help: `wsgiref` (v0.1.2) was included because `project[wsgi]` (v0.1.0) depends on `wsgiref`
"###);
@ -5083,8 +5093,9 @@ fn sync_derivation_chain_group() -> Result<()> {
----- stderr -----
Resolved 2 packages in [TIME]
× Failed to download and build `wsgiref==0.1.2`
Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `wsgiref==0.1.2`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -5104,6 +5115,7 @@ fn sync_derivation_chain_group() -> Result<()> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
hint: This usually indicates a problem with the package or the build environment.
help: `wsgiref` (v0.1.2) was included because `project:wsgi` (v0.1.0) depends on `wsgiref`
"###);

View file

@ -1469,8 +1469,9 @@ fn tool_install_uninstallable() {
----- stderr -----
Resolved 1 package in [TIME]
× Failed to download and build `pyenv==0.0.1`
Build backend failed to build wheel through `build_wheel` (exit status: 1)
× Failed to build `pyenv==0.0.1`
The build backend returned an error
Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stdout]
running bdist_wheel
@ -1486,6 +1487,9 @@ fn tool_install_uninstallable() {
https://github.com/pyenv/pyenv#installation
#
hint: This usually indicates a problem with the package or the build environment.
"###);
// Ensure the tool environment is not created.

View file

@ -12,22 +12,23 @@ unsupported version of Python:
```console
$ uv pip install -p 3.13 'numpy<1.20'
Resolved 1 package in 62ms
× Failed to download and build `numpy==1.19.5`
├─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `numpy==1.19.5`
├─▶ The build backend returned an error
╰─▶ Call to `setuptools.build_meta:__legacy__.build_wheel()` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
File "<string>", line 8, in <module>
from setuptools.build_meta import __legacy__ as backend
│ File "/Users/example/.cache/uv/builds-v0/.tmp96A0WB/lib/python3.13/site-packages/setuptools/__init__.py", line 9, in <module>
import distutils.core
ModuleNotFoundError: No module named 'distutils'
[stderr]
Traceback (most recent call last):
File "<string>", line 8, in <module>
from setuptools.build_meta import __legacy__ as backend
File "/home/konsti/.cache/uv/builds-v0/.tmpi4bgKb/lib/python3.13/site-packages/setuptools/__init__.py", line 9, in <module>
import distutils.core
ModuleNotFoundError: No module named 'distutils'
╰─▶ distutils was removed from the standard library in Python 3.12. Consider adding a constraint
(like `numpy >1.19.5`) to avoid building a version of numpy that depends on distutils.
hint: `distutils` was removed from the standard library in Python 3.12. Consider adding a constraint (like `numpy >1.19.5`) to avoid building a version of `numpy` that depends
on `distutils`.
```
Notice that the error message is prefaced by "Build backend failed to determine requirements".
Notice that the error message is prefaced by "The build backend returned an error".
The build failure includes the `[stderr]` (and `[stdout]`, if present) from the build backend that
was used for the build. The error logs are not from uv itself.
@ -118,8 +119,9 @@ If the build error mentions a missing command, for example, `gcc`:
<!-- docker run --platform linux/x86_64 -it ghcr.io/astral-sh/uv:python3.10-bookworm-slim /bin/bash -c "uv pip install --system pysha3==1.0.2" -->
```hl_lines="17"
× Failed to download and build `pysha3==1.0.2`
╰─▶ Build backend failed to build wheel through `build_wheel` (exit status: 1)
× Failed to build `pysha3==1.0.2`
├─▶ The build backend returned an error
╰─▶ Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stdout]
running bdist_wheel
@ -130,8 +132,8 @@ If the build error mentions a missing command, for example, `gcc`:
running build_ext
building '_pysha3' extension
creating build/temp.linux-x86_64-cpython-310/Modules/_sha3
gcc -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -DPY_WITH_KECCAK=1 -I/root/.cache/uv/builds-v0/.tmpxAJdUa/include -I/usr/local/include/python3.10 -c Modules/_sha3/sha3module.c -o
build/temp.linux-x86_64-cpython-310/Modules/_sha3/sha3module.o
gcc -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -DPY_WITH_KECCAK=1 -I/root/.cache/uv/builds-v0/.tmp8V4iEk/include -I/usr/local/include/python3.10 -c
Modules/_sha3/sha3module.c -o build/temp.linux-x86_64-cpython-310/Modules/_sha3/sha3module.o
[stderr]
error: command 'gcc' failed: No such file or directory
@ -165,8 +167,9 @@ For example, installing `pygraphviz` requires Graphviz to be installed:
<!-- docker run --platform linux/x86_64 -it ghcr.io/astral-sh/uv:python3.12-bookworm /bin/bash -c "uv pip install --system 'pygraphviz'" -->
```hl_lines="18-19"
× Failed to download and build `pygraphviz==1.14`
╰─▶ Build backend failed to build wheel through `build_wheel` (exit status: 1)
× Failed to build `pygraphviz==1.14`
├─▶ The build backend returned an error
╰─▶ Call to `setuptools.build_meta.build_wheel` failed (exit status: 1)
[stdout]
running bdist_wheel
@ -215,8 +218,9 @@ dependency:
<!-- docker run --platform linux/x86_64 -it ghcr.io/astral-sh/uv:python3.12-bookworm-slim /bin/bash -c "uv pip install --system chumpy" -->
```hl_lines="7"
× Failed to download and build `chumpy==0.70`
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `chumpy==0.70`
├─▶ The build backend returned an error
╰─▶ Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
@ -270,8 +274,9 @@ apache-beam<=2.49.0
<!-- docker run --platform linux/x86_64 -it ghcr.io/astral-sh/uv:python3.10-bookworm-slim /bin/bash -c "printf 'dill<0.3.9,>=0.2.2\napache-beam<=2.49.0' | uv pip compile -" -->
```hl_lines="1"
× Failed to download and build `apache-beam==2.0.0`
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
× Failed to build `apache-beam==2.0.0`
├─▶ The build backend returned an error
╰─▶ Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit status: 1)
[stderr]
...