From 804f1ff808a3b7eea4f6f683a5a33a7a48161faa Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 27 Oct 2025 19:15:17 +0100 Subject: [PATCH] Add `--clear` to `uv build` to remove old build artifacts (#16371) Add `uv build --clear` that behaves like `rm -r ./dist && uv build` to clear artifacts from previous builds. This avoids accidentally publishing the wrong artifacts and removes accumulated old builds. See https://github.com/astral-sh/uv/issues/10293#issuecomment-3405904013 Fixes #10293 --- crates/uv-cli/src/lib.rs | 4 ++ crates/uv/src/commands/build_frontend.rs | 10 ++++ crates/uv/src/lib.rs | 1 + crates/uv/src/settings.rs | 5 +- crates/uv/tests/it/build.rs | 67 ++++++++++++++++++++++++ docs/reference/cli.md | 3 +- 6 files changed, 88 insertions(+), 2 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index f643e1e8d..a21250089 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2635,6 +2635,10 @@ pub struct BuildArgs { #[arg(long, conflicts_with = "list")] pub force_pep517: bool, + /// Clear the output directory before the build, removing stale artifacts. + #[arg(long)] + pub clear: bool, + /// Constrain build dependencies using the given requirements files when building distributions. /// /// Constraints files are `requirements.txt`-like files that only control the _version_ of a diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 08b5a32df..742cb28fc 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -109,6 +109,7 @@ pub(crate) async fn build_frontend( list: bool, build_logs: bool, force_pep517: bool, + clear: bool, build_constraints: Vec, hash_checking: Option, python: Option, @@ -134,6 +135,7 @@ pub(crate) async fn build_frontend( list, build_logs, force_pep517, + clear, &build_constraints, hash_checking, python.as_deref(), @@ -177,6 +179,7 @@ async fn build_impl( list: bool, build_logs: bool, force_pep517: bool, + clear: bool, build_constraints: &[RequirementsSource], hash_checking: Option, python_request: Option<&str>, @@ -342,6 +345,7 @@ async fn build_impl( hash_checking, build_logs, force_pep517, + clear, build_constraints, build_isolation, extra_build_dependencies, @@ -449,6 +453,7 @@ async fn build_package( hash_checking: Option, build_logs: bool, force_pep517: bool, + clear: bool, build_constraints: &[RequirementsSource], build_isolation: &BuildIsolation, extra_build_dependencies: &ExtraBuildDependencies, @@ -481,6 +486,11 @@ async fn build_package( } }; + // Clear the output directory if requested + if clear && output_dir.exists() { + fs_err::remove_dir_all(&*output_dir)?; + } + // (1) Explicit request from user let mut interpreter_request = python_request.map(PythonRequest::parse); diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 35b1569d2..710fdf1a7 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1073,6 +1073,7 @@ async fn run(mut cli: Cli) -> Result { args.list, args.build_logs, args.force_pep517, + args.clear, build_constraints, args.hash_checking, args.python, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 25d11cab7..bef88c8cf 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2873,6 +2873,7 @@ pub(crate) struct BuildSettings { pub(crate) list: bool, pub(crate) build_logs: bool, pub(crate) force_pep517: bool, + pub(crate) clear: bool, pub(crate) build_constraints: Vec, pub(crate) hash_checking: Option, pub(crate) python: Option, @@ -2897,6 +2898,7 @@ impl BuildSettings { wheel, list, force_pep517, + clear, build_constraints, require_hashes, no_require_hashes, @@ -2923,11 +2925,12 @@ impl BuildSettings { wheel, list, build_logs: flag(build_logs, no_build_logs, "build-logs").unwrap_or(true), + force_pep517, + clear, build_constraints: build_constraints .into_iter() .filter_map(Maybe::into_option) .collect(), - force_pep517, hash_checking: HashCheckingMode::from_args( flag(require_hashes, no_require_hashes, "require-hashes"), flag(verify_hashes, no_verify_hashes, "verify-hashes"), diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 6d55a5a01..ef815fc2a 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -2146,3 +2146,70 @@ fn test_workspace_trailing_slash() { Successfully built dist/child-0.1.0-py3-none-any.whl "); } + +/// Test `uv build --clear`. +#[test] +fn build_clear() -> Result<()> { + let context = TestContext::new("3.12"); + + let project = context.temp_dir.child("project"); + + context.init().arg(project.path()).assert().success(); + + // Regular build + uv_snapshot!(&context.filters(), context.build().arg("project").arg("--no-build-logs"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building source distribution... + Building wheel from source distribution... + Successfully built project/dist/project-0.1.0.tar.gz + Successfully built project/dist/project-0.1.0-py3-none-any.whl + "###); + + project + .child("dist") + .child("project-0.1.0.tar.gz") + .assert(predicate::path::is_file()); + project + .child("dist") + .child("project-0.1.0-py3-none-any.whl") + .assert(predicate::path::is_file()); + + // Add a marker file to verify `--clear` removes it + fs_err::write(project.child("dist").child("marker.txt"), "marker")?; + project + .child("dist") + .child("marker.txt") + .assert(predicate::path::is_file()); + + // Build with `--clear` to remove the marker file + uv_snapshot!(&context.filters(), context.build().arg("project").arg("--clear").arg("--no-build-logs"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building source distribution... + Building wheel from source distribution... + Successfully built project/dist/project-0.1.0.tar.gz + Successfully built project/dist/project-0.1.0-py3-none-any.whl + "###); + + project + .child("dist") + .child("marker.txt") + .assert(predicate::path::missing()); + project + .child("dist") + .child("project-0.1.0.tar.gz") + .assert(predicate::path::is_file()); + project + .child("dist") + .child("project-0.1.0-py3-none-any.whl") + .assert(predicate::path::is_file()); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 6c083ddc7..ac8207c0a 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -5641,7 +5641,8 @@ uv build [OPTIONS] [SRC]

May also be set with the UV_BUILD_CONSTRAINT environment variable.

--cache-dir cache-dir

Path to the cache directory.

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

To view the location of the cache directory, run uv cache dir.

-

May also be set with the UV_CACHE_DIR environment variable.

--color color-choice

Control the use of color in output.

+

May also be set with the UV_CACHE_DIR environment variable.

--clear

Clear the output directory before the build, removing stale artifacts

+
--color color-choice

Control the use of color in output.

By default, uv will automatically detect support for colors when writing to a terminal.

Possible values: