mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 22:07:47 +00:00
Build path
sources without build systems by default (#14413)
We currently treat path sources as virtual if they do not specify a build system, which is surprising behavior. This PR updates the behavior to treat path sources as packages unless the path source is explicitly marked as `package = false` or its own `tool.uv.package` is set to `false`. Closes #12015 --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
b98ac8c224
commit
ff30f14d50
7 changed files with 172 additions and 48 deletions
|
@ -729,12 +729,14 @@ fn path_source(
|
|||
})
|
||||
} else {
|
||||
// Determine whether the project is a package or virtual.
|
||||
// If the `package` option is unset, check if `tool.uv.package` is set
|
||||
// on the path source (otherwise, default to `true`).
|
||||
let is_package = package.unwrap_or_else(|| {
|
||||
let pyproject_path = install_path.join("pyproject.toml");
|
||||
fs_err::read_to_string(&pyproject_path)
|
||||
.ok()
|
||||
.and_then(|contents| PyProjectToml::from_string(contents).ok())
|
||||
.map(|pyproject_toml| pyproject_toml.is_package())
|
||||
.and_then(|pyproject_toml| pyproject_toml.tool_uv_package())
|
||||
.unwrap_or(true)
|
||||
});
|
||||
|
||||
|
|
|
@ -83,12 +83,7 @@ impl PyProjectToml {
|
|||
/// non-package ("virtual") project.
|
||||
pub fn is_package(&self) -> bool {
|
||||
// If `tool.uv.package` is set, defer to that explicit setting.
|
||||
if let Some(is_package) = self
|
||||
.tool
|
||||
.as_ref()
|
||||
.and_then(|tool| tool.uv.as_ref())
|
||||
.and_then(|uv| uv.package)
|
||||
{
|
||||
if let Some(is_package) = self.tool_uv_package() {
|
||||
return is_package;
|
||||
}
|
||||
|
||||
|
@ -96,6 +91,14 @@ impl PyProjectToml {
|
|||
self.build_system.is_some()
|
||||
}
|
||||
|
||||
/// Returns the value of `tool.uv.package` if set.
|
||||
pub fn tool_uv_package(&self) -> Option<bool> {
|
||||
self.tool
|
||||
.as_ref()
|
||||
.and_then(|tool| tool.uv.as_ref())
|
||||
.and_then(|uv| uv.package)
|
||||
}
|
||||
|
||||
/// Returns `true` if the project uses a dynamic version.
|
||||
pub fn is_dynamic(&self) -> bool {
|
||||
self.project
|
||||
|
|
|
@ -13381,7 +13381,9 @@ fn add_path_with_no_workspace() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Audited in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ dep==0.1.0 (from file://[TEMP_DIR]/dep)
|
||||
");
|
||||
|
||||
let pyproject_toml = context.read("pyproject.toml");
|
||||
|
@ -13452,7 +13454,9 @@ fn add_path_outside_workspace_no_default() -> Result<()> {
|
|||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: .venv
|
||||
Resolved 2 packages in [TIME]
|
||||
Audited in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ dep==0.1.0 (from file://[TEMP_DIR]/external_dep)
|
||||
");
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(workspace_toml)?;
|
||||
|
|
|
@ -7205,12 +7205,12 @@ fn lock_exclusion() -> Result<()> {
|
|||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "project", virtual = "../" }]
|
||||
requires-dist = [{ name = "project", directory = "../" }]
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "../" }
|
||||
source = { directory = "../" }
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
@ -7793,7 +7793,7 @@ fn lock_dev_transitive() -> Result<()> {
|
|||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "baz", editable = "baz" },
|
||||
{ name = "foo", virtual = "../foo" },
|
||||
{ name = "foo", directory = "../foo" },
|
||||
{ name = "iniconfig", specifier = ">1" },
|
||||
]
|
||||
|
||||
|
@ -7815,7 +7815,7 @@ fn lock_dev_transitive() -> Result<()> {
|
|||
[[package]]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "../foo" }
|
||||
source = { directory = "../foo" }
|
||||
|
||||
[package.metadata]
|
||||
|
||||
|
@ -13651,7 +13651,7 @@ fn lock_narrowed_python_version_upper() -> Result<()> {
|
|||
[[package]]
|
||||
name = "dependency"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "dependency" }
|
||||
source = { directory = "dependency" }
|
||||
dependencies = [
|
||||
{ name = "iniconfig", marker = "python_full_version >= '3.10'" },
|
||||
]
|
||||
|
@ -13677,7 +13677,7 @@ fn lock_narrowed_python_version_upper() -> Result<()> {
|
|||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "dependency", marker = "python_full_version >= '3.10'", virtual = "dependency" }]
|
||||
requires-dist = [{ name = "dependency", marker = "python_full_version >= '3.10'", directory = "dependency" }]
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
@ -17173,10 +17173,10 @@ fn lock_implicit_virtual_project() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Lock a project that has a path dependency that is implicitly virtual (by way of omitting
|
||||
/// `build-system`).
|
||||
/// Lock a project that has a path dependency that is implicitly non-virtual (despite
|
||||
/// omitting `build-system`).
|
||||
#[test]
|
||||
fn lock_implicit_virtual_path() -> Result<()> {
|
||||
fn lock_implicit_package_path() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
|
@ -17243,7 +17243,7 @@ fn lock_implicit_virtual_path() -> Result<()> {
|
|||
[[package]]
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "child" }
|
||||
source = { directory = "child" }
|
||||
dependencies = [
|
||||
{ name = "iniconfig" },
|
||||
]
|
||||
|
@ -17281,7 +17281,7 @@ fn lock_implicit_virtual_path() -> Result<()> {
|
|||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "anyio", specifier = ">3" },
|
||||
{ name = "child", virtual = "child" },
|
||||
{ name = "child", directory = "child" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -17317,20 +17317,21 @@ fn lock_implicit_virtual_path() -> Result<()> {
|
|||
Resolved 6 packages in [TIME]
|
||||
"###);
|
||||
|
||||
// Install from the lockfile. The virtual project should _not_ be installed.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
// Install from the lockfile. The path dependency should be installed.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Prepared 4 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
Prepared 5 packages in [TIME]
|
||||
Installed 5 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ child==0.1.0 (from file://[TEMP_DIR]/child)
|
||||
+ idna==3.6
|
||||
+ iniconfig==2.0.0
|
||||
+ sniffio==1.3.1
|
||||
"###);
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -5939,6 +5939,91 @@ fn sync_override_package() -> Result<()> {
|
|||
~ project==0.0.0 (from file://[TEMP_DIR]/)
|
||||
");
|
||||
|
||||
// Update the source `tool.uv` to `package = true`
|
||||
let pyproject_toml = context.temp_dir.child("core").child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "core"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv]
|
||||
package = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Mark the source as `package = false`.
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.0.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["core"]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv.sources]
|
||||
core = { path = "./core", package = false }
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Syncing the project should _not_ install `core`.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
~ project==0.0.0 (from file://[TEMP_DIR]/)
|
||||
");
|
||||
|
||||
// Remove the `package = false` mark.
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.0.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["core"]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv.sources]
|
||||
core = { path = "./core" }
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Syncing the project _should_ install `core`.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Uninstalled 1 package in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ core==0.1.0 (from file://[TEMP_DIR]/core)
|
||||
~ project==0.0.0 (from file://[TEMP_DIR]/)
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue