Build backend: Add `--list` option (#9610)

Add the `uv build --list`, a "subcommand" to list the files that would
be included when building a distribution. It does not build the
distribution, except when a source dist is required for source dist ->
wheel. This is an important debugging tool for the include and exclude
options: Did i actually include the files I wanted, or am i shipping a
broken distribution? Are there any temporary files I still need to
exclude?

Cargo offers this as `cargo package --list`.

`--list` is preview-exclusive, since it requires the fast path, which I
also put into preview.

Examples:


![image](https://github.com/user-attachments/assets/55e3f169-3051-4217-987d-0cb01ae5050e)


![image](https://github.com/user-attachments/assets/1da75245-358d-4bee-9199-f720089f0a70)


![image](https://github.com/user-attachments/assets/4d97a893-552e-43a1-9c22-78fc67f1e9f5)

I'll fix the error handling in a follow-up.

Tagging as enhancement because it changes the stable output slightly
(two lines instead of one).

CC @charliermarsh for uv-wide consistency in the stdout/stderr handling.
This commit is contained in:
konsti 2024-12-04 09:52:27 +01:00 committed by GitHub
parent d7b74f964e
commit 7d82cbff01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 570 additions and 177 deletions

View File

@ -2175,6 +2175,18 @@ pub struct BuildArgs {
#[arg(long)] #[arg(long)]
pub wheel: bool, pub wheel: bool,
/// When using the uv build backend, list the files that would be included when building.
///
/// Skips building the actual distribution, except when the source distribution is needed to
/// build the wheel. The file list is collected directly without a PEP 517 environment. It only
/// works with the uv build backend, there is no PEP 517 file list build hook.
///
/// This option can be combined with `--sdist` and `--wheel` for inspecting different build
/// paths.
// Hidden while in preview.
#[arg(long, hide = true)]
pub list: bool,
#[arg(long, overrides_with("no_build_logs"), hide = true)] #[arg(long, overrides_with("no_build_logs"), hide = true)]
pub build_logs: bool, pub build_logs: bool,
@ -2187,7 +2199,7 @@ pub struct BuildArgs {
/// By default, uv won't create a PEP 517 build environment for packages using the uv build /// By default, uv won't create a PEP 517 build environment for packages using the uv build
/// backend, but use a fast path that calls into the build backend directly. This option forces /// backend, but use a fast path that calls into the build backend directly. This option forces
/// always using PEP 517. /// always using PEP 517.
#[arg(long)] #[arg(long, conflicts_with = "list")]
pub force_pep517: bool, pub force_pep517: bool,
/// Constrain build dependencies using the given requirements files when building /// Constrain build dependencies using the given requirements files when building

View File

@ -4,13 +4,9 @@ use std::io;
use std::io::Write as _; use std::io::Write as _;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::Result; use anyhow::{bail, Context, Result};
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use tracing::{debug, instrument, trace}; use tracing::{debug, instrument, trace};
use uv_distribution_filename::SourceDistExtension;
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, SourceDist};
use uv_install_wheel::linker::LinkMode;
use uv_auth::store_credentials; use uv_auth::store_credentials;
use uv_build_backend::PyProjectToml; use uv_build_backend::PyProjectToml;
@ -18,10 +14,14 @@ use uv_cache::{Cache, CacheBucket};
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{ use uv_configuration::{
BuildKind, BuildOptions, BuildOutput, Concurrency, ConfigSettings, Constraints, BuildKind, BuildOptions, BuildOutput, Concurrency, ConfigSettings, Constraints,
HashCheckingMode, IndexStrategy, KeyringProviderType, LowerBound, SourceStrategy, TrustedHost, HashCheckingMode, IndexStrategy, KeyringProviderType, LowerBound, PreviewMode, SourceStrategy,
TrustedHost,
}; };
use uv_dispatch::{BuildDispatch, SharedState}; use uv_dispatch::{BuildDispatch, SharedState};
use uv_fs::Simplified; use uv_distribution_filename::SourceDistExtension;
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, SourceDist};
use uv_fs::{relative_to, Simplified};
use uv_install_wheel::linker::LinkMode;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_python::{ use uv_python::{
EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation, EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation,
@ -51,6 +51,7 @@ pub(crate) async fn build_frontend(
output_dir: Option<PathBuf>, output_dir: Option<PathBuf>,
sdist: bool, sdist: bool,
wheel: bool, wheel: bool,
list: bool,
build_logs: bool, build_logs: bool,
force_pep517: bool, force_pep517: bool,
build_constraints: Vec<RequirementsSource>, build_constraints: Vec<RequirementsSource>,
@ -67,6 +68,7 @@ pub(crate) async fn build_frontend(
allow_insecure_host: &[TrustedHost], allow_insecure_host: &[TrustedHost],
cache: &Cache, cache: &Cache,
printer: Printer, printer: Printer,
preview: PreviewMode,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
let build_result = build_impl( let build_result = build_impl(
project_dir, project_dir,
@ -76,6 +78,7 @@ pub(crate) async fn build_frontend(
output_dir.as_deref(), output_dir.as_deref(),
sdist, sdist,
wheel, wheel,
list,
build_logs, build_logs,
force_pep517, force_pep517,
&build_constraints, &build_constraints,
@ -92,6 +95,7 @@ pub(crate) async fn build_frontend(
allow_insecure_host, allow_insecure_host,
cache, cache,
printer, printer,
preview,
) )
.await?; .await?;
@ -119,6 +123,7 @@ async fn build_impl(
output_dir: Option<&Path>, output_dir: Option<&Path>,
sdist: bool, sdist: bool,
wheel: bool, wheel: bool,
list: bool,
build_logs: bool, build_logs: bool,
force_pep517: bool, force_pep517: bool,
build_constraints: &[RequirementsSource], build_constraints: &[RequirementsSource],
@ -135,7 +140,17 @@ async fn build_impl(
allow_insecure_host: &[TrustedHost], allow_insecure_host: &[TrustedHost],
cache: &Cache, cache: &Cache,
printer: Printer, printer: Printer,
preview: PreviewMode,
) -> Result<BuildResult> { ) -> Result<BuildResult> {
if list && preview.is_disabled() {
// We need the fast path for list and that is preview only.
writeln!(
printer.stderr(),
"The `--list` option is only available in preview mode; add the `--preview` flag to use `--list`"
)?;
return Ok(BuildResult::Failure);
}
// Extract the resolver settings. // Extract the resolver settings.
let ResolverSettingsRef { let ResolverSettingsRef {
index_locations, index_locations,
@ -286,9 +301,11 @@ async fn build_impl(
build_options, build_options,
sdist, sdist,
wheel, wheel,
list,
dependency_metadata, dependency_metadata,
link_mode, link_mode,
config_setting, config_setting,
preview,
); );
async { async {
let result = future.await; let result = future.await;
@ -300,30 +317,11 @@ async fn build_impl(
let mut success = true; let mut success = true;
for (source, result) in results { for (source, result) in results {
match result { match result {
Ok(assets) => match assets { Ok(messages) => {
BuiltDistributions::Wheel(wheel) => { for message in messages {
writeln!( message.print(printer)?;
printer.stderr(),
"Successfully built {}",
wheel.user_display().bold().cyan()
)?;
} }
BuiltDistributions::Sdist(sdist) => { }
writeln!(
printer.stderr(),
"Successfully built {}",
sdist.user_display().bold().cyan()
)?;
}
BuiltDistributions::Both(sdist, wheel) => {
writeln!(
printer.stderr(),
"Successfully built {} and {}",
sdist.user_display().bold().cyan(),
wheel.user_display().bold().cyan()
)?;
}
},
Err(err) => { Err(err) => {
#[derive(Debug, miette::Diagnostic, thiserror::Error)] #[derive(Debug, miette::Diagnostic, thiserror::Error)]
#[error("Failed to build `{source}`", source = source.cyan())] #[error("Failed to build `{source}`", source = source.cyan())]
@ -383,10 +381,12 @@ async fn build_package(
build_options: &BuildOptions, build_options: &BuildOptions,
sdist: bool, sdist: bool,
wheel: bool, wheel: bool,
list: bool,
dependency_metadata: &DependencyMetadata, dependency_metadata: &DependencyMetadata,
link_mode: LinkMode, link_mode: LinkMode,
config_setting: &ConfigSettings, config_setting: &ConfigSettings,
) -> Result<BuiltDistributions> { preview: PreviewMode,
) -> Result<Vec<BuildMessage>> {
let output_dir = if let Some(output_dir) = output_dir { let output_dir = if let Some(output_dir) = output_dir {
Cow::Owned(std::path::absolute(output_dir)?) Cow::Owned(std::path::absolute(output_dir)?)
} else { } else {
@ -538,7 +538,22 @@ async fn build_package(
// Check if the build backend is matching uv version that allows calling in the uv build backend // Check if the build backend is matching uv version that allows calling in the uv build backend
// directly. // directly.
let fast_path = !force_pep517 && check_fast_path(source.path()); let build_action = if list {
if force_pep517 {
bail!("Can't use `--force-pep517` with `--list`");
}
if !check_fast_path(source.path()) {
// TODO(konsti): Provide more context on what mismatched
bail!("Can only use `--list` with the uv backend");
}
BuildAction::List
} else if preview.is_enabled() && !force_pep517 && check_fast_path(source.path()) {
BuildAction::FastPath
} else {
BuildAction::Pep517
};
// Prepare some common arguments for the build. // Prepare some common arguments for the build.
let dist = None; let dist = None;
@ -556,15 +571,36 @@ async fn build_package(
Printer::Quiet => BuildOutput::Quiet, Printer::Quiet => BuildOutput::Quiet,
}; };
let assets = match plan { let mut build_results = Vec::new();
match plan {
BuildPlan::SdistToWheel => { BuildPlan::SdistToWheel => {
let sdist = build_sdist( // Even when listing files, we still need to build the source distribution for the wheel
// build.
if list {
let sdist_list = build_sdist(
source.path(),
&output_dir,
build_action,
&source,
printer,
"source distribution",
&build_dispatch,
sources,
dist,
subdirectory,
version_id,
build_output,
)
.await?;
build_results.push(sdist_list);
}
let sdist_build = build_sdist(
source.path(), source.path(),
&output_dir, &output_dir,
fast_path, build_action.force_build(),
&source, &source,
printer, printer,
"Building source distribution", "source distribution",
&build_dispatch, &build_dispatch,
sources, sources,
dist, dist,
@ -573,9 +609,10 @@ async fn build_package(
build_output, build_output,
) )
.await?; .await?;
build_results.push(sdist_build.clone());
// Extract the source distribution into a temporary directory. // Extract the source distribution into a temporary directory.
let path = output_dir.join(&sdist); let path = output_dir.join(sdist_build.filename());
let reader = fs_err::tokio::File::open(&path).await?; let reader = fs_err::tokio::File::open(&path).await?;
let ext = SourceDistExtension::from_path(path.as_path()).map_err(|err| { let ext = SourceDistExtension::from_path(path.as_path()).map_err(|err| {
anyhow::anyhow!("`{}` is not a valid source distribution, as it ends with an unsupported extension. Expected one of: {err}.", path.user_display()) anyhow::anyhow!("`{}` is not a valid source distribution, as it ends with an unsupported extension. Expected one of: {err}.", path.user_display())
@ -590,13 +627,13 @@ async fn build_package(
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
let wheel = build_wheel( let wheel_build = build_wheel(
&extracted, &extracted,
&output_dir, &output_dir,
fast_path, build_action,
&source, &source,
printer, printer,
"Building wheel from source distribution", "wheel from source distribution",
&build_dispatch, &build_dispatch,
sources, sources,
dist, dist,
@ -605,17 +642,16 @@ async fn build_package(
build_output, build_output,
) )
.await?; .await?;
build_results.push(wheel_build);
BuiltDistributions::Both(output_dir.join(sdist), output_dir.join(wheel))
} }
BuildPlan::Sdist => { BuildPlan::Sdist => {
let sdist = build_sdist( let sdist_build = build_sdist(
source.path(), source.path(),
&output_dir, &output_dir,
fast_path, build_action,
&source, &source,
printer, printer,
"Building source distribution", "source distribution",
&build_dispatch, &build_dispatch,
sources, sources,
dist, dist,
@ -624,17 +660,16 @@ async fn build_package(
build_output, build_output,
) )
.await?; .await?;
build_results.push(sdist_build);
BuiltDistributions::Sdist(output_dir.join(sdist))
} }
BuildPlan::Wheel => { BuildPlan::Wheel => {
let wheel = build_wheel( let wheel_build = build_wheel(
source.path(), source.path(),
&output_dir, &output_dir,
fast_path, build_action,
&source, &source,
printer, printer,
"Building wheel", "wheel",
&build_dispatch, &build_dispatch,
sources, sources,
dist, dist,
@ -643,17 +678,16 @@ async fn build_package(
build_output, build_output,
) )
.await?; .await?;
build_results.push(wheel_build);
BuiltDistributions::Wheel(output_dir.join(wheel))
} }
BuildPlan::SdistAndWheel => { BuildPlan::SdistAndWheel => {
let sdist = build_sdist( let sdist_build = build_sdist(
source.path(), source.path(),
&output_dir, &output_dir,
fast_path, build_action,
&source, &source,
printer, printer,
"Building source distribution", "source distribution",
&build_dispatch, &build_dispatch,
sources, sources,
dist, dist,
@ -662,14 +696,15 @@ async fn build_package(
build_output, build_output,
) )
.await?; .await?;
build_results.push(sdist_build);
let wheel = build_wheel( let wheel_build = build_wheel(
source.path(), source.path(),
&output_dir, &output_dir,
fast_path, build_action,
&source, &source,
printer, printer,
"Building wheel", "wheel",
&build_dispatch, &build_dispatch,
sources, sources,
dist, dist,
@ -678,8 +713,7 @@ async fn build_package(
build_output, build_output,
) )
.await?; .await?;
build_results.push(wheel_build);
BuiltDistributions::Both(output_dir.join(&sdist), output_dir.join(&wheel))
} }
BuildPlan::WheelFromSdist => { BuildPlan::WheelFromSdist => {
// Extract the source distribution into a temporary directory. // Extract the source distribution into a temporary directory.
@ -700,13 +734,13 @@ async fn build_package(
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
let wheel = build_wheel( let wheel_build = build_wheel(
&extracted, &extracted,
&output_dir, &output_dir,
fast_path, build_action,
&source, &source,
printer, printer,
"Building wheel from source distribution", "wheel from source distribution",
&build_dispatch, &build_dispatch,
sources, sources,
dist, dist,
@ -715,12 +749,33 @@ async fn build_package(
build_output, build_output,
) )
.await?; .await?;
build_results.push(wheel_build);
BuiltDistributions::Wheel(output_dir.join(wheel))
} }
}; }
Ok(assets) Ok(build_results)
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum BuildAction {
/// Only list the files that would be included, don't actually build.
List,
/// Build by calling directly into the build backend.
FastPath,
/// Build through the PEP 517 hooks.
Pep517,
}
impl BuildAction {
/// If in list mode, still build the distribution.
fn force_build(self) -> Self {
match self {
// List is only available for the uv build backend
Self::List => Self::FastPath,
Self::FastPath => Self::FastPath,
Self::Pep517 => Self::Pep517,
}
}
} }
/// Build a source distribution, either through PEP 517 or through the fast path. /// Build a source distribution, either through PEP 517 or through the fast path.
@ -728,10 +783,10 @@ async fn build_package(
async fn build_sdist( async fn build_sdist(
source_tree: &Path, source_tree: &Path,
output_dir: &Path, output_dir: &Path,
fast_path: bool, action: BuildAction,
source: &AnnotatedSource<'_>, source: &AnnotatedSource<'_>,
printer: Printer, printer: Printer,
message: &str, build_kind_message: &str,
// Below is only used with PEP 517 builds // Below is only used with PEP 517 builds
build_dispatch: &BuildDispatch<'_>, build_dispatch: &BuildDispatch<'_>,
sources: SourceStrategy, sources: SourceStrategy,
@ -739,46 +794,80 @@ async fn build_sdist(
subdirectory: Option<&Path>, subdirectory: Option<&Path>,
version_id: Option<&str>, version_id: Option<&str>,
build_output: BuildOutput, build_output: BuildOutput,
) -> Result<String> { ) -> Result<BuildMessage> {
let sdist = if fast_path { let build_result = match action {
writeln!( BuildAction::List => {
printer.stderr(), let source_tree_ = source_tree.to_path_buf();
"{}", let (filename, file_list) = tokio::task::spawn_blocking(move || {
format!( uv_build_backend::list_source_dist(&source_tree_, uv_version::version())
"{}{} (uv build backend)...", })
source.message_prefix(), .await??;
message
) BuildMessage::List {
.bold() filename: filename.to_string(),
)?; source_tree: source_tree.to_path_buf(),
let source_tree = source_tree.to_path_buf(); file_list,
let output_dir = output_dir.to_path_buf(); }
tokio::task::spawn_blocking(move || { }
uv_build_backend::build_source_dist(&source_tree, &output_dir, uv_version::version()) BuildAction::FastPath => {
}) writeln!(
.await?? printer.stderr(),
.to_string() "{}",
} else { format!(
writeln!( "{}Building {} (uv build backend)...",
printer.stderr(), source.message_prefix(),
"{}", build_kind_message
format!("{}{}...", source.message_prefix(), message).bold() )
)?; .bold()
let builder = build_dispatch )?;
.setup_build( let source_tree = source_tree.to_path_buf();
source_tree, let output_dir_ = output_dir.to_path_buf();
subdirectory, let filename = tokio::task::spawn_blocking(move || {
source.path(), uv_build_backend::build_source_dist(
version_id.map(ToString::to_string), &source_tree,
dist, &output_dir_,
sources, uv_version::version(),
BuildKind::Sdist, )
build_output, })
) .await??
.await?; .to_string();
builder.build(output_dir).await?
BuildMessage::Build {
filename,
output_dir: output_dir.to_path_buf(),
}
}
BuildAction::Pep517 => {
writeln!(
printer.stderr(),
"{}",
format!(
"{}Building {}...",
source.message_prefix(),
build_kind_message
)
.bold()
)?;
let builder = build_dispatch
.setup_build(
source_tree,
subdirectory,
source.path(),
version_id.map(ToString::to_string),
dist,
sources,
BuildKind::Sdist,
build_output,
)
.await?;
let filename = builder.build(output_dir).await?;
BuildMessage::Build {
filename,
output_dir: output_dir.to_path_buf(),
}
}
}; };
Ok(sdist) Ok(build_result)
} }
/// Build a wheel, either through PEP 517 or through the fast path. /// Build a wheel, either through PEP 517 or through the fast path.
@ -786,10 +875,10 @@ async fn build_sdist(
async fn build_wheel( async fn build_wheel(
source_tree: &Path, source_tree: &Path,
output_dir: &Path, output_dir: &Path,
fast_path: bool, action: BuildAction,
source: &AnnotatedSource<'_>, source: &AnnotatedSource<'_>,
printer: Printer, printer: Printer,
message: &str, build_kind_message: &str,
// Below is only used with PEP 517 builds // Below is only used with PEP 517 builds
build_dispatch: &BuildDispatch<'_>, build_dispatch: &BuildDispatch<'_>,
sources: SourceStrategy, sources: SourceStrategy,
@ -797,46 +886,80 @@ async fn build_wheel(
subdirectory: Option<&Path>, subdirectory: Option<&Path>,
version_id: Option<&str>, version_id: Option<&str>,
build_output: BuildOutput, build_output: BuildOutput,
) -> Result<String> { ) -> Result<BuildMessage> {
let wheel = if fast_path { let build_message = match action {
writeln!( BuildAction::List => {
printer.stderr(), let source_tree_ = source_tree.to_path_buf();
"{}", let (name, file_list) = tokio::task::spawn_blocking(move || {
format!( uv_build_backend::list_wheel(&source_tree_, uv_version::version())
"{}{} (uv build backend)...", })
source.message_prefix(), .await??;
message BuildMessage::List {
) filename: name.to_string(),
.bold() source_tree: source_tree.to_path_buf(),
)?; file_list,
let source_tree = source_tree.to_path_buf(); }
let output_dir = output_dir.to_path_buf(); }
tokio::task::spawn_blocking(move || { BuildAction::FastPath => {
uv_build_backend::build_wheel(&source_tree, &output_dir, None, uv_version::version()) writeln!(
}) printer.stderr(),
.await?? "{}",
.to_string() format!(
} else { "{}Building {} (uv build backend)...",
writeln!( source.message_prefix(),
printer.stderr(), build_kind_message
"{}", )
format!("{}{}...", source.message_prefix(), message).bold() .bold()
)?; )?;
let builder = build_dispatch let source_tree = source_tree.to_path_buf();
.setup_build( let output_dir_ = output_dir.to_path_buf();
source_tree, let filename = tokio::task::spawn_blocking(move || {
subdirectory, uv_build_backend::build_wheel(
source.path(), &source_tree,
version_id.map(ToString::to_string), &output_dir_,
dist, None,
sources, uv_version::version(),
BuildKind::Wheel, )
build_output, })
) .await??
.await?; .to_string();
builder.build(output_dir).await?
BuildMessage::Build {
filename,
output_dir: output_dir.to_path_buf(),
}
}
BuildAction::Pep517 => {
writeln!(
printer.stderr(),
"{}",
format!(
"{}Building {}...",
source.message_prefix(),
build_kind_message
)
.bold()
)?;
let builder = build_dispatch
.setup_build(
source_tree,
subdirectory,
source.path(),
version_id.map(ToString::to_string),
dist,
sources,
BuildKind::Wheel,
build_output,
)
.await?;
let filename = builder.build(output_dir).await?;
BuildMessage::Build {
filename,
output_dir: output_dir.to_path_buf(),
}
}
}; };
Ok(wheel) Ok(build_message)
} }
/// Create the output directory and add a `.gitignore`. /// Create the output directory and add a `.gitignore`.
@ -926,14 +1049,76 @@ impl<'a> Source<'a> {
} }
} }
/// We run all builds in parallel, so we wait until all builds are done to show the success messages
/// in order.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
enum BuiltDistributions { enum BuildMessage {
/// A built wheel. /// A built wheel or source distribution.
Wheel(PathBuf), Build {
/// A built source distribution. /// The name of the built distribution.
Sdist(PathBuf), filename: String,
/// A built source distribution and wheel. /// The location of the built distribution.
Both(PathBuf, PathBuf), output_dir: PathBuf,
},
/// Show the list of files that would be included in a distribution.
List {
/// The name of the build distribution.
filename: String,
// All source files are relative to the source tree.
source_tree: PathBuf,
// Included file and source file, if not generated.
file_list: Vec<(String, Option<PathBuf>)>,
},
}
impl BuildMessage {
/// The filename of the wheel or source distribution.
fn filename(&self) -> &str {
match self {
BuildMessage::Build { filename: name, .. } => name,
BuildMessage::List { filename: name, .. } => name,
}
}
fn print(&self, printer: Printer) -> Result<()> {
match self {
BuildMessage::Build {
filename,
output_dir,
} => {
writeln!(
printer.stderr(),
"Successfully built {}",
output_dir.join(filename).user_display().bold().cyan()
)?;
}
BuildMessage::List {
filename,
file_list,
source_tree,
} => {
writeln!(
printer.stdout(),
"{}",
format!("Building {filename} will include the following files:").bold()
)?;
for (file, source) in file_list {
if let Some(source) = source {
writeln!(
printer.stdout(),
"{file} ({})",
relative_to(source, source_tree)
.context("Included files must be relative to source tree")?
.display()
)?;
} else {
writeln!(printer.stdout(), "{file} (generated)")?;
}
}
}
}
Ok(())
}
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]

View File

@ -138,7 +138,7 @@ pub(crate) async fn install(
let start = std::time::Instant::now(); let start = std::time::Instant::now();
if default && !preview.is_enabled() { if default && !preview.is_enabled() {
writeln!(printer.stderr(), "The `--default` flag is only available in preview mode; add the `--preview` flag to use `--default")?; writeln!(printer.stderr(), "The `--default` flag is only available in preview mode; add the `--preview` flag to use `--default`")?;
return Ok(ExitStatus::Failure); return Ok(ExitStatus::Failure);
} }

View File

@ -733,6 +733,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.out_dir, args.out_dir,
args.sdist, args.sdist,
args.wheel, args.wheel,
args.list,
args.build_logs, args.build_logs,
args.force_pep517, args.force_pep517,
build_constraints, build_constraints,
@ -749,6 +750,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
&globals.allow_insecure_host, &globals.allow_insecure_host,
&cache, &cache,
printer, printer,
globals.preview,
) )
.await .await
} }

View File

@ -2030,6 +2030,7 @@ pub(crate) struct BuildSettings {
pub(crate) out_dir: Option<PathBuf>, pub(crate) out_dir: Option<PathBuf>,
pub(crate) sdist: bool, pub(crate) sdist: bool,
pub(crate) wheel: bool, pub(crate) wheel: bool,
pub(crate) list: bool,
pub(crate) build_logs: bool, pub(crate) build_logs: bool,
pub(crate) force_pep517: bool, pub(crate) force_pep517: bool,
pub(crate) build_constraints: Vec<PathBuf>, pub(crate) build_constraints: Vec<PathBuf>,
@ -2050,6 +2051,7 @@ impl BuildSettings {
all_packages, all_packages,
sdist, sdist,
wheel, wheel,
list,
force_pep517, force_pep517,
build_constraints, build_constraints,
require_hashes, require_hashes,
@ -2076,6 +2078,7 @@ impl BuildSettings {
out_dir, out_dir,
sdist, sdist,
wheel, wheel,
list,
build_logs: flag(build_logs, no_build_logs).unwrap_or(true), build_logs: flag(build_logs, no_build_logs).unwrap_or(true),
build_constraints: build_constraints build_constraints: build_constraints
.into_iter() .into_iter()

View File

@ -121,7 +121,8 @@ fn build() -> Result<()> {
adding 'project-0.1.0.dist-info/top_level.txt' adding 'project-0.1.0.dist-info/top_level.txt'
adding 'project-0.1.0.dist-info/RECORD' adding 'project-0.1.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel 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 Successfully built project/dist/project-0.1.0.tar.gz
Successfully built project/dist/project-0.1.0-py3-none-any.whl
"###); "###);
project project
@ -213,7 +214,8 @@ fn build() -> Result<()> {
adding 'project-0.1.0.dist-info/top_level.txt' adding 'project-0.1.0.dist-info/top_level.txt'
adding 'project-0.1.0.dist-info/RECORD' adding 'project-0.1.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel 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 Successfully built dist/project-0.1.0.tar.gz
Successfully built dist/project-0.1.0-py3-none-any.whl
"###); "###);
project project
@ -317,7 +319,8 @@ fn build() -> Result<()> {
adding 'project-0.1.0.dist-info/top_level.txt' adding 'project-0.1.0.dist-info/top_level.txt'
adding 'project-0.1.0.dist-info/RECORD' adding 'project-0.1.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel 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 Successfully built out/project-0.1.0.tar.gz
Successfully built out/project-0.1.0-py3-none-any.whl
"###); "###);
project project
@ -630,7 +633,8 @@ fn sdist_wheel() -> Result<()> {
adding 'project-0.1.0.dist-info/top_level.txt' adding 'project-0.1.0.dist-info/top_level.txt'
adding 'project-0.1.0.dist-info/RECORD' adding 'project-0.1.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel 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 Successfully built dist/project-0.1.0.tar.gz
Successfully built dist/project-0.1.0-py3-none-any.whl
"###); "###);
project project
@ -1052,7 +1056,8 @@ fn workspace() -> Result<()> {
adding 'member-0.1.0.dist-info/top_level.txt' adding 'member-0.1.0.dist-info/top_level.txt'
adding 'member-0.1.0.dist-info/RECORD' adding 'member-0.1.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel removing build/bdist.linux-x86_64/wheel
Successfully built dist/member-0.1.0.tar.gz and dist/member-0.1.0-py3-none-any.whl Successfully built dist/member-0.1.0.tar.gz
Successfully built dist/member-0.1.0-py3-none-any.whl
"###); "###);
project project
@ -1075,8 +1080,10 @@ fn workspace() -> Result<()> {
[PKG] Building source distribution... [PKG] Building source distribution...
[PKG] Building wheel from source distribution... [PKG] Building wheel from source distribution...
[PKG] Building wheel from source distribution... [PKG] Building wheel from source distribution...
Successfully built dist/member-0.1.0.tar.gz and dist/member-0.1.0-py3-none-any.whl Successfully built dist/member-0.1.0.tar.gz
Successfully built dist/project-0.1.0.tar.gz and dist/project-0.1.0-py3-none-any.whl Successfully built dist/member-0.1.0-py3-none-any.whl
Successfully built dist/project-0.1.0.tar.gz
Successfully built dist/project-0.1.0-py3-none-any.whl
"###); "###);
project project
@ -1174,7 +1181,8 @@ fn workspace() -> Result<()> {
adding 'member-0.1.0.dist-info/top_level.txt' adding 'member-0.1.0.dist-info/top_level.txt'
adding 'member-0.1.0.dist-info/RECORD' adding 'member-0.1.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel removing build/bdist.linux-x86_64/wheel
Successfully built project/dist/member-0.1.0.tar.gz and project/dist/member-0.1.0-py3-none-any.whl Successfully built project/dist/member-0.1.0.tar.gz
Successfully built project/dist/member-0.1.0-py3-none-any.whl
"###); "###);
// If a source is provided, discover the workspace from the source. // If a source is provided, discover the workspace from the source.
@ -1188,8 +1196,10 @@ fn workspace() -> Result<()> {
[PKG] Building source distribution... [PKG] Building source distribution...
[PKG] Building wheel from source distribution... [PKG] Building wheel from source distribution...
[PKG] Building wheel from source distribution... [PKG] Building wheel from source distribution...
Successfully built project/dist/member-0.1.0.tar.gz and project/dist/member-0.1.0-py3-none-any.whl Successfully built project/dist/member-0.1.0.tar.gz
Successfully built project/dist/project-0.1.0.tar.gz and project/dist/project-0.1.0-py3-none-any.whl Successfully built project/dist/member-0.1.0-py3-none-any.whl
Successfully built project/dist/project-0.1.0.tar.gz
Successfully built project/dist/project-0.1.0-py3-none-any.whl
"###); "###);
// Fail when `--package` is provided without a workspace. // Fail when `--package` is provided without a workspace.
@ -1331,10 +1341,12 @@ fn build_all_with_failure() -> Result<()> {
[PKG] Building source distribution... [PKG] Building source distribution...
[PKG] Building wheel from source distribution... [PKG] Building wheel from source distribution...
[PKG] Building wheel from source distribution... [PKG] Building wheel from source distribution...
Successfully built dist/member_a-0.1.0.tar.gz and dist/member_a-0.1.0-py3-none-any.whl 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` × Failed to build `member-b @ [TEMP_DIR]/project/packages/member_b`
Build backend failed to determine requirements with `build_sdist()` (exit status: 1) Build backend failed to determine requirements with `build_sdist()` (exit status: 1)
Successfully built dist/project-0.1.0.tar.gz and dist/project-0.1.0-py3-none-any.whl Successfully built dist/project-0.1.0.tar.gz
Successfully built dist/project-0.1.0-py3-none-any.whl
"###); "###);
// project and member_a should be built, regardless of member_b build failure // project and member_a should be built, regardless of member_b build failure
@ -1628,7 +1640,8 @@ fn sha() -> Result<()> {
adding 'project-0.1.0.dist-info/top_level.txt' adding 'project-0.1.0.dist-info/top_level.txt'
adding 'project-0.1.0.dist-info/RECORD' adding 'project-0.1.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel 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 Successfully built dist/project-0.1.0.tar.gz
Successfully built dist/project-0.1.0-py3-none-any.whl
"###); "###);
project project
@ -1710,7 +1723,8 @@ fn build_no_build_logs() -> Result<()> {
----- stderr ----- ----- stderr -----
Building source distribution... Building source distribution...
Building wheel from source distribution... Building wheel from source distribution...
Successfully built project/dist/project-0.1.0.tar.gz and project/dist/project-0.1.0-py3-none-any.whl Successfully built project/dist/project-0.1.0.tar.gz
Successfully built project/dist/project-0.1.0-py3-none-any.whl
"###); "###);
Ok(()) Ok(())
@ -1867,7 +1881,8 @@ fn tool_uv_sources() -> Result<()> {
adding 'project-0.1.0.dist-info/top_level.txt' adding 'project-0.1.0.dist-info/top_level.txt'
adding 'project-0.1.0.dist-info/RECORD' adding 'project-0.1.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel 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 Successfully built dist/project-0.1.0.tar.gz
Successfully built dist/project-0.1.0-py3-none-any.whl
"###); "###);
project project
@ -1915,7 +1930,8 @@ fn git_boundary_in_dist_build() -> Result<()> {
----- stderr ----- ----- stderr -----
Building source distribution... Building source distribution...
Building wheel from source distribution... Building wheel from source distribution...
Successfully built dist/demo-0.1.0.tar.gz and dist/demo-0.1.0-py3-none-any.whl Successfully built dist/demo-0.1.0.tar.gz
Successfully built dist/demo-0.1.0-py3-none-any.whl
"###); "###);
// Check that the source file is included // Check that the source file is included
@ -2048,6 +2064,7 @@ fn build_fast_path() -> Result<()> {
let built_by_uv = current_dir()?.join("../../scripts/packages/built-by-uv"); let built_by_uv = current_dir()?.join("../../scripts/packages/built-by-uv");
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output1")), @r###" .arg(context.temp_dir.join("output1")), @r###"
@ -2058,7 +2075,8 @@ fn build_fast_path() -> Result<()> {
----- stderr ----- ----- stderr -----
Building source distribution (uv build backend)... Building source distribution (uv build backend)...
Building wheel from source distribution (uv build backend)... Building wheel from source distribution (uv build backend)...
Successfully built output1/built_by_uv-0.1.0.tar.gz and output1/built_by_uv-0.1.0-py3-none-any.whl Successfully built output1/built_by_uv-0.1.0.tar.gz
Successfully built output1/built_by_uv-0.1.0-py3-none-any.whl
"###); "###);
context context
.temp_dir .temp_dir
@ -2072,6 +2090,7 @@ fn build_fast_path() -> Result<()> {
.assert(predicate::path::is_file()); .assert(predicate::path::is_file());
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output2")) .arg(context.temp_dir.join("output2"))
@ -2091,6 +2110,7 @@ fn build_fast_path() -> Result<()> {
.assert(predicate::path::is_file()); .assert(predicate::path::is_file());
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output3")) .arg(context.temp_dir.join("output3"))
@ -2110,6 +2130,7 @@ fn build_fast_path() -> Result<()> {
.assert(predicate::path::is_file()); .assert(predicate::path::is_file());
uv_snapshot!(context.build() uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv) .arg(&built_by_uv)
.arg("--out-dir") .arg("--out-dir")
.arg(context.temp_dir.join("output4")) .arg(context.temp_dir.join("output4"))
@ -2122,7 +2143,8 @@ fn build_fast_path() -> Result<()> {
----- stderr ----- ----- stderr -----
Building source distribution (uv build backend)... Building source distribution (uv build backend)...
Building wheel (uv build backend)... Building wheel (uv build backend)...
Successfully built output4/built_by_uv-0.1.0.tar.gz and output4/built_by_uv-0.1.0-py3-none-any.whl Successfully built output4/built_by_uv-0.1.0.tar.gz
Successfully built output4/built_by_uv-0.1.0-py3-none-any.whl
"###); "###);
context context
.temp_dir .temp_dir
@ -2137,3 +2159,172 @@ fn build_fast_path() -> Result<()> {
Ok(()) Ok(())
} }
/// Test the `--list` option.
#[test]
fn list_files() -> Result<()> {
let context = TestContext::new("3.12");
let built_by_uv = current_dir()?.join("../../scripts/packages/built-by-uv");
// By default, we build the wheel from the source dist, which we need to do even for the list
// task.
uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv)
.arg("--out-dir")
.arg(context.temp_dir.join("output1"))
.arg("--list"), @r###"
success: true
exit_code: 0
----- stdout -----
Building built_by_uv-0.1.0.tar.gz will include the following files:
built_by_uv-0.1.0/LICENSE-APACHE (LICENSE-APACHE)
built_by_uv-0.1.0/LICENSE-MIT (LICENSE-MIT)
built_by_uv-0.1.0/PKG-INFO (generated)
built_by_uv-0.1.0/README.md (README.md)
built_by_uv-0.1.0/assets/data.csv (assets/data.csv)
built_by_uv-0.1.0/header/built_by_uv.h (header/built_by_uv.h)
built_by_uv-0.1.0/pyproject.toml (pyproject.toml)
built_by_uv-0.1.0/scripts/whoami.sh (scripts/whoami.sh)
built_by_uv-0.1.0/src/built_by_uv/__init__.py (src/built_by_uv/__init__.py)
built_by_uv-0.1.0/src/built_by_uv/arithmetic/__init__.py (src/built_by_uv/arithmetic/__init__.py)
built_by_uv-0.1.0/src/built_by_uv/arithmetic/circle.py (src/built_by_uv/arithmetic/circle.py)
built_by_uv-0.1.0/src/built_by_uv/arithmetic/pi.txt (src/built_by_uv/arithmetic/pi.txt)
built_by_uv-0.1.0/src/built_by_uv/build-only.h (src/built_by_uv/build-only.h)
built_by_uv-0.1.0/third-party-licenses/PEP-401.txt (third-party-licenses/PEP-401.txt)
Building built_by_uv-0.1.0-py3-none-any.whl will include the following files:
built_by_uv-0.1.0.data/data/data.csv (assets/data.csv)
built_by_uv-0.1.0.data/headers/built_by_uv.h (header/built_by_uv.h)
built_by_uv-0.1.0.data/scripts/whoami.sh (scripts/whoami.sh)
built_by_uv-0.1.0.dist-info/METADATA (generated)
built_by_uv-0.1.0.dist-info/WHEEL (generated)
built_by_uv-0.1.0.dist-info/licenses/LICENSE-APACHE (LICENSE-APACHE)
built_by_uv-0.1.0.dist-info/licenses/LICENSE-MIT (LICENSE-MIT)
built_by_uv-0.1.0.dist-info/licenses/third-party-licenses/PEP-401.txt (third-party-licenses/PEP-401.txt)
built_by_uv/__init__.py (src/built_by_uv/__init__.py)
built_by_uv/arithmetic/__init__.py (src/built_by_uv/arithmetic/__init__.py)
built_by_uv/arithmetic/circle.py (src/built_by_uv/arithmetic/circle.py)
built_by_uv/arithmetic/pi.txt (src/built_by_uv/arithmetic/pi.txt)
----- stderr -----
Building source distribution (uv build backend)...
Successfully built output1/built_by_uv-0.1.0.tar.gz
"###);
context
.temp_dir
.child("output1")
.child("built_by_uv-0.1.0.tar.gz")
.assert(predicate::path::is_file());
context
.temp_dir
.child("output1")
.child("built_by_uv-0.1.0-py3-none-any.whl")
.assert(predicate::path::missing());
uv_snapshot!(context.build()
.arg("--preview")
.arg(&built_by_uv)
.arg("--out-dir")
.arg(context.temp_dir.join("output2"))
.arg("--list")
.arg("--sdist")
.arg("--wheel"), @r###"
success: true
exit_code: 0
----- stdout -----
Building built_by_uv-0.1.0.tar.gz will include the following files:
built_by_uv-0.1.0/LICENSE-APACHE (LICENSE-APACHE)
built_by_uv-0.1.0/LICENSE-MIT (LICENSE-MIT)
built_by_uv-0.1.0/PKG-INFO (generated)
built_by_uv-0.1.0/README.md (README.md)
built_by_uv-0.1.0/assets/data.csv (assets/data.csv)
built_by_uv-0.1.0/header/built_by_uv.h (header/built_by_uv.h)
built_by_uv-0.1.0/pyproject.toml (pyproject.toml)
built_by_uv-0.1.0/scripts/whoami.sh (scripts/whoami.sh)
built_by_uv-0.1.0/src/built_by_uv/__init__.py (src/built_by_uv/__init__.py)
built_by_uv-0.1.0/src/built_by_uv/arithmetic/__init__.py (src/built_by_uv/arithmetic/__init__.py)
built_by_uv-0.1.0/src/built_by_uv/arithmetic/circle.py (src/built_by_uv/arithmetic/circle.py)
built_by_uv-0.1.0/src/built_by_uv/arithmetic/pi.txt (src/built_by_uv/arithmetic/pi.txt)
built_by_uv-0.1.0/src/built_by_uv/build-only.h (src/built_by_uv/build-only.h)
built_by_uv-0.1.0/third-party-licenses/PEP-401.txt (third-party-licenses/PEP-401.txt)
Building built_by_uv-0.1.0-py3-none-any.whl will include the following files:
built_by_uv-0.1.0.data/data/data.csv (assets/data.csv)
built_by_uv-0.1.0.data/headers/built_by_uv.h (header/built_by_uv.h)
built_by_uv-0.1.0.data/scripts/whoami.sh (scripts/whoami.sh)
built_by_uv-0.1.0.dist-info/METADATA (generated)
built_by_uv-0.1.0.dist-info/WHEEL (generated)
built_by_uv-0.1.0.dist-info/licenses/LICENSE-APACHE (LICENSE-APACHE)
built_by_uv-0.1.0.dist-info/licenses/LICENSE-MIT (LICENSE-MIT)
built_by_uv-0.1.0.dist-info/licenses/third-party-licenses/PEP-401.txt (third-party-licenses/PEP-401.txt)
built_by_uv/__init__.py (src/built_by_uv/__init__.py)
built_by_uv/arithmetic/__init__.py (src/built_by_uv/arithmetic/__init__.py)
built_by_uv/arithmetic/circle.py (src/built_by_uv/arithmetic/circle.py)
built_by_uv/arithmetic/pi.txt (src/built_by_uv/arithmetic/pi.txt)
----- stderr -----
"###);
context
.temp_dir
.child("output2")
.child("built_by_uv-0.1.0.tar.gz")
.assert(predicate::path::missing());
context
.temp_dir
.child("output2")
.child("built_by_uv-0.1.0-py3-none-any.whl")
.assert(predicate::path::missing());
Ok(())
}
/// Test `--list` option errors.
#[test]
fn list_files_errors() -> Result<()> {
let context = TestContext::new("3.12");
let built_by_uv = current_dir()?.join("../../scripts/packages/built-by-uv");
let mut filters = context.filters();
// In CI, we run with link mode settings.
filters.push(("--link-mode <LINK_MODE> ", ""));
uv_snapshot!(filters, context.build()
.arg("--preview")
.arg(&built_by_uv)
.arg("--out-dir")
.arg(context.temp_dir.join("output1"))
.arg("--list")
.arg("--force-pep517"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: the argument '--list' cannot be used with '--force-pep517'
Usage: uv build --cache-dir [CACHE_DIR] --out-dir <OUT_DIR> --exclude-newer <EXCLUDE_NEWER> <SRC>
For more information, try '--help'.
"###);
// Not a uv build backend package, we can't list it.
let anyio_local = current_dir()?.join("../../scripts/packages/anyio_local");
let mut filters = context.filters();
// Windows normalization
filters.push(("/crates/uv/../../", "/"));
uv_snapshot!(filters, context.build()
.arg("--preview")
.arg(&anyio_local)
.arg("--out-dir")
.arg(context.temp_dir.join("output2"))
.arg("--list"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
× Failed to build `[WORKSPACE]/scripts/packages/anyio_local`
Can only use `--list` with the uv backend
"###);
Ok(())
}

View File

@ -543,7 +543,7 @@ fn python_install_default() {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
The `--default` flag is only available in preview mode; add the `--preview` flag to use `--default The `--default` flag is only available in preview mode; add the `--preview` flag to use `--default`
"###); "###);
// Install a specific version // Install a specific version