From f22af0f88a45e5a6443d7de870bc36247cf135fc Mon Sep 17 00:00:00 2001 From: Mikayla Thompson Date: Mon, 10 Nov 2025 16:33:08 -0700 Subject: [PATCH] Deprecate `--project` arg for init (#16674) Addresses https://github.com/astral-sh/uv/issues/15790 ## Summary After discussion, the functionality of `--project` vs `--directory` was quite unclear in this case, so deprecating `--project` for `init` is probably the clearest behavior option. This is a breaking change, so it requires being under preview before being rolled out fully. Included in the PR now: - new feature flag (`init --project` is deprecated if `--preview` or `--preview-features deprecate-project-for-init` are provided) - tests (for `--directory` behavior, as well as the current warning and future error) - documentation updated in docs/concepts/projects/init.md --------- Signed-off-by: Mikayla Thompson --- crates/uv-preview/src/lib.rs | 3 + crates/uv/src/lib.rs | 27 +++++++ crates/uv/tests/it/init.rs | 110 ++++++++++++++++++++++++++++ crates/uv/tests/it/show_settings.rs | 4 +- docs/concepts/projects/init.md | 6 +- 5 files changed, 146 insertions(+), 4 deletions(-) diff --git a/crates/uv-preview/src/lib.rs b/crates/uv-preview/src/lib.rs index a2453e4d8..661808df7 100644 --- a/crates/uv-preview/src/lib.rs +++ b/crates/uv-preview/src/lib.rs @@ -21,6 +21,7 @@ bitflags::bitflags! { const NATIVE_AUTH = 1 << 9; const S3_ENDPOINT = 1 << 10; const CACHE_SIZE = 1 << 11; + const INIT_PROJECT_FLAG = 1 << 12; } } @@ -42,6 +43,7 @@ impl PreviewFeatures { Self::NATIVE_AUTH => "native-auth", Self::S3_ENDPOINT => "s3-endpoint", Self::CACHE_SIZE => "cache-size", + Self::INIT_PROJECT_FLAG => "init-project-flag", _ => panic!("`flag_as_str` can only be used for exactly one feature flag"), } } @@ -91,6 +93,7 @@ impl FromStr for PreviewFeatures { "native-auth" => Self::NATIVE_AUTH, "s3-endpoint" => Self::S3_ENDPOINT, "cache-size" => Self::CACHE_SIZE, + "init-project-flag" => Self::INIT_PROJECT_FLAG, _ => { warn_user_once!("Unknown preview feature: `{part}`"); continue; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index a4279e4d7..a42a37e24 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -35,6 +35,7 @@ use uv_fs::{CWD, Simplified}; #[cfg(feature = "self-update")] use uv_pep440::release_specifiers_to_ranges; use uv_pep508::VersionOrUrl; +use uv_preview::PreviewFeatures; use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl}; use uv_python::PythonRequest; use uv_requirements::{GroupsSpecification, RequirementsSource}; @@ -1805,6 +1806,32 @@ async fn run_project( let args = settings::InitSettings::resolve(args, filesystem, environment); show_settings!(args); + // The `--project` arg is being deprecated for `init` with a warning now and an error in preview. + if explicit_project { + if globals + .preview + .is_enabled(PreviewFeatures::INIT_PROJECT_FLAG) + { + bail!( + "The `--project` option cannot be used in `uv init`. {}", + if args.path.is_some() { + "Use `--directory` instead." + } else { + "Use `--directory` or a positional path instead." + } + ) + } + + warn_user!( + "Use of the `--project` option in `uv init` is deprecated and will be removed in a future release. {}", + if args.path.is_some() { + "Since a positional path was provided, the `--project` option has no effect. Consider using `--directory` instead." + } else { + "Consider using `uv init ` instead." + } + ); + } + // Initialize the cache. let cache = cache.init()?; diff --git a/crates/uv/tests/it/init.rs b/crates/uv/tests/it/init.rs index 75cb499e1..311a09398 100644 --- a/crates/uv/tests/it/init.rs +++ b/crates/uv/tests/it/init.rs @@ -4043,3 +4043,113 @@ fn git_states() { "); assert!(!context.temp_dir.child("broken-git/.git").is_dir()); } + +/// Using `uv init` with `--project` isn't allowed +#[test] +fn init_project_flag_is_not_allowed_under_preview() -> Result<()> { + let context = TestContext::new("3.12"); + + let child = context.temp_dir.child("foo"); + child.create_dir_all()?; + + // Positional `path` provided + uv_snapshot!(context.filters(), context.init().arg("--preview-features").arg("init-project-flag").arg("--project").arg("foo").arg("bar"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: The `--project` option cannot be used in `uv init`. Use `--directory` instead. + "###); + + // No positional `path` provided + uv_snapshot!(context.filters(), context.init().arg("--preview-features").arg("init-project-flag").arg("--project").arg("foo"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: The `--project` option cannot be used in `uv init`. Use `--directory` or a positional path instead. + "###); + + Ok(()) +} + +#[test] +fn init_project_flag_is_ignored_with_explicit_path() { + let context = TestContext::new("3.12"); + + // with explicit path + uv_snapshot!(context.filters(), context.init().arg("--project").arg("bar").arg("foo"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Use of the `--project` option in `uv init` is deprecated and will be removed in a future release. Since a positional path was provided, the `--project` option has no effect. Consider using `--directory` instead. + Initialized project `foo` at `[TEMP_DIR]/foo` + "###); + + let pyproject = context.read("foo/pyproject.toml"); + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject, @r###" + [project] + name = "foo" + version = "0.1.0" + description = "Add your description here" + readme = "README.md" + requires-python = ">=3.12" + dependencies = [] + "### + ); + }); +} + +#[test] +fn init_project_flag_is_warned_without_path() { + let context = TestContext::new("3.12"); + + // with explicit path + uv_snapshot!(context.filters(), context.init().arg("--project").arg("bar"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Use of the `--project` option in `uv init` is deprecated and will be removed in a future release. Consider using `uv init ` instead. + Initialized project `bar` + "###); + + context + .temp_dir + .child("bar/pyproject.toml") + .assert(predicate::path::is_file()); +} + +/// The `--directory` flag is used as the base for path +#[test] +fn init_working_directory_change() -> Result<()> { + let context = TestContext::new("3.12"); + + let child = context.temp_dir.child("bar"); + child.create_dir_all()?; + + uv_snapshot!(context.filters(), context.init().arg("--directory").arg("bar").arg("foo"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Initialized project `foo` at `[TEMP_DIR]/bar/foo` + "###); + + context + .temp_dir + .child("bar/foo/pyproject.toml") + .assert(predicate::path::is_file()); + + Ok(()) +} diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index 01b6703c9..40b32cabd 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -7831,7 +7831,7 @@ fn preview_features() { show_settings: true, preview: Preview { flags: PreviewFeatures( - PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE, + PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG, ), }, python_preference: Managed, @@ -8059,7 +8059,7 @@ fn preview_features() { show_settings: true, preview: Preview { flags: PreviewFeatures( - PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE, + PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG, ), }, python_preference: Managed, diff --git a/docs/concepts/projects/init.md b/docs/concepts/projects/init.md index f1dd3896f..cc836f975 100644 --- a/docs/concepts/projects/init.md +++ b/docs/concepts/projects/init.md @@ -9,8 +9,10 @@ flag can be used to create a project for a library instead. ## Target directory uv will create a project in the working directory, or, in a target directory by providing a name, -e.g., `uv init foo`. If there's already a project in the target directory, i.e., if there's a -`pyproject.toml`, uv will exit with an error. +e.g., `uv init foo`. The working directory can be modified with the `--directory` option, which will +cause the target directory path will be interpreted relative to the specified working directory. If +there's already a project in the target directory, i.e., if there's a `pyproject.toml`, uv will exit +with an error. ## Applications