diff --git a/Cargo.lock b/Cargo.lock index 681371b74b88..cc5c5ee1cd4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4561,6 +4561,7 @@ dependencies = [ name = "uv-build" version = "0.0.1" dependencies = [ + "anstream", "anyhow", "distribution-types", "fs-err", diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index ef9ed1a47e36..db445b2ab13a 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -24,6 +24,7 @@ uv-python = { workspace = true } uv-types = { workspace = true } uv-virtualenv = { workspace = true } +anstream = { workspace = true } anyhow = { workspace = true } fs-err = { workspace = true } indoc = { workspace = true } diff --git a/crates/uv-build/src/lib.rs b/crates/uv-build/src/lib.rs index 93a10570efae..1f3f666e5a9c 100644 --- a/crates/uv-build/src/lib.rs +++ b/crates/uv-build/src/lib.rs @@ -10,6 +10,7 @@ use rustc_hash::FxHashMap; use serde::de::{value, SeqAccess, Visitor}; use serde::{de, Deserialize, Deserializer}; use std::ffi::OsString; +use std::fmt::Write; use std::fmt::{Display, Formatter}; use std::io; use std::path::{Path, PathBuf}; @@ -29,7 +30,7 @@ use distribution_types::Resolution; use pep440_rs::Version; use pep508_rs::PackageName; use pypi_types::{Requirement, VerbatimParsedUrl}; -use uv_configuration::{BuildKind, ConfigSettings}; +use uv_configuration::{BuildKind, BuildOutput, ConfigSettings}; use uv_fs::{rename_with_retry, PythonExt, Simplified}; use uv_python::{Interpreter, PythonEnvironment}; use uv_types::{BuildContext, BuildIsolation, SourceBuildTrait}; @@ -93,7 +94,7 @@ pub enum Error { #[error("Failed to run `{0}`")] CommandFailed(PathBuf, #[source] io::Error), #[error("{message} with {exit_code}\n--- stdout:\n{stdout}\n--- stderr:\n{stderr}\n---")] - BuildBackend { + BuildBackendOutput { message: String, exit_code: ExitStatus, stdout: String, @@ -101,7 +102,7 @@ pub enum Error { }, /// Nudge the user towards installing the missing dev library #[error("{message} with {exit_code}\n--- stdout:\n{stdout}\n--- stderr:\n{stderr}\n---")] - MissingHeader { + MissingHeaderOutput { message: String, exit_code: ExitStatus, stdout: String, @@ -109,6 +110,18 @@ pub enum Error { #[source] missing_header_cause: MissingHeaderCause, }, + #[error("{message} with {exit_code}")] + BuildBackend { + message: String, + exit_code: ExitStatus, + }, + #[error("{message} with {exit_code}")] + MissingHeader { + message: String, + exit_code: ExitStatus, + #[source] + missing_header_cause: MissingHeaderCause, + }, #[error("Failed to build PATH for build script")] BuildScriptPath(#[source] env::JoinPathsError), } @@ -161,6 +174,7 @@ impl Error { fn from_command_output( message: String, output: &PythonRunnerOutput, + level: BuildOutput, version_id: impl Into, ) -> Self { // In the cases I've seen it was the 5th and 3rd last line (see test case), 10 seems like a reasonable cutoff. @@ -186,24 +200,71 @@ impl Error { }); if let Some(missing_library) = missing_library { - return Self::MissingHeader { + return match level { + BuildOutput::Stderr => Self::MissingHeader { + message, + exit_code: output.status, + missing_header_cause: MissingHeaderCause { + missing_library, + version_id: version_id.into(), + }, + }, + BuildOutput::Debug => Self::MissingHeaderOutput { + message, + exit_code: output.status, + stdout: output.stdout.iter().join("\n"), + stderr: output.stderr.iter().join("\n"), + missing_header_cause: MissingHeaderCause { + missing_library, + version_id: version_id.into(), + }, + }, + }; + } + + match level { + BuildOutput::Stderr => Self::BuildBackend { + message, + exit_code: output.status, + }, + BuildOutput::Debug => Self::BuildBackendOutput { message, exit_code: output.status, stdout: output.stdout.iter().join("\n"), stderr: output.stderr.iter().join("\n"), - missing_header_cause: MissingHeaderCause { - missing_library, - version_id: version_id.into(), - }, - }; + }, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Printer { + /// Send the build backend output to `stderr`. + Stderr, + /// Send the build backend output to `tracing`. + Debug, +} + +impl From for Printer { + fn from(output: BuildOutput) -> Self { + match output { + BuildOutput::Stderr => Self::Stderr, + BuildOutput::Debug => Self::Debug, } + } +} - Self::BuildBackend { - message, - exit_code: output.status, - stdout: output.stdout.iter().join("\n"), - stderr: output.stderr.iter().join("\n"), +impl Write for Printer { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + match self { + Self::Stderr => { + anstream::eprint!("{s}"); + } + Self::Debug => { + debug!("{}", s); + } } + Ok(()) } } @@ -380,6 +441,8 @@ pub struct SourceBuild { version_id: String, /// Whether we do a regular PEP 517 build or an PEP 660 editable build build_kind: BuildKind, + /// Whether to send build output to `stderr` or `tracing`, etc. + level: BuildOutput, /// Modified PATH that contains the `venv_bin`, `user_path` and `system_path` variables in that order modified_path: OsString, /// Environment variables to be passed in during metadata or wheel building @@ -405,6 +468,7 @@ impl SourceBuild { build_isolation: BuildIsolation<'_>, build_kind: BuildKind, mut environment_variables: FxHashMap, + level: BuildOutput, concurrent_builds: usize, ) -> Result { let temp_dir = build_context.cache().environment()?; @@ -488,7 +552,7 @@ impl SourceBuild { // Create the PEP 517 build environment. If build isolation is disabled, we assume the build // environment is already setup. - let runner = PythonRunner::new(concurrent_builds); + let runner = PythonRunner::new(concurrent_builds, level); if build_isolation.is_isolated(package_name) { create_pep517_build_environment( &runner, @@ -498,6 +562,7 @@ impl SourceBuild { build_context, &version_id, build_kind, + level, &config_settings, &environment_variables, &modified_path, @@ -513,6 +578,7 @@ impl SourceBuild { project, venv, build_kind, + level, config_settings, metadata_directory: None, version_id, @@ -698,6 +764,7 @@ impl SourceBuild { return Err(Error::from_command_output( format!("Build backend failed to determine metadata through `prepare_metadata_for_build_{}`", self.build_kind), &output, + self.level, &self.version_id, )); } @@ -827,6 +894,7 @@ impl SourceBuild { self.build_kind, self.build_kind, ), &output, + self.level, &self.version_id, )); } @@ -839,6 +907,7 @@ impl SourceBuild { self.build_kind, self.build_kind, ), &output, + self.level, &self.version_id, )); } @@ -871,6 +940,7 @@ async fn create_pep517_build_environment( build_context: &impl BuildContext, version_id: &str, build_kind: BuildKind, + level: BuildOutput, config_settings: &ConfigSettings, environment_variables: &FxHashMap, modified_path: &OsString, @@ -924,6 +994,7 @@ async fn create_pep517_build_environment( return Err(Error::from_command_output( format!("Build backend failed to determine extra requires with `build_{build_kind}()`"), &output, + level, version_id, )); } @@ -935,6 +1006,7 @@ async fn create_pep517_build_environment( "Build backend failed to read extra requires from `get_requires_for_build_{build_kind}`: {err}" ), &output, + level, version_id, ) })?; @@ -946,6 +1018,7 @@ async fn create_pep517_build_environment( "Build backend failed to return extra requires with `get_requires_for_build_{build_kind}`: {err}" ), &output, + level, version_id, ) })?; @@ -985,6 +1058,7 @@ async fn create_pep517_build_environment( #[derive(Debug)] struct PythonRunner { control: Semaphore, + level: BuildOutput, } #[derive(Debug)] @@ -995,10 +1069,11 @@ struct PythonRunnerOutput { } impl PythonRunner { - /// Create a `PythonRunner` with the provided concurrency limit. - fn new(concurrency: usize) -> PythonRunner { - PythonRunner { + /// Create a `PythonRunner` with the provided concurrency limit and output level. + fn new(concurrency: usize, level: BuildOutput) -> Self { + Self { control: Semaphore::new(concurrency), + level, } } @@ -1019,12 +1094,13 @@ impl PythonRunner { /// Read lines from a reader and store them in a buffer. async fn read_from( mut reader: tokio::io::Lines>, + mut printer: Printer, buffer: &mut Vec, ) -> io::Result<()> { loop { match reader.next_line().await? { Some(line) => { - debug!("{line}"); + let _ = writeln!(printer, "{line}"); buffer.push(line); } None => return Ok(()), @@ -1055,9 +1131,10 @@ impl PythonRunner { let stderr_reader = tokio::io::BufReader::new(child.stderr.take().unwrap()).lines(); // Asynchronously read from the in-memory pipes. + let printer = Printer::from(self.level); let result = tokio::join!( - read_from(stdout_reader, &mut stdout_buf), - read_from(stderr_reader, &mut stderr_buf), + read_from(stdout_reader, printer, &mut stdout_buf), + read_from(stderr_reader, printer, &mut stderr_buf), ); match result { (Ok(()), Ok(())) => {} @@ -1087,9 +1164,9 @@ impl PythonRunner { mod test { use std::process::ExitStatus; - use indoc::indoc; - use crate::{Error, PythonRunnerOutput}; + use indoc::indoc; + use uv_configuration::BuildOutput; #[test] fn missing_header() { @@ -1120,9 +1197,10 @@ mod test { let err = Error::from_command_output( "Failed building wheel through setup.py".to_string(), &output, + BuildOutput::Debug, "pygraphviz-1.11", ); - assert!(matches!(err, Error::MissingHeader { .. })); + assert!(matches!(err, Error::MissingHeaderOutput { .. })); // Unix uses exit status, Windows uses exit code. let formatted = err.to_string().replace("exit status: ", "exit code: "); insta::assert_snapshot!(formatted, @r###" @@ -1172,9 +1250,10 @@ mod test { let err = Error::from_command_output( "Failed building wheel through setup.py".to_string(), &output, + BuildOutput::Debug, "pygraphviz-1.11", ); - assert!(matches!(err, Error::MissingHeader { .. })); + assert!(matches!(err, Error::MissingHeaderOutput { .. })); // Unix uses exit status, Windows uses exit code. let formatted = err.to_string().replace("exit status: ", "exit code: "); insta::assert_snapshot!(formatted, @r###" @@ -1217,9 +1296,10 @@ mod test { let err = Error::from_command_output( "Failed building wheel through setup.py".to_string(), &output, + BuildOutput::Debug, "pygraphviz-1.11", ); - assert!(matches!(err, Error::MissingHeader { .. })); + assert!(matches!(err, Error::MissingHeaderOutput { .. })); // Unix uses exit status, Windows uses exit code. let formatted = err.to_string().replace("exit status: ", "exit code: "); insta::assert_snapshot!(formatted, @r###" diff --git a/crates/uv-configuration/src/build_options.rs b/crates/uv-configuration/src/build_options.rs index 360f130911b6..850eb2543276 100644 --- a/crates/uv-configuration/src/build_options.rs +++ b/crates/uv-configuration/src/build_options.rs @@ -25,6 +25,14 @@ impl Display for BuildKind { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum BuildOutput { + /// Send the build backend output to `stderr`. + Stderr, + /// Send the build backend output to `tracing`. + Debug, +} + #[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct BuildOptions { diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index b44837b4dd6d..ea23d86facb2 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -16,10 +16,10 @@ use pypi_types::Requirement; use uv_build::{SourceBuild, SourceBuildContext}; use uv_cache::Cache; use uv_client::RegistryClient; -use uv_configuration::Concurrency; use uv_configuration::{ BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, Reinstall, SourceStrategy, }; +use uv_configuration::{BuildOutput, Concurrency}; use uv_distribution::DistributionDatabase; use uv_git::GitResolver; use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages}; @@ -299,6 +299,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { version_id: &'data str, dist: Option<&'data SourceDist>, build_kind: BuildKind, + build_output: BuildOutput, ) -> Result { let dist_name = dist.map(distribution_types::Name::name); // Note we can only prevent builds by name for packages with names @@ -330,6 +331,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { self.build_isolation, build_kind, self.build_extra_env_vars.clone(), + build_output, self.concurrency.builds, ) .boxed_local() diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index b22fbccf6cfe..6e7acccb519d 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -27,7 +27,7 @@ use uv_cache::{ use uv_client::{ CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient, }; -use uv_configuration::BuildKind; +use uv_configuration::{BuildKind, BuildOutput}; use uv_extract::hash::Hasher; use uv_fs::{rename_with_retry, write_atomic, LockedFile}; use uv_types::{BuildContext, SourceBuildTrait}; @@ -1436,6 +1436,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } else { BuildKind::Wheel }, + BuildOutput::Debug, ) .await .map_err(Error::Build)? @@ -1477,6 +1478,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { } else { BuildKind::Wheel }, + BuildOutput::Debug, ) .await .map_err(Error::Build)?; diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index a7aaad7e8b7d..619ac7109ab9 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -7,7 +7,7 @@ use distribution_types::{CachedDist, IndexLocations, InstalledDist, Resolution, use pep508_rs::PackageName; use pypi_types::Requirement; use uv_cache::Cache; -use uv_configuration::{BuildKind, BuildOptions, SourceStrategy}; +use uv_configuration::{BuildKind, BuildOptions, BuildOutput, SourceStrategy}; use uv_git::GitResolver; use uv_python::PythonEnvironment; @@ -97,6 +97,7 @@ pub trait BuildContext { version_id: &'a str, dist: Option<&'a SourceDist>, build_kind: BuildKind, + build_output: BuildOutput, ) -> impl Future> + 'a; } diff --git a/crates/uv/src/commands/build.rs b/crates/uv/src/commands/build.rs index f3e32896f6f7..7b7d2f6bd621 100644 --- a/crates/uv/src/commands/build.rs +++ b/crates/uv/src/commands/build.rs @@ -12,7 +12,7 @@ use std::path::{Path, PathBuf}; use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; -use uv_configuration::{BuildKind, Concurrency}; +use uv_configuration::{BuildKind, BuildOutput, Concurrency}; use uv_dispatch::BuildDispatch; use uv_fs::{Simplified, CWD}; use uv_python::{ @@ -303,6 +303,8 @@ async fn build_impl( let assets = match plan { BuildPlan::SdistToWheel => { + anstream::eprintln!("{}", "Building source distribution...".bold()); + // Build the sdist. let builder = build_dispatch .setup_build( @@ -311,6 +313,7 @@ async fn build_impl( &version_id, dist, BuildKind::Sdist, + BuildOutput::Stderr, ) .await?; let sdist = builder.build(&output_dir).await?; @@ -331,6 +334,8 @@ async fn build_impl( Err(err) => return Err(err.into()), }; + anstream::eprintln!("{}", "Building wheel from source distribution...".bold()); + // Build a wheel from the source distribution. let builder = build_dispatch .setup_build( @@ -339,6 +344,7 @@ async fn build_impl( &version_id, dist, BuildKind::Wheel, + BuildOutput::Stderr, ) .await?; let wheel = builder.build(&output_dir).await?; @@ -346,6 +352,8 @@ async fn build_impl( BuiltDistributions::Both(output_dir.join(sdist), output_dir.join(wheel)) } BuildPlan::Sdist => { + anstream::eprintln!("{}", "Building source distribution...".bold()); + let builder = build_dispatch .setup_build( src.path(), @@ -353,6 +361,7 @@ async fn build_impl( &version_id, dist, BuildKind::Sdist, + BuildOutput::Stderr, ) .await?; let sdist = builder.build(&output_dir).await?; @@ -360,6 +369,8 @@ async fn build_impl( BuiltDistributions::Sdist(output_dir.join(sdist)) } BuildPlan::Wheel => { + anstream::eprintln!("{}", "Building wheel...".bold()); + let builder = build_dispatch .setup_build( src.path(), @@ -367,6 +378,7 @@ async fn build_impl( &version_id, dist, BuildKind::Wheel, + BuildOutput::Stderr, ) .await?; let wheel = builder.build(&output_dir).await?; @@ -374,6 +386,7 @@ async fn build_impl( BuiltDistributions::Wheel(output_dir.join(wheel)) } BuildPlan::SdistAndWheel => { + anstream::eprintln!("{}", "Building source distribution...".bold()); let builder = build_dispatch .setup_build( src.path(), @@ -381,10 +394,12 @@ async fn build_impl( &version_id, dist, BuildKind::Sdist, + BuildOutput::Stderr, ) .await?; let sdist = builder.build(&output_dir).await?; + anstream::eprintln!("{}", "Building wheel...".bold()); let builder = build_dispatch .setup_build( src.path(), @@ -392,6 +407,7 @@ async fn build_impl( &version_id, dist, BuildKind::Wheel, + BuildOutput::Stderr, ) .await?; let wheel = builder.build(&output_dir).await?; @@ -399,6 +415,8 @@ async fn build_impl( BuiltDistributions::Both(output_dir.join(&sdist), output_dir.join(&wheel)) } BuildPlan::WheelFromSdist => { + anstream::eprintln!("{}", "Building source distribution from wheel...".bold()); + // Extract the source distribution into a temporary directory. let reader = fs_err::tokio::File::open(src.path()).await?; let ext = SourceDistExtension::from_path(src.path()).map_err(|err| { @@ -422,6 +440,7 @@ async fn build_impl( &version_id, dist, BuildKind::Wheel, + BuildOutput::Stderr, ) .await?; let wheel = builder.build(&output_dir).await?; diff --git a/crates/uv/tests/build.rs b/crates/uv/tests/build.rs index 38d27312f492..05c88acbd575 100644 --- a/crates/uv/tests/build.rs +++ b/crates/uv/tests/build.rs @@ -10,6 +10,15 @@ mod common; #[test] fn build() -> Result<()> { let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + ]) + .collect::>(); let project = context.temp_dir.child("project"); @@ -29,14 +38,88 @@ fn build() -> Result<()> { )?; project.child("src").child("__init__.py").touch()?; + project.child("README").touch()?; // Build the specified path. - uv_snapshot!(context.filters(), context.build().arg("project"), @r###" + uv_snapshot!(&filters, context.build().arg("project"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- + Building source distribution... + running egg_info + creating src/project.egg-info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + writing manifest file 'src/project.egg-info/SOURCES.txt' + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running sdist + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running check + creating project-0.1.0 + creating project-0.1.0/src + creating project-0.1.0/src/project.egg-info + copying files to project-0.1.0... + copying README -> project-0.1.0 + copying pyproject.toml -> project-0.1.0 + copying src/__init__.py -> project-0.1.0/src + copying src/project.egg-info/PKG-INFO -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/dependency_links.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/requires.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/top_level.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + Writing project-0.1.0/setup.cfg + Creating tar archive + removing 'project-0.1.0' (and everything under it) + Building wheel from source distribution... + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running bdist_wheel + running build + running build_py + creating build + creating build/lib + copying src/__init__.py -> build/lib + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + installing to build/bdist.linux-x86_64/wheel + running install + running install_lib + creating build/bdist.linux-x86_64 + creating build/bdist.linux-x86_64/wheel + copying build/lib/__init__.py -> build/bdist.linux-x86_64/wheel + running install_egg_info + Copying src/project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.12.egg-info + running install_scripts + creating build/bdist.linux-x86_64/wheel/project-0.1.0.dist-info/WHEEL + creating '[TEMP_DIR]/project/dist/[TMP]/wheel' to it + adding '__init__.py' + adding 'project-0.1.0.dist-info/METADATA' + adding 'project-0.1.0.dist-info/WHEEL' + adding 'project-0.1.0.dist-info/top_level.txt' + adding 'project-0.1.0.dist-info/RECORD' + removing build/bdist.linux-x86_64/wheel Successfully built project/dist/project-0.1.0.tar.gz and project/dist/project-0.1.0-py3-none-any.whl "###); @@ -52,12 +135,83 @@ fn build() -> Result<()> { fs_err::remove_dir_all(project.child("dist"))?; // Build the current working directory. - uv_snapshot!(context.filters(), context.build().current_dir(project.path()), @r###" + uv_snapshot!(&filters, context.build().current_dir(project.path()), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- + Building source distribution... + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running sdist + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running check + creating project-0.1.0 + creating project-0.1.0/src + creating project-0.1.0/src/project.egg-info + copying files to project-0.1.0... + copying README -> project-0.1.0 + copying pyproject.toml -> project-0.1.0 + copying src/__init__.py -> project-0.1.0/src + copying src/project.egg-info/PKG-INFO -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/dependency_links.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/requires.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/top_level.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + Writing project-0.1.0/setup.cfg + Creating tar archive + removing 'project-0.1.0' (and everything under it) + Building wheel from source distribution... + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running bdist_wheel + running build + running build_py + creating build + creating build/lib + copying src/__init__.py -> build/lib + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + installing to build/bdist.linux-x86_64/wheel + running install + running install_lib + creating build/bdist.linux-x86_64 + creating build/bdist.linux-x86_64/wheel + copying build/lib/__init__.py -> build/bdist.linux-x86_64/wheel + running install_egg_info + Copying src/project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.12.egg-info + running install_scripts + creating build/bdist.linux-x86_64/wheel/project-0.1.0.dist-info/WHEEL + creating '[TEMP_DIR]/project/dist/[TMP]/wheel' to it + adding '__init__.py' + adding 'project-0.1.0.dist-info/METADATA' + adding 'project-0.1.0.dist-info/WHEEL' + adding 'project-0.1.0.dist-info/top_level.txt' + adding 'project-0.1.0.dist-info/RECORD' + removing build/bdist.linux-x86_64/wheel Successfully built dist/project-0.1.0.tar.gz and dist/project-0.1.0-py3-none-any.whl "###); @@ -73,22 +227,95 @@ fn build() -> Result<()> { fs_err::remove_dir_all(project.child("dist"))?; // Error if there's nothing to build. - uv_snapshot!(context.filters(), context.build(), @r###" + uv_snapshot!(&filters, context.build(), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- + Building source distribution... error: [TEMP_DIR]/ does not appear to be a Python project, as neither `pyproject.toml` nor `setup.py` are present in the directory + "###); // Build to a specified path. - uv_snapshot!(context.filters(), context.build().arg("--out-dir").arg("out").current_dir(project.path()), @r###" + uv_snapshot!(&filters, context.build().arg("--out-dir").arg("out").current_dir(project.path()), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- + Building source distribution... + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running sdist + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running check + creating project-0.1.0 + creating project-0.1.0/src + creating project-0.1.0/src/project.egg-info + copying files to project-0.1.0... + copying README -> project-0.1.0 + copying pyproject.toml -> project-0.1.0 + copying src/__init__.py -> project-0.1.0/src + copying src/project.egg-info/PKG-INFO -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/dependency_links.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/requires.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/top_level.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + Writing project-0.1.0/setup.cfg + Creating tar archive + removing 'project-0.1.0' (and everything under it) + Building wheel from source distribution... + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running bdist_wheel + running build + running build_py + creating build + creating build/lib + copying src/__init__.py -> build/lib + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + installing to build/bdist.linux-x86_64/wheel + running install + running install_lib + creating build/bdist.linux-x86_64 + creating build/bdist.linux-x86_64/wheel + copying build/lib/__init__.py -> build/bdist.linux-x86_64/wheel + running install_egg_info + Copying src/project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.12.egg-info + running install_scripts + creating build/bdist.linux-x86_64/wheel/project-0.1.0.dist-info/WHEEL + creating '[TEMP_DIR]/project/out/[TMP]/wheel' to it + adding '__init__.py' + adding 'project-0.1.0.dist-info/METADATA' + adding 'project-0.1.0.dist-info/WHEEL' + adding 'project-0.1.0.dist-info/top_level.txt' + adding 'project-0.1.0.dist-info/RECORD' + removing build/bdist.linux-x86_64/wheel Successfully built out/project-0.1.0.tar.gz and out/project-0.1.0-py3-none-any.whl "###); @@ -107,6 +334,15 @@ fn build() -> Result<()> { #[test] fn sdist() -> Result<()> { let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + ]) + .collect::>(); let project = context.temp_dir.child("project"); @@ -126,14 +362,50 @@ fn sdist() -> Result<()> { )?; project.child("src").child("__init__.py").touch()?; + project.child("README").touch()?; // Build the specified path. - uv_snapshot!(context.filters(), context.build().arg("--sdist").current_dir(&project), @r###" + uv_snapshot!(&filters, context.build().arg("--sdist").current_dir(&project), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- + Building source distribution... + running egg_info + creating src/project.egg-info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + writing manifest file 'src/project.egg-info/SOURCES.txt' + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running sdist + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running check + creating project-0.1.0 + creating project-0.1.0/src + creating project-0.1.0/src/project.egg-info + copying files to project-0.1.0... + copying README -> project-0.1.0 + copying pyproject.toml -> project-0.1.0 + copying src/__init__.py -> project-0.1.0/src + copying src/project.egg-info/PKG-INFO -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/dependency_links.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/requires.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/top_level.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + Writing project-0.1.0/setup.cfg + Creating tar archive + removing 'project-0.1.0' (and everything under it) Successfully built dist/project-0.1.0.tar.gz "###); @@ -152,6 +424,15 @@ fn sdist() -> Result<()> { #[test] fn wheel() -> Result<()> { let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + ]) + .collect::>(); let project = context.temp_dir.child("project"); @@ -171,14 +452,55 @@ fn wheel() -> Result<()> { )?; project.child("src").child("__init__.py").touch()?; + project.child("README").touch()?; // Build the specified path. - uv_snapshot!(context.filters(), context.build().arg("--wheel").current_dir(&project), @r###" + uv_snapshot!(&filters, context.build().arg("--wheel").current_dir(&project), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- + Building wheel... + running egg_info + creating src/project.egg-info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + writing manifest file 'src/project.egg-info/SOURCES.txt' + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running bdist_wheel + running build + running build_py + creating build + creating build/lib + copying src/__init__.py -> build/lib + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + installing to build/bdist.linux-x86_64/wheel + running install + running install_lib + creating build/bdist.linux-x86_64 + creating build/bdist.linux-x86_64/wheel + copying build/lib/__init__.py -> build/bdist.linux-x86_64/wheel + running install_egg_info + Copying src/project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.12.egg-info + running install_scripts + creating build/bdist.linux-x86_64/wheel/project-0.1.0.dist-info/WHEEL + creating '[TEMP_DIR]/project/dist/[TMP]/wheel' to it + adding '__init__.py' + adding 'project-0.1.0.dist-info/METADATA' + adding 'project-0.1.0.dist-info/WHEEL' + adding 'project-0.1.0.dist-info/top_level.txt' + adding 'project-0.1.0.dist-info/RECORD' + removing build/bdist.linux-x86_64/wheel Successfully built dist/project-0.1.0-py3-none-any.whl "###); @@ -197,6 +519,15 @@ fn wheel() -> Result<()> { #[test] fn sdist_wheel() -> Result<()> { let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + ]) + .collect::>(); let project = context.temp_dir.child("project"); @@ -216,14 +547,88 @@ fn sdist_wheel() -> Result<()> { )?; project.child("src").child("__init__.py").touch()?; + project.child("README").touch()?; // Build the specified path. - uv_snapshot!(context.filters(), context.build().arg("--sdist").arg("--wheel").current_dir(&project), @r###" + uv_snapshot!(&filters, context.build().arg("--sdist").arg("--wheel").current_dir(&project), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- + Building source distribution... + running egg_info + creating src/project.egg-info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + writing manifest file 'src/project.egg-info/SOURCES.txt' + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running sdist + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running check + creating project-0.1.0 + creating project-0.1.0/src + creating project-0.1.0/src/project.egg-info + copying files to project-0.1.0... + copying README -> project-0.1.0 + copying pyproject.toml -> project-0.1.0 + copying src/__init__.py -> project-0.1.0/src + copying src/project.egg-info/PKG-INFO -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/dependency_links.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/requires.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/top_level.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + Writing project-0.1.0/setup.cfg + Creating tar archive + removing 'project-0.1.0' (and everything under it) + Building wheel... + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running bdist_wheel + running build + running build_py + creating build + creating build/lib + copying src/__init__.py -> build/lib + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + installing to build/bdist.linux-x86_64/wheel + running install + running install_lib + creating build/bdist.linux-x86_64 + creating build/bdist.linux-x86_64/wheel + copying build/lib/__init__.py -> build/bdist.linux-x86_64/wheel + running install_egg_info + Copying src/project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.12.egg-info + running install_scripts + creating build/bdist.linux-x86_64/wheel/project-0.1.0.dist-info/WHEEL + creating '[TEMP_DIR]/project/dist/[TMP]/wheel' to it + adding '__init__.py' + adding 'project-0.1.0.dist-info/METADATA' + adding 'project-0.1.0.dist-info/WHEEL' + adding 'project-0.1.0.dist-info/top_level.txt' + adding 'project-0.1.0.dist-info/RECORD' + removing build/bdist.linux-x86_64/wheel Successfully built dist/project-0.1.0.tar.gz and dist/project-0.1.0-py3-none-any.whl "###); @@ -242,6 +647,15 @@ fn sdist_wheel() -> Result<()> { #[test] fn wheel_from_sdist() -> Result<()> { let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + ]) + .collect::>(); let project = context.temp_dir.child("project"); @@ -261,14 +675,50 @@ fn wheel_from_sdist() -> Result<()> { )?; project.child("src").child("__init__.py").touch()?; + project.child("README").touch()?; // Build the sdist. - uv_snapshot!(context.filters(), context.build().arg("--sdist").current_dir(&project), @r###" + uv_snapshot!(&filters, context.build().arg("--sdist").current_dir(&project), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- + Building source distribution... + running egg_info + creating src/project.egg-info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + writing manifest file 'src/project.egg-info/SOURCES.txt' + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running sdist + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running check + creating project-0.1.0 + creating project-0.1.0/src + creating project-0.1.0/src/project.egg-info + copying files to project-0.1.0... + copying README -> project-0.1.0 + copying pyproject.toml -> project-0.1.0 + copying src/__init__.py -> project-0.1.0/src + copying src/project.egg-info/PKG-INFO -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/dependency_links.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/requires.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/top_level.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + Writing project-0.1.0/setup.cfg + Creating tar archive + removing 'project-0.1.0' (and everything under it) Successfully built dist/project-0.1.0.tar.gz "###); @@ -282,7 +732,7 @@ fn wheel_from_sdist() -> Result<()> { .assert(predicate::path::missing()); // Error if `--wheel` is not specified. - uv_snapshot!(context.filters(), context.build().arg("./dist/project-0.1.0.tar.gz").current_dir(&project), @r###" + uv_snapshot!(&filters, context.build().arg("./dist/project-0.1.0.tar.gz").current_dir(&project), @r###" success: false exit_code: 2 ----- stdout ----- @@ -292,7 +742,7 @@ fn wheel_from_sdist() -> Result<()> { "###); // Error if `--sdist` is specified. - uv_snapshot!(context.filters(), context.build().arg("./dist/project-0.1.0.tar.gz").arg("--sdist").current_dir(&project), @r###" + uv_snapshot!(&filters, context.build().arg("./dist/project-0.1.0.tar.gz").arg("--sdist").current_dir(&project), @r###" success: false exit_code: 2 ----- stdout ----- @@ -302,12 +752,50 @@ fn wheel_from_sdist() -> Result<()> { "###); // Build the wheel from the sdist. - uv_snapshot!(context.filters(), context.build().arg("./dist/project-0.1.0.tar.gz").arg("--wheel").current_dir(&project), @r###" + uv_snapshot!(&filters, context.build().arg("./dist/project-0.1.0.tar.gz").arg("--wheel").current_dir(&project), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- + Building source distribution from wheel... + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running bdist_wheel + running build + running build_py + creating build + creating build/lib + copying src/__init__.py -> build/lib + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + installing to build/bdist.linux-x86_64/wheel + running install + running install_lib + creating build/bdist.linux-x86_64 + creating build/bdist.linux-x86_64/wheel + copying build/lib/__init__.py -> build/bdist.linux-x86_64/wheel + running install_egg_info + Copying src/project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.12.egg-info + running install_scripts + creating build/bdist.linux-x86_64/wheel/project-0.1.0.dist-info/WHEEL + creating '[TEMP_DIR]/project/dist/[TMP]/wheel' to it + adding '__init__.py' + adding 'project-0.1.0.dist-info/METADATA' + adding 'project-0.1.0.dist-info/WHEEL' + adding 'project-0.1.0.dist-info/top_level.txt' + adding 'project-0.1.0.dist-info/RECORD' + removing build/bdist.linux-x86_64/wheel Successfully built dist/project-0.1.0-py3-none-any.whl "###); @@ -321,14 +809,87 @@ fn wheel_from_sdist() -> Result<()> { .assert(predicate::path::is_file()); // Passing a wheel is an error. - uv_snapshot!(context.filters(), context.build().arg("./dist/project-0.1.0-py3-none-any.whl").arg("--wheel").current_dir(&project), @r###" + uv_snapshot!(&filters, context.build().arg("./dist/project-0.1.0-py3-none-any.whl").arg("--wheel").current_dir(&project), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- + Building source distribution from wheel... error: `dist/project-0.1.0-py3-none-any.whl` is not a valid build source. Expected to receive a source directory, or a source distribution ending in one of: `.zip`, `.tar.gz`, `.tar.bz2`, `.tar.xz`, or `.tar.zst`. "###); Ok(()) } + +#[test] +fn fail() -> Result<()> { + let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + ]) + .collect::>(); + + let project = context.temp_dir.child("project"); + + let pyproject_toml = project.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#, + )?; + + project.child("src").child("__init__.py").touch()?; + project.child("README").touch()?; + + project.child("setup.py").write_str( + r#" + from setuptools import setup + + setup( + name="project", + version="0.1.0", + packages=["project"], + install_requires=["foo==3.7.0"], + ) + "#, + )?; + + // Build the specified path. + uv_snapshot!(&filters, context.build().arg("project"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Building source distribution... + Traceback (most recent call last): + File "", line 14, in + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 328, in get_requires_for_build_sdist + return self._get_build_requires(config_settings, requirements=[]) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires + self.run_setup() + File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup + exec(code, locals()) + File "", line 2 + from setuptools import setup + IndentationError: unexpected indent + error: Build backend failed to determine extra requires with `build_sdist()` with exit status: 1 + "###); + + Ok(()) +}