From cfa1de311e4d65ac077770af27a57b4c9258d9d9 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 28 Oct 2025 13:25:31 +0100 Subject: [PATCH] Add `--no-create-gitignore` to `uv build` (#16369) Fixes #16332 --- crates/uv-cli/src/lib.rs | 10 +++++ crates/uv/src/commands/build_frontend.rs | 27 ++++++++----- crates/uv/src/lib.rs | 1 + crates/uv/src/settings.rs | 5 +++ crates/uv/tests/it/build.rs | 50 ++++++++++++++++++++++++ docs/reference/cli.md | 4 +- 6 files changed, 86 insertions(+), 11 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index a21250089..3a7d06183 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2639,6 +2639,16 @@ pub struct BuildArgs { #[arg(long)] pub clear: bool, + #[arg(long, overrides_with("no_create_gitignore"), hide = true)] + pub create_gitignore: bool, + + /// Do not create a `.gitignore` file in the output directory. + /// + /// By default, uv creates a `.gitignore` file in the output directory to exclude build + /// artifacts from version control. When this flag is used, the file will be omitted. + #[arg(long, overrides_with("create_gitignore"))] + pub no_create_gitignore: 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 742cb28fc..1d2d4303c 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -108,6 +108,7 @@ pub(crate) async fn build_frontend( wheel: bool, list: bool, build_logs: bool, + gitignore: bool, force_pep517: bool, clear: bool, build_constraints: Vec, @@ -134,6 +135,7 @@ pub(crate) async fn build_frontend( wheel, list, build_logs, + gitignore, force_pep517, clear, &build_constraints, @@ -178,6 +180,7 @@ async fn build_impl( wheel: bool, list: bool, build_logs: bool, + gitignore: bool, force_pep517: bool, clear: bool, build_constraints: &[RequirementsSource], @@ -344,6 +347,7 @@ async fn build_impl( client_builder.clone(), hash_checking, build_logs, + gitignore, force_pep517, clear, build_constraints, @@ -452,6 +456,7 @@ async fn build_package( client_builder: BaseClientBuilder<'_>, hash_checking: Option, build_logs: bool, + gitignore: bool, force_pep517: bool, clear: bool, build_constraints: &[RequirementsSource], @@ -627,7 +632,7 @@ async fn build_package( preview, ); - prepare_output_directory(&output_dir).await?; + prepare_output_directory(&output_dir, gitignore).await?; // Determine the build plan. let plan = BuildPlan::determine(&source, sdist, wheel).map_err(Error::BuildPlan)?; @@ -1094,19 +1099,21 @@ async fn build_wheel( } /// Create the output directory and add a `.gitignore`. -async fn prepare_output_directory(output_dir: &Path) -> Result<(), Error> { +async fn prepare_output_directory(output_dir: &Path, gitignore: bool) -> Result<(), Error> { // Create the output directory. fs_err::tokio::create_dir_all(&output_dir).await?; // Add a .gitignore. - match fs_err::OpenOptions::new() - .write(true) - .create_new(true) - .open(output_dir.join(".gitignore")) - { - Ok(mut file) => file.write_all(b"*")?, - Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (), - Err(err) => return Err(err.into()), + if gitignore { + match fs_err::OpenOptions::new() + .write(true) + .create_new(true) + .open(output_dir.join(".gitignore")) + { + Ok(mut file) => file.write_all(b"*")?, + Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (), + Err(err) => return Err(err.into()), + } } Ok(()) } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 710fdf1a7..ca5b52069 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1072,6 +1072,7 @@ async fn run(mut cli: Cli) -> Result { args.wheel, args.list, args.build_logs, + args.gitignore, args.force_pep517, args.clear, build_constraints, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index bef88c8cf..01a7e7c18 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2872,6 +2872,7 @@ pub(crate) struct BuildSettings { pub(crate) wheel: bool, pub(crate) list: bool, pub(crate) build_logs: bool, + pub(crate) gitignore: bool, pub(crate) force_pep517: bool, pub(crate) clear: bool, pub(crate) build_constraints: Vec, @@ -2906,6 +2907,8 @@ impl BuildSettings { no_verify_hashes, build_logs, no_build_logs, + create_gitignore, + no_create_gitignore, python, build, refresh, @@ -2927,6 +2930,8 @@ impl BuildSettings { build_logs: flag(build_logs, no_build_logs, "build-logs").unwrap_or(true), force_pep517, clear, + gitignore: flag(create_gitignore, no_create_gitignore, "create-gitignore") + .unwrap_or(true), build_constraints: build_constraints .into_iter() .filter_map(Maybe::into_option) diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index ef815fc2a..ee9ffa737 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -2213,3 +2213,53 @@ fn build_clear() -> Result<()> { Ok(()) } + +/// Test `uv build --no-create-gitignore`. +#[test] +fn build_no_gitignore() -> Result<()> { + let context = TestContext::new("3.12"); + + let project = context.temp_dir.child("project"); + + context.init().arg(project.path()).assert().success(); + + // Default build with `.gitignore` + 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(".gitignore") + .assert(predicate::path::is_file()); + + fs_err::remove_dir_all(project.child("dist"))?; + + // Build with `--no-create-gitignore` that does not create `.gitignore` + uv_snapshot!(&context.filters(), context.build().arg("project").arg("--no-create-gitignore").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(".gitignore") + .assert(predicate::path::missing()); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index ac8207c0a..6ddf7bcd0 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -5730,7 +5730,9 @@ uv build [OPTIONS] [SRC]

May also be set with the UV_NO_BUILD_PACKAGE environment variable.

--no-cache, --no-cache-dir, -n

Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation

May also be set with the UV_NO_CACHE environment variable.

--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

-

May also be set with the UV_NO_CONFIG environment variable.

--no-index

Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via --find-links

+

May also be set with the UV_NO_CONFIG environment variable.

--no-create-gitignore

Do not create a .gitignore file in the output directory.

+

By default, uv creates a .gitignore file in the output directory to exclude build artifacts from version control. When this flag is used, the file will be omitted.

+
--no-index

Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via --find-links

--no-managed-python

Disable use of uv-managed Python versions.

Instead, uv will search for a suitable Python version on the system.

May also be set with the UV_NO_MANAGED_PYTHON environment variable.

--no-progress

Hide all progress outputs.