mirror of https://github.com/astral-sh/uv
Warn when trying to `uv sync` a package without build configuration (#7420)
This enhances `uv sync` logic in order to detect and warn if it is trying to operate on a packaged project with entrypoints. Ref: https://github.com/astral-sh/uv/issues/6998#issuecomment-2329291764 Closes: https://github.com/astral-sh/uv/issues/7034
This commit is contained in:
parent
d4f4dedc3b
commit
23494d85ab
|
|
@ -44,7 +44,7 @@ pub struct PyProjectToml {
|
|||
#[serde(skip)]
|
||||
pub raw: String,
|
||||
|
||||
/// Used to determine whether a `build-system` is present.
|
||||
/// Used to determine whether a `build-system` section is present.
|
||||
#[serde(default, skip_serializing)]
|
||||
build_system: Option<serde::de::IgnoredAny>,
|
||||
}
|
||||
|
|
@ -82,6 +82,15 @@ impl PyProjectToml {
|
|||
// Otherwise, a project is assumed to be a package if `build-system` is present.
|
||||
self.build_system.is_some()
|
||||
}
|
||||
|
||||
/// Returns whether the project manifest contains any script table.
|
||||
pub fn has_scripts(&self) -> bool {
|
||||
if let Some(ref project) = self.project {
|
||||
project.gui_scripts.is_some() || project.scripts.is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore raw document in comparison.
|
||||
|
|
@ -102,7 +111,7 @@ impl AsRef<[u8]> for PyProjectToml {
|
|||
/// PEP 621 project metadata (`project`).
|
||||
///
|
||||
/// See <https://packaging.python.org/en/latest/specifications/pyproject-toml>.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Project {
|
||||
/// The name of the project
|
||||
|
|
@ -113,6 +122,13 @@ pub struct Project {
|
|||
pub requires_python: Option<VersionSpecifiers>,
|
||||
/// The optional dependencies of the project.
|
||||
pub optional_dependencies: Option<BTreeMap<ExtraName, Vec<String>>>,
|
||||
|
||||
/// Used to determine whether a `gui-scripts` section is present.
|
||||
#[serde(default, skip_serializing)]
|
||||
pub(crate) gui_scripts: Option<serde::de::IgnoredAny>,
|
||||
/// Used to determine whether a `scripts` section is present.
|
||||
#[serde(default, skip_serializing)]
|
||||
pub(crate) scripts: Option<serde::de::IgnoredAny>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use uv_normalize::{PackageName, DEV_DEPENDENCIES};
|
|||
use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
|
||||
use uv_resolver::{FlatIndex, Lock};
|
||||
use uv_types::{BuildIsolation, HashStrategy};
|
||||
use uv_warnings::warn_user;
|
||||
use uv_workspace::{DiscoveryOptions, InstallTarget, MemberDiscovery, VirtualProject, Workspace};
|
||||
|
||||
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
|
||||
|
|
@ -74,6 +75,14 @@ pub(crate) async fn sync(
|
|||
InstallTarget::from(&project)
|
||||
};
|
||||
|
||||
// TODO(lucab): improve warning content
|
||||
// <https://github.com/astral-sh/uv/issues/7428>
|
||||
if project.workspace().pyproject_toml().has_scripts()
|
||||
&& !project.workspace().pyproject_toml().is_package()
|
||||
{
|
||||
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`");
|
||||
}
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
let venv = project::get_or_init_environment(
|
||||
target.workspace(),
|
||||
|
|
|
|||
|
|
@ -1950,3 +1950,45 @@ fn run_invalid_project_table() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn run_script_without_build_system() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! { r#"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
entry = "foo:custom_entry"
|
||||
"#
|
||||
})?;
|
||||
|
||||
let test_script = context.temp_dir.child("src/__init__.py");
|
||||
test_script.write_str(indoc! { r#"
|
||||
def custom_entry():
|
||||
print!("Hello")
|
||||
"#
|
||||
})?;
|
||||
|
||||
// TODO(lucab): this should match `entry` and warn
|
||||
// <https://github.com/astral-sh/uv/issues/7428>
|
||||
uv_snapshot!(context.filters(), context.run().arg("entry"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Audited in [TIME]
|
||||
error: Failed to spawn: `entry`
|
||||
Caused by: No such file or directory (os error 2)
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2328,3 +2328,94 @@ fn transitive_dev() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check warning message for <https://github.com/astral-sh/uv/issues/6998>
|
||||
/// if no `build-system` section is defined.
|
||||
fn sync_scripts_without_build_system() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
entry = "foo:custom_entry"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let test_script = context.temp_dir.child("src/__init__.py");
|
||||
test_script.write_str(
|
||||
r#"
|
||||
def custom_entry():
|
||||
print!("Hello")
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- 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]
|
||||
Audited in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check warning message for <https://github.com/astral-sh/uv/issues/6998>
|
||||
/// if the project is marked as `package = false`.
|
||||
fn sync_scripts_project_not_packaged() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
entry = "foo:custom_entry"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv]
|
||||
package = false
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let test_script = context.temp_dir.child("src/__init__.py");
|
||||
test_script.write_str(
|
||||
r#"
|
||||
def custom_entry():
|
||||
print!("Hello")
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- 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]
|
||||
Audited in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue