diff --git a/Cargo.lock b/Cargo.lock index 88ad7ea2bcb0..a9b9c9dfd2ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5019,7 +5019,6 @@ dependencies = [ name = "uv-distribution-types" version = "0.0.1" dependencies = [ - "anyhow", "arcstr", "bitflags 2.8.0", "fs-err 3.0.0", @@ -5200,6 +5199,7 @@ dependencies = [ "uv-distribution", "uv-distribution-types", "uv-fs", + "uv-git", "uv-install-wheel", "uv-normalize", "uv-pep440", diff --git a/crates/uv-distribution-types/Cargo.toml b/crates/uv-distribution-types/Cargo.toml index b65ac8cfcc4d..3f2dc829302e 100644 --- a/crates/uv-distribution-types/Cargo.toml +++ b/crates/uv-distribution-types/Cargo.toml @@ -28,7 +28,6 @@ uv-pep508 = { workspace = true } uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } -anyhow = { workspace = true } arcstr = { workspace = true } bitflags = { workspace = true } fs-err = { workspace = true } diff --git a/crates/uv-distribution-types/src/cached.rs b/crates/uv-distribution-types/src/cached.rs index 14cd1358b65b..7ae7f631a843 100644 --- a/crates/uv-distribution-types/src/cached.rs +++ b/crates/uv-distribution-types/src/cached.rs @@ -1,12 +1,9 @@ use std::path::{Path, PathBuf}; -use anyhow::{anyhow, Result}; - use uv_cache_info::CacheInfo; use uv_distribution_filename::WheelFilename; use uv_normalize::PackageName; -use uv_pep508::VerbatimUrl; -use uv_pypi_types::{HashDigest, ParsedDirectoryUrl}; +use uv_pypi_types::{HashDigest, VerbatimParsedUrl}; use crate::{ BuiltDist, Dist, DistributionMetadata, Hashed, InstalledMetadata, InstalledVersion, Name, @@ -33,10 +30,8 @@ pub struct CachedRegistryDist { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct CachedDirectUrlDist { pub filename: WheelFilename, - pub url: VerbatimUrl, + pub url: VerbatimParsedUrl, pub path: PathBuf, - pub editable: bool, - pub r#virtual: bool, pub hashes: Vec, pub cache_info: CacheInfo, } @@ -59,21 +54,23 @@ impl CachedDist { }), Dist::Built(BuiltDist::DirectUrl(dist)) => Self::Url(CachedDirectUrlDist { filename, - url: dist.url, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url, + }, hashes, cache_info, path, - editable: false, - r#virtual: false, }), Dist::Built(BuiltDist::Path(dist)) => Self::Url(CachedDirectUrlDist { filename, - url: dist.url, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url, + }, hashes, cache_info, path, - editable: false, - r#virtual: false, }), Dist::Source(SourceDist::Registry(_dist)) => Self::Registry(CachedRegistryDist { filename, @@ -83,39 +80,43 @@ impl CachedDist { }), Dist::Source(SourceDist::DirectUrl(dist)) => Self::Url(CachedDirectUrlDist { filename, - url: dist.url, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url, + }, hashes, cache_info, path, - editable: false, - r#virtual: false, }), Dist::Source(SourceDist::Git(dist)) => Self::Url(CachedDirectUrlDist { filename, - url: dist.url, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url, + }, hashes, cache_info, path, - editable: false, - r#virtual: false, }), Dist::Source(SourceDist::Path(dist)) => Self::Url(CachedDirectUrlDist { filename, - url: dist.url, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url, + }, hashes, cache_info, path, - editable: false, - r#virtual: false, }), Dist::Source(SourceDist::Directory(dist)) => Self::Url(CachedDirectUrlDist { filename, - url: dist.url, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url, + }, hashes, cache_info, path, - editable: dist.editable, - r#virtual: dist.r#virtual, }), } } @@ -137,26 +138,10 @@ impl CachedDist { } /// Return the [`ParsedUrl`] of the distribution, if it exists. - pub fn parsed_url(&self) -> Result> { + pub fn parsed_url(&self) -> Option<&ParsedUrl> { match self { - Self::Registry(_) => Ok(None), - Self::Url(dist) => { - if dist.editable { - assert_eq!(dist.url.scheme(), "file", "{}", dist.url); - let path = dist - .url - .to_file_path() - .map_err(|()| anyhow!("Invalid path in file URL"))?; - Ok(Some(ParsedUrl::Directory(ParsedDirectoryUrl { - url: dist.url.raw().clone(), - install_path: path, - editable: dist.editable, - r#virtual: dist.r#virtual, - }))) - } else { - Ok(Some(ParsedUrl::try_from(dist.url.to_url())?)) - } - } + Self::Registry(_) => None, + Self::Url(dist) => Some(&dist.url.parsed_url), } } @@ -175,27 +160,6 @@ impl Hashed for CachedRegistryDist { } } -impl CachedDirectUrlDist { - /// Initialize a [`CachedDirectUrlDist`] from a [`WheelFilename`], [`url::Url`], and [`Path`]. - pub fn from_url( - filename: WheelFilename, - url: VerbatimUrl, - hashes: Vec, - cache_info: CacheInfo, - path: PathBuf, - ) -> Self { - Self { - filename, - url, - hashes, - cache_info, - path, - editable: false, - r#virtual: false, - } - } -} - impl Name for CachedRegistryDist { fn name(&self) -> &PackageName { &self.filename.name @@ -225,7 +189,7 @@ impl DistributionMetadata for CachedRegistryDist { impl DistributionMetadata for CachedDirectUrlDist { fn version_or_url(&self) -> VersionOrUrlRef { - VersionOrUrlRef::Url(&self.url) + VersionOrUrlRef::Url(&self.url.verbatim) } } @@ -246,7 +210,7 @@ impl InstalledMetadata for CachedRegistryDist { impl InstalledMetadata for CachedDirectUrlDist { fn installed_version(&self) -> InstalledVersion { - InstalledVersion::Url(&self.url, &self.filename.version) + InstalledVersion::Url(&self.url.verbatim, &self.filename.version) } } diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index 2d9708fac49a..8a7d5a2c889f 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -47,7 +47,9 @@ use uv_git::GitUrl; use uv_normalize::PackageName; use uv_pep440::Version; use uv_pep508::{Pep508Url, VerbatimUrl}; -use uv_pypi_types::{ParsedUrl, VerbatimParsedUrl}; +use uv_pypi_types::{ + ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, VerbatimParsedUrl, +}; pub use crate::annotation::*; pub use crate::any::*; @@ -662,6 +664,74 @@ impl RegistryBuiltDist { } } +impl DirectUrlBuiltDist { + /// Return the [`ParsedUrl`] for the distribution. + pub fn parsed_url(&self) -> ParsedUrl { + ParsedUrl::Archive(ParsedArchiveUrl::from_source( + (*self.location).clone(), + None, + DistExtension::Wheel, + )) + } +} + +impl PathBuiltDist { + /// Return the [`ParsedUrl`] for the distribution. + pub fn parsed_url(&self) -> ParsedUrl { + ParsedUrl::Path(ParsedPathUrl::from_source( + self.install_path.clone(), + DistExtension::Wheel, + self.url.to_url(), + )) + } +} + +impl PathSourceDist { + /// Return the [`ParsedUrl`] for the distribution. + pub fn parsed_url(&self) -> ParsedUrl { + ParsedUrl::Path(ParsedPathUrl::from_source( + self.install_path.clone(), + DistExtension::Source(self.ext), + self.url.to_url(), + )) + } +} + +impl DirectUrlSourceDist { + /// Return the [`ParsedUrl`] for the distribution. + pub fn parsed_url(&self) -> ParsedUrl { + ParsedUrl::Archive(ParsedArchiveUrl::from_source( + (*self.location).clone(), + self.subdirectory.clone(), + DistExtension::Source(self.ext), + )) + } +} + +impl GitSourceDist { + /// Return the [`ParsedUrl`] for the distribution. + pub fn parsed_url(&self) -> ParsedUrl { + ParsedUrl::Git(ParsedGitUrl::from_source( + self.git.repository().clone(), + self.git.reference().clone(), + self.git.precise(), + self.subdirectory.clone(), + )) + } +} + +impl DirectorySourceDist { + /// Return the [`ParsedUrl`] for the distribution. + pub fn parsed_url(&self) -> ParsedUrl { + ParsedUrl::Directory(ParsedDirectoryUrl::from_source( + self.install_path.clone(), + self.editable, + self.r#virtual, + self.url.to_url(), + )) + } +} + impl Name for RegistryBuiltWheel { fn name(&self) -> &PackageName { &self.filename.name diff --git a/crates/uv-distribution/src/index/cached_wheel.rs b/crates/uv-distribution/src/index/cached_wheel.rs index bdddcf250aa4..c8186ce6f321 100644 --- a/crates/uv-distribution/src/index/cached_wheel.rs +++ b/crates/uv-distribution/src/index/cached_wheel.rs @@ -1,13 +1,16 @@ use std::path::Path; -use crate::archive::Archive; -use crate::{HttpArchivePointer, LocalArchivePointer}; use uv_cache::{Cache, CacheBucket, CacheEntry}; use uv_cache_info::CacheInfo; use uv_distribution_filename::WheelFilename; -use uv_distribution_types::{CachedDirectUrlDist, CachedRegistryDist, Hashed}; -use uv_pep508::VerbatimUrl; -use uv_pypi_types::HashDigest; +use uv_distribution_types::{ + CachedDirectUrlDist, CachedRegistryDist, DirectUrlSourceDist, DirectorySourceDist, + GitSourceDist, Hashed, PathSourceDist, +}; +use uv_pypi_types::{HashDigest, VerbatimParsedUrl}; + +use crate::archive::Archive; +use crate::{HttpArchivePointer, LocalArchivePointer}; #[derive(Debug, Clone)] pub struct CachedWheel { @@ -53,40 +56,61 @@ impl CachedWheel { } } - /// Convert a [`CachedWheel`] into a [`CachedDirectUrlDist`]. - pub fn into_url_dist(self, url: VerbatimUrl) -> CachedDirectUrlDist { + /// Convert a [`CachedWheel`] into a [`CachedDirectUrlDist`] by merging in the given + /// [`DirectUrlSourceDist`]. + pub fn into_url_dist(self, dist: &DirectUrlSourceDist) -> CachedDirectUrlDist { + CachedDirectUrlDist { + filename: self.filename, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url.clone(), + }, + path: self.entry.into_path_buf(), + hashes: self.hashes, + cache_info: self.cache_info, + } + } + + /// Convert a [`CachedWheel`] into a [`CachedDirectUrlDist`] by merging in the given + /// [`PathSourceDist`]. + pub fn into_path_dist(self, dist: &PathSourceDist) -> CachedDirectUrlDist { CachedDirectUrlDist { filename: self.filename, - url, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url.clone(), + }, path: self.entry.into_path_buf(), - editable: false, - r#virtual: false, hashes: self.hashes, cache_info: self.cache_info, } } - /// Convert a [`CachedWheel`] into an editable [`CachedDirectUrlDist`]. - pub fn into_editable(self, url: VerbatimUrl) -> CachedDirectUrlDist { + /// Convert a [`CachedWheel`] into a [`CachedDirectUrlDist`] by merging in the given + /// [`DirectorySourceDist`]. + pub fn into_directory_dist(self, dist: &DirectorySourceDist) -> CachedDirectUrlDist { CachedDirectUrlDist { filename: self.filename, - url, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url.clone(), + }, path: self.entry.into_path_buf(), - editable: true, - r#virtual: false, hashes: self.hashes, cache_info: self.cache_info, } } - /// Convert a [`CachedWheel`] into an editable [`CachedDirectUrlDist`]. - pub fn into_virtual(self, url: VerbatimUrl) -> CachedDirectUrlDist { + /// Convert a [`CachedWheel`] into a [`CachedDirectUrlDist`] by merging in the given + /// [`GitSourceDist`]. + pub fn into_git_dist(self, dist: &GitSourceDist) -> CachedDirectUrlDist { CachedDirectUrlDist { filename: self.filename, - url, + url: VerbatimParsedUrl { + parsed_url: dist.parsed_url(), + verbatim: dist.url.clone(), + }, path: self.entry.into_path_buf(), - editable: false, - r#virtual: true, hashes: self.hashes, cache_info: self.cache_info, } diff --git a/crates/uv-installer/Cargo.toml b/crates/uv-installer/Cargo.toml index e00ab8daef6f..ae247c8ad20f 100644 --- a/crates/uv-installer/Cargo.toml +++ b/crates/uv-installer/Cargo.toml @@ -23,6 +23,7 @@ uv-configuration = { workspace = true } uv-distribution = { workspace = true } uv-distribution-types = { workspace = true } uv-fs = { workspace = true } +uv-git = { workspace = true } uv-install-wheel = { workspace = true, default-features = false } uv-normalize = { workspace = true } uv-pep440 = { workspace = true } diff --git a/crates/uv-installer/src/installer.rs b/crates/uv-installer/src/installer.rs index e2db41b4ce98..a613259a1d44 100644 --- a/crates/uv-installer/src/installer.rs +++ b/crates/uv-installer/src/installer.rs @@ -166,10 +166,8 @@ fn install( wheel.path(), wheel.filename(), wheel - .parsed_url()? - .as_ref() - .map(uv_pypi_types::DirectUrl::try_from) - .transpose()? + .parsed_url() + .map(uv_pypi_types::DirectUrl::from) .as_ref(), if wheel.cache_info().is_empty() { None diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index a8b477f2fcd9..1e737e4f73a1 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Result}; use tracing::{debug, warn}; use uv_cache::{Cache, CacheBucket, WheelCache}; -use uv_cache_info::{CacheInfo, Timestamp}; +use uv_cache_info::Timestamp; use uv_configuration::{BuildOptions, ConfigSettings, Reinstall}; use uv_distribution::{ BuiltWheelIndex, HttpArchivePointer, LocalArchivePointer, RegistryWheelIndex, @@ -13,7 +13,7 @@ use uv_distribution_types::{ }; use uv_fs::Simplified; use uv_platform_tags::Tags; -use uv_pypi_types::RequirementSource; +use uv_pypi_types::{RequirementSource, VerbatimParsedUrl}; use uv_python::PythonEnvironment; use uv_types::HashStrategy; @@ -163,18 +163,21 @@ impl<'a> Planner<'a> { // Read the HTTP pointer. if let Some(pointer) = HttpArchivePointer::read_from(&cache_entry)? { + let cache_info = pointer.to_cache_info(); let archive = pointer.into_archive(); if archive.satisfies(hasher.get(dist)) { - let cached_dist = CachedDirectUrlDist::from_url( - wheel.filename.clone(), - wheel.url.clone(), - archive.hashes, - CacheInfo::default(), - cache.archive(&archive.id), - ); + let cached_dist = CachedDirectUrlDist { + filename: wheel.filename.clone(), + url: VerbatimParsedUrl { + parsed_url: wheel.parsed_url(), + verbatim: wheel.url.clone(), + }, + hashes: archive.hashes, + cache_info, + path: cache.archive(&archive.id), + }; debug!("URL wheel requirement already cached: {cached_dist}"); - // STOPSHIP(charlie): If these are mismatched, skip and warn. cached.push(CachedDist::Url(cached_dist)); continue; } @@ -215,13 +218,16 @@ impl<'a> Planner<'a> { let cache_info = pointer.to_cache_info(); let archive = pointer.into_archive(); if archive.satisfies(hasher.get(dist)) { - let cached_dist = CachedDirectUrlDist::from_url( - wheel.filename.clone(), - wheel.url.clone(), - archive.hashes, + let cached_dist = CachedDirectUrlDist { + filename: wheel.filename.clone(), + url: VerbatimParsedUrl { + parsed_url: wheel.parsed_url(), + verbatim: wheel.url.clone(), + }, + hashes: archive.hashes, cache_info, - cache.archive(&archive.id), - ); + path: cache.archive(&archive.id), + }; debug!("Path wheel requirement already cached: {cached_dist}"); cached.push(CachedDist::Url(cached_dist)); @@ -259,7 +265,7 @@ impl<'a> Planner<'a> { // the filename in advance. if let Some(wheel) = built_index.url(sdist)? { if wheel.filename.name == sdist.name { - let cached_dist = wheel.into_url_dist(sdist.url.clone()); + let cached_dist = wheel.into_url_dist(sdist); debug!("URL source requirement already cached: {cached_dist}"); cached.push(CachedDist::Url(cached_dist)); continue; @@ -277,7 +283,7 @@ impl<'a> Planner<'a> { // the filename in advance. if let Some(wheel) = built_index.git(sdist) { if wheel.filename.name == sdist.name { - let cached_dist = wheel.into_url_dist(sdist.url.clone()); + let cached_dist = wheel.into_git_dist(sdist); debug!("Git source requirement already cached: {cached_dist}"); cached.push(CachedDist::Url(cached_dist)); continue; @@ -300,7 +306,7 @@ impl<'a> Planner<'a> { // the filename in advance. if let Some(wheel) = built_index.path(sdist)? { if wheel.filename.name == sdist.name { - let cached_dist = wheel.into_url_dist(sdist.url.clone()); + let cached_dist = wheel.into_path_dist(sdist); debug!("Path source requirement already cached: {cached_dist}"); cached.push(CachedDist::Url(cached_dist)); continue; @@ -323,11 +329,7 @@ impl<'a> Planner<'a> { // the filename in advance. if let Some(wheel) = built_index.directory(sdist)? { if wheel.filename.name == sdist.name { - let cached_dist = if sdist.editable { - wheel.into_editable(sdist.url.clone()) - } else { - wheel.into_url_dist(sdist.url.clone()) - }; + let cached_dist = wheel.into_directory_dist(sdist); debug!("Directory source requirement already cached: {cached_dist}"); cached.push(CachedDist::Url(cached_dist)); continue; diff --git a/crates/uv-installer/src/satisfies.rs b/crates/uv-installer/src/satisfies.rs index 329fadc5660d..6427e9d0d833 100644 --- a/crates/uv-installer/src/satisfies.rs +++ b/crates/uv-installer/src/satisfies.rs @@ -7,6 +7,7 @@ use url::Url; use uv_cache_info::CacheInfo; use uv_cache_key::{CanonicalUrl, RepositoryUrl}; use uv_distribution_types::{InstalledDirectUrlDist, InstalledDist}; +use uv_git::GitOid; use uv_pypi_types::{DirInfo, DirectUrl, RequirementSource, VcsInfo, VcsKind}; #[derive(Debug, Copy, Clone)] @@ -97,7 +98,7 @@ impl RequirementSatisfaction { RequirementSource::Git { url: _, repository: requested_repository, - reference: requested_reference, + reference: _, precise: requested_precise, subdirectory: requested_subdirectory, } => { @@ -110,8 +111,8 @@ impl RequirementSatisfaction { vcs_info: VcsInfo { vcs: VcsKind::Git, - requested_revision: installed_reference, - commit_id: _, + requested_revision: _, + commit_id: installed_precise, }, subdirectory: installed_subdirectory, } = direct_url.as_ref() @@ -137,12 +138,12 @@ impl RequirementSatisfaction { return Ok(Self::Mismatch); } - if installed_reference.as_deref() != requested_reference.as_str() - && installed_reference != &requested_precise.map(|git_sha| git_sha.to_string()) - { + // TODO(charlie): It would be more consistent for us to compare the requested + // revisions here. + if installed_precise.as_deref() != requested_precise.as_ref().map(GitOid::as_str) { debug!( - "Reference mismatch: {:?} vs. {:?} and {:?}", - installed_reference, requested_reference, requested_precise + "Precise mismatch: {:?} vs. {:?}", + installed_precise, requested_precise ); return Ok(Self::OutOfDate); } diff --git a/crates/uv-pypi-types/src/parsed_url.rs b/crates/uv-pypi-types/src/parsed_url.rs index d3c9de22ec54..b9d22ed2e98c 100644 --- a/crates/uv-pypi-types/src/parsed_url.rs +++ b/crates/uv-pypi-types/src/parsed_url.rs @@ -406,67 +406,57 @@ impl TryFrom for ParsedUrl { } } -impl TryFrom<&ParsedUrl> for DirectUrl { - type Error = ParsedUrlError; - - fn try_from(value: &ParsedUrl) -> Result { +impl From<&ParsedUrl> for DirectUrl { + fn from(value: &ParsedUrl) -> Self { match value { - ParsedUrl::Path(value) => Self::try_from(value), - ParsedUrl::Directory(value) => Self::try_from(value), - ParsedUrl::Git(value) => Self::try_from(value), - ParsedUrl::Archive(value) => Self::try_from(value), + ParsedUrl::Path(value) => Self::from(value), + ParsedUrl::Directory(value) => Self::from(value), + ParsedUrl::Git(value) => Self::from(value), + ParsedUrl::Archive(value) => Self::from(value), } } } -impl TryFrom<&ParsedPathUrl> for DirectUrl { - type Error = ParsedUrlError; - - fn try_from(value: &ParsedPathUrl) -> Result { - Ok(Self::ArchiveUrl { +impl From<&ParsedPathUrl> for DirectUrl { + fn from(value: &ParsedPathUrl) -> Self { + Self::ArchiveUrl { url: value.url.to_string(), archive_info: ArchiveInfo { hash: None, hashes: None, }, subdirectory: None, - }) + } } } -impl TryFrom<&ParsedDirectoryUrl> for DirectUrl { - type Error = ParsedUrlError; - - fn try_from(value: &ParsedDirectoryUrl) -> Result { - Ok(Self::LocalDirectory { +impl From<&ParsedDirectoryUrl> for DirectUrl { + fn from(value: &ParsedDirectoryUrl) -> Self { + Self::LocalDirectory { url: value.url.to_string(), dir_info: DirInfo { editable: value.editable.then_some(true), }, - }) + } } } -impl TryFrom<&ParsedArchiveUrl> for DirectUrl { - type Error = ParsedUrlError; - - fn try_from(value: &ParsedArchiveUrl) -> Result { - Ok(Self::ArchiveUrl { +impl From<&ParsedArchiveUrl> for DirectUrl { + fn from(value: &ParsedArchiveUrl) -> Self { + Self::ArchiveUrl { url: value.url.to_string(), archive_info: ArchiveInfo { hash: None, hashes: None, }, subdirectory: value.subdirectory.clone(), - }) + } } } -impl TryFrom<&ParsedGitUrl> for DirectUrl { - type Error = ParsedUrlError; - - fn try_from(value: &ParsedGitUrl) -> Result { - Ok(Self::VcsUrl { +impl From<&ParsedGitUrl> for DirectUrl { + fn from(value: &ParsedGitUrl) -> Self { + Self::VcsUrl { url: value.url.repository().to_string(), vcs_info: VcsInfo { vcs: VcsKind::Git, @@ -474,7 +464,7 @@ impl TryFrom<&ParsedGitUrl> for DirectUrl { requested_revision: value.url.reference().as_str().map(ToString::to_string), }, subdirectory: value.subdirectory.clone(), - }) + } } } diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index a7d10bb87d1e..167e959025f9 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -5825,6 +5825,7 @@ fn already_installed_remote_url() { ----- stdout ----- ----- stderr ----- + Resolved 1 package in [TIME] Audited 1 package in [TIME] "###); @@ -8257,3 +8258,114 @@ fn cyclic_build_dependency() { "### ); } + +#[test] +fn direct_url_json_git_default() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage", + )?; + + uv_snapshot!(context.pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--strict"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389) + "### + ); + + let direct_url = context.venv.child(if cfg!(windows) { + "Lib\\site-packages\\uv_public_pypackage-0.1.0.dist-info\\direct_url.json" + } else { + "lib/python3.12/site-packages/uv_public_pypackage-0.1.0.dist-info/direct_url.json" + }); + direct_url.assert(predicates::path::is_file()); + + let direct_url_content = fs_err::read_to_string(direct_url.path())?; + insta::assert_snapshot!(direct_url_content, @r###"{"url":"https://github.com/astral-test/uv-public-pypackage","vcs_info":{"vcs":"git","commit_id":"b270df1a2fb5d012294e9aaf05e7e0bab1e6a389"}}"###); + + Ok(()) +} + +#[test] +fn direct_url_json_git_tag() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "uv-public-pypackage @ git+https://github.com/astral-test/uv-public-pypackage@0.0.1", + )?; + + uv_snapshot!(context.pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--strict"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + uv-public-pypackage==0.1.0 (from git+https://github.com/astral-test/uv-public-pypackage@0dacfd662c64cb4ceb16e6cf65a157a8b715b979) + "### + ); + + let direct_url = context.venv.child(if cfg!(windows) { + "Lib\\site-packages\\uv_public_pypackage-0.1.0.dist-info\\direct_url.json" + } else { + "lib/python3.12/site-packages/uv_public_pypackage-0.1.0.dist-info/direct_url.json" + }); + direct_url.assert(predicates::path::is_file()); + + let direct_url_content = fs_err::read_to_string(direct_url.path())?; + insta::assert_snapshot!(direct_url_content, @r###"{"url":"https://github.com/astral-test/uv-public-pypackage","vcs_info":{"vcs":"git","commit_id":"0dacfd662c64cb4ceb16e6cf65a157a8b715b979","requested_revision":"0.0.1"}}"###); + + Ok(()) +} + +#[test] +fn direct_url_json_direct_url() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str( + "source-distribution @ https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz", + )?; + + uv_snapshot!(context.pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--strict"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + source-distribution==0.0.3 (from https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz) + "### + ); + + let direct_url = context.venv.child(if cfg!(windows) { + "Lib\\site-packages\\source_distribution-0.0.3.dist-info\\direct_url.json" + } else { + "lib/python3.12/site-packages/source_distribution-0.0.3.dist-info/direct_url.json" + }); + direct_url.assert(predicates::path::is_file()); + + let direct_url_content = fs_err::read_to_string(direct_url.path())?; + insta::assert_snapshot!(direct_url_content, @r###"{"url":"https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz","archive_info":{}}"###); + + Ok(()) +}