diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 8d56869666701..5777a9dc837f0 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use anyhow::Result; use itertools::Itertools; use owo_colors::OwoColorize; +use rustc_hash::FxHashSet; use serde::Serialize; use tracing::warn; use uv_cache::Cache; @@ -133,16 +134,6 @@ pub(crate) async fn sync( project }; - // TODO(lucab): improve warning content - // - if project.workspace().pyproject_toml().has_scripts() - && !project.workspace().pyproject_toml().is_package(true) - { - warn_user!( - "Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system`" - ); - } - SyncTarget::Project(project) }; @@ -394,6 +385,23 @@ pub(crate) async fn sync( // Identify the installation target. let sync_target = identify_installation_target(&target, outcome.lock(), all_packages, &package); + // TODO(lucab): improve warning content + // + if let SyncTarget::Project(project) = &target { + let roots = sync_target.roots().collect::>(); + for (name, member) in project.workspace().packages() { + if roots.contains(name) + && member.pyproject_toml().has_scripts() + && !member.pyproject_toml().is_package(true) + { + warn_user!( + "Skipping installation of entry points (`project.scripts`) for package `{}` because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system`", + name + ); + } + } + } + let state = state.fork(); // Perform the sync operation. diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 327e3fe5489ab..bfbbd33b94584 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -8318,8 +8318,8 @@ fn sync_scripts_without_build_system() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system` Resolved 1 package in [TIME] + warning: Skipping installation of entry points (`project.scripts`) for package `foo` because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system` Audited in [TIME] "); @@ -8367,8 +8367,108 @@ fn sync_scripts_project_not_packaged() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system` Resolved 1 package in [TIME] + warning: Skipping installation of entry points (`project.scripts`) for package `foo` because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system` + Audited in [TIME] + "); + + Ok(()) +} + +#[test] +/// Check warning message for +/// if a workspace member has scripts but no `build-system`. +fn sync_scripts_workspace_member_not_packaged() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "root" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [tool.uv.workspace] + members = ["member"] + "#, + )?; + + let member = context.temp_dir.child("member"); + fs_err::create_dir_all(&member)?; + + let member_pyproject_toml = member.child("pyproject.toml"); + member_pyproject_toml.write_str( + r#" + [project] + name = "member" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [project.scripts] + member = "main:main" + "#, + )?; + + uv_snapshot!(context.filters(), context.sync().arg("--all-packages"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + warning: Skipping installation of entry points (`project.scripts`) for package `member` because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system` + Audited in [TIME] + "); + + Ok(()) +} + +#[test] +/// Check that the warning is not emitted for workspace members that are not being synced. +fn sync_scripts_workspace_member_not_packaged_not_synced() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "root" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [tool.uv.workspace] + members = ["member"] + "#, + )?; + + let member = context.temp_dir.child("member"); + fs_err::create_dir_all(&member)?; + + let member_pyproject_toml = member.child("pyproject.toml"); + member_pyproject_toml.write_str( + r#" + [project] + name = "member" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [project.scripts] + member = "main:main" + "#, + )?; + + uv_snapshot!(context.filters(), context.sync(), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] Audited in [TIME] ");