Skip to content

Commit 7ac6c69

Browse files
Allow virtual packages with --no-build (#12314)
## Summary Closes #12311.
1 parent 149102a commit 7ac6c69

File tree

4 files changed

+157
-16
lines changed

4 files changed

+157
-16
lines changed

crates/uv-distribution-types/src/lib.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -639,22 +639,31 @@ impl SourceDist {
639639
}
640640
}
641641

642+
/// Returns the [`Version`] of the distribution, if it is known.
642643
pub fn version(&self) -> Option<&Version> {
643644
match self {
644645
Self::Registry(source_dist) => Some(&source_dist.version),
645646
Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
646647
}
647648
}
648649

649-
/// Return true if the distribution is editable.
650+
/// Returns `true` if the distribution is editable.
650651
pub fn is_editable(&self) -> bool {
651652
match self {
652653
Self::Directory(DirectorySourceDist { editable, .. }) => *editable,
653654
_ => false,
654655
}
655656
}
656657

657-
/// Return true if the distribution refers to a local file or directory.
658+
/// Returns `true` if the distribution is virtual.
659+
pub fn is_virtual(&self) -> bool {
660+
match self {
661+
Self::Directory(DirectorySourceDist { r#virtual, .. }) => *r#virtual,
662+
_ => false,
663+
}
664+
}
665+
666+
/// Returns `true` if the distribution refers to a local file or directory.
658667
pub fn is_local(&self) -> bool {
659668
matches!(self, Self::Directory(_) | Self::Path(_))
660669
}
@@ -668,7 +677,7 @@ impl SourceDist {
668677
}
669678
}
670679

671-
/// Return the source tree of the distribution, if available.
680+
/// Returns the source tree of the distribution, if available.
672681
pub fn source_tree(&self) -> Option<&Path> {
673682
match self {
674683
Self::Directory(dist) => Some(&dist.install_path),

crates/uv-resolver/src/lock/mod.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -2196,8 +2196,11 @@ impl Package {
21962196
};
21972197
}
21982198

2199-
if !no_build {
2200-
if let Some(sdist) = self.to_source_dist(workspace_root)? {
2199+
if let Some(sdist) = self.to_source_dist(workspace_root)? {
2200+
// Even with `--no-build`, allow virtual packages. (In the future, we may want to allow
2201+
// any local source tree, or at least editable source trees, which we allow in
2202+
// `uv pip`.)
2203+
if !no_build || sdist.is_virtual() {
22012204
return Ok(Dist::Source(sdist));
22022205
}
22032206
}

crates/uv/tests/it/lock.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -23005,14 +23005,16 @@ fn lock_no_build_static_metadata() -> Result<()> {
2300523005
"###);
2300623006

2300723007
// Install from the lockfile.
23008-
uv_snapshot!(context.filters(), context.sync().arg("--no-build").arg("--frozen"), @r###"
23009-
success: false
23010-
exit_code: 2
23008+
uv_snapshot!(context.filters(), context.sync().arg("--no-build").arg("--frozen"), @r"
23009+
success: true
23010+
exit_code: 0
2301123011
----- stdout -----
2301223012

2301323013
----- stderr -----
23014-
error: Distribution `dummy==0.1.0 @ virtual+.` can't be installed because it is marked as `--no-build` but has no binary distribution
23015-
"###);
23014+
Prepared 1 package in [TIME]
23015+
Installed 1 package in [TIME]
23016+
+ iniconfig==2.0.0
23017+
");
2301623018

2301723019
Ok(())
2301823020
}

crates/uv/tests/it/sync.rs

+133-6
Original file line numberDiff line numberDiff line change
@@ -3544,6 +3544,133 @@ fn no_install_project_no_build() -> Result<()> {
35443544
Ok(())
35453545
}
35463546

3547+
#[test]
3548+
fn virtual_no_build() -> Result<()> {
3549+
let context = TestContext::new("3.12");
3550+
3551+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
3552+
pyproject_toml.write_str(
3553+
r#"
3554+
[project]
3555+
name = "project"
3556+
version = "0.1.0"
3557+
requires-python = ">=3.12"
3558+
dependencies = ["anyio==3.7.0"]
3559+
"#,
3560+
)?;
3561+
3562+
// Generate a lockfile.
3563+
context.lock().assert().success();
3564+
3565+
// Clear the cache.
3566+
fs_err::remove_dir_all(&context.cache_dir)?;
3567+
3568+
// `--no-build` should not raise an error, since we don't install virtual projects.
3569+
uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r"
3570+
success: true
3571+
exit_code: 0
3572+
----- stdout -----
3573+
3574+
----- stderr -----
3575+
Resolved 4 packages in [TIME]
3576+
Prepared 3 packages in [TIME]
3577+
Installed 3 packages in [TIME]
3578+
+ anyio==3.7.0
3579+
+ idna==3.6
3580+
+ sniffio==1.3.1
3581+
");
3582+
3583+
Ok(())
3584+
}
3585+
3586+
#[test]
3587+
fn virtual_no_build_dynamic_cached() -> Result<()> {
3588+
let context = TestContext::new("3.12");
3589+
3590+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
3591+
pyproject_toml.write_str(
3592+
r#"
3593+
[project]
3594+
name = "project"
3595+
version = "0.1.0"
3596+
requires-python = ">=3.12"
3597+
dynamic = ["dependencies"]
3598+
3599+
[tool.setuptools.dynamic]
3600+
dependencies = {file = ["requirements.txt"]}
3601+
"#,
3602+
)?;
3603+
3604+
context
3605+
.temp_dir
3606+
.child("requirements.txt")
3607+
.write_str("anyio==3.7.0")?;
3608+
3609+
// Generate a lockfile.
3610+
context.lock().assert().success();
3611+
3612+
// `--no-build` should not raise an error, since we don't build or install the project (given
3613+
// that it's virtual and the metadata is cached).
3614+
uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r"
3615+
success: true
3616+
exit_code: 0
3617+
----- stdout -----
3618+
3619+
----- stderr -----
3620+
Resolved 4 packages in [TIME]
3621+
Prepared 3 packages in [TIME]
3622+
Installed 3 packages in [TIME]
3623+
+ anyio==3.7.0
3624+
+ idna==3.6
3625+
+ sniffio==1.3.1
3626+
");
3627+
3628+
Ok(())
3629+
}
3630+
3631+
#[test]
3632+
fn virtual_no_build_dynamic_no_cache() -> Result<()> {
3633+
let context = TestContext::new("3.12");
3634+
3635+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
3636+
pyproject_toml.write_str(
3637+
r#"
3638+
[project]
3639+
name = "project"
3640+
version = "0.1.0"
3641+
requires-python = ">=3.12"
3642+
dynamic = ["dependencies"]
3643+
3644+
[tool.setuptools.dynamic]
3645+
dependencies = {file = ["requirements.txt"]}
3646+
"#,
3647+
)?;
3648+
3649+
context
3650+
.temp_dir
3651+
.child("requirements.txt")
3652+
.write_str("anyio==3.7.0")?;
3653+
3654+
// Generate a lockfile.
3655+
context.lock().assert().success();
3656+
3657+
// Clear the cache.
3658+
fs_err::remove_dir_all(&context.cache_dir)?;
3659+
3660+
// `--no-build` should raise an error, since we need to build the project.
3661+
uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r"
3662+
success: false
3663+
exit_code: 2
3664+
----- stdout -----
3665+
3666+
----- stderr -----
3667+
error: Failed to generate package metadata for `project==0.1.0 @ virtual+.`
3668+
Caused by: Building source distributions for `project` is disabled
3669+
");
3670+
3671+
Ok(())
3672+
}
3673+
35473674
/// Convert from a package to a virtual project.
35483675
#[test]
35493676
fn convert_to_virtual() -> Result<()> {
@@ -4815,25 +4942,25 @@ fn no_build_error() -> Result<()> {
48154942
error: Distribution `django-allauth==0.51.0 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-build` but has no binary distribution
48164943
"###);
48174944

4818-
uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r###"
4945+
uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r"
48194946
success: false
48204947
exit_code: 2
48214948
----- stdout -----
48224949
48234950
----- stderr -----
48244951
Resolved 19 packages in [TIME]
4825-
error: Distribution `project==0.1.0 @ virtual+.` can't be installed because it is marked as `--no-build` but has no binary distribution
4826-
"###);
4952+
error: Distribution `django-allauth==0.51.0 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-build` but has no binary distribution
4953+
");
48274954

4828-
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD", "1"), @r###"
4955+
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD", "1"), @r"
48294956
success: false
48304957
exit_code: 2
48314958
----- stdout -----
48324959
48334960
----- stderr -----
48344961
Resolved 19 packages in [TIME]
4835-
error: Distribution `project==0.1.0 @ virtual+.` can't be installed because it is marked as `--no-build` but has no binary distribution
4836-
"###);
4962+
error: Distribution `django-allauth==0.51.0 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-build` but has no binary distribution
4963+
");
48374964

48384965
uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD_PACKAGE", "django-allauth"), @r###"
48394966
success: false

0 commit comments

Comments
 (0)