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)]
|
#[serde(skip)]
|
||||||
pub raw: String,
|
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)]
|
#[serde(default, skip_serializing)]
|
||||||
build_system: Option<serde::de::IgnoredAny>,
|
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.
|
// Otherwise, a project is assumed to be a package if `build-system` is present.
|
||||||
self.build_system.is_some()
|
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.
|
// Ignore raw document in comparison.
|
||||||
|
|
@ -102,7 +111,7 @@ impl AsRef<[u8]> for PyProjectToml {
|
||||||
/// PEP 621 project metadata (`project`).
|
/// PEP 621 project metadata (`project`).
|
||||||
///
|
///
|
||||||
/// See <https://packaging.python.org/en/latest/specifications/pyproject-toml>.
|
/// 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")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
/// The name of the project
|
/// The name of the project
|
||||||
|
|
@ -113,6 +122,13 @@ pub struct Project {
|
||||||
pub requires_python: Option<VersionSpecifiers>,
|
pub requires_python: Option<VersionSpecifiers>,
|
||||||
/// The optional dependencies of the project.
|
/// The optional dependencies of the project.
|
||||||
pub optional_dependencies: Option<BTreeMap<ExtraName, Vec<String>>>,
|
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)]
|
#[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_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
|
||||||
use uv_resolver::{FlatIndex, Lock};
|
use uv_resolver::{FlatIndex, Lock};
|
||||||
use uv_types::{BuildIsolation, HashStrategy};
|
use uv_types::{BuildIsolation, HashStrategy};
|
||||||
|
use uv_warnings::warn_user;
|
||||||
use uv_workspace::{DiscoveryOptions, InstallTarget, MemberDiscovery, VirtualProject, Workspace};
|
use uv_workspace::{DiscoveryOptions, InstallTarget, MemberDiscovery, VirtualProject, Workspace};
|
||||||
|
|
||||||
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
|
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
|
||||||
|
|
@ -74,6 +75,14 @@ pub(crate) async fn sync(
|
||||||
InstallTarget::from(&project)
|
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.
|
// Discover or create the virtual environment.
|
||||||
let venv = project::get_or_init_environment(
|
let venv = project::get_or_init_environment(
|
||||||
target.workspace(),
|
target.workspace(),
|
||||||
|
|
|
||||||
|
|
@ -1950,3 +1950,45 @@ fn run_invalid_project_table() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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(())
|
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