2024-06-11 13:58:03 +02:00
use std ::borrow ::Cow ;
2024-07-18 20:07:49 +02:00
use std ::collections ::{ BTreeMap , BTreeSet , VecDeque } ;
2024-07-31 12:00:37 -04:00
use std ::convert ::Infallible ;
2025-01-20 12:46:46 -05:00
use std ::error ::Error ;
2025-02-25 06:28:16 -08:00
use std ::fmt ::{ Debug , Display , Formatter } ;
2024-08-23 22:19:10 -04:00
use std ::io ;
2024-05-17 17:23:40 -04:00
use std ::path ::{ Path , PathBuf } ;
use std ::str ::FromStr ;
2024-10-12 16:46:55 +02:00
use std ::sync ::{ Arc , LazyLock } ;
2024-12-24 17:32:29 -05:00
use itertools ::Itertools ;
2025-04-20 21:58:12 -04:00
use jiff ::Timestamp ;
2025-01-18 13:50:20 -05:00
use owo_colors ::OwoColorize ;
2024-12-24 17:32:29 -05:00
use petgraph ::graph ::NodeIndex ;
use petgraph ::visit ::EdgeRef ;
use rustc_hash ::{ FxHashMap , FxHashSet } ;
2024-12-24 20:24:26 -05:00
use serde ::Serializer ;
2025-05-18 19:38:43 -04:00
use toml_edit ::{ Array , ArrayOfTables , InlineTable , Item , Table , Value , value } ;
2025-01-22 12:06:05 -05:00
use tracing ::debug ;
2024-05-14 19:07:24 -04:00
use url ::Url ;
2024-10-01 20:15:32 -04:00
use uv_cache_key ::RepositoryUrl ;
2025-03-27 17:11:50 -04:00
use uv_configuration ::{ BuildOptions , Constraints } ;
2025-02-21 18:51:04 -10:00
use uv_distribution ::{ DistributionDatabase , FlatRequiresDist } ;
2024-12-06 07:40:49 -05:00
use uv_distribution_filename ::{
BuildTag , DistExtension , ExtensionError , SourceDistExtension , WheelFilename ,
} ;
2024-10-01 20:15:32 -04:00
use uv_distribution_types ::{
2025-05-18 19:38:43 -04:00
BuiltDist , DependencyMetadata , DirectUrlBuiltDist , DirectUrlSourceDist , DirectorySourceDist ,
Dist , DistributionMetadata , FileLocation , GitSourceDist , IndexLocations , IndexMetadata ,
IndexUrl , Name , PathBuiltDist , PathSourceDist , RegistryBuiltDist , RegistryBuiltWheel ,
2025-06-13 18:04:13 -04:00
RegistrySourceDist , RemoteSource , Requirement , RequirementSource , RequiresPython , ResolvedDist ,
SimplifiedMarkerTree , StaticMetadata , ToUrlError , UrlString ,
2024-07-02 14:03:20 -04:00
} ;
2025-05-18 19:38:43 -04:00
use uv_fs ::{ PortablePath , PortablePathBuf , relative_to } ;
2025-02-17 10:37:55 +01:00
use uv_git ::{ RepositoryReference , ResolvedRepositoryReference } ;
2025-02-18 03:14:06 +01:00
use uv_git_types ::{ GitOid , GitReference , GitUrl , GitUrlParseError } ;
2024-06-05 21:40:17 -04:00
use uv_normalize ::{ ExtraName , GroupName , PackageName } ;
2024-10-01 20:15:32 -04:00
use uv_pep440 ::Version ;
2025-05-18 19:38:43 -04:00
use uv_pep508 ::{ MarkerEnvironment , MarkerTree , VerbatimUrl , VerbatimUrlError , split_scheme } ;
2025-01-20 12:46:46 -05:00
use uv_platform_tags ::{
AbiTag , IncompatibleTag , LanguageTag , PlatformTag , TagCompatibility , TagPriority , Tags ,
} ;
2024-10-01 20:15:32 -04:00
use uv_pypi_types ::{
2025-04-21 17:21:17 -04:00
ConflictPackage , Conflicts , HashAlgorithm , HashDigest , HashDigests , Hashes , ParsedArchiveUrl ,
ParsedGitUrl ,
2024-10-01 20:15:32 -04:00
} ;
2025-05-27 00:05:30 +02:00
use uv_redacted ::DisplaySafeUrl ;
2025-02-24 21:06:57 -10:00
use uv_small_str ::SmallString ;
2024-10-12 16:46:55 +02:00
use uv_types ::{ BuildContext , HashStrategy } ;
2024-12-24 19:26:31 -05:00
use uv_workspace ::WorkspaceMember ;
2024-04-29 14:03:17 -04:00
2024-12-24 17:32:29 -05:00
use crate ::fork_strategy ::ForkStrategy ;
2025-04-21 18:48:54 -04:00
pub ( crate ) use crate ::lock ::export ::PylockTomlPackage ;
2025-04-17 23:10:03 -04:00
pub use crate ::lock ::export ::RequirementsTxtExport ;
2025-04-27 12:56:50 -04:00
pub use crate ::lock ::export ::{ PylockToml , PylockTomlErrorKind } ;
2024-12-24 19:01:50 -05:00
pub use crate ::lock ::installable ::Installable ;
2024-12-24 17:32:29 -05:00
pub use crate ::lock ::map ::PackageMap ;
pub use crate ::lock ::tree ::TreeDisplay ;
use crate ::resolution ::{ AnnotatedDist , ResolutionGraphNode } ;
use crate ::universal_marker ::{ ConflictMarker , UniversalMarker } ;
use crate ::{
2025-06-13 18:04:13 -04:00
ExcludeNewer , InMemoryIndex , MetadataResponse , PrereleaseMode , ResolutionMode , ResolverOutput ,
2024-12-24 17:32:29 -05:00
} ;
2025-04-17 23:10:03 -04:00
mod export ;
2024-12-24 19:01:50 -05:00
mod installable ;
2024-11-07 15:10:46 -05:00
mod map ;
2024-08-28 18:04:45 -04:00
mod tree ;
2024-07-25 10:22:36 -04:00
/// The current version of the lockfile format.
2024-10-24 12:23:56 -04:00
pub const VERSION : u32 = 1 ;
2024-06-24 14:29:01 -04:00
2025-02-14 11:17:26 -05:00
/// The current revision of the lockfile format.
2025-04-20 21:58:12 -04:00
const REVISION : u32 = 2 ;
2025-02-14 11:17:26 -05:00
2024-11-21 14:01:55 -05:00
static LINUX_MARKERS : LazyLock < UniversalMarker > = LazyLock ::new ( | | {
2024-12-18 10:29:34 -05:00
let pep508 = MarkerTree ::from_str ( " os_name == 'posix' and sys_platform == 'linux' " ) . unwrap ( ) ;
2024-12-10 10:57:22 -05:00
UniversalMarker ::new ( pep508 , ConflictMarker ::TRUE )
2024-09-04 11:08:37 +02:00
} ) ;
2024-11-21 14:01:55 -05:00
static WINDOWS_MARKERS : LazyLock < UniversalMarker > = LazyLock ::new ( | | {
2024-12-18 10:29:34 -05:00
let pep508 = MarkerTree ::from_str ( " os_name == 'nt' and sys_platform == 'win32' " ) . unwrap ( ) ;
2024-12-10 10:57:22 -05:00
UniversalMarker ::new ( pep508 , ConflictMarker ::TRUE )
2024-09-04 11:08:37 +02:00
} ) ;
2024-11-21 14:01:55 -05:00
static MAC_MARKERS : LazyLock < UniversalMarker > = LazyLock ::new ( | | {
2024-12-18 10:29:34 -05:00
let pep508 = MarkerTree ::from_str ( " os_name == 'posix' and sys_platform == 'darwin' " ) . unwrap ( ) ;
2024-12-10 10:57:22 -05:00
UniversalMarker ::new ( pep508 , ConflictMarker ::TRUE )
2024-09-04 11:08:37 +02:00
} ) ;
2025-01-14 09:39:21 -05:00
static ARM_MARKERS : LazyLock < UniversalMarker > = LazyLock ::new ( | | {
let pep508 =
2025-02-20 14:27:19 -08:00
MarkerTree ::from_str ( " platform_machine == 'aarch64' or platform_machine == 'arm64' or platform_machine == 'ARM64' " )
2025-01-14 09:39:21 -05:00
. unwrap ( ) ;
UniversalMarker ::new ( pep508 , ConflictMarker ::TRUE )
} ) ;
static X86_64_MARKERS : LazyLock < UniversalMarker > = LazyLock ::new ( | | {
let pep508 =
2025-02-20 14:27:19 -08:00
MarkerTree ::from_str ( " platform_machine == 'x86_64' or platform_machine == 'amd64' or platform_machine == 'AMD64' " )
2025-01-14 09:39:21 -05:00
. unwrap ( ) ;
UniversalMarker ::new ( pep508 , ConflictMarker ::TRUE )
} ) ;
static X86_MARKERS : LazyLock < UniversalMarker > = LazyLock ::new ( | | {
let pep508 = MarkerTree ::from_str (
" platform_machine == 'i686' or platform_machine == 'i386' or platform_machine == 'win32' or platform_machine == 'x86' " ,
)
. unwrap ( ) ;
UniversalMarker ::new ( pep508 , ConflictMarker ::TRUE )
} ) ;
2024-09-04 11:08:37 +02:00
2024-08-15 08:17:28 -04:00
#[ derive(Clone, Debug, serde::Deserialize) ]
2024-05-30 15:08:29 -04:00
#[ serde(try_from = " LockWire " ) ]
2024-04-29 14:03:17 -04:00
pub struct Lock {
2025-02-14 11:17:26 -05:00
/// The (major) version of the lockfile format.
///
/// Changes to the major version indicate backwards- and forwards-incompatible changes to the
/// lockfile format. A given uv version only supports a single major version of the lockfile
/// format.
///
/// In other words, a version of uv that supports version 2 of the lockfile format will not be
/// able to read lockfiles generated under version 1 or 3.
2024-04-29 14:03:17 -04:00
version : u32 ,
2025-02-14 11:17:26 -05:00
/// The revision of the lockfile format.
///
/// Changes to the revision indicate backwards-compatible changes to the lockfile format.
/// In other words, versions of uv that only support revision 1 _will_ be able to read lockfiles
/// with a revision greater than 1 (though they may ignore newer fields).
revision : u32 ,
2024-07-30 13:11:18 +02:00
/// If this lockfile was built from a forking resolution with non-identical forks, store the
/// forks in the lockfile so we can recreate them in subsequent resolutions.
2024-11-21 14:01:55 -05:00
fork_markers : Vec < UniversalMarker > ,
2024-11-13 09:52:28 -05:00
/// The conflicting groups/extras specified by the user.
2024-11-13 10:15:47 -05:00
conflicts : Conflicts ,
2024-08-20 09:28:04 -04:00
/// The list of supported environments specified by the user.
supported_environments : Vec < MarkerTree > ,
2025-02-14 15:11:18 -05:00
/// The list of required platforms specified by the user.
required_environments : Vec < MarkerTree > ,
2024-06-05 16:21:59 -04:00
/// The range of supported Python versions.
2024-09-02 14:23:42 -04:00
requires_python : RequiresPython ,
2024-08-14 20:00:15 -04:00
/// We discard the lockfile if these options don't match.
2024-08-07 20:11:59 +02:00
options : ResolverOptions ,
2024-07-30 13:11:18 +02:00
/// The actual locked version and their metadata.
2024-08-08 17:25:06 +02:00
packages : Vec < Package > ,
/// A map from package ID to index in `packages`.
2024-05-03 08:18:36 -04:00
///
2024-08-08 17:25:06 +02:00
/// This can be used to quickly lookup the full package for any ID
/// in this lock. For example, the dependencies for each package are
/// listed as package IDs. This map can be used to find the full
/// package for each such dependency.
2024-05-03 08:18:36 -04:00
///
2024-08-08 17:25:06 +02:00
/// It is guaranteed that every package in this lock has an entry in
/// this map, and that every dependency for every package has an ID
2024-05-03 08:18:36 -04:00
/// that exists in this map. That is, there are no dependencies that don't
2024-08-08 17:25:06 +02:00
/// have a corresponding locked package entry in the same lockfile.
by_id : FxHashMap < PackageId , usize > ,
2024-08-14 20:00:15 -04:00
/// The input requirements to the resolution.
manifest : ResolverManifest ,
2024-04-29 14:03:17 -04:00
}
impl Lock {
2024-11-14 09:51:11 -05:00
/// Initialize a [`Lock`] from a [`ResolverOutput`].
pub fn from_resolution ( resolution : & ResolverOutput , root : & Path ) -> Result < Self , LockError > {
2024-09-03 09:48:54 +02:00
let mut packages = BTreeMap ::new ( ) ;
2024-11-14 09:51:11 -05:00
let requires_python = resolution . requires_python . clone ( ) ;
2024-06-03 15:00:35 -04:00
2024-10-20 14:42:21 -04:00
// Determine the set of packages included at multiple versions.
let mut seen = FxHashSet ::default ( ) ;
let mut duplicates = FxHashSet ::default ( ) ;
2024-11-14 09:51:11 -05:00
for node_index in resolution . graph . node_indices ( ) {
let ResolutionGraphNode ::Dist ( dist ) = & resolution . graph [ node_index ] else {
2024-10-20 14:42:21 -04:00
continue ;
} ;
if ! dist . is_base ( ) {
continue ;
}
if ! seen . insert ( dist . name ( ) ) {
duplicates . insert ( dist . name ( ) ) ;
}
}
2024-06-03 15:00:35 -04:00
// Lock all base packages.
2024-11-14 09:51:11 -05:00
for node_index in resolution . graph . node_indices ( ) {
let ResolutionGraphNode ::Dist ( dist ) = & resolution . graph [ node_index ] else {
2024-06-25 16:55:58 -04:00
continue ;
} ;
2024-09-03 09:48:54 +02:00
if ! dist . is_base ( ) {
continue ;
}
2024-10-20 14:42:21 -04:00
// If there are multiple distributions for the same package, include the markers of all
// forks that included the current distribution.
let fork_markers = if duplicates . contains ( dist . name ( ) ) {
2024-11-14 09:51:11 -05:00
resolution
2024-10-20 14:42:21 -04:00
. fork_markers
. iter ( )
2024-12-10 10:57:22 -05:00
. filter ( | fork_markers | ! fork_markers . is_disjoint ( dist . marker ) )
2024-11-30 14:07:07 -05:00
. copied ( )
2024-10-20 14:42:21 -04:00
. collect ( )
} else {
vec! [ ]
} ;
2024-09-03 09:48:54 +02:00
let mut package = Package ::from_annotated_dist ( dist , fork_markers , root ) ? ;
2024-11-14 09:51:11 -05:00
Self ::remove_unreachable_wheels ( resolution , & requires_python , node_index , & mut package ) ;
2024-09-03 09:48:54 +02:00
// Add all dependencies
2024-11-14 09:51:11 -05:00
for edge in resolution . graph . edges ( node_index ) {
let ResolutionGraphNode ::Dist ( dependency_dist ) = & resolution . graph [ edge . target ( ) ]
2024-09-03 09:48:54 +02:00
else {
continue ;
} ;
2024-11-30 14:07:07 -05:00
let marker = * edge . weight ( ) ;
2024-08-19 18:05:01 -04:00
package . add_dependency ( & requires_python , dependency_dist , marker , root ) ? ;
2024-09-03 09:48:54 +02:00
}
let id = package . id . clone ( ) ;
if let Some ( locked_dist ) = packages . insert ( id , package ) {
return Err ( LockErrorKind ::DuplicatePackage {
id : locked_dist . id . clone ( ) ,
2024-06-05 21:40:17 -04:00
}
2024-09-03 09:48:54 +02:00
. into ( ) ) ;
2024-06-03 15:00:35 -04:00
}
}
2024-06-05 21:40:17 -04:00
// Lock all extras and development dependencies.
2024-11-14 09:51:11 -05:00
for node_index in resolution . graph . node_indices ( ) {
let ResolutionGraphNode ::Dist ( dist ) = & resolution . graph [ node_index ] else {
2024-06-25 16:55:58 -04:00
continue ;
} ;
2024-06-03 15:00:35 -04:00
if let Some ( extra ) = dist . extra . as_ref ( ) {
2024-08-23 22:19:10 -04:00
let id = PackageId ::from_annotated_dist ( dist , root ) ? ;
2024-09-03 09:48:54 +02:00
let Some ( package ) = packages . get_mut ( & id ) else {
2024-06-11 04:40:48 -07:00
return Err ( LockErrorKind ::MissingExtraBase {
id ,
extra : extra . clone ( ) ,
}
. into ( ) ) ;
2024-06-03 15:00:35 -04:00
} ;
2024-11-14 09:51:11 -05:00
for edge in resolution . graph . edges ( node_index ) {
let ResolutionGraphNode ::Dist ( dependency_dist ) =
& resolution . graph [ edge . target ( ) ]
2024-06-25 16:55:58 -04:00
else {
continue ;
} ;
2024-11-30 14:07:07 -05:00
let marker = * edge . weight ( ) ;
2024-09-03 09:48:54 +02:00
package . add_optional_dependency (
2024-08-19 18:05:01 -04:00
& requires_python ,
2024-08-23 22:19:10 -04:00
extra . clone ( ) ,
dependency_dist ,
marker ,
root ,
) ? ;
2024-06-03 15:00:35 -04:00
}
}
2024-06-05 21:40:17 -04:00
if let Some ( group ) = dist . dev . as_ref ( ) {
2024-08-23 22:19:10 -04:00
let id = PackageId ::from_annotated_dist ( dist , root ) ? ;
2024-09-03 09:48:54 +02:00
let Some ( package ) = packages . get_mut ( & id ) else {
2024-06-11 04:40:48 -07:00
return Err ( LockErrorKind ::MissingDevBase {
id ,
group : group . clone ( ) ,
}
. into ( ) ) ;
2024-06-05 21:40:17 -04:00
} ;
2024-11-14 09:51:11 -05:00
for edge in resolution . graph . edges ( node_index ) {
let ResolutionGraphNode ::Dist ( dependency_dist ) =
& resolution . graph [ edge . target ( ) ]
2024-06-25 16:55:58 -04:00
else {
continue ;
} ;
2024-11-30 14:07:07 -05:00
let marker = * edge . weight ( ) ;
2024-10-21 22:56:43 -04:00
package . add_group_dependency (
2024-08-19 18:05:01 -04:00
& requires_python ,
group . clone ( ) ,
dependency_dist ,
marker ,
root ,
) ? ;
2024-06-05 21:40:17 -04:00
}
}
2024-06-03 15:00:35 -04:00
}
2024-09-03 09:48:54 +02:00
let packages = packages . into_values ( ) . collect ( ) ;
2024-08-07 20:11:59 +02:00
let options = ResolverOptions {
2024-11-14 09:51:11 -05:00
resolution_mode : resolution . options . resolution_mode ,
prerelease_mode : resolution . options . prerelease_mode ,
2024-12-13 16:05:07 -05:00
fork_strategy : resolution . options . fork_strategy ,
2024-11-14 09:51:11 -05:00
exclude_newer : resolution . options . exclude_newer ,
2024-08-07 20:11:59 +02:00
} ;
2024-07-22 08:28:22 -04:00
let lock = Self ::new (
VERSION ,
2025-02-14 11:17:26 -05:00
REVISION ,
2024-08-08 17:25:06 +02:00
packages ,
2024-07-22 08:28:22 -04:00
requires_python ,
2024-08-07 20:11:59 +02:00
options ,
2024-08-14 20:00:15 -04:00
ResolverManifest ::default ( ) ,
2024-11-13 10:08:36 -05:00
Conflicts ::empty ( ) ,
2024-08-20 09:28:04 -04:00
vec! [ ] ,
2025-02-14 15:11:18 -05:00
vec! [ ] ,
2024-11-14 09:51:11 -05:00
resolution . fork_markers . clone ( ) ,
2024-07-22 08:28:22 -04:00
) ? ;
2024-06-03 15:00:35 -04:00
Ok ( lock )
}
2024-09-03 09:48:54 +02:00
/// Remove wheels that can't be selected for installation due to environment markers.
2024-09-04 11:08:37 +02:00
///
/// For example, a package included under `sys_platform == 'win32'` does not need Linux
/// wheels.
fn remove_unreachable_wheels (
2024-11-14 09:51:11 -05:00
graph : & ResolverOutput ,
2024-09-04 11:08:37 +02:00
requires_python : & RequiresPython ,
node_index : NodeIndex ,
locked_dist : & mut Package ,
) {
// Remove wheels that don't match `requires-python` and can't be selected for installation.
locked_dist
2024-09-03 09:48:54 +02:00
. wheels
. retain ( | wheel | requires_python . matches_wheel_tag ( & wheel . filename ) ) ;
2024-09-04 11:08:37 +02:00
// Filter by platform tags.
locked_dist . wheels . retain ( | wheel | {
// Naively, we'd check whether `platform_system == 'Linux'` is disjoint, or
// `os_name == 'posix'` is disjoint, or `sys_platform == 'linux'` is disjoint (each on its
// own sufficient to exclude linux wheels), but due to
// `(A ∩ (B ∩ C) = ∅) => ((A ∩ B = ∅) or (A ∩ C = ∅))`
// a single disjointness check with the intersection is sufficient, so we have one
// constant per platform.
2025-01-14 09:49:17 -05:00
let platform_tags = wheel . filename . platform_tags ( ) ;
2025-01-18 13:22:09 -05:00
if platform_tags . iter ( ) . all ( PlatformTag ::is_linux ) {
2025-01-14 09:39:21 -05:00
if graph . graph [ node_index ] . marker ( ) . is_disjoint ( * LINUX_MARKERS ) {
return false ;
}
}
2025-01-18 13:22:09 -05:00
if platform_tags . iter ( ) . all ( PlatformTag ::is_windows ) {
2025-01-14 09:39:21 -05:00
if graph . graph [ node_index ]
2024-09-09 09:20:25 -04:00
. marker ( )
2024-12-10 10:57:22 -05:00
. is_disjoint ( * WINDOWS_MARKERS )
2025-01-14 09:39:21 -05:00
{
return false ;
}
}
2025-01-18 13:22:09 -05:00
if platform_tags . iter ( ) . all ( PlatformTag ::is_macos ) {
2025-01-14 09:39:21 -05:00
if graph . graph [ node_index ] . marker ( ) . is_disjoint ( * MAC_MARKERS ) {
return false ;
}
}
2025-01-18 13:22:09 -05:00
if platform_tags . iter ( ) . all ( PlatformTag ::is_arm ) {
2025-01-14 09:39:21 -05:00
if graph . graph [ node_index ] . marker ( ) . is_disjoint ( * ARM_MARKERS ) {
return false ;
}
}
2025-01-18 13:22:09 -05:00
if platform_tags . iter ( ) . all ( PlatformTag ::is_x86_64 ) {
2025-01-14 09:39:21 -05:00
if graph . graph [ node_index ]
. marker ( )
. is_disjoint ( * X86_64_MARKERS )
{
return false ;
}
2024-09-04 11:08:37 +02:00
}
2025-01-14 09:39:21 -05:00
2025-01-18 13:22:09 -05:00
if platform_tags . iter ( ) . all ( PlatformTag ::is_x86 ) {
2025-01-14 09:39:21 -05:00
if graph . graph [ node_index ] . marker ( ) . is_disjoint ( * X86_MARKERS ) {
return false ;
}
}
true
2024-09-04 11:08:37 +02:00
} ) ;
2024-09-03 09:48:54 +02:00
}
2024-08-08 17:25:06 +02:00
/// Initialize a [`Lock`] from a list of [`Package`] entries.
2024-06-05 16:21:59 -04:00
fn new (
2024-06-24 14:29:01 -04:00
version : u32 ,
2025-02-14 11:17:26 -05:00
revision : u32 ,
2024-08-08 17:25:06 +02:00
mut packages : Vec < Package > ,
2024-09-02 14:23:42 -04:00
requires_python : RequiresPython ,
2024-08-07 20:11:59 +02:00
options : ResolverOptions ,
2024-08-14 20:00:15 -04:00
manifest : ResolverManifest ,
2024-11-13 10:15:47 -05:00
conflicts : Conflicts ,
2024-08-20 09:28:04 -04:00
supported_environments : Vec < MarkerTree > ,
2025-02-14 15:11:18 -05:00
required_environments : Vec < MarkerTree > ,
2024-11-21 14:01:55 -05:00
fork_markers : Vec < UniversalMarker > ,
2024-06-05 16:21:59 -04:00
) -> Result < Self , LockError > {
2024-08-08 17:25:06 +02:00
// Put all dependencies for each package in a canonical order and
2024-06-24 14:29:01 -04:00
// check for duplicates.
2024-08-08 17:25:06 +02:00
for package in & mut packages {
package . dependencies . sort ( ) ;
for windows in package . dependencies . windows ( 2 ) {
2024-06-24 14:29:01 -04:00
let ( dep1 , dep2 ) = ( & windows [ 0 ] , & windows [ 1 ] ) ;
if dep1 = = dep2 {
return Err ( LockErrorKind ::DuplicateDependency {
2024-08-08 17:25:06 +02:00
id : package . id . clone ( ) ,
2024-06-24 14:29:01 -04:00
dependency : dep1 . clone ( ) ,
}
. into ( ) ) ;
}
}
// Perform the same validation for optional dependencies.
2024-08-08 17:25:06 +02:00
for ( extra , dependencies ) in & mut package . optional_dependencies {
2024-06-24 14:29:01 -04:00
dependencies . sort ( ) ;
for windows in dependencies . windows ( 2 ) {
let ( dep1 , dep2 ) = ( & windows [ 0 ] , & windows [ 1 ] ) ;
if dep1 = = dep2 {
return Err ( LockErrorKind ::DuplicateOptionalDependency {
2024-08-08 17:25:06 +02:00
id : package . id . clone ( ) ,
2024-06-24 14:29:01 -04:00
extra : extra . clone ( ) ,
dependency : dep1 . clone ( ) ,
}
. into ( ) ) ;
}
}
}
// Perform the same validation for dev dependencies.
2024-10-21 22:56:43 -04:00
for ( group , dependencies ) in & mut package . dependency_groups {
2024-06-24 14:29:01 -04:00
dependencies . sort ( ) ;
for windows in dependencies . windows ( 2 ) {
let ( dep1 , dep2 ) = ( & windows [ 0 ] , & windows [ 1 ] ) ;
if dep1 = = dep2 {
return Err ( LockErrorKind ::DuplicateDevDependency {
2024-08-08 17:25:06 +02:00
id : package . id . clone ( ) ,
2024-06-24 14:29:01 -04:00
group : group . clone ( ) ,
dependency : dep1 . clone ( ) ,
}
. into ( ) ) ;
}
}
}
}
2024-08-08 17:25:06 +02:00
packages . sort_by ( | dist1 , dist2 | dist1 . id . cmp ( & dist2 . id ) ) ;
2024-06-24 14:29:01 -04:00
2024-08-08 17:25:06 +02:00
// Check for duplicate package IDs and also build up the map for
// packages keyed by their ID.
2024-06-24 14:29:01 -04:00
let mut by_id = FxHashMap ::default ( ) ;
2024-08-08 17:25:06 +02:00
for ( i , dist ) in packages . iter ( ) . enumerate ( ) {
2024-06-24 14:29:01 -04:00
if by_id . insert ( dist . id . clone ( ) , i ) . is_some ( ) {
2024-08-08 17:25:06 +02:00
return Err ( LockErrorKind ::DuplicatePackage {
2024-06-24 14:29:01 -04:00
id : dist . id . clone ( ) ,
}
. into ( ) ) ;
}
}
// Build up a map from ID to extras.
let mut extras_by_id = FxHashMap ::default ( ) ;
2024-08-08 17:25:06 +02:00
for dist in & packages {
2024-06-24 14:29:01 -04:00
for extra in dist . optional_dependencies . keys ( ) {
extras_by_id
. entry ( dist . id . clone ( ) )
. or_insert_with ( FxHashSet ::default )
. insert ( extra . clone ( ) ) ;
}
}
// Remove any non-existent extras (e.g., extras that were requested but don't exist).
2024-08-08 17:25:06 +02:00
for dist in & mut packages {
2024-07-18 20:07:49 +02:00
for dep in dist
. dependencies
. iter_mut ( )
. chain ( dist . optional_dependencies . values_mut ( ) . flatten ( ) )
2024-10-21 22:56:43 -04:00
. chain ( dist . dependency_groups . values_mut ( ) . flatten ( ) )
2024-07-18 20:07:49 +02:00
{
dep . extra . retain ( | extra | {
2024-06-24 14:29:01 -04:00
extras_by_id
2024-08-08 17:25:06 +02:00
. get ( & dep . package_id )
2024-06-24 14:29:01 -04:00
. is_some_and ( | extras | extras . contains ( extra ) )
} ) ;
}
}
// Check that every dependency has an entry in `by_id`. If any don't,
// it implies we somehow have a dependency with no corresponding locked
2024-08-08 17:25:06 +02:00
// package.
for dist in & packages {
2024-06-24 14:29:01 -04:00
for dep in & dist . dependencies {
2024-08-08 17:25:06 +02:00
if ! by_id . contains_key ( & dep . package_id ) {
2024-06-24 14:29:01 -04:00
return Err ( LockErrorKind ::UnrecognizedDependency {
id : dist . id . clone ( ) ,
dependency : dep . clone ( ) ,
}
. into ( ) ) ;
}
}
// Perform the same validation for optional dependencies.
for dependencies in dist . optional_dependencies . values ( ) {
for dep in dependencies {
2024-08-08 17:25:06 +02:00
if ! by_id . contains_key ( & dep . package_id ) {
2024-06-24 14:29:01 -04:00
return Err ( LockErrorKind ::UnrecognizedDependency {
id : dist . id . clone ( ) ,
dependency : dep . clone ( ) ,
}
. into ( ) ) ;
}
}
}
// Perform the same validation for dev dependencies.
2024-10-21 22:56:43 -04:00
for dependencies in dist . dependency_groups . values ( ) {
2024-06-24 14:29:01 -04:00
for dep in dependencies {
2024-08-08 17:25:06 +02:00
if ! by_id . contains_key ( & dep . package_id ) {
2024-06-24 14:29:01 -04:00
return Err ( LockErrorKind ::UnrecognizedDependency {
id : dist . id . clone ( ) ,
dependency : dep . clone ( ) ,
}
. into ( ) ) ;
}
}
}
// Also check that our sources are consistent with whether we have
// hashes or not.
2024-07-17 16:29:49 -04:00
if let Some ( requires_hash ) = dist . id . source . requires_hash ( ) {
for wheel in & dist . wheels {
if requires_hash ! = wheel . hash . is_some ( ) {
return Err ( LockErrorKind ::Hash {
id : dist . id . clone ( ) ,
artifact_type : " wheel " ,
expected : requires_hash ,
}
. into ( ) ) ;
2024-06-24 14:29:01 -04:00
}
}
}
}
2024-09-20 12:53:34 -04:00
let lock = Self {
2024-06-24 14:29:01 -04:00
version ,
2025-02-14 11:17:26 -05:00
revision ,
2024-07-30 13:11:18 +02:00
fork_markers ,
2024-11-13 10:15:47 -05:00
conflicts ,
2024-08-20 09:28:04 -04:00
supported_environments ,
2025-02-14 15:11:18 -05:00
required_environments ,
2024-06-05 16:21:59 -04:00
requires_python ,
2024-08-07 20:11:59 +02:00
options ,
2024-08-08 17:25:06 +02:00
packages ,
2024-06-24 14:29:01 -04:00
by_id ,
2024-08-14 20:00:15 -04:00
manifest ,
2024-09-20 12:53:34 -04:00
} ;
Ok ( lock )
2024-05-03 08:18:36 -04:00
}
2024-08-14 20:00:15 -04:00
/// Record the requirements that were used to generate this lock.
#[ must_use ]
pub fn with_manifest ( mut self , manifest : ResolverManifest ) -> Self {
self . manifest = manifest ;
self
}
2024-11-13 09:52:28 -05:00
/// Record the conflicting groups that were used to generate this lock.
#[ must_use ]
2024-11-13 10:15:47 -05:00
pub fn with_conflicts ( mut self , conflicts : Conflicts ) -> Self {
self . conflicts = conflicts ;
2024-11-13 09:52:28 -05:00
self
}
2024-08-20 09:28:04 -04:00
/// Record the supported environments that were used to generate this lock.
#[ must_use ]
pub fn with_supported_environments ( mut self , supported_environments : Vec < MarkerTree > ) -> Self {
2024-08-19 18:05:01 -04:00
// We "complexify" the markers given, since the supported
// environments given might be coming directly from what's written in
// `pyproject.toml`, and those are assumed to be simplified (i.e.,
// they assume `requires-python` is true). But a `Lock` always uses
// non-simplified markers internally, so we need to re-complexify them
// here.
//
// The nice thing about complexifying is that it's a no-op if the
// markers given have already been complexified.
self . supported_environments = supported_environments
. into_iter ( )
. map ( | marker | self . requires_python . complexify_markers ( marker ) )
. collect ( ) ;
2024-08-20 09:28:04 -04:00
self
}
2025-02-14 15:11:18 -05:00
/// Record the required platforms that were used to generate this lock.
#[ must_use ]
pub fn with_required_environments ( mut self , required_environments : Vec < MarkerTree > ) -> Self {
self . required_environments = required_environments
. into_iter ( )
. map ( | marker | self . requires_python . complexify_markers ( marker ) )
. collect ( ) ;
self
}
2025-02-21 18:51:04 -10:00
/// Returns `true` if this [`Lock`] includes `provides-extra` metadata.
pub fn supports_provides_extra ( & self ) -> bool {
// `provides-extra` was added in Version 1 Revision 1.
( self . version ( ) , self . revision ( ) ) > = ( 1 , 1 )
}
2025-03-06 09:44:20 -08:00
/// Returns `true` if this [`Lock`] includes entries for empty `dependency-group` metadata.
pub fn includes_empty_groups ( & self ) -> bool {
// Empty dependency groups are included as of https://github.com/astral-sh/uv/pull/8598,
// but Version 1 Revision 1 is the first revision published after that change.
( self . version ( ) , self . revision ( ) ) > = ( 1 , 1 )
}
2024-10-24 12:23:56 -04:00
/// Returns the lockfile version.
pub fn version ( & self ) -> u32 {
self . version
}
2025-02-14 11:17:26 -05:00
/// Returns the lockfile revision.
pub fn revision ( & self ) -> u32 {
self . revision
}
2024-08-14 20:00:15 -04:00
/// Returns the number of packages in the lockfile.
pub fn len ( & self ) -> usize {
self . packages . len ( )
}
/// Returns `true` if the lockfile contains no packages.
pub fn is_empty ( & self ) -> bool {
self . packages . is_empty ( )
}
2024-08-08 17:25:06 +02:00
/// Returns the [`Package`] entries in this lock.
pub fn packages ( & self ) -> & [ Package ] {
& self . packages
2024-05-30 13:59:53 -04:00
}
2024-06-05 16:22:08 -04:00
/// Returns the supported Python version range for the lockfile, if present.
2024-09-02 14:23:42 -04:00
pub fn requires_python ( & self ) -> & RequiresPython {
& self . requires_python
2024-06-05 16:22:08 -04:00
}
2024-07-22 08:28:22 -04:00
/// Returns the resolution mode used to generate this lock.
pub fn resolution_mode ( & self ) -> ResolutionMode {
2024-08-07 20:11:59 +02:00
self . options . resolution_mode
2024-07-22 08:28:22 -04:00
}
/// Returns the pre-release mode used to generate this lock.
2024-08-01 16:56:29 -04:00
pub fn prerelease_mode ( & self ) -> PrereleaseMode {
2024-08-07 20:11:59 +02:00
self . options . prerelease_mode
2024-07-22 08:28:22 -04:00
}
2024-12-13 16:05:07 -05:00
/// Returns the multi-version mode used to generate this lock.
pub fn fork_strategy ( & self ) -> ForkStrategy {
self . options . fork_strategy
}
2024-07-22 08:28:22 -04:00
/// Returns the exclude newer setting used to generate this lock.
pub fn exclude_newer ( & self ) -> Option < ExcludeNewer > {
2024-08-07 20:11:59 +02:00
self . options . exclude_newer
2024-07-22 08:28:22 -04:00
}
2024-11-13 09:52:28 -05:00
/// Returns the conflicting groups that were used to generate this lock.
2024-11-13 10:15:47 -05:00
pub fn conflicts ( & self ) -> & Conflicts {
& self . conflicts
2024-11-13 09:52:28 -05:00
}
2024-08-20 09:28:04 -04:00
/// Returns the supported environments that were used to generate this lock.
pub fn supported_environments ( & self ) -> & [ MarkerTree ] {
& self . supported_environments
}
2025-02-14 15:11:18 -05:00
/// Returns the required platforms that were used to generate this lock.
pub fn required_environments ( & self ) -> & [ MarkerTree ] {
& self . required_environments
}
2024-08-28 07:58:50 -04:00
/// Returns the workspace members that were used to generate this lock.
pub fn members ( & self ) -> & BTreeSet < PackageName > {
& self . manifest . members
}
2024-12-24 17:32:29 -05:00
/// Returns the dependency groups that were used to generate this lock.
pub fn requirements ( & self ) -> & BTreeSet < Requirement > {
& self . manifest . requirements
}
/// Returns the dependency groups that were used to generate this lock.
pub fn dependency_groups ( & self ) -> & BTreeMap < GroupName , BTreeSet < Requirement > > {
& self . manifest . dependency_groups
}
2025-03-27 17:11:50 -04:00
/// Returns the build constraints that were used to generate this lock.
pub fn build_constraints ( & self , root : & Path ) -> Constraints {
Constraints ::from_requirements (
self . manifest
. build_constraints
. iter ( )
. cloned ( )
. map ( | requirement | requirement . to_absolute ( root ) ) ,
)
}
2024-11-01 22:48:55 -04:00
/// Return the workspace root used to generate this lock.
pub fn root ( & self ) -> Option < & Package > {
self . packages . iter ( ) . find ( | package | {
let ( Source ::Editable ( path ) | Source ::Virtual ( path ) ) = & package . id . source else {
return false ;
} ;
2025-03-25 17:56:06 -04:00
path . as_ref ( ) = = Path ::new ( " " )
2024-11-01 22:48:55 -04:00
} )
}
2024-08-19 18:05:01 -04:00
/// Returns the supported environments that were used to generate this
/// lock.
///
/// The markers returned here are "simplified" with respect to the lock
/// file's `requires-python` setting. This means these should only be used
/// for direct comparison purposes with the supported environments written
/// by a human in `pyproject.toml`. (Think of "supported environments" in
/// `pyproject.toml` as having an implicit `and python_full_version >=
/// '{requires-python-bound}'` attached to each one.)
pub fn simplified_supported_environments ( & self ) -> Vec < MarkerTree > {
self . supported_environments ( )
. iter ( )
2024-11-30 14:07:07 -05:00
. copied ( )
2024-10-03 14:15:07 +01:00
. map ( | marker | self . simplify_environment ( marker ) )
2024-08-19 18:05:01 -04:00
. collect ( )
}
2025-02-14 15:11:18 -05:00
/// Returns the required platforms that were used to generate this
/// lock.
pub fn simplified_required_environments ( & self ) -> Vec < MarkerTree > {
self . required_environments ( )
. iter ( )
. copied ( )
. map ( | marker | self . simplify_environment ( marker ) )
. collect ( )
}
2024-10-03 14:15:07 +01:00
/// Simplify the given marker environment with respect to the lockfile's
/// `requires-python` setting.
pub fn simplify_environment ( & self , marker : MarkerTree ) -> MarkerTree {
self . requires_python . simplify_markers ( marker )
}
2024-07-30 13:11:18 +02:00
/// If this lockfile was built from a forking resolution with non-identical forks, return the
/// markers of those forks, otherwise `None`.
2024-11-21 14:01:55 -05:00
pub fn fork_markers ( & self ) -> & [ UniversalMarker ] {
2024-08-14 09:20:12 -04:00
self . fork_markers . as_slice ( )
2024-07-30 13:11:18 +02:00
}
2025-03-13 15:49:37 +01:00
/// Checks whether the fork markers cover the entire supported marker space.
///
/// Returns the actually covered and the expected marker space on validation error.
pub fn check_marker_coverage ( & self ) -> Result < ( ) , ( MarkerTree , MarkerTree ) > {
let fork_markers_union = if self . fork_markers ( ) . is_empty ( ) {
self . requires_python . to_marker_tree ( )
} else {
let mut fork_markers_union = MarkerTree ::FALSE ;
for fork_marker in self . fork_markers ( ) {
fork_markers_union . or ( fork_marker . pep508 ( ) ) ;
}
fork_markers_union
} ;
let mut environments_union = if ! self . supported_environments . is_empty ( ) {
let mut environments_union = MarkerTree ::FALSE ;
for fork_marker in & self . supported_environments {
environments_union . or ( * fork_marker ) ;
}
environments_union
} else {
MarkerTree ::TRUE
} ;
// When a user defines environments, they are implicitly constrained by requires-python.
environments_union . and ( self . requires_python . to_marker_tree ( ) ) ;
if fork_markers_union . negate ( ) . is_disjoint ( environments_union ) {
Ok ( ( ) )
} else {
Err ( ( fork_markers_union , environments_union ) )
}
}
2025-06-17 11:50:05 -04:00
/// Checks whether the new requires-python specification is disjoint with
/// the fork markers in this lock file.
///
/// If they are disjoint, then the union of the fork markers along with the
/// given requires-python specification (converted to a marker tree) are
/// returned.
///
/// When disjoint, the fork markers in the lock file should be dropped and
/// not used.
pub fn requires_python_coverage (
& self ,
new_requires_python : & RequiresPython ,
) -> Result < ( ) , ( MarkerTree , MarkerTree ) > {
let fork_markers_union = if self . fork_markers ( ) . is_empty ( ) {
self . requires_python . to_marker_tree ( )
} else {
let mut fork_markers_union = MarkerTree ::FALSE ;
for fork_marker in self . fork_markers ( ) {
fork_markers_union . or ( fork_marker . pep508 ( ) ) ;
}
fork_markers_union
} ;
let new_requires_python = new_requires_python . to_marker_tree ( ) ;
if fork_markers_union . is_disjoint ( new_requires_python ) {
Err ( ( fork_markers_union , new_requires_python ) )
} else {
Ok ( ( ) )
}
}
2024-07-25 10:22:36 -04:00
/// Returns the TOML representation of this lockfile.
2024-12-24 20:24:26 -05:00
pub fn to_toml ( & self ) -> Result < String , toml_edit ::ser ::Error > {
2025-03-13 15:49:37 +01:00
// Catch a lockfile where the union of fork markers doesn't cover the supported
// environments.
debug_assert! ( self . check_marker_coverage ( ) . is_ok ( ) ) ;
2024-05-30 15:08:29 -04:00
// We construct a TOML document manually instead of going through Serde to enable
// the use of inline tables.
let mut doc = toml_edit ::DocumentMut ::new ( ) ;
doc . insert ( " version " , value ( i64 ::from ( self . version ) ) ) ;
2025-02-14 11:17:26 -05:00
if self . revision > 0 {
doc . insert ( " revision " , value ( i64 ::from ( self . revision ) ) ) ;
}
2024-09-02 14:23:42 -04:00
doc . insert ( " requires-python " , value ( self . requires_python . to_string ( ) ) ) ;
2024-08-20 09:28:04 -04:00
2024-08-14 09:20:12 -04:00
if ! self . fork_markers . is_empty ( ) {
2024-08-09 13:40:02 -04:00
let fork_markers = each_element_on_its_line_array (
2025-01-21 14:04:31 -05:00
simplified_universal_markers ( & self . fork_markers , & self . requires_python ) . into_iter ( ) ,
2024-08-09 13:40:02 -04:00
) ;
2024-12-10 18:18:10 +01:00
if ! fork_markers . is_empty ( ) {
doc . insert ( " resolution-markers " , value ( fork_markers ) ) ;
}
2024-07-30 13:11:18 +02:00
}
2024-06-05 16:21:59 -04:00
2024-08-20 09:28:04 -04:00
if ! self . supported_environments . is_empty ( ) {
let supported_environments = each_element_on_its_line_array (
self . supported_environments
. iter ( )
2024-11-30 14:07:07 -05:00
. copied ( )
. map ( | marker | SimplifiedMarkerTree ::new ( & self . requires_python , marker ) )
2024-12-24 10:23:28 -05:00
. filter_map ( SimplifiedMarkerTree ::try_to_string ) ,
2024-08-20 09:28:04 -04:00
) ;
doc . insert ( " supported-markers " , value ( supported_environments ) ) ;
}
2025-02-14 15:11:18 -05:00
if ! self . required_environments . is_empty ( ) {
let required_environments = each_element_on_its_line_array (
self . required_environments
. iter ( )
. copied ( )
. map ( | marker | SimplifiedMarkerTree ::new ( & self . requires_python , marker ) )
. filter_map ( SimplifiedMarkerTree ::try_to_string ) ,
) ;
doc . insert ( " required-markers " , value ( required_environments ) ) ;
}
2024-11-13 10:15:47 -05:00
if ! self . conflicts . is_empty ( ) {
2024-11-13 09:52:28 -05:00
let mut list = Array ::new ( ) ;
2024-11-13 12:47:38 -05:00
for set in self . conflicts . iter ( ) {
list . push ( each_element_on_its_line_array ( set . iter ( ) . map ( | item | {
2024-11-13 09:52:28 -05:00
let mut table = InlineTable ::new ( ) ;
2024-11-13 12:47:38 -05:00
table . insert ( " package " , Value ::from ( item . package ( ) . to_string ( ) ) ) ;
match item . conflict ( ) {
2025-05-16 10:34:22 +02:00
ConflictPackage ::Extra ( extra ) = > {
2024-11-13 12:47:38 -05:00
table . insert ( " extra " , Value ::from ( extra . to_string ( ) ) ) ;
}
2025-05-16 10:34:22 +02:00
ConflictPackage ::Group ( group ) = > {
2024-11-13 12:47:38 -05:00
table . insert ( " group " , Value ::from ( group . to_string ( ) ) ) ;
}
}
2024-11-13 09:52:28 -05:00
table
} ) ) ) ;
}
2024-11-13 10:15:47 -05:00
doc . insert ( " conflicts " , value ( list ) ) ;
2024-11-13 09:52:28 -05:00
}
2024-07-22 08:28:22 -04:00
// Write the settings that were used to generate the resolution.
// This enables us to invalidate the lockfile if the user changes
// their settings.
2024-08-14 20:00:15 -04:00
{
2024-08-07 20:11:59 +02:00
let mut options_table = Table ::new ( ) ;
if self . options . resolution_mode ! = ResolutionMode ::default ( ) {
options_table . insert (
" resolution-mode " ,
value ( self . options . resolution_mode . to_string ( ) ) ,
) ;
}
if self . options . prerelease_mode ! = PrereleaseMode ::default ( ) {
options_table . insert (
" prerelease-mode " ,
value ( self . options . prerelease_mode . to_string ( ) ) ,
) ;
}
2024-12-13 16:05:07 -05:00
if self . options . fork_strategy ! = ForkStrategy ::default ( ) {
options_table . insert (
" fork-strategy " ,
value ( self . options . fork_strategy . to_string ( ) ) ,
) ;
}
2024-08-07 20:11:59 +02:00
if let Some ( exclude_newer ) = self . options . exclude_newer {
options_table . insert ( " exclude-newer " , value ( exclude_newer . to_string ( ) ) ) ;
}
2024-08-14 20:00:15 -04:00
if ! options_table . is_empty ( ) {
doc . insert ( " options " , Item ::Table ( options_table ) ) ;
}
}
// Write the manifest that was used to generate the resolution.
{
let mut manifest_table = Table ::new ( ) ;
if ! self . manifest . members . is_empty ( ) {
manifest_table . insert (
" members " ,
value ( each_element_on_its_line_array (
self . manifest
. members
. iter ( )
. map ( std ::string ::ToString ::to_string ) ,
) ) ,
) ;
}
2024-08-20 21:25:38 -04:00
if ! self . manifest . requirements . is_empty ( ) {
let requirements = self
. manifest
. requirements
. iter ( )
. map ( | requirement | {
serde ::Serialize ::serialize (
& requirement ,
toml_edit ::ser ::ValueSerializer ::new ( ) ,
)
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
let requirements = match requirements . as_slice ( ) {
[ ] = > Array ::new ( ) ,
[ requirement ] = > Array ::from_iter ( [ requirement ] ) ,
requirements = > each_element_on_its_line_array ( requirements . iter ( ) ) ,
} ;
manifest_table . insert ( " requirements " , value ( requirements ) ) ;
}
2024-08-14 20:00:15 -04:00
if ! self . manifest . constraints . is_empty ( ) {
let constraints = self
. manifest
. constraints
. iter ( )
. map ( | requirement | {
serde ::Serialize ::serialize (
& requirement ,
toml_edit ::ser ::ValueSerializer ::new ( ) ,
)
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
let constraints = match constraints . as_slice ( ) {
[ ] = > Array ::new ( ) ,
[ requirement ] = > Array ::from_iter ( [ requirement ] ) ,
constraints = > each_element_on_its_line_array ( constraints . iter ( ) ) ,
} ;
manifest_table . insert ( " constraints " , value ( constraints ) ) ;
}
if ! self . manifest . overrides . is_empty ( ) {
let overrides = self
. manifest
. overrides
. iter ( )
. map ( | requirement | {
serde ::Serialize ::serialize (
& requirement ,
toml_edit ::ser ::ValueSerializer ::new ( ) ,
)
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
let overrides = match overrides . as_slice ( ) {
[ ] = > Array ::new ( ) ,
[ requirement ] = > Array ::from_iter ( [ requirement ] ) ,
overrides = > each_element_on_its_line_array ( overrides . iter ( ) ) ,
} ;
manifest_table . insert ( " overrides " , value ( overrides ) ) ;
}
2025-03-27 17:11:50 -04:00
if ! self . manifest . build_constraints . is_empty ( ) {
let build_constraints = self
. manifest
. build_constraints
. iter ( )
. map ( | requirement | {
serde ::Serialize ::serialize (
& requirement ,
toml_edit ::ser ::ValueSerializer ::new ( ) ,
)
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
let build_constraints = match build_constraints . as_slice ( ) {
[ ] = > Array ::new ( ) ,
[ requirement ] = > Array ::from_iter ( [ requirement ] ) ,
build_constraints = > each_element_on_its_line_array ( build_constraints . iter ( ) ) ,
} ;
manifest_table . insert ( " build-constraints " , value ( build_constraints ) ) ;
}
2024-12-24 17:32:29 -05:00
if ! self . manifest . dependency_groups . is_empty ( ) {
let mut dependency_groups = Table ::new ( ) ;
for ( extra , requirements ) in & self . manifest . dependency_groups {
let requirements = requirements
. iter ( )
. map ( | requirement | {
serde ::Serialize ::serialize (
& requirement ,
toml_edit ::ser ::ValueSerializer ::new ( ) ,
)
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
let requirements = match requirements . as_slice ( ) {
[ ] = > Array ::new ( ) ,
[ requirement ] = > Array ::from_iter ( [ requirement ] ) ,
requirements = > each_element_on_its_line_array ( requirements . iter ( ) ) ,
} ;
if ! requirements . is_empty ( ) {
dependency_groups . insert ( extra . as_ref ( ) , value ( requirements ) ) ;
}
}
if ! dependency_groups . is_empty ( ) {
manifest_table . insert ( " dependency-groups " , Item ::Table ( dependency_groups ) ) ;
}
}
2024-09-17 23:18:05 -04:00
if ! self . manifest . dependency_metadata . is_empty ( ) {
let mut tables = ArrayOfTables ::new ( ) ;
for metadata in & self . manifest . dependency_metadata {
let mut table = Table ::new ( ) ;
table . insert ( " name " , value ( metadata . name . to_string ( ) ) ) ;
if let Some ( version ) = metadata . version . as_ref ( ) {
table . insert ( " version " , value ( version . to_string ( ) ) ) ;
}
if ! metadata . requires_dist . is_empty ( ) {
table . insert (
" requires-dist " ,
value ( serde ::Serialize ::serialize (
& metadata . requires_dist ,
toml_edit ::ser ::ValueSerializer ::new ( ) ,
) ? ) ,
) ;
}
if let Some ( requires_python ) = metadata . requires_python . as_ref ( ) {
table . insert ( " requires-python " , value ( requires_python . to_string ( ) ) ) ;
}
if ! metadata . provides_extras . is_empty ( ) {
table . insert (
" provides-extras " ,
value ( serde ::Serialize ::serialize (
& metadata . provides_extras ,
toml_edit ::ser ::ValueSerializer ::new ( ) ,
) ? ) ,
) ;
}
tables . push ( table ) ;
}
manifest_table . insert ( " dependency-metadata " , Item ::ArrayOfTables ( tables ) ) ;
}
2024-08-14 20:00:15 -04:00
if ! manifest_table . is_empty ( ) {
doc . insert ( " manifest " , Item ::Table ( manifest_table ) ) ;
}
2024-07-22 08:28:22 -04:00
}
2024-08-08 17:25:06 +02:00
// Count the number of packages for each package name. When
// there's only one package for a particular package name (the
2024-06-24 15:03:45 -04:00
// overwhelmingly common case), we can omit some data (like source and
// version) on dependency edges since it is strictly redundant.
let mut dist_count_by_name : FxHashMap < PackageName , u64 > = FxHashMap ::default ( ) ;
2024-08-08 17:25:06 +02:00
for dist in & self . packages {
2024-06-24 15:03:45 -04:00
* dist_count_by_name . entry ( dist . id . name . clone ( ) ) . or_default ( ) + = 1 ;
}
2024-08-08 17:25:06 +02:00
let mut packages = ArrayOfTables ::new ( ) ;
for dist in & self . packages {
2024-08-19 18:05:01 -04:00
packages . push ( dist . to_toml ( & self . requires_python , & dist_count_by_name ) ? ) ;
2024-05-30 15:08:29 -04:00
}
2024-08-08 17:25:06 +02:00
doc . insert ( " package " , Item ::ArrayOfTables ( packages ) ) ;
2024-05-30 15:08:29 -04:00
Ok ( doc . to_string ( ) )
}
2024-06-25 12:41:40 -04:00
2024-08-08 17:25:06 +02:00
/// Returns the package with the given name. If there are multiple
/// matching packages, then an error is returned. If there are no
/// matching packages, then `Ok(None)` is returned.
fn find_by_name ( & self , name : & PackageName ) -> Result < Option < & Package > , String > {
2024-06-25 12:41:40 -04:00
let mut found_dist = None ;
2024-08-08 17:25:06 +02:00
for dist in & self . packages {
2024-06-25 12:41:40 -04:00
if & dist . id . name = = name {
if found_dist . is_some ( ) {
2024-08-08 17:25:06 +02:00
return Err ( format! ( " found multiple packages matching ` {name} ` " ) ) ;
2024-06-25 12:41:40 -04:00
}
found_dist = Some ( dist ) ;
}
}
Ok ( found_dist )
}
2024-08-25 20:31:42 -04:00
/// Returns the package with the given name.
///
/// If there are multiple matching packages, returns the package that
/// corresponds to the given marker tree.
///
/// If there are multiple packages that are relevant to the current
/// markers, then an error is returned.
///
/// If there are no matching packages, then `Ok(None)` is returned.
fn find_by_markers (
& self ,
name : & PackageName ,
marker_env : & MarkerEnvironment ,
) -> Result < Option < & Package > , String > {
let mut found_dist = None ;
for dist in & self . packages {
if & dist . id . name = = name {
if dist . fork_markers . is_empty ( )
| | dist
. fork_markers
. iter ( )
2024-12-10 10:57:22 -05:00
. any ( | marker | marker . evaluate_no_extras ( marker_env ) )
2024-08-25 20:31:42 -04:00
{
if found_dist . is_some ( ) {
return Err ( format! ( " found multiple packages matching ` {name} ` " ) ) ;
}
found_dist = Some ( dist ) ;
}
}
}
Ok ( found_dist )
}
2024-08-08 17:25:06 +02:00
fn find_by_id ( & self , id : & PackageId ) -> & Package {
let index = * self . by_id . get ( id ) . expect ( " locked package for ID " ) ;
2025-05-18 19:38:43 -04:00
( self . packages . get ( index ) . expect ( " valid index for package " ) ) as _
2024-06-25 12:41:40 -04:00
}
2024-07-12 18:49:28 -04:00
2025-02-21 18:51:04 -10:00
/// Return a [`SatisfiesResult`] if the given extras do not match the [`Package`] metadata.
fn satisfies_provides_extra < ' lock > (
& self ,
2025-03-27 17:09:26 -04:00
provides_extra : Box < [ ExtraName ] > ,
2025-02-21 18:51:04 -10:00
package : & ' lock Package ,
) -> SatisfiesResult < ' lock > {
if ! self . supports_provides_extra ( ) {
return SatisfiesResult ::Satisfied ;
}
let expected : BTreeSet < _ > = provides_extra . iter ( ) . collect ( ) ;
let actual : BTreeSet < _ > = package . metadata . provides_extras . iter ( ) . collect ( ) ;
if expected ! = actual {
2025-03-27 17:09:26 -04:00
let expected = Box ::into_iter ( provides_extra ) . collect ( ) ;
2025-02-21 18:51:04 -10:00
return SatisfiesResult ::MismatchedPackageProvidesExtra (
& package . id . name ,
package . id . version . as_ref ( ) ,
expected ,
actual ,
) ;
}
SatisfiesResult ::Satisfied
}
/// Return a [`SatisfiesResult`] if the given requirements do not match the [`Package`] metadata.
#[ allow(clippy::unused_self) ]
2025-02-07 11:54:40 +01:00
fn satisfies_requires_dist < ' lock > (
2025-02-21 18:51:04 -10:00
& self ,
2025-03-27 17:09:26 -04:00
requires_dist : Box < [ Requirement ] > ,
dependency_groups : BTreeMap < GroupName , Box < [ Requirement ] > > ,
2025-02-07 11:54:40 +01:00
package : & ' lock Package ,
root : & Path ,
) -> Result < SatisfiesResult < ' lock > , LockError > {
// Special-case: if the version is dynamic, compare the flattened requirements.
let flattened = if package . is_dynamic ( ) {
Some (
2025-02-21 18:51:04 -10:00
FlatRequiresDist ::from_requirements ( requires_dist . clone ( ) , & package . id . name )
. into_iter ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | {
normalize_requirement ( requirement , root , & self . requires_python )
} )
2025-02-21 18:51:04 -10:00
. collect ::< Result < BTreeSet < _ > , _ > > ( ) ? ,
2025-02-07 11:54:40 +01:00
)
} else {
None
} ;
// Validate the `requires-dist` metadata.
2025-03-27 17:09:26 -04:00
let expected : BTreeSet < _ > = Box ::into_iter ( requires_dist )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2025-02-07 11:54:40 +01:00
. collect ::< Result < _ , _ > > ( ) ? ;
let actual : BTreeSet < _ > = package
. metadata
. requires_dist
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2025-02-07 11:54:40 +01:00
. collect ::< Result < _ , _ > > ( ) ? ;
if expected ! = actual & & flattened . is_none_or ( | expected | expected ! = actual ) {
return Ok ( SatisfiesResult ::MismatchedPackageRequirements (
& package . id . name ,
package . id . version . as_ref ( ) ,
expected ,
actual ,
) ) ;
}
// Validate the `dependency-groups` metadata.
2025-02-21 18:51:04 -10:00
let expected : BTreeMap < GroupName , BTreeSet < Requirement > > = dependency_groups
2025-02-07 11:54:40 +01:00
. into_iter ( )
2025-03-06 09:44:20 -08:00
. filter ( | ( _ , requirements ) | self . includes_empty_groups ( ) | | ! requirements . is_empty ( ) )
2025-02-07 11:54:40 +01:00
. map ( | ( group , requirements ) | {
Ok ::< _ , LockError > ( (
group ,
2025-03-27 17:09:26 -04:00
Box ::into_iter ( requirements )
2025-05-26 14:17:19 -04:00
. map ( | requirement | {
normalize_requirement ( requirement , root , & self . requires_python )
} )
2025-02-07 11:54:40 +01:00
. collect ::< Result < _ , _ > > ( ) ? ,
) )
} )
. collect ::< Result < _ , _ > > ( ) ? ;
let actual : BTreeMap < GroupName , BTreeSet < Requirement > > = package
. metadata
. dependency_groups
. iter ( )
2025-03-06 09:44:20 -08:00
. filter ( | ( _ , requirements ) | self . includes_empty_groups ( ) | | ! requirements . is_empty ( ) )
2025-02-07 11:54:40 +01:00
. map ( | ( group , requirements ) | {
Ok ::< _ , LockError > ( (
group . clone ( ) ,
requirements
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | {
normalize_requirement ( requirement , root , & self . requires_python )
} )
2025-02-07 11:54:40 +01:00
. collect ::< Result < _ , _ > > ( ) ? ,
) )
} )
. collect ::< Result < _ , _ > > ( ) ? ;
if expected ! = actual {
return Ok ( SatisfiesResult ::MismatchedPackageDependencyGroups (
& package . id . name ,
package . id . version . as_ref ( ) ,
expected ,
actual ,
) ) ;
}
Ok ( SatisfiesResult ::Satisfied )
}
2024-08-14 20:00:15 -04:00
/// Convert the [`Lock`] to a [`Resolution`] using the given marker environment, tags, and root.
pub async fn satisfies < Context : BuildContext > (
2024-07-12 18:49:28 -04:00
& self ,
2024-12-24 19:26:31 -05:00
root : & Path ,
packages : & BTreeMap < PackageName , WorkspaceMember > ,
2024-08-14 20:00:15 -04:00
members : & [ PackageName ] ,
2024-08-20 21:25:38 -04:00
requirements : & [ Requirement ] ,
2024-08-14 20:00:15 -04:00
constraints : & [ Requirement ] ,
overrides : & [ Requirement ] ,
2025-03-27 17:11:50 -04:00
build_constraints : & [ Requirement ] ,
2024-12-24 17:32:29 -05:00
dependency_groups : & BTreeMap < GroupName , Vec < Requirement > > ,
2024-09-17 23:18:05 -04:00
dependency_metadata : & DependencyMetadata ,
2024-08-15 13:19:40 -04:00
indexes : Option < & IndexLocations > ,
2024-08-14 20:00:15 -04:00
tags : & Tags ,
2024-10-12 16:46:55 +02:00
hasher : & HashStrategy ,
index : & InMemoryIndex ,
2024-08-14 20:00:15 -04:00
database : & DistributionDatabase < '_ , Context > ,
2024-08-15 13:19:40 -04:00
) -> Result < SatisfiesResult < '_ > , LockError > {
2024-08-14 20:00:15 -04:00
let mut queue : VecDeque < & Package > = VecDeque ::new ( ) ;
let mut seen = FxHashSet ::default ( ) ;
// Validate that the lockfile was generated with the same root members.
{
2024-08-15 09:00:35 -04:00
let expected = members . iter ( ) . cloned ( ) . collect ::< BTreeSet < _ > > ( ) ;
2024-08-14 20:00:15 -04:00
let actual = & self . manifest . members ;
2024-08-15 09:00:35 -04:00
if expected ! = * actual {
2024-08-15 13:19:40 -04:00
return Ok ( SatisfiesResult ::MismatchedMembers ( expected , actual ) ) ;
2024-07-12 18:49:28 -04:00
}
2024-08-14 20:00:15 -04:00
}
2024-07-12 18:49:28 -04:00
2025-01-22 12:58:15 -05:00
// Validate that the member sources have not changed (e.g., that they've switched from
// virtual to non-virtual or vice versa).
for ( name , member ) in packages {
let expected = ! member . pyproject_toml ( ) . is_package ( ) ;
let actual = self
. find_by_name ( name )
. ok ( )
. flatten ( )
. map ( | package | matches! ( package . id . source , Source ::Virtual ( _ ) ) ) ;
if actual ! = Some ( expected ) {
return Ok ( SatisfiesResult ::MismatchedVirtual ( name . clone ( ) , expected ) ) ;
2024-09-05 18:14:57 -04:00
}
2024-08-28 11:11:16 -04:00
}
2024-08-20 21:25:38 -04:00
// Validate that the lockfile was generated with the same requirements.
{
let expected : BTreeSet < _ > = requirements
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2024-08-20 21:25:38 -04:00
. collect ::< Result < _ , _ > > ( ) ? ;
let actual : BTreeSet < _ > = self
. manifest
. requirements
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2024-08-20 21:25:38 -04:00
. collect ::< Result < _ , _ > > ( ) ? ;
if expected ! = actual {
2024-12-26 18:25:38 -05:00
return Ok ( SatisfiesResult ::MismatchedRequirements ( expected , actual ) ) ;
2024-08-20 21:25:38 -04:00
}
}
2024-08-14 20:00:15 -04:00
// Validate that the lockfile was generated with the same constraints.
{
2024-08-15 09:00:35 -04:00
let expected : BTreeSet < _ > = constraints
2024-08-14 20:00:15 -04:00
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2024-08-16 13:24:27 -04:00
. collect ::< Result < _ , _ > > ( ) ? ;
2024-08-15 09:00:35 -04:00
let actual : BTreeSet < _ > = self
2024-08-14 20:00:15 -04:00
. manifest
. constraints
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2024-08-16 13:24:27 -04:00
. collect ::< Result < _ , _ > > ( ) ? ;
2024-08-14 20:00:15 -04:00
if expected ! = actual {
2024-08-15 13:19:40 -04:00
return Ok ( SatisfiesResult ::MismatchedConstraints ( expected , actual ) ) ;
2024-07-12 18:49:28 -04:00
}
2024-08-14 20:00:15 -04:00
}
2024-07-12 18:49:28 -04:00
2024-08-14 20:00:15 -04:00
// Validate that the lockfile was generated with the same overrides.
{
2024-08-15 09:00:35 -04:00
let expected : BTreeSet < _ > = overrides
2024-08-14 20:00:15 -04:00
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2024-08-16 13:24:27 -04:00
. collect ::< Result < _ , _ > > ( ) ? ;
2024-08-15 09:00:35 -04:00
let actual : BTreeSet < _ > = self
2024-08-14 20:00:15 -04:00
. manifest
. overrides
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2024-08-16 13:24:27 -04:00
. collect ::< Result < _ , _ > > ( ) ? ;
2024-08-14 20:00:15 -04:00
if expected ! = actual {
2024-08-15 13:19:40 -04:00
return Ok ( SatisfiesResult ::MismatchedOverrides ( expected , actual ) ) ;
2024-07-12 18:49:28 -04:00
}
2024-08-14 20:00:15 -04:00
}
2025-03-27 17:11:50 -04:00
// Validate that the lockfile was generated with the same build constraints.
{
let expected : BTreeSet < _ > = build_constraints
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2025-03-27 17:11:50 -04:00
. collect ::< Result < _ , _ > > ( ) ? ;
let actual : BTreeSet < _ > = self
. manifest
. build_constraints
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | normalize_requirement ( requirement , root , & self . requires_python ) )
2025-03-27 17:11:50 -04:00
. collect ::< Result < _ , _ > > ( ) ? ;
if expected ! = actual {
return Ok ( SatisfiesResult ::MismatchedBuildConstraints (
expected , actual ,
) ) ;
}
}
2024-12-24 17:32:29 -05:00
// Validate that the lockfile was generated with the dependency groups.
{
let expected : BTreeMap < GroupName , BTreeSet < Requirement > > = dependency_groups
. iter ( )
. filter ( | ( _ , requirements ) | ! requirements . is_empty ( ) )
. map ( | ( group , requirements ) | {
Ok ::< _ , LockError > ( (
group . clone ( ) ,
requirements
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | {
normalize_requirement ( requirement , root , & self . requires_python )
} )
2024-12-24 17:32:29 -05:00
. collect ::< Result < _ , _ > > ( ) ? ,
) )
} )
. collect ::< Result < _ , _ > > ( ) ? ;
let actual : BTreeMap < GroupName , BTreeSet < Requirement > > = self
. manifest
. dependency_groups
. iter ( )
. filter ( | ( _ , requirements ) | ! requirements . is_empty ( ) )
. map ( | ( group , requirements ) | {
Ok ::< _ , LockError > ( (
group . clone ( ) ,
requirements
. iter ( )
. cloned ( )
2025-05-26 14:17:19 -04:00
. map ( | requirement | {
normalize_requirement ( requirement , root , & self . requires_python )
} )
2024-12-24 17:32:29 -05:00
. collect ::< Result < _ , _ > > ( ) ? ,
) )
} )
. collect ::< Result < _ , _ > > ( ) ? ;
if expected ! = actual {
return Ok ( SatisfiesResult ::MismatchedDependencyGroups (
expected , actual ,
) ) ;
}
}
2024-09-17 23:18:05 -04:00
// Validate that the lockfile was generated with the same static metadata.
{
let expected = dependency_metadata
. values ( )
. cloned ( )
. collect ::< BTreeSet < _ > > ( ) ;
let actual = & self . manifest . dependency_metadata ;
if expected ! = * actual {
return Ok ( SatisfiesResult ::MismatchedStaticMetadata ( expected , actual ) ) ;
}
}
2024-08-15 13:19:40 -04:00
// Collect the set of available indexes (both `--index-url` and `--find-links` entries).
2024-08-24 22:41:47 -04:00
let remotes = indexes . map ( | locations | {
2024-08-15 13:19:40 -04:00
locations
2024-10-15 15:24:23 -07:00
. allowed_indexes ( )
2024-10-16 08:52:26 -07:00
. into_iter ( )
2024-10-15 15:24:23 -07:00
. filter_map ( | index | match index . url ( ) {
2024-08-24 22:41:47 -04:00
IndexUrl ::Pypi ( _ ) | IndexUrl ::Url ( _ ) = > {
2025-05-27 00:05:30 +02:00
Some ( UrlString ::from ( index . url ( ) . without_credentials ( ) . as_ref ( ) ) )
2024-08-24 22:41:47 -04:00
}
IndexUrl ::Path ( _ ) = > None ,
} )
. collect ::< BTreeSet < _ > > ( )
} ) ;
let locals = indexes . map ( | locations | {
locations
2024-10-15 15:24:23 -07:00
. allowed_indexes ( )
2024-10-16 08:52:26 -07:00
. into_iter ( )
2024-10-15 15:24:23 -07:00
. filter_map ( | index | match index . url ( ) {
2024-08-24 22:41:47 -04:00
IndexUrl ::Pypi ( _ ) | IndexUrl ::Url ( _ ) = > None ,
2024-10-15 15:24:23 -07:00
IndexUrl ::Path ( url ) = > {
let path = url . to_file_path ( ) . ok ( ) ? ;
2024-12-24 19:26:31 -05:00
let path = relative_to ( & path , root )
2024-08-24 22:41:47 -04:00
. or_else ( | _ | std ::path ::absolute ( path ) )
2025-03-25 17:56:06 -04:00
. ok ( ) ?
. into_boxed_path ( ) ;
2024-08-24 22:41:47 -04:00
Some ( path )
}
} )
2024-08-15 13:19:40 -04:00
. collect ::< BTreeSet < _ > > ( )
} ) ;
2024-08-14 20:00:15 -04:00
// Add the workspace packages to the queue.
2024-12-24 19:26:31 -05:00
for root_name in packages . keys ( ) {
2024-08-14 20:00:15 -04:00
let root = self
. find_by_name ( root_name )
. expect ( " found too many packages matching root " ) ;
2024-07-12 18:49:28 -04:00
2024-08-14 20:00:15 -04:00
let Some ( root ) = root else {
// The package is not in the lockfile, so it can't be satisfied.
2024-08-15 13:19:40 -04:00
return Ok ( SatisfiesResult ::MissingRoot ( root_name . clone ( ) ) ) ;
2024-08-14 20:00:15 -04:00
} ;
2024-07-12 18:49:28 -04:00
2024-08-14 20:00:15 -04:00
// Add the base package.
queue . push_back ( root ) ;
2024-07-12 18:49:28 -04:00
}
2024-08-14 20:00:15 -04:00
while let Some ( package ) = queue . pop_front ( ) {
2024-08-15 13:19:40 -04:00
// If the lockfile references an index that was not provided, we can't validate it.
if let Source ::Registry ( index ) = & package . id . source {
2024-08-24 22:41:47 -04:00
match index {
RegistrySource ::Url ( url ) = > {
2025-06-27 17:11:21 +02:00
// Normalize URL before validating.
let url = url . without_trailing_slash ( ) ;
2024-08-24 22:41:47 -04:00
if remotes
. as_ref ( )
2025-06-27 17:11:21 +02:00
. is_some_and ( | remotes | ! remotes . contains ( & url ) )
2024-08-24 22:41:47 -04:00
{
2025-01-15 11:54:32 -05:00
let name = & package . id . name ;
let version = & package
. id
. version
. as_ref ( )
. expect ( " version for registry source " ) ;
2025-06-27 19:54:52 +02:00
return Ok ( SatisfiesResult ::MissingRemoteIndex (
name ,
version ,
url . into_owned ( ) ,
) ) ;
2024-08-24 22:41:47 -04:00
}
}
RegistrySource ::Path ( path ) = > {
if locals . as_ref ( ) . is_some_and ( | locals | ! locals . contains ( path ) ) {
2025-01-15 11:54:32 -05:00
let name = & package . id . name ;
let version = & package
. id
. version
. as_ref ( )
. expect ( " version for registry source " ) ;
return Ok ( SatisfiesResult ::MissingLocalIndex ( name , version , path ) ) ;
2024-08-24 22:41:47 -04:00
}
}
2025-04-04 11:49:26 -04:00
}
2024-08-15 13:19:40 -04:00
}
2024-08-15 08:51:41 -04:00
// If the package is immutable, we don't need to validate it (or its dependencies).
if package . id . source . is_immutable ( ) {
2024-08-14 20:00:15 -04:00
continue ;
}
2025-01-22 12:58:15 -05:00
if let Some ( version ) = package . id . version . as_ref ( ) {
// For a non-dynamic package, fetch the metadata from the distribution database.
let dist =
package . to_dist ( root , TagPolicy ::Preferred ( tags ) , & BuildOptions ::default ( ) ) ? ;
2025-01-16 23:27:46 -05:00
let metadata = {
let id = dist . version_id ( ) ;
if let Some ( archive ) =
index
. distributions ( )
. get ( & id )
. as_deref ( )
. and_then ( | response | {
if let MetadataResponse ::Found ( archive , .. ) = response {
Some ( archive )
} else {
None
}
} )
{
// If the metadata is already in the index, return it.
archive . metadata . clone ( )
} else {
// Run the PEP 517 build process to extract metadata from the source distribution.
let archive = database
. get_or_build_wheel_metadata ( & dist , hasher . get ( & dist ) )
. await
. map_err ( | err | LockErrorKind ::Resolution {
id : package . id . clone ( ) ,
err ,
} ) ? ;
let metadata = archive . metadata . clone ( ) ;
// Insert the metadata into the index.
index
. distributions ( )
. done ( id , Arc ::new ( MetadataResponse ::Found ( archive ) ) ) ;
metadata
}
} ;
2024-10-12 16:46:55 +02:00
2025-01-22 12:58:15 -05:00
// If this is a local package, validate that it hasn't become dynamic (in which
// case, we'd expect the version to be omitted).
if package . id . source . is_source_tree ( ) {
if metadata . dynamic {
2025-01-22 16:22:07 -05:00
return Ok ( SatisfiesResult ::MismatchedDynamic ( & package . id . name , false ) ) ;
2025-01-22 12:58:15 -05:00
}
}
// Validate the `version` metadata.
if metadata . version ! = * version {
return Ok ( SatisfiesResult ::MismatchedVersion (
2025-01-22 16:22:07 -05:00
& package . id . name ,
2025-01-22 12:58:15 -05:00
version . clone ( ) ,
Some ( metadata . version . clone ( ) ) ,
) ) ;
}
2025-02-21 18:51:04 -10:00
// Validate the `provides-extras` metadata.
match self . satisfies_provides_extra ( metadata . provides_extras , package ) {
SatisfiesResult ::Satisfied = > { }
result = > return Ok ( result ) ,
}
2025-01-22 12:58:15 -05:00
// Validate that the requirements are unchanged.
2025-02-21 18:51:04 -10:00
match self . satisfies_requires_dist (
metadata . requires_dist ,
metadata . dependency_groups ,
package ,
root ,
) ? {
2025-01-20 19:45:56 -05:00
SatisfiesResult ::Satisfied = > { }
result = > return Ok ( result ) ,
2024-08-14 20:00:15 -04:00
}
2025-01-22 12:58:15 -05:00
} else if let Some ( source_tree ) = package . id . source . as_source_tree ( ) {
// For dynamic packages, we don't need the version. We only need to know that the
// package is still dynamic, and that the requirements are unchanged.
//
// If the distribution is a source tree, attempt to extract the requirements from the
// `pyproject.toml` directly. The distribution database will do this too, but we can be
// even more aggressive here since we _only_ need the requirements. So, for example,
// even if the version is dynamic, we can still extract the requirements without
// performing a build, unlike in the database where we typically construct a "complete"
// metadata object.
let metadata = database
. requires_dist ( root . join ( source_tree ) )
. await
. map_err ( | err | LockErrorKind ::Resolution {
id : package . id . clone ( ) ,
err ,
} ) ? ;
let satisfied = metadata . is_some_and ( | metadata | {
// Validate that the package is still dynamic.
if ! metadata . dynamic {
debug! ( " Static `requires-dist` for `{}` is out-of-date; falling back to distribution database " , package . id ) ;
return false ;
}
2025-02-21 18:51:04 -10:00
// Validate that the extras are unchanged.
if let SatisfiesResult ::Satisfied = self . satisfies_provides_extra ( metadata . provides_extras , package , ) {
debug! ( " Static `provides-extra` for `{}` is up-to-date " , package . id ) ;
} else {
debug! ( " Static `provides-extra` for `{}` is out-of-date; falling back to distribution database " , package . id ) ;
return false ;
}
2025-01-22 12:58:15 -05:00
// Validate that the requirements are unchanged.
2025-02-21 18:51:04 -10:00
match self . satisfies_requires_dist ( metadata . requires_dist , metadata . dependency_groups , package , root ) {
2025-01-22 12:58:15 -05:00
Ok ( SatisfiesResult ::Satisfied ) = > {
debug! ( " Static `requires-dist` for `{}` is up-to-date " , package . id ) ;
} ,
Ok ( .. ) = > {
debug! ( " Static `requires-dist` for `{}` is out-of-date; falling back to distribution database " , package . id ) ;
2025-02-21 18:51:04 -10:00
return false ;
2025-01-22 12:58:15 -05:00
} ,
Err ( .. ) = > {
debug! ( " Static `requires-dist` for `{}` is invalid; falling back to distribution database " , package . id ) ;
2025-02-21 18:51:04 -10:00
return false ;
2025-01-22 12:58:15 -05:00
} ,
}
2025-02-21 18:51:04 -10:00
true
2025-01-22 12:58:15 -05:00
} ) ;
// If the `requires-dist` metadata matches the requirements, we're done; otherwise,
// fetch the "full" metadata, which may involve invoking the build system. In some
// cases, build backends return metadata that does _not_ match the `pyproject.toml`
// exactly. For example, `hatchling` will flatten any recursive (or self-referential)
// extras, while `setuptools` will not.
if ! satisfied {
let dist = package . to_dist (
root ,
TagPolicy ::Preferred ( tags ) ,
& BuildOptions ::default ( ) ,
) ? ;
let metadata = {
let id = dist . version_id ( ) ;
if let Some ( archive ) =
index
. distributions ( )
. get ( & id )
. as_deref ( )
. and_then ( | response | {
if let MetadataResponse ::Found ( archive , .. ) = response {
Some ( archive )
} else {
None
}
} )
{
// If the metadata is already in the index, return it.
archive . metadata . clone ( )
} else {
// Run the PEP 517 build process to extract metadata from the source distribution.
let archive = database
. get_or_build_wheel_metadata ( & dist , hasher . get ( & dist ) )
. await
. map_err ( | err | LockErrorKind ::Resolution {
id : package . id . clone ( ) ,
err ,
} ) ? ;
let metadata = archive . metadata . clone ( ) ;
// Insert the metadata into the index.
index
. distributions ( )
. done ( id , Arc ::new ( MetadataResponse ::Found ( archive ) ) ) ;
metadata
}
} ;
// Validate that the package is still dynamic.
if ! metadata . dynamic {
2025-01-22 16:22:07 -05:00
return Ok ( SatisfiesResult ::MismatchedDynamic ( & package . id . name , true ) ) ;
2025-01-22 12:58:15 -05:00
}
2025-02-21 18:51:04 -10:00
// Validate that the extras are unchanged.
match self . satisfies_provides_extra ( metadata . provides_extras , package ) {
SatisfiesResult ::Satisfied = > { }
result = > return Ok ( result ) ,
}
2025-01-22 12:58:15 -05:00
// Validate that the requirements are unchanged.
2025-02-21 18:51:04 -10:00
match self . satisfies_requires_dist (
metadata . requires_dist ,
metadata . dependency_groups ,
2025-02-07 11:54:40 +01:00
package ,
root ,
) ? {
2025-01-22 12:58:15 -05:00
SatisfiesResult ::Satisfied = > { }
result = > return Ok ( result ) ,
}
}
} else {
2025-01-22 16:22:07 -05:00
return Ok ( SatisfiesResult ::MissingVersion ( & package . id . name ) ) ;
2024-08-14 20:00:15 -04:00
}
// Recurse.
for dep in & package . dependencies {
if seen . insert ( & dep . package_id ) {
let dep_dist = self . find_by_id ( & dep . package_id ) ;
queue . push_back ( dep_dist ) ;
}
}
for dependencies in package . optional_dependencies . values ( ) {
for dep in dependencies {
if seen . insert ( & dep . package_id ) {
let dep_dist = self . find_by_id ( & dep . package_id ) ;
queue . push_back ( dep_dist ) ;
}
}
}
2024-10-21 22:56:43 -04:00
for dependencies in package . dependency_groups . values ( ) {
2024-08-14 20:00:15 -04:00
for dep in dependencies {
if seen . insert ( & dep . package_id ) {
let dep_dist = self . find_by_id ( & dep . package_id ) ;
queue . push_back ( dep_dist ) ;
}
}
}
}
2024-08-15 13:19:40 -04:00
Ok ( SatisfiesResult ::Satisfied )
2024-07-12 18:49:28 -04:00
}
2024-06-25 12:41:40 -04:00
}
2024-09-11 15:17:07 -04:00
#[ derive(Debug, Copy, Clone) ]
enum TagPolicy < ' tags > {
/// Exclusively consider wheels that match the specified platform tags.
Required ( & ' tags Tags ) ,
/// Prefer wheels that match the specified platform tags, but fall back to incompatible wheels
2024-11-13 15:49:08 -05:00
/// if necessary.
2024-09-11 15:17:07 -04:00
Preferred ( & ' tags Tags ) ,
}
impl < ' tags > TagPolicy < ' tags > {
/// Returns the platform tags to consider.
fn tags ( & self ) -> & ' tags Tags {
match self {
TagPolicy ::Required ( tags ) | TagPolicy ::Preferred ( tags ) = > tags ,
}
}
}
2024-08-15 13:19:40 -04:00
/// The result of checking if a lockfile satisfies a set of requirements.
#[ derive(Debug) ]
pub enum SatisfiesResult < ' lock > {
/// The lockfile satisfies the requirements.
Satisfied ,
/// The lockfile uses a different set of workspace members.
MismatchedMembers ( BTreeSet < PackageName > , & ' lock BTreeSet < PackageName > ) ,
2025-01-15 11:54:32 -05:00
/// A workspace member switched from virtual to non-virtual or vice versa.
MismatchedVirtual ( PackageName , bool ) ,
2025-01-22 12:58:15 -05:00
/// A source tree switched from dynamic to non-dynamic or vice versa.
2025-01-22 16:22:07 -05:00
MismatchedDynamic ( & ' lock PackageName , bool ) ,
2024-09-05 18:14:57 -04:00
/// The lockfile uses a different set of version for its workspace members.
2025-01-22 16:22:07 -05:00
MismatchedVersion ( & ' lock PackageName , Version , Option < Version > ) ,
2024-08-20 21:25:38 -04:00
/// The lockfile uses a different set of requirements.
MismatchedRequirements ( BTreeSet < Requirement > , BTreeSet < Requirement > ) ,
2024-08-15 13:19:40 -04:00
/// The lockfile uses a different set of constraints.
MismatchedConstraints ( BTreeSet < Requirement > , BTreeSet < Requirement > ) ,
/// The lockfile uses a different set of overrides.
MismatchedOverrides ( BTreeSet < Requirement > , BTreeSet < Requirement > ) ,
2025-03-27 17:11:50 -04:00
/// The lockfile uses a different set of build constraints.
MismatchedBuildConstraints ( BTreeSet < Requirement > , BTreeSet < Requirement > ) ,
2024-12-24 17:32:29 -05:00
/// The lockfile uses a different set of dependency groups.
MismatchedDependencyGroups (
BTreeMap < GroupName , BTreeSet < Requirement > > ,
BTreeMap < GroupName , BTreeSet < Requirement > > ,
) ,
2024-09-17 23:18:05 -04:00
/// The lockfile uses different static metadata.
MismatchedStaticMetadata ( BTreeSet < StaticMetadata > , & ' lock BTreeSet < StaticMetadata > ) ,
2024-08-15 13:19:40 -04:00
/// The lockfile is missing a workspace member.
MissingRoot ( PackageName ) ,
2024-08-24 22:41:47 -04:00
/// The lockfile referenced a remote index that was not provided
2025-06-27 17:11:21 +02:00
MissingRemoteIndex ( & ' lock PackageName , & ' lock Version , UrlString ) ,
2024-08-24 22:41:47 -04:00
/// The lockfile referenced a local index that was not provided
2025-03-25 17:56:06 -04:00
MissingLocalIndex ( & ' lock PackageName , & ' lock Version , & ' lock Path ) ,
2024-08-15 13:19:40 -04:00
/// A package in the lockfile contains different `requires-dist` metadata than expected.
2024-12-24 17:32:29 -05:00
MismatchedPackageRequirements (
2024-08-15 13:19:40 -04:00
& ' lock PackageName ,
2025-01-15 11:54:32 -05:00
Option < & ' lock Version > ,
2024-08-15 13:19:40 -04:00
BTreeSet < Requirement > ,
BTreeSet < Requirement > ,
) ,
2025-02-21 18:51:04 -10:00
/// A package in the lockfile contains different `provides-extra` metadata than expected.
MismatchedPackageProvidesExtra (
& ' lock PackageName ,
Option < & ' lock Version > ,
BTreeSet < ExtraName > ,
BTreeSet < & ' lock ExtraName > ,
) ,
2025-02-04 12:16:05 +01:00
/// A package in the lockfile contains different `dependency-groups` metadata than expected.
2024-12-24 17:32:29 -05:00
MismatchedPackageDependencyGroups (
2024-08-15 13:19:40 -04:00
& ' lock PackageName ,
2025-01-15 11:54:32 -05:00
Option < & ' lock Version > ,
2024-08-15 13:19:40 -04:00
BTreeMap < GroupName , BTreeSet < Requirement > > ,
BTreeMap < GroupName , BTreeSet < Requirement > > ,
) ,
2025-01-22 12:58:15 -05:00
/// The lockfile is missing a version.
2025-01-22 16:22:07 -05:00
MissingVersion ( & ' lock PackageName ) ,
2024-08-15 13:19:40 -04:00
}
2024-08-07 20:11:59 +02:00
/// We discard the lockfile if these options match.
#[ derive(Clone, Debug, Default, serde::Deserialize, PartialEq, Eq) ]
#[ serde(rename_all = " kebab-case " ) ]
struct ResolverOptions {
/// The [`ResolutionMode`] used to generate this lock.
#[ serde(default) ]
resolution_mode : ResolutionMode ,
/// The [`PrereleaseMode`] used to generate this lock.
#[ serde(default) ]
prerelease_mode : PrereleaseMode ,
2024-12-13 16:05:07 -05:00
/// The [`ForkStrategy`] used to generate this lock.
#[ serde(default) ]
fork_strategy : ForkStrategy ,
2024-08-07 20:11:59 +02:00
/// The [`ExcludeNewer`] used to generate this lock.
exclude_newer : Option < ExcludeNewer > ,
}
2024-08-14 20:00:15 -04:00
#[ derive(Clone, Debug, Default, serde::Deserialize, PartialEq, Eq) ]
#[ serde(rename_all = " kebab-case " ) ]
pub struct ResolverManifest {
/// The workspace members included in the lockfile.
#[ serde(default) ]
2024-08-15 09:00:35 -04:00
members : BTreeSet < PackageName > ,
2024-08-20 21:25:38 -04:00
/// The requirements provided to the resolver, exclusive of the workspace members.
2024-12-24 17:32:29 -05:00
///
/// These are requirements that are attached to the project, but not to any of its
/// workspace members. For example, the requirements in a PEP 723 script would be included here.
2024-08-20 21:25:38 -04:00
#[ serde(default) ]
requirements : BTreeSet < Requirement > ,
2024-12-24 17:32:29 -05:00
/// The dependency groups provided to the resolver, exclusive of the workspace members.
///
/// These are dependency groups that are attached to the project, but not to any of its
/// workspace members. For example, the dependency groups in a `pyproject.toml` without a
/// `[project]` table would be included here.
#[ serde(default) ]
dependency_groups : BTreeMap < GroupName , BTreeSet < Requirement > > ,
2024-08-14 20:00:15 -04:00
/// The constraints provided to the resolver.
#[ serde(default) ]
2024-08-15 09:00:35 -04:00
constraints : BTreeSet < Requirement > ,
2024-08-14 20:00:15 -04:00
/// The overrides provided to the resolver.
#[ serde(default) ]
2024-08-15 09:00:35 -04:00
overrides : BTreeSet < Requirement > ,
2025-03-27 17:11:50 -04:00
/// The build constraints provided to the resolver.
#[ serde(default) ]
build_constraints : BTreeSet < Requirement > ,
2024-09-17 23:18:05 -04:00
/// The static metadata provided to the resolver.
#[ serde(default) ]
dependency_metadata : BTreeSet < StaticMetadata > ,
2024-08-14 20:00:15 -04:00
}
impl ResolverManifest {
2024-08-23 22:19:10 -04:00
/// Initialize a [`ResolverManifest`] with the given members, requirements, constraints, and
/// overrides.
2024-08-14 20:00:15 -04:00
pub fn new (
2024-08-15 09:00:35 -04:00
members : impl IntoIterator < Item = PackageName > ,
2024-08-20 21:25:38 -04:00
requirements : impl IntoIterator < Item = Requirement > ,
2024-08-15 09:00:35 -04:00
constraints : impl IntoIterator < Item = Requirement > ,
overrides : impl IntoIterator < Item = Requirement > ,
2025-03-27 17:11:50 -04:00
build_constraints : impl IntoIterator < Item = Requirement > ,
2024-12-24 17:32:29 -05:00
dependency_groups : impl IntoIterator < Item = ( GroupName , Vec < Requirement > ) > ,
2024-09-17 23:18:05 -04:00
dependency_metadata : impl IntoIterator < Item = StaticMetadata > ,
2024-08-14 20:00:15 -04:00
) -> Self {
Self {
2024-08-15 09:00:35 -04:00
members : members . into_iter ( ) . collect ( ) ,
2024-08-20 21:25:38 -04:00
requirements : requirements . into_iter ( ) . collect ( ) ,
2024-08-15 09:00:35 -04:00
constraints : constraints . into_iter ( ) . collect ( ) ,
overrides : overrides . into_iter ( ) . collect ( ) ,
2025-03-27 17:11:50 -04:00
build_constraints : build_constraints . into_iter ( ) . collect ( ) ,
2024-12-24 17:32:29 -05:00
dependency_groups : dependency_groups
. into_iter ( )
. map ( | ( group , requirements ) | ( group , requirements . into_iter ( ) . collect ( ) ) )
. collect ( ) ,
2024-09-17 23:18:05 -04:00
dependency_metadata : dependency_metadata . into_iter ( ) . collect ( ) ,
2024-08-14 20:00:15 -04:00
}
}
2024-08-23 22:19:10 -04:00
/// Convert the manifest to a relative form using the given workspace.
2024-12-24 19:26:31 -05:00
pub fn relative_to ( self , root : & Path ) -> Result < Self , io ::Error > {
2024-08-23 22:19:10 -04:00
Ok ( Self {
members : self . members ,
requirements : self
. requirements
. into_iter ( )
2024-12-24 19:26:31 -05:00
. map ( | requirement | requirement . relative_to ( root ) )
2024-08-23 22:19:10 -04:00
. collect ::< Result < BTreeSet < _ > , _ > > ( ) ? ,
constraints : self
. constraints
. into_iter ( )
2024-12-24 19:26:31 -05:00
. map ( | requirement | requirement . relative_to ( root ) )
2024-08-23 22:19:10 -04:00
. collect ::< Result < BTreeSet < _ > , _ > > ( ) ? ,
overrides : self
. overrides
. into_iter ( )
2024-12-24 19:26:31 -05:00
. map ( | requirement | requirement . relative_to ( root ) )
2024-08-23 22:19:10 -04:00
. collect ::< Result < BTreeSet < _ > , _ > > ( ) ? ,
2025-03-27 17:11:50 -04:00
build_constraints : self
. build_constraints
. into_iter ( )
. map ( | requirement | requirement . relative_to ( root ) )
. collect ::< Result < BTreeSet < _ > , _ > > ( ) ? ,
2024-12-24 17:32:29 -05:00
dependency_groups : self
. dependency_groups
. into_iter ( )
. map ( | ( group , requirements ) | {
Ok ::< _ , io ::Error > ( (
group ,
requirements
. into_iter ( )
2024-12-24 19:26:31 -05:00
. map ( | requirement | requirement . relative_to ( root ) )
2024-12-24 17:32:29 -05:00
. collect ::< Result < BTreeSet < _ > , _ > > ( ) ? ,
) )
} )
. collect ::< Result < BTreeMap < _ , _ > , _ > > ( ) ? ,
2024-09-17 23:18:05 -04:00
dependency_metadata : self . dependency_metadata ,
2024-08-23 22:19:10 -04:00
} )
}
2024-08-14 20:00:15 -04:00
}
2024-06-25 12:41:40 -04:00
#[ derive(Clone, Debug, serde::Deserialize) ]
2024-07-22 08:28:22 -04:00
#[ serde(rename_all = " kebab-case " ) ]
2024-06-25 12:41:40 -04:00
struct LockWire {
version : u32 ,
2025-02-14 11:17:26 -05:00
revision : Option < u32 > ,
2024-09-02 14:23:42 -04:00
requires_python : RequiresPython ,
2024-07-30 13:11:18 +02:00
/// If this lockfile was built from a forking resolution with non-identical forks, store the
/// forks in the lockfile so we can recreate them in subsequent resolutions.
2024-08-20 09:14:55 -04:00
#[ serde(rename = " resolution-markers " , default) ]
2024-08-19 18:05:01 -04:00
fork_markers : Vec < SimplifiedMarkerTree > ,
2024-08-20 09:28:04 -04:00
#[ serde(rename = " supported-markers " , default) ]
2024-08-19 18:05:01 -04:00
supported_environments : Vec < SimplifiedMarkerTree > ,
2025-02-14 15:11:18 -05:00
#[ serde(rename = " required-markers " , default) ]
required_environments : Vec < SimplifiedMarkerTree > ,
2024-11-13 10:15:47 -05:00
#[ serde(rename = " conflicts " , default) ]
conflicts : Option < Conflicts > ,
2024-08-07 20:11:59 +02:00
/// We discard the lockfile if these options match.
2024-07-22 08:28:22 -04:00
#[ serde(default) ]
2024-08-07 20:11:59 +02:00
options : ResolverOptions ,
2024-08-14 20:00:15 -04:00
#[ serde(default) ]
manifest : ResolverManifest ,
2024-08-08 17:25:06 +02:00
#[ serde(rename = " package " , alias = " distribution " , default) ]
packages : Vec < PackageWire > ,
2024-06-25 12:41:40 -04:00
}
2024-05-03 08:18:36 -04:00
impl TryFrom < LockWire > for Lock {
type Error = LockError ;
2024-06-24 14:29:01 -04:00
fn try_from ( wire : LockWire ) -> Result < Lock , LockError > {
2024-08-08 17:25:06 +02:00
// Count the number of sources for each package name. When
// there's only one source for a particular package name (the
2024-06-24 15:03:45 -04:00
// overwhelmingly common case), we can omit some data (like source and
// version) on dependency edges since it is strictly redundant.
2024-08-08 17:25:06 +02:00
let mut unambiguous_package_ids : FxHashMap < PackageName , PackageId > = FxHashMap ::default ( ) ;
2024-06-24 15:03:45 -04:00
let mut ambiguous = FxHashSet ::default ( ) ;
2024-08-08 17:25:06 +02:00
for dist in & wire . packages {
2024-06-24 15:03:45 -04:00
if ambiguous . contains ( & dist . id . name ) {
continue ;
}
2025-02-10 15:26:57 -05:00
if let Some ( id ) = unambiguous_package_ids . remove ( & dist . id . name ) {
ambiguous . insert ( id . name ) ;
2024-06-24 15:03:45 -04:00
continue ;
}
2024-08-08 17:25:06 +02:00
unambiguous_package_ids . insert ( dist . id . name . clone ( ) , dist . id . clone ( ) ) ;
2024-06-24 15:03:45 -04:00
}
2024-08-08 17:25:06 +02:00
let packages = wire
. packages
2024-06-24 14:29:01 -04:00
. into_iter ( )
2024-08-19 18:05:01 -04:00
. map ( | dist | dist . unwire ( & wire . requires_python , & unambiguous_package_ids ) )
2024-06-24 14:29:01 -04:00
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
2024-08-19 18:05:01 -04:00
let supported_environments = wire
. supported_environments
. into_iter ( )
. map ( | simplified_marker | simplified_marker . into_marker ( & wire . requires_python ) )
. collect ( ) ;
2025-02-14 15:11:18 -05:00
let required_environments = wire
. required_environments
. into_iter ( )
. map ( | simplified_marker | simplified_marker . into_marker ( & wire . requires_python ) )
. collect ( ) ;
2024-08-19 18:05:01 -04:00
let fork_markers = wire
. fork_markers
. into_iter ( )
. map ( | simplified_marker | simplified_marker . into_marker ( & wire . requires_python ) )
2025-01-21 14:04:31 -05:00
. map ( UniversalMarker ::from_combined )
2024-08-19 18:05:01 -04:00
. collect ( ) ;
let lock = Lock ::new (
2024-07-22 08:28:22 -04:00
wire . version ,
2025-02-14 11:17:26 -05:00
wire . revision . unwrap_or ( 0 ) ,
2024-08-08 17:25:06 +02:00
packages ,
2024-07-22 08:28:22 -04:00
wire . requires_python ,
2024-08-07 20:11:59 +02:00
wire . options ,
2024-08-14 20:00:15 -04:00
wire . manifest ,
2024-11-13 10:15:47 -05:00
wire . conflicts . unwrap_or_else ( Conflicts ::empty ) ,
2024-08-19 18:05:01 -04:00
supported_environments ,
2025-02-14 15:11:18 -05:00
required_environments ,
2024-08-19 18:05:01 -04:00
fork_markers ,
) ? ;
Ok ( lock )
2024-04-29 14:03:17 -04:00
}
}
2024-10-24 12:23:56 -04:00
/// Like [`Lock`], but limited to the version field. Used for error reporting: by limiting parsing
/// to the version field, we can verify compatibility for lockfiles that may otherwise be
/// unparsable.
#[ derive(Clone, Debug, serde::Deserialize) ]
2025-04-28 11:01:17 -04:00
#[ serde(rename_all = " kebab-case " ) ]
2024-10-24 12:23:56 -04:00
pub struct LockVersion {
version : u32 ,
}
impl LockVersion {
/// Returns the lockfile version.
pub fn version ( & self ) -> u32 {
self . version
}
}
2024-07-09 17:08:27 -04:00
#[ derive(Clone, Debug, PartialEq, Eq) ]
2024-08-08 17:25:06 +02:00
pub struct Package {
pub ( crate ) id : PackageId ,
2024-06-03 15:00:35 -04:00
sdist : Option < SourceDist > ,
wheels : Vec < Wheel > ,
2024-08-08 17:25:06 +02:00
/// If there are multiple versions or sources for the same package name, we add the markers of
/// the fork(s) that contained this version or source, so we can set the correct preferences in
/// the next resolution.
2024-07-30 13:11:18 +02:00
///
2024-08-20 09:14:55 -04:00
/// Named `resolution-markers` in `uv.lock`.
2024-11-21 14:01:55 -05:00
fork_markers : Vec < UniversalMarker > ,
2024-08-14 20:00:15 -04:00
/// The resolved dependencies of the package.
2024-06-03 15:00:35 -04:00
dependencies : Vec < Dependency > ,
2024-08-14 20:00:15 -04:00
/// The resolved optional dependencies of the package.
2024-06-12 08:46:16 -07:00
optional_dependencies : BTreeMap < ExtraName , Vec < Dependency > > ,
2024-10-21 22:56:43 -04:00
/// The resolved PEP 735 dependency groups of the package.
dependency_groups : BTreeMap < GroupName , Vec < Dependency > > ,
2024-08-14 20:00:15 -04:00
/// The exact requirements from the package metadata.
metadata : PackageMetadata ,
2024-04-29 14:03:17 -04:00
}
2024-08-08 17:25:06 +02:00
impl Package {
2024-07-30 13:11:18 +02:00
fn from_annotated_dist (
annotated_dist : & AnnotatedDist ,
2024-11-21 14:01:55 -05:00
fork_markers : Vec < UniversalMarker > ,
2024-08-23 22:19:10 -04:00
root : & Path ,
2024-07-30 13:11:18 +02:00
) -> Result < Self , LockError > {
2024-08-23 22:19:10 -04:00
let id = PackageId ::from_annotated_dist ( annotated_dist , root ) ? ;
2024-06-25 14:29:49 -04:00
let sdist = SourceDist ::from_annotated_dist ( & id , annotated_dist ) ? ;
2024-06-02 21:33:18 -04:00
let wheels = Wheel ::from_annotated_dist ( annotated_dist ) ? ;
2024-08-15 08:51:41 -04:00
let requires_dist = if id . source . is_immutable ( ) {
2025-02-14 11:17:26 -05:00
BTreeSet ::default ( )
2024-08-14 20:00:15 -04:00
} else {
2025-02-14 11:17:26 -05:00
annotated_dist
. metadata
. as_ref ( )
. expect ( " metadata is present " )
. requires_dist
. iter ( )
. cloned ( )
. map ( | requirement | requirement . relative_to ( root ) )
. collect ::< Result < _ , _ > > ( )
. map_err ( LockErrorKind ::RequirementRelativePath ) ?
2025-02-11 14:38:38 -05:00
} ;
let provides_extras = if id . source . is_immutable ( ) {
2025-03-27 17:09:26 -04:00
Box ::default ( )
2025-02-11 14:38:38 -05:00
} else {
2025-02-14 11:17:26 -05:00
annotated_dist
. metadata
. as_ref ( )
. expect ( " metadata is present " )
. provides_extras
. clone ( )
2024-08-14 20:00:15 -04:00
} ;
2024-10-21 22:56:43 -04:00
let dependency_groups = if id . source . is_immutable ( ) {
2024-08-14 20:00:15 -04:00
BTreeMap ::default ( )
} else {
2024-08-15 09:00:35 -04:00
annotated_dist
. metadata
2024-09-06 21:26:28 -04:00
. as_ref ( )
. expect ( " metadata is present " )
2024-10-21 22:56:43 -04:00
. dependency_groups
2024-08-15 09:00:35 -04:00
. iter ( )
2024-08-23 22:19:10 -04:00
. map ( | ( group , requirements ) | {
let requirements = requirements
. iter ( )
. cloned ( )
. map ( | requirement | requirement . relative_to ( root ) )
. collect ::< Result < _ , _ > > ( )
. map_err ( LockErrorKind ::RequirementRelativePath ) ? ;
Ok ::< _ , LockError > ( ( group . clone ( ) , requirements ) )
} )
. collect ::< Result < _ , _ > > ( ) ?
2024-08-14 20:00:15 -04:00
} ;
2024-08-08 17:25:06 +02:00
Ok ( Package {
2024-04-29 14:03:17 -04:00
id ,
2024-05-14 16:56:00 -04:00
sdist ,
2024-04-29 14:03:17 -04:00
wheels ,
2024-07-30 13:11:18 +02:00
fork_markers ,
2024-04-29 14:03:17 -04:00
dependencies : vec ! [ ] ,
2024-06-12 08:46:16 -07:00
optional_dependencies : BTreeMap ::default ( ) ,
2024-10-21 22:56:43 -04:00
dependency_groups : BTreeMap ::default ( ) ,
2024-08-14 20:00:15 -04:00
metadata : PackageMetadata {
requires_dist ,
2025-02-11 14:38:38 -05:00
provides_extras ,
2024-10-21 22:56:43 -04:00
dependency_groups ,
2024-08-14 20:00:15 -04:00
} ,
2024-04-29 14:03:17 -04:00
} )
}
2024-08-08 17:25:06 +02:00
/// Add the [`AnnotatedDist`] as a dependency of the [`Package`].
2024-08-23 22:19:10 -04:00
fn add_dependency (
& mut self ,
2024-08-19 18:05:01 -04:00
requires_python : & RequiresPython ,
2024-08-23 22:19:10 -04:00
annotated_dist : & AnnotatedDist ,
2024-11-21 14:01:55 -05:00
marker : UniversalMarker ,
2024-08-23 22:19:10 -04:00
root : & Path ,
) -> Result < ( ) , LockError > {
2024-08-19 18:05:01 -04:00
let new_dep =
Dependency ::from_annotated_dist ( requires_python , annotated_dist , marker , root ) ? ;
2024-07-18 20:07:49 +02:00
for existing_dep in & mut self . dependencies {
2024-08-08 17:25:06 +02:00
if existing_dep . package_id = = new_dep . package_id
2024-08-19 18:05:01 -04:00
// It's important that we do a comparison on
// *simplified* markers here. In particular, when
// we write markers out to the lock file, we use
// "simplified" markers, or markers that are simplified
// *given* that `requires-python` is satisfied. So if
// we don't do equality based on what the simplified
// marker is, we might wind up not merging dependencies
// that ought to be merged and thus writing out extra
// entries.
//
// For example, if `requires-python = '>=3.8'` and we
// have `foo==1` and
// `foo==1 ; python_version >= '3.8'` dependencies,
// then they don't have equivalent complexified
// markers, but their simplified markers are identical.
//
// NOTE: It does seem like perhaps this should
// be implemented semantically/algebraically on
// `MarkerTree` itself, but it wasn't totally clear
// how to do that. I think `pep508` would need to
// grow a concept of "requires python" and provide an
// operation specifically for that.
& & existing_dep . simplified_marker = = new_dep . simplified_marker
2024-07-18 20:07:49 +02:00
{
existing_dep . extra . extend ( new_dep . extra ) ;
2024-08-23 22:19:10 -04:00
return Ok ( ( ) ) ;
2024-07-18 20:07:49 +02:00
}
}
2024-08-23 22:19:10 -04:00
2024-07-18 20:07:49 +02:00
self . dependencies . push ( new_dep ) ;
2024-08-23 22:19:10 -04:00
Ok ( ( ) )
2024-04-29 14:03:17 -04:00
}
2024-05-03 08:18:36 -04:00
2024-08-08 17:25:06 +02:00
/// Add the [`AnnotatedDist`] as an optional dependency of the [`Package`].
2024-06-10 05:40:51 -07:00
fn add_optional_dependency (
& mut self ,
2024-08-19 18:05:01 -04:00
requires_python : & RequiresPython ,
2024-06-10 05:40:51 -07:00
extra : ExtraName ,
annotated_dist : & AnnotatedDist ,
2024-11-21 14:01:55 -05:00
marker : UniversalMarker ,
2024-08-23 22:19:10 -04:00
root : & Path ,
) -> Result < ( ) , LockError > {
2024-08-19 18:05:01 -04:00
let dep = Dependency ::from_annotated_dist ( requires_python , annotated_dist , marker , root ) ? ;
2024-08-21 18:36:51 -04:00
let optional_deps = self . optional_dependencies . entry ( extra ) . or_default ( ) ;
for existing_dep in & mut * optional_deps {
2024-08-19 18:05:01 -04:00
if existing_dep . package_id = = dep . package_id
// See note in add_dependency for why we use
// simplified markers here.
& & existing_dep . simplified_marker = = dep . simplified_marker
{
2024-08-21 18:36:51 -04:00
existing_dep . extra . extend ( dep . extra ) ;
2024-08-23 22:19:10 -04:00
return Ok ( ( ) ) ;
2024-08-21 18:36:51 -04:00
}
}
2024-08-23 22:19:10 -04:00
2024-08-21 18:36:51 -04:00
optional_deps . push ( dep ) ;
2024-08-23 22:19:10 -04:00
Ok ( ( ) )
2024-06-03 15:00:35 -04:00
}
2024-10-21 22:56:43 -04:00
/// Add the [`AnnotatedDist`] to a dependency group of the [`Package`].
fn add_group_dependency (
2024-06-10 05:40:51 -07:00
& mut self ,
2024-08-19 18:05:01 -04:00
requires_python : & RequiresPython ,
2024-10-21 22:56:43 -04:00
group : GroupName ,
2024-06-10 05:40:51 -07:00
annotated_dist : & AnnotatedDist ,
2024-11-21 14:01:55 -05:00
marker : UniversalMarker ,
2024-08-23 22:19:10 -04:00
root : & Path ,
) -> Result < ( ) , LockError > {
2024-08-19 18:05:01 -04:00
let dep = Dependency ::from_annotated_dist ( requires_python , annotated_dist , marker , root ) ? ;
2024-10-21 22:56:43 -04:00
let deps = self . dependency_groups . entry ( group ) . or_default ( ) ;
for existing_dep in & mut * deps {
2024-08-19 18:05:01 -04:00
if existing_dep . package_id = = dep . package_id
// See note in add_dependency for why we use
// simplified markers here.
& & existing_dep . simplified_marker = = dep . simplified_marker
{
2024-08-21 18:36:51 -04:00
existing_dep . extra . extend ( dep . extra ) ;
2024-08-23 22:19:10 -04:00
return Ok ( ( ) ) ;
2024-08-21 18:36:51 -04:00
}
}
2024-08-23 22:19:10 -04:00
2024-10-21 22:56:43 -04:00
deps . push ( dep ) ;
2024-08-23 22:19:10 -04:00
Ok ( ( ) )
2024-06-05 21:40:17 -04:00
}
2024-08-08 17:25:06 +02:00
/// Convert the [`Package`] to a [`Dist`] that can be used in installation.
2024-09-05 17:45:42 -04:00
fn to_dist (
& self ,
workspace_root : & Path ,
2024-09-11 15:17:07 -04:00
tag_policy : TagPolicy < '_ > ,
2024-09-05 17:45:42 -04:00
build_options : & BuildOptions ,
) -> Result < Dist , LockError > {
let no_binary = build_options . no_binary_package ( & self . id . name ) ;
let no_build = build_options . no_build_package ( & self . id . name ) ;
if ! no_binary {
2024-09-11 15:17:07 -04:00
if let Some ( best_wheel_index ) = self . find_best_wheel ( tag_policy ) {
2024-09-05 17:45:42 -04:00
return match & self . id . source {
Source ::Registry ( source ) = > {
let wheels = self
. wheels
. iter ( )
2025-04-21 18:10:30 -04:00
. map ( | wheel | wheel . to_registry_wheel ( source , workspace_root ) )
2024-09-05 17:45:42 -04:00
. collect ::< Result < _ , LockError > > ( ) ? ;
let reg_built_dist = RegistryBuiltDist {
wheels ,
best_wheel_index ,
sdist : None ,
} ;
Ok ( Dist ::Built ( BuiltDist ::Registry ( reg_built_dist ) ) )
}
Source ::Path ( path ) = > {
let filename : WheelFilename =
self . wheels [ best_wheel_index ] . filename . clone ( ) ;
2024-12-12 13:49:05 -05:00
let install_path = absolute_path ( workspace_root , path ) ? ;
2024-09-05 17:45:42 -04:00
let path_dist = PathBuiltDist {
filename ,
2024-12-12 13:49:05 -05:00
url : verbatim_url ( & install_path , & self . id ) ? ,
2025-03-25 17:56:06 -04:00
install_path : absolute_path ( workspace_root , path ) ? . into_boxed_path ( ) ,
2024-09-05 17:45:42 -04:00
} ;
let built_dist = BuiltDist ::Path ( path_dist ) ;
Ok ( Dist ::Built ( built_dist ) )
}
Source ::Direct ( url , direct ) = > {
let filename : WheelFilename =
self . wheels [ best_wheel_index ] . filename . clone ( ) ;
2025-05-27 00:05:30 +02:00
let url = DisplaySafeUrl ::from ( ParsedArchiveUrl {
2025-01-01 12:35:30 -05:00
url : url . to_url ( ) . map_err ( LockErrorKind ::InvalidUrl ) ? ,
2024-12-20 19:28:36 -05:00
subdirectory : direct . subdirectory . clone ( ) ,
2024-09-05 17:45:42 -04:00
ext : DistExtension ::Wheel ,
} ) ;
let direct_dist = DirectUrlBuiltDist {
filename ,
2025-01-08 15:33:19 +01:00
location : Box ::new ( url . clone ( ) ) ,
2024-09-05 17:45:42 -04:00
url : VerbatimUrl ::from_url ( url ) ,
} ;
let built_dist = BuiltDist ::DirectUrl ( direct_dist ) ;
Ok ( Dist ::Built ( built_dist ) )
}
Source ::Git ( _ , _ ) = > Err ( LockErrorKind ::InvalidWheelSource {
id : self . id . clone ( ) ,
source_type : " Git " ,
}
. into ( ) ) ,
Source ::Directory ( _ ) = > Err ( LockErrorKind ::InvalidWheelSource {
id : self . id . clone ( ) ,
source_type : " directory " ,
}
. into ( ) ) ,
Source ::Editable ( _ ) = > Err ( LockErrorKind ::InvalidWheelSource {
id : self . id . clone ( ) ,
source_type : " editable " ,
}
. into ( ) ) ,
Source ::Virtual ( _ ) = > Err ( LockErrorKind ::InvalidWheelSource {
id : self . id . clone ( ) ,
source_type : " virtual " ,
}
. into ( ) ) ,
} ;
2025-04-04 11:49:26 -04:00
}
2024-09-05 17:45:42 -04:00
}
2024-05-15 12:46:45 -04:00
2025-03-22 09:00:02 -07:00
if let Some ( sdist ) = self . to_source_dist ( workspace_root ) ? {
// Even with `--no-build`, allow virtual packages. (In the future, we may want to allow
// any local source tree, or at least editable source trees, which we allow in
// `uv pip`.)
if ! no_build | | sdist . is_virtual ( ) {
2024-09-05 17:45:42 -04:00
return Ok ( Dist ::Source ( sdist ) ) ;
}
2024-07-12 18:49:28 -04:00
}
2024-09-05 17:45:42 -04:00
match ( no_binary , no_build ) {
( true , true ) = > Err ( LockErrorKind ::NoBinaryNoBuild {
id : self . id . clone ( ) ,
}
. into ( ) ) ,
2024-09-11 15:53:08 -04:00
( true , false ) if self . id . source . is_wheel ( ) = > Err ( LockErrorKind ::NoBinaryWheelOnly {
id : self . id . clone ( ) ,
}
. into ( ) ) ,
2024-09-05 17:45:42 -04:00
( true , false ) = > Err ( LockErrorKind ::NoBinary {
id : self . id . clone ( ) ,
}
. into ( ) ) ,
( false , true ) = > Err ( LockErrorKind ::NoBuild {
id : self . id . clone ( ) ,
}
. into ( ) ) ,
2025-01-20 12:46:46 -05:00
( false , false ) if self . id . source . is_wheel ( ) = > Err ( LockError {
kind : Box ::new ( LockErrorKind ::IncompatibleWheelOnly {
id : self . id . clone ( ) ,
} ) ,
hint : self . tag_hint ( tag_policy ) ,
} ) ,
( false , false ) = > Err ( LockError {
kind : Box ::new ( LockErrorKind ::NeitherSourceDistNorWheel {
2024-09-11 15:53:08 -04:00
id : self . id . clone ( ) ,
2025-01-20 12:46:46 -05:00
} ) ,
hint : self . tag_hint ( tag_policy ) ,
} ) ,
}
}
2025-04-27 12:56:50 -04:00
/// Generate a [`WheelTagHint`] based on wheel-tag incompatibilities.
fn tag_hint ( & self , tag_policy : TagPolicy < '_ > ) -> Option < WheelTagHint > {
let filenames = self
2025-01-20 12:46:46 -05:00
. wheels
. iter ( )
2025-04-27 12:56:50 -04:00
. map ( | wheel | & wheel . filename )
. collect ::< Vec < _ > > ( ) ;
WheelTagHint ::from_wheels (
& self . id . name ,
self . id . version . as_ref ( ) ,
& filenames ,
tag_policy . tags ( ) ,
)
2024-07-12 18:49:28 -04:00
}
2024-08-08 17:25:06 +02:00
/// Convert the source of this [`Package`] to a [`SourceDist`] that can be used in installation.
2024-07-12 18:49:28 -04:00
///
/// Returns `Ok(None)` if the source cannot be converted because `self.sdist` is `None`. This is required
/// for registry sources.
fn to_source_dist (
& self ,
workspace_root : & Path ,
2024-10-01 20:15:32 -04:00
) -> Result < Option < uv_distribution_types ::SourceDist > , LockError > {
2024-07-12 18:49:28 -04:00
let sdist = match & self . id . source {
2024-06-25 13:56:21 -04:00
Source ::Path ( path ) = > {
2024-09-11 15:10:04 -04:00
// A direct path source can also be a wheel, so validate the extension.
2025-06-23 14:52:07 -04:00
let DistExtension ::Source ( ext ) = DistExtension ::from_path ( path ) . map_err ( | err | {
LockErrorKind ::MissingExtension {
id : self . id . clone ( ) ,
err ,
}
} ) ?
else {
2024-09-11 15:10:04 -04:00
return Ok ( None ) ;
} ;
2024-12-12 13:49:05 -05:00
let install_path = absolute_path ( workspace_root , path ) ? ;
2024-06-25 13:56:21 -04:00
let path_dist = PathSourceDist {
name : self . id . name . clone ( ) ,
2025-01-15 11:54:32 -05:00
version : self . id . version . clone ( ) ,
2024-12-12 13:49:05 -05:00
url : verbatim_url ( & install_path , & self . id ) ? ,
2025-03-25 17:56:06 -04:00
install_path : install_path . into_boxed_path ( ) ,
2024-09-11 15:10:04 -04:00
ext ,
2024-06-25 13:56:21 -04:00
} ;
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Path ( path_dist )
2024-06-25 13:56:21 -04:00
}
Source ::Directory ( path ) = > {
2024-12-12 13:49:05 -05:00
let install_path = absolute_path ( workspace_root , path ) ? ;
2024-06-25 13:56:21 -04:00
let dir_dist = DirectorySourceDist {
name : self . id . name . clone ( ) ,
2024-12-12 13:49:05 -05:00
url : verbatim_url ( & install_path , & self . id ) ? ,
2025-03-25 17:56:06 -04:00
install_path : install_path . into_boxed_path ( ) ,
2024-06-25 13:56:21 -04:00
editable : false ,
2024-08-27 21:19:05 -04:00
r#virtual : false ,
2024-06-25 13:56:21 -04:00
} ;
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Directory ( dir_dist )
2024-06-25 13:56:21 -04:00
}
Source ::Editable ( path ) = > {
2024-12-12 13:49:05 -05:00
let install_path = absolute_path ( workspace_root , path ) ? ;
2024-06-25 13:56:21 -04:00
let dir_dist = DirectorySourceDist {
name : self . id . name . clone ( ) ,
2024-12-12 13:49:05 -05:00
url : verbatim_url ( & install_path , & self . id ) ? ,
2025-03-25 17:56:06 -04:00
install_path : install_path . into_boxed_path ( ) ,
2024-06-25 13:56:21 -04:00
editable : true ,
2024-08-27 21:19:05 -04:00
r#virtual : false ,
} ;
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Directory ( dir_dist )
2024-08-27 21:19:05 -04:00
}
Source ::Virtual ( path ) = > {
2024-12-12 13:49:05 -05:00
let install_path = absolute_path ( workspace_root , path ) ? ;
2024-08-27 21:19:05 -04:00
let dir_dist = DirectorySourceDist {
name : self . id . name . clone ( ) ,
2024-12-12 13:49:05 -05:00
url : verbatim_url ( & install_path , & self . id ) ? ,
2025-03-25 17:56:06 -04:00
install_path : install_path . into_boxed_path ( ) ,
2024-08-27 21:19:05 -04:00
editable : false ,
r#virtual : true ,
2024-06-25 13:56:21 -04:00
} ;
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Directory ( dir_dist )
2024-06-25 13:56:21 -04:00
}
Source ::Git ( url , git ) = > {
2024-08-07 11:47:48 -04:00
// Remove the fragment and query from the URL; they're already present in the
// `GitSource`.
2025-01-01 12:35:30 -05:00
let mut url = url . to_url ( ) . map_err ( LockErrorKind ::InvalidUrl ) ? ;
2024-08-07 11:47:48 -04:00
url . set_fragment ( None ) ;
url . set_query ( None ) ;
2024-06-25 13:56:21 -04:00
// Reconstruct the `GitUrl` from the `GitSource`.
2025-03-27 17:11:50 -04:00
let git_url =
GitUrl ::from_commit ( url , GitReference ::from ( git . kind . clone ( ) ) , git . precise ) ? ;
2024-06-25 13:56:21 -04:00
// Reconstruct the PEP 508-compatible URL from the `GitSource`.
2025-05-27 00:05:30 +02:00
let url = DisplaySafeUrl ::from ( ParsedGitUrl {
2024-06-25 13:56:21 -04:00
url : git_url . clone ( ) ,
2024-12-20 19:28:36 -05:00
subdirectory : git . subdirectory . clone ( ) ,
2024-06-25 13:56:21 -04:00
} ) ;
2024-05-17 17:23:40 -04:00
2024-06-25 13:56:21 -04:00
let git_dist = GitSourceDist {
name : self . id . name . clone ( ) ,
url : VerbatimUrl ::from_url ( url ) ,
git : Box ::new ( git_url ) ,
2024-12-20 19:28:36 -05:00
subdirectory : git . subdirectory . clone ( ) ,
2024-06-25 13:56:21 -04:00
} ;
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Git ( git_dist )
2024-06-25 13:56:21 -04:00
}
Source ::Direct ( url , direct ) = > {
2024-09-11 15:10:04 -04:00
// A direct URL source can also be a wheel, so validate the extension.
2025-06-23 14:52:07 -04:00
let DistExtension ::Source ( ext ) =
DistExtension ::from_path ( url . base_str ( ) ) . map_err ( | err | {
LockErrorKind ::MissingExtension {
id : self . id . clone ( ) ,
err ,
}
} ) ?
else {
2024-09-11 15:10:04 -04:00
return Ok ( None ) ;
} ;
2025-01-01 12:35:30 -05:00
let location = url . to_url ( ) . map_err ( LockErrorKind ::InvalidUrl ) ? ;
2025-05-27 00:05:30 +02:00
let url = DisplaySafeUrl ::from ( ParsedArchiveUrl {
2024-12-20 19:00:50 -05:00
url : location . clone ( ) ,
2025-03-25 17:56:06 -04:00
subdirectory : direct . subdirectory . clone ( ) ,
2024-08-08 21:39:47 -04:00
ext : DistExtension ::Source ( ext ) ,
2024-06-25 13:56:21 -04:00
} ) ;
let direct_dist = DirectUrlSourceDist {
name : self . id . name . clone ( ) ,
2025-01-14 11:53:46 -05:00
location : Box ::new ( location ) ,
2025-03-25 17:56:06 -04:00
subdirectory : direct . subdirectory . clone ( ) ,
2024-08-08 21:39:47 -04:00
ext ,
2024-06-25 13:56:21 -04:00
url : VerbatimUrl ::from_url ( url ) ,
} ;
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::DirectUrl ( direct_dist )
2024-06-25 13:56:21 -04:00
}
2024-08-24 22:41:47 -04:00
Source ::Registry ( RegistrySource ::Url ( url ) ) = > {
2024-07-12 18:49:28 -04:00
let Some ( ref sdist ) = self . sdist else {
return Ok ( None ) ;
} ;
2025-01-15 11:54:32 -05:00
let name = & self . id . name ;
let version = self
. id
. version
. as_ref ( )
. expect ( " version for registry source " ) ;
2024-07-12 18:49:28 -04:00
let file_url = sdist . url ( ) . ok_or_else ( | | LockErrorKind ::MissingUrl {
2025-01-15 11:54:32 -05:00
name : name . clone ( ) ,
version : version . clone ( ) ,
2024-07-12 18:49:28 -04:00
} ) ? ;
let filename = sdist
. filename ( )
. ok_or_else ( | | LockErrorKind ::MissingFilename {
2024-06-12 10:01:29 -07:00
id : self . id . clone ( ) ,
} ) ? ;
2025-06-23 14:52:07 -04:00
let ext = SourceDistExtension ::from_path ( filename . as_ref ( ) ) . map_err ( | err | {
LockErrorKind ::MissingExtension {
id : self . id . clone ( ) ,
err ,
}
} ) ? ;
2024-10-01 20:15:32 -04:00
let file = Box ::new ( uv_distribution_types ::File {
2024-07-12 18:49:28 -04:00
dist_info_metadata : false ,
2025-02-24 21:06:57 -10:00
filename : SmallString ::from ( filename ) ,
2025-02-24 07:11:44 -10:00
hashes : sdist . hash ( ) . map_or ( HashDigests ::empty ( ) , | hash | {
HashDigests ::from ( hash . 0. clone ( ) )
} ) ,
2024-07-12 18:49:28 -04:00
requires_python : None ,
size : sdist . size ( ) ,
2025-04-20 21:58:12 -04:00
upload_time_utc_ms : sdist . upload_time ( ) . map ( Timestamp ::as_millisecond ) ,
2024-07-19 22:11:26 -04:00
url : FileLocation ::AbsoluteUrl ( file_url . clone ( ) ) ,
2024-07-12 18:49:28 -04:00
yanked : None ,
} ) ;
2025-01-01 12:35:30 -05:00
let index = IndexUrl ::from ( VerbatimUrl ::from_url (
url . to_url ( ) . map_err ( LockErrorKind ::InvalidUrl ) ? ,
) ) ;
2024-07-12 18:49:28 -04:00
2024-08-24 22:41:47 -04:00
let reg_dist = RegistrySourceDist {
2025-01-15 11:54:32 -05:00
name : name . clone ( ) ,
version : version . clone ( ) ,
2024-08-24 22:41:47 -04:00
file ,
ext ,
index ,
wheels : vec ! [ ] ,
} ;
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Registry ( reg_dist )
2024-08-24 22:41:47 -04:00
}
Source ::Registry ( RegistrySource ::Path ( path ) ) = > {
let Some ( ref sdist ) = self . sdist else {
return Ok ( None ) ;
} ;
2025-01-15 11:54:32 -05:00
let name = & self . id . name ;
let version = self
. id
. version
. as_ref ( )
. expect ( " version for registry source " ) ;
2025-06-26 16:17:42 -04:00
let file_url = match sdist {
SourceDist ::Url { url : file_url , .. } = > {
FileLocation ::AbsoluteUrl ( file_url . clone ( ) )
2025-06-26 15:42:04 -04:00
}
2025-06-26 16:17:42 -04:00
SourceDist ::Path {
path : file_path , ..
} = > {
let file_path = workspace_root . join ( path ) . join ( file_path ) ;
let file_url =
DisplaySafeUrl ::from_file_path ( & file_path ) . map_err ( | ( ) | {
LockErrorKind ::PathToUrl {
path : file_path . into_boxed_path ( ) ,
}
} ) ? ;
FileLocation ::AbsoluteUrl ( UrlString ::from ( file_url ) )
}
SourceDist ::Metadata { .. } = > {
return Err ( LockErrorKind ::MissingPath {
name : name . clone ( ) ,
version : version . clone ( ) ,
}
. into ( ) ) ;
}
} ;
2024-08-24 22:41:47 -04:00
let filename = sdist
. filename ( )
. ok_or_else ( | | LockErrorKind ::MissingFilename {
id : self . id . clone ( ) ,
} ) ? ;
2025-06-23 14:52:07 -04:00
let ext = SourceDistExtension ::from_path ( filename . as_ref ( ) ) . map_err ( | err | {
LockErrorKind ::MissingExtension {
id : self . id . clone ( ) ,
err ,
}
} ) ? ;
2024-10-01 20:15:32 -04:00
let file = Box ::new ( uv_distribution_types ::File {
2024-08-24 22:41:47 -04:00
dist_info_metadata : false ,
2025-02-24 21:06:57 -10:00
filename : SmallString ::from ( filename ) ,
2025-02-24 07:11:44 -10:00
hashes : sdist . hash ( ) . map_or ( HashDigests ::empty ( ) , | hash | {
HashDigests ::from ( hash . 0. clone ( ) )
} ) ,
2024-08-24 22:41:47 -04:00
requires_python : None ,
size : sdist . size ( ) ,
2025-04-20 21:58:12 -04:00
upload_time_utc_ms : sdist . upload_time ( ) . map ( Timestamp ::as_millisecond ) ,
2025-06-26 16:17:42 -04:00
url : file_url ,
2024-08-24 22:41:47 -04:00
yanked : None ,
} ) ;
2025-06-26 16:17:42 -04:00
2024-09-16 19:33:32 -04:00
let index = IndexUrl ::from (
2024-08-24 22:41:47 -04:00
VerbatimUrl ::from_absolute_path ( workspace_root . join ( path ) )
. map_err ( LockErrorKind ::RegistryVerbatimUrl ) ? ,
) ;
2024-07-12 18:49:28 -04:00
let reg_dist = RegistrySourceDist {
2025-01-15 11:54:32 -05:00
name : name . clone ( ) ,
version : version . clone ( ) ,
2024-07-12 18:49:28 -04:00
file ,
2024-08-08 21:39:47 -04:00
ext ,
2024-07-12 18:49:28 -04:00
index ,
wheels : vec ! [ ] ,
} ;
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Registry ( reg_dist )
2024-07-12 18:49:28 -04:00
}
} ;
Ok ( Some ( sdist ) )
}
2024-08-19 18:05:01 -04:00
fn to_toml (
& self ,
requires_python : & RequiresPython ,
dist_count_by_name : & FxHashMap < PackageName , u64 > ,
2024-12-24 20:24:26 -05:00
) -> Result < Table , toml_edit ::ser ::Error > {
2024-06-27 12:51:57 -04:00
let mut table = Table ::new ( ) ;
2024-06-28 09:41:25 -04:00
self . id . to_toml ( None , & mut table ) ;
2024-06-27 12:51:57 -04:00
2024-08-14 09:20:12 -04:00
if ! self . fork_markers . is_empty ( ) {
2024-12-10 18:18:10 +01:00
let fork_markers = each_element_on_its_line_array (
2025-01-21 14:04:31 -05:00
simplified_universal_markers ( & self . fork_markers , requires_python ) . into_iter ( ) ,
2024-08-09 13:40:02 -04:00
) ;
2024-12-10 18:18:10 +01:00
if ! fork_markers . is_empty ( ) {
table . insert ( " resolution-markers " , value ( fork_markers ) ) ;
}
2024-07-30 13:11:18 +02:00
}
2024-06-27 12:51:57 -04:00
if ! self . dependencies . is_empty ( ) {
2024-08-19 18:05:01 -04:00
let deps = each_element_on_its_line_array ( self . dependencies . iter ( ) . map ( | dep | {
dep . to_toml ( requires_python , dist_count_by_name )
. into_inline_table ( )
} ) ) ;
2024-06-27 12:51:57 -04:00
table . insert ( " dependencies " , value ( deps ) ) ;
}
if ! self . optional_dependencies . is_empty ( ) {
let mut optional_deps = Table ::new ( ) ;
for ( extra , deps ) in & self . optional_dependencies {
2024-08-19 18:05:01 -04:00
let deps = each_element_on_its_line_array ( deps . iter ( ) . map ( | dep | {
dep . to_toml ( requires_python , dist_count_by_name )
. into_inline_table ( )
} ) ) ;
2024-08-14 20:00:15 -04:00
if ! deps . is_empty ( ) {
optional_deps . insert ( extra . as_ref ( ) , value ( deps ) ) ;
}
}
if ! optional_deps . is_empty ( ) {
table . insert ( " optional-dependencies " , Item ::Table ( optional_deps ) ) ;
2024-06-27 12:51:57 -04:00
}
}
2024-10-21 22:56:43 -04:00
if ! self . dependency_groups . is_empty ( ) {
let mut dependency_groups = Table ::new ( ) ;
for ( extra , deps ) in & self . dependency_groups {
2024-08-19 18:05:01 -04:00
let deps = each_element_on_its_line_array ( deps . iter ( ) . map ( | dep | {
dep . to_toml ( requires_python , dist_count_by_name )
. into_inline_table ( )
} ) ) ;
2024-08-14 20:00:15 -04:00
if ! deps . is_empty ( ) {
2024-10-21 22:56:43 -04:00
dependency_groups . insert ( extra . as_ref ( ) , value ( deps ) ) ;
2024-08-14 20:00:15 -04:00
}
}
2024-10-21 22:56:43 -04:00
if ! dependency_groups . is_empty ( ) {
2024-10-28 18:08:33 -04:00
table . insert ( " dev-dependencies " , Item ::Table ( dependency_groups ) ) ;
2024-06-27 12:51:57 -04:00
}
}
2024-07-08 09:25:05 -05:00
if let Some ( ref sdist ) = self . sdist {
table . insert ( " sdist " , value ( sdist . to_toml ( ) ? ) ) ;
}
2024-06-27 12:51:57 -04:00
if ! self . wheels . is_empty ( ) {
let wheels = each_element_on_its_line_array (
self . wheels
. iter ( )
. map ( Wheel ::to_toml )
2024-12-24 20:24:26 -05:00
. collect ::< Result < Vec < _ > , _ > > ( ) ?
2024-06-27 12:51:57 -04:00
. into_iter ( ) ,
) ;
table . insert ( " wheels " , value ( wheels ) ) ;
}
2024-08-14 20:00:15 -04:00
// Write the package metadata, if non-empty.
{
let mut metadata_table = Table ::new ( ) ;
2025-02-14 11:17:26 -05:00
if ! self . metadata . requires_dist . is_empty ( ) {
let requires_dist = self
. metadata
. requires_dist
2024-08-14 20:00:15 -04:00
. iter ( )
. map ( | requirement | {
serde ::Serialize ::serialize (
& requirement ,
toml_edit ::ser ::ValueSerializer ::new ( ) ,
)
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
let requires_dist = match requires_dist . as_slice ( ) {
[ ] = > Array ::new ( ) ,
[ requirement ] = > Array ::from_iter ( [ requirement ] ) ,
requires_dist = > each_element_on_its_line_array ( requires_dist . iter ( ) ) ,
} ;
metadata_table . insert ( " requires-dist " , value ( requires_dist ) ) ;
}
2024-10-21 22:56:43 -04:00
if ! self . metadata . dependency_groups . is_empty ( ) {
let mut dependency_groups = Table ::new ( ) ;
for ( extra , deps ) in & self . metadata . dependency_groups {
2024-08-14 20:00:15 -04:00
let deps = deps
. iter ( )
. map ( | requirement | {
serde ::Serialize ::serialize (
& requirement ,
toml_edit ::ser ::ValueSerializer ::new ( ) ,
)
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
let deps = match deps . as_slice ( ) {
[ ] = > Array ::new ( ) ,
[ requirement ] = > Array ::from_iter ( [ requirement ] ) ,
deps = > each_element_on_its_line_array ( deps . iter ( ) ) ,
} ;
2024-10-26 13:29:07 -04:00
dependency_groups . insert ( extra . as_ref ( ) , value ( deps ) ) ;
2024-08-14 20:00:15 -04:00
}
2024-10-21 22:56:43 -04:00
if ! dependency_groups . is_empty ( ) {
2024-10-28 18:08:33 -04:00
metadata_table . insert ( " requires-dev " , Item ::Table ( dependency_groups ) ) ;
2024-08-14 20:00:15 -04:00
}
}
2025-02-14 11:17:26 -05:00
if ! self . metadata . provides_extras . is_empty ( ) {
let provides_extras = self
. metadata
. provides_extras
. iter ( )
. map ( | extra | {
serde ::Serialize ::serialize ( & extra , toml_edit ::ser ::ValueSerializer ::new ( ) )
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
// This is just a list of names, so linebreaking it is excessive.
let provides_extras = Array ::from_iter ( provides_extras ) ;
metadata_table . insert ( " provides-extras " , value ( provides_extras ) ) ;
}
2024-08-14 20:00:15 -04:00
if ! metadata_table . is_empty ( ) {
table . insert ( " metadata " , Item ::Table ( metadata_table ) ) ;
}
}
2024-06-27 12:51:57 -04:00
Ok ( table )
}
2024-09-11 15:17:07 -04:00
fn find_best_wheel ( & self , tag_policy : TagPolicy < '_ > ) -> Option < usize > {
2024-12-06 07:40:49 -05:00
type WheelPriority < ' lock > = ( TagPriority , Option < & ' lock BuildTag > ) ;
let mut best : Option < ( WheelPriority , usize ) > = None ;
2024-05-15 15:07:28 -04:00
for ( i , wheel ) in self . wheels . iter ( ) . enumerate ( ) {
2024-12-06 07:40:49 -05:00
let TagCompatibility ::Compatible ( tag_priority ) =
2024-09-11 15:17:07 -04:00
wheel . filename . compatibility ( tag_policy . tags ( ) )
else {
2024-05-03 08:18:36 -04:00
continue ;
} ;
2025-01-14 09:49:17 -05:00
let build_tag = wheel . filename . build_tag ( ) ;
2024-12-06 07:40:49 -05:00
let wheel_priority = ( tag_priority , build_tag ) ;
2024-05-03 08:18:36 -04:00
match best {
None = > {
2024-12-06 07:40:49 -05:00
best = Some ( ( wheel_priority , i ) ) ;
2024-05-03 08:18:36 -04:00
}
Some ( ( best_priority , _ ) ) = > {
2024-12-06 07:40:49 -05:00
if wheel_priority > best_priority {
best = Some ( ( wheel_priority , i ) ) ;
2024-05-03 08:18:36 -04:00
}
}
}
}
2024-09-11 15:17:07 -04:00
let best = best . map ( | ( _ , i ) | i ) ;
match tag_policy {
TagPolicy ::Required ( _ ) = > best ,
TagPolicy ::Preferred ( _ ) = > best . or_else ( | | self . wheels . first ( ) . map ( | _ | 0 ) ) ,
}
2024-05-03 08:18:36 -04:00
}
2024-06-01 08:40:11 -04:00
2024-08-08 17:25:06 +02:00
/// Returns the [`PackageName`] of the package.
2024-06-01 08:40:11 -04:00
pub fn name ( & self ) -> & PackageName {
& self . id . name
}
2024-08-08 17:25:06 +02:00
/// Returns the [`Version`] of the package.
2025-01-15 11:54:32 -05:00
pub fn version ( & self ) -> Option < & Version > {
self . id . version . as_ref ( )
2024-07-24 11:33:09 -04:00
}
2024-08-13 19:42:04 -04:00
/// Return the fork markers for this package, if any.
2024-11-21 14:01:55 -05:00
pub fn fork_markers ( & self ) -> & [ UniversalMarker ] {
2024-08-14 09:20:12 -04:00
self . fork_markers . as_slice ( )
2024-07-31 17:18:58 +02:00
}
2024-11-07 15:10:46 -05:00
/// Returns the [`IndexUrl`] for the package, if it is a registry source.
pub fn index ( & self , root : & Path ) -> Result < Option < IndexUrl > , LockError > {
match & self . id . source {
Source ::Registry ( RegistrySource ::Url ( url ) ) = > {
2025-01-01 12:35:30 -05:00
let index = IndexUrl ::from ( VerbatimUrl ::from_url (
url . to_url ( ) . map_err ( LockErrorKind ::InvalidUrl ) ? ,
) ) ;
2024-11-07 15:10:46 -05:00
Ok ( Some ( index ) )
}
Source ::Registry ( RegistrySource ::Path ( path ) ) = > {
let index = IndexUrl ::from (
VerbatimUrl ::from_absolute_path ( root . join ( path ) )
. map_err ( LockErrorKind ::RegistryVerbatimUrl ) ? ,
) ;
Ok ( Some ( index ) )
}
_ = > Ok ( None ) ,
}
}
2024-08-08 17:25:06 +02:00
/// Returns all the hashes associated with this [`Package`].
2025-02-24 07:11:44 -10:00
fn hashes ( & self ) -> HashDigests {
let mut hashes = Vec ::with_capacity (
usize ::from ( self . sdist . as_ref ( ) . and_then ( | sdist | sdist . hash ( ) ) . is_some ( ) )
+ self
. wheels
. iter ( )
. map ( | wheel | usize ::from ( wheel . hash . is_some ( ) ) )
. sum ::< usize > ( ) ,
) ;
2024-07-12 18:49:28 -04:00
if let Some ( ref sdist ) = self . sdist {
2024-07-17 16:29:49 -04:00
if let Some ( hash ) = sdist . hash ( ) {
hashes . push ( hash . 0. clone ( ) ) ;
}
2024-07-12 18:49:28 -04:00
}
for wheel in & self . wheels {
hashes . extend ( wheel . hash . as_ref ( ) . map ( | h | h . 0. clone ( ) ) ) ;
}
2025-02-24 07:11:44 -10:00
HashDigests ::from ( hashes )
2024-07-12 18:49:28 -04:00
}
2024-08-08 17:25:06 +02:00
/// Returns the [`ResolvedRepositoryReference`] for the package, if it is a Git source.
2025-01-01 12:35:30 -05:00
pub fn as_git_ref ( & self ) -> Result < Option < ResolvedRepositoryReference > , LockError > {
2024-06-11 13:58:03 +02:00
match & self . id . source {
2025-01-01 12:35:30 -05:00
Source ::Git ( url , git ) = > Ok ( Some ( ResolvedRepositoryReference {
2024-06-01 08:40:11 -04:00
reference : RepositoryReference {
2025-01-01 12:35:30 -05:00
url : RepositoryUrl ::new ( & url . to_url ( ) . map_err ( LockErrorKind ::InvalidUrl ) ? ) ,
2024-06-01 08:40:11 -04:00
reference : GitReference ::from ( git . kind . clone ( ) ) ,
} ,
sha : git . precise ,
2025-01-01 12:35:30 -05:00
} ) ) ,
_ = > Ok ( None ) ,
2024-06-01 08:40:11 -04:00
}
}
2025-01-15 11:54:32 -05:00
/// Returns `true` if the package is a dynamic source tree.
fn is_dynamic ( & self ) -> bool {
self . id . version . is_none ( )
}
2025-02-12 18:29:09 +01:00
/// Returns the extras the package provides, if any.
2025-02-14 11:17:26 -05:00
pub fn provides_extras ( & self ) -> & [ ExtraName ] {
& self . metadata . provides_extras
2025-02-12 18:29:09 +01:00
}
2025-02-14 10:54:28 -05:00
/// Returns the dependency groups the package provides, if any.
pub fn dependency_groups ( & self ) -> & BTreeMap < GroupName , BTreeSet < Requirement > > {
& self . metadata . dependency_groups
}
2024-04-29 14:03:17 -04:00
}
2025-01-20 16:36:18 -05:00
/// Attempts to construct a `VerbatimUrl` from the given normalized `Path`.
2024-12-12 13:49:05 -05:00
fn verbatim_url ( path : & Path , id : & PackageId ) -> Result < VerbatimUrl , LockError > {
2025-01-20 16:36:18 -05:00
let url =
VerbatimUrl ::from_normalized_path ( path ) . map_err ( | err | LockErrorKind ::VerbatimUrl {
id : id . clone ( ) ,
err ,
} ) ? ;
2024-07-02 14:03:20 -04:00
Ok ( url )
}
2024-12-12 13:49:05 -05:00
/// Attempts to construct an absolute path from the given `Path`.
fn absolute_path ( workspace_root : & Path , path : & Path ) -> Result < PathBuf , LockError > {
let path = uv_fs ::normalize_absolute_path ( & workspace_root . join ( path ) )
. map_err ( LockErrorKind ::AbsolutePath ) ? ;
Ok ( path )
}
2024-06-24 14:29:01 -04:00
#[ derive(Clone, Debug, serde::Deserialize) ]
#[ serde(rename_all = " kebab-case " ) ]
2024-08-08 17:25:06 +02:00
struct PackageWire {
2024-06-24 14:29:01 -04:00
#[ serde(flatten) ]
2024-08-08 17:25:06 +02:00
id : PackageId ,
2024-06-24 14:29:01 -04:00
#[ serde(default) ]
2024-08-14 20:00:15 -04:00
metadata : PackageMetadata ,
#[ serde(default) ]
2024-06-24 14:29:01 -04:00
sdist : Option < SourceDist > ,
2024-07-24 15:30:58 +02:00
#[ serde(default) ]
2024-06-24 14:29:01 -04:00
wheels : Vec < Wheel > ,
2024-08-20 09:14:55 -04:00
#[ serde(default, rename = " resolution-markers " ) ]
2024-08-19 18:05:01 -04:00
fork_markers : Vec < SimplifiedMarkerTree > ,
2024-07-24 15:30:58 +02:00
#[ serde(default) ]
2024-06-24 14:29:01 -04:00
dependencies : Vec < DependencyWire > ,
2024-07-24 15:30:58 +02:00
#[ serde(default) ]
2024-06-24 14:29:01 -04:00
optional_dependencies : BTreeMap < ExtraName , Vec < DependencyWire > > ,
2024-10-28 18:08:33 -04:00
#[ serde(default, rename = " dev-dependencies " , alias = " dependency-groups " ) ]
2024-10-21 22:56:43 -04:00
dependency_groups : BTreeMap < GroupName , Vec < DependencyWire > > ,
2024-06-24 14:29:01 -04:00
}
2024-08-14 20:00:15 -04:00
#[ derive(Clone, Default, Debug, Eq, PartialEq, serde::Deserialize) ]
#[ serde(rename_all = " kebab-case " ) ]
struct PackageMetadata {
2025-02-11 14:38:38 -05:00
#[ serde(default) ]
2025-02-14 11:17:26 -05:00
requires_dist : BTreeSet < Requirement > ,
2024-08-14 20:00:15 -04:00
#[ serde(default) ]
2025-03-27 17:09:26 -04:00
provides_extras : Box < [ ExtraName ] > ,
2024-10-28 18:08:33 -04:00
#[ serde(default, rename = " requires-dev " , alias = " dependency-groups " ) ]
2024-10-21 22:56:43 -04:00
dependency_groups : BTreeMap < GroupName , BTreeSet < Requirement > > ,
2024-08-14 20:00:15 -04:00
}
2025-05-26 14:17:19 -04:00
impl PackageMetadata {
fn unwire ( self , requires_python : & RequiresPython ) -> PackageMetadata {
// We need to complexify these markers so things like
// `requires_python < '0'` get normalized to False
let unwire_requirements = | requirements : BTreeSet < Requirement > | -> BTreeSet < Requirement > {
requirements
. into_iter ( )
. map ( | mut requirement | {
let complexified_marker =
requires_python . complexify_markers ( requirement . marker ) ;
requirement . marker = complexified_marker ;
requirement
} )
. collect ( )
} ;
PackageMetadata {
requires_dist : unwire_requirements ( self . requires_dist ) ,
provides_extras : self . provides_extras ,
dependency_groups : self
. dependency_groups
. into_iter ( )
. map ( | ( group , requirements ) | ( group , unwire_requirements ( requirements ) ) )
. collect ( ) ,
}
}
}
2024-08-08 17:25:06 +02:00
impl PackageWire {
2024-06-24 15:03:45 -04:00
fn unwire (
self ,
2024-08-19 18:05:01 -04:00
requires_python : & RequiresPython ,
2024-08-08 17:25:06 +02:00
unambiguous_package_ids : & FxHashMap < PackageName , PackageId > ,
) -> Result < Package , LockError > {
2025-03-17 23:33:32 +01:00
// Consistency check
if let Some ( version ) = & self . id . version {
for wheel in & self . wheels {
2025-03-18 08:12:40 -07:00
if * version ! = wheel . filename . version
& & * version ! = wheel . filename . version . clone ( ) . without_local ( )
{
2025-03-17 23:33:32 +01:00
return Err ( LockError ::from ( LockErrorKind ::InconsistentVersions {
name : self . id . name ,
2025-03-17 17:34:51 -05:00
version : version . clone ( ) ,
wheel : wheel . clone ( ) ,
2025-03-17 23:33:32 +01:00
} ) ) ;
}
}
// We can't check the source dist version since it does not need to contain the version
// in the filename.
}
2024-06-24 14:29:01 -04:00
let unwire_deps = | deps : Vec < DependencyWire > | -> Result < Vec < Dependency > , LockError > {
2024-06-24 15:03:45 -04:00
deps . into_iter ( )
2024-08-19 18:05:01 -04:00
. map ( | dep | dep . unwire ( requires_python , unambiguous_package_ids ) )
2024-06-24 15:03:45 -04:00
. collect ( )
2024-06-24 14:29:01 -04:00
} ;
2025-05-26 14:17:19 -04:00
2024-08-08 17:25:06 +02:00
Ok ( Package {
2024-06-24 14:29:01 -04:00
id : self . id ,
2025-05-26 14:17:19 -04:00
metadata : self . metadata . unwire ( requires_python ) ,
2024-06-24 14:29:01 -04:00
sdist : self . sdist ,
wheels : self . wheels ,
2024-08-19 18:05:01 -04:00
fork_markers : self
. fork_markers
. into_iter ( )
. map ( | simplified_marker | simplified_marker . into_marker ( requires_python ) )
2025-01-21 14:04:31 -05:00
. map ( UniversalMarker ::from_combined )
2024-08-19 18:05:01 -04:00
. collect ( ) ,
2024-06-24 14:29:01 -04:00
dependencies : unwire_deps ( self . dependencies ) ? ,
optional_dependencies : self
. optional_dependencies
. into_iter ( )
. map ( | ( extra , deps ) | Ok ( ( extra , unwire_deps ( deps ) ? ) ) )
. collect ::< Result < _ , LockError > > ( ) ? ,
2024-10-21 22:56:43 -04:00
dependency_groups : self
. dependency_groups
2024-06-24 14:29:01 -04:00
. into_iter ( )
. map ( | ( group , deps ) | Ok ( ( group , unwire_deps ( deps ) ? ) ) )
. collect ::< Result < _ , LockError > > ( ) ? ,
} )
}
}
2024-08-08 17:25:06 +02:00
/// Inside the lockfile, we match a dependency entry to a package entry through a key made up
2024-06-26 00:19:00 +02:00
/// of the name, the version and the source url.
2024-05-30 15:08:29 -04:00
#[ derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize) ]
2025-04-28 11:01:17 -04:00
#[ serde(rename_all = " kebab-case " ) ]
2024-08-08 17:25:06 +02:00
pub ( crate ) struct PackageId {
2024-05-03 08:18:36 -04:00
pub ( crate ) name : PackageName ,
2025-01-15 11:54:32 -05:00
pub ( crate ) version : Option < Version > ,
2024-06-03 15:00:35 -04:00
source : Source ,
2024-04-29 14:03:17 -04:00
}
2024-08-08 17:25:06 +02:00
impl PackageId {
2024-08-23 22:19:10 -04:00
fn from_annotated_dist (
annotated_dist : & AnnotatedDist ,
root : & Path ,
) -> Result < PackageId , LockError > {
2025-01-15 11:54:32 -05:00
// Identify the source of the package.
2024-08-23 22:19:10 -04:00
let source = Source ::from_resolved_dist ( & annotated_dist . dist , root ) ? ;
2025-01-15 11:54:32 -05:00
// Omit versions for dynamic source trees.
let version = if source . is_source_tree ( )
& & annotated_dist
. metadata
. as_ref ( )
. is_some_and ( | metadata | metadata . dynamic )
{
None
} else {
Some ( annotated_dist . version . clone ( ) )
} ;
let name = annotated_dist . name . clone ( ) ;
2024-08-23 22:19:10 -04:00
Ok ( Self {
2024-04-29 14:03:17 -04:00
name ,
version ,
source ,
2024-08-23 22:19:10 -04:00
} )
2024-04-29 14:03:17 -04:00
}
2024-06-28 09:41:25 -04:00
2024-08-08 17:25:06 +02:00
/// Writes this package ID inline into the table given.
2024-06-28 09:41:25 -04:00
///
/// When a map is given, and if the package name in this ID is unambiguous
/// (i.e., it has a count of 1 in the map), then the `version` and `source`
/// fields are omitted. In all other cases, including when a map is not
/// given, the `version` and `source` fields are written.
fn to_toml ( & self , dist_count_by_name : Option < & FxHashMap < PackageName , u64 > > , table : & mut Table ) {
let count = dist_count_by_name . and_then ( | map | map . get ( & self . name ) . copied ( ) ) ;
table . insert ( " name " , value ( self . name . to_string ( ) ) ) ;
if count . map ( | count | count > 1 ) . unwrap_or ( true ) {
2025-01-15 11:54:32 -05:00
if let Some ( version ) = & self . version {
table . insert ( " version " , value ( version . to_string ( ) ) ) ;
}
2024-06-28 09:41:25 -04:00
self . source . to_toml ( table ) ;
}
}
2024-04-29 14:03:17 -04:00
}
2024-12-25 14:18:01 -05:00
impl Display for PackageId {
2024-04-29 14:03:17 -04:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
2025-01-15 11:54:32 -05:00
if let Some ( version ) = & self . version {
write! ( f , " {}=={} @ {} " , self . name , version , self . source )
} else {
write! ( f , " {} @ {} " , self . name , self . source )
}
2024-04-29 14:03:17 -04:00
}
}
2024-06-24 14:29:01 -04:00
#[ derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize) ]
2025-04-28 11:01:17 -04:00
#[ serde(rename_all = " kebab-case " ) ]
2024-08-08 17:25:06 +02:00
struct PackageIdForDependency {
2024-06-24 14:29:01 -04:00
name : PackageName ,
2024-06-24 15:03:45 -04:00
version : Option < Version > ,
source : Option < Source > ,
2024-06-24 14:29:01 -04:00
}
2024-08-08 17:25:06 +02:00
impl PackageIdForDependency {
2024-06-24 15:03:45 -04:00
fn unwire (
self ,
2024-08-08 17:25:06 +02:00
unambiguous_package_ids : & FxHashMap < PackageName , PackageId > ,
) -> Result < PackageId , LockError > {
let unambiguous_package_id = unambiguous_package_ids . get ( & self . name ) ;
2024-06-24 15:03:45 -04:00
let source = self . source . map ( Ok ::< _ , LockError > ) . unwrap_or_else ( | | {
2024-08-08 17:25:06 +02:00
let Some ( package_id ) = unambiguous_package_id else {
2024-06-24 15:03:45 -04:00
return Err ( LockErrorKind ::MissingDependencySource {
name : self . name . clone ( ) ,
}
. into ( ) ) ;
} ;
2024-08-08 17:25:06 +02:00
Ok ( package_id . source . clone ( ) )
2024-06-24 15:03:45 -04:00
} ) ? ;
2025-02-10 15:21:02 -05:00
let version = if let Some ( version ) = self . version {
Some ( version )
} else {
if let Some ( package_id ) = unambiguous_package_id {
package_id . version . clone ( )
} else {
// If the package is a source tree, assume that the missing `self.version` field is
// indicative of a dynamic version.
if source . is_source_tree ( ) {
None
} else {
return Err ( LockErrorKind ::MissingDependencyVersion {
name : self . name . clone ( ) ,
}
. into ( ) ) ;
}
}
} ;
2024-08-08 17:25:06 +02:00
Ok ( PackageId {
2024-06-24 14:29:01 -04:00
name : self . name ,
2024-06-24 15:03:45 -04:00
version ,
source ,
2024-06-24 14:29:01 -04:00
} )
}
}
2024-08-08 17:25:06 +02:00
impl From < PackageId > for PackageIdForDependency {
fn from ( id : PackageId ) -> PackageIdForDependency {
PackageIdForDependency {
2024-06-24 14:29:01 -04:00
name : id . name ,
2025-01-15 11:54:32 -05:00
version : id . version ,
2024-06-24 15:03:45 -04:00
source : Some ( id . source ) ,
2024-06-24 14:29:01 -04:00
}
}
}
2024-08-08 17:25:06 +02:00
/// A unique identifier to differentiate between different sources for the same version of a
2024-06-26 00:19:00 +02:00
/// package.
///
2024-06-11 13:58:03 +02:00
/// NOTE: Care should be taken when adding variants to this enum. Namely, new
/// variants should be added without changing the relative ordering of other
2024-07-25 10:22:36 -04:00
/// variants. Otherwise, this could cause the lockfile to have a different
2024-08-08 17:25:06 +02:00
/// canonical ordering of sources.
2024-06-28 11:23:06 -04:00
#[ derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize) ]
#[ serde(try_from = " SourceWire " ) ]
2024-06-11 13:58:03 +02:00
enum Source {
2024-08-24 22:41:47 -04:00
/// A registry or `--find-links` index.
Registry ( RegistrySource ) ,
/// A Git repository.
2024-08-06 08:58:09 -04:00
Git ( UrlString , GitSource ) ,
2024-08-24 22:41:47 -04:00
/// A direct HTTP(S) URL.
2024-08-06 08:58:09 -04:00
Direct ( UrlString , DirectSource ) ,
2024-08-24 22:41:47 -04:00
/// A path to a local source or built archive.
2025-03-25 17:56:06 -04:00
Path ( Box < Path > ) ,
2024-08-24 22:41:47 -04:00
/// A path to a local directory.
2025-03-25 17:56:06 -04:00
Directory ( Box < Path > ) ,
2024-08-24 22:41:47 -04:00
/// A path to a local directory that should be installed as editable.
2025-03-25 17:56:06 -04:00
Editable ( Box < Path > ) ,
2024-08-27 21:19:05 -04:00
/// A path to a local directory that should not be built or installed.
2025-03-25 17:56:06 -04:00
Virtual ( Box < Path > ) ,
2024-06-11 13:58:03 +02:00
}
2024-04-29 14:03:17 -04:00
impl Source {
2024-08-23 22:19:10 -04:00
fn from_resolved_dist ( resolved_dist : & ResolvedDist , root : & Path ) -> Result < Source , LockError > {
2024-04-29 14:03:17 -04:00
match * resolved_dist {
2024-08-07 15:22:56 +02:00
// We pass empty installed packages for locking.
2024-11-13 19:06:16 -05:00
ResolvedDist ::Installed { .. } = > unreachable! ( ) ,
ResolvedDist ::Installable { ref dist , .. } = > Source ::from_dist ( dist , root ) ,
2024-04-29 14:03:17 -04:00
}
}
2024-08-23 22:19:10 -04:00
fn from_dist ( dist : & Dist , root : & Path ) -> Result < Source , LockError > {
2024-04-29 14:03:17 -04:00
match * dist {
2024-08-23 22:19:10 -04:00
Dist ::Built ( ref built_dist ) = > Source ::from_built_dist ( built_dist , root ) ,
Dist ::Source ( ref source_dist ) = > Source ::from_source_dist ( source_dist , root ) ,
2024-04-29 14:03:17 -04:00
}
}
2024-08-23 22:19:10 -04:00
fn from_built_dist ( built_dist : & BuiltDist , root : & Path ) -> Result < Source , LockError > {
2024-04-29 14:03:17 -04:00
match * built_dist {
2024-08-24 22:41:47 -04:00
BuiltDist ::Registry ( ref reg_dist ) = > Source ::from_registry_built_dist ( reg_dist , root ) ,
2024-08-23 22:19:10 -04:00
BuiltDist ::DirectUrl ( ref direct_dist ) = > {
Ok ( Source ::from_direct_built_dist ( direct_dist ) )
}
BuiltDist ::Path ( ref path_dist ) = > Source ::from_path_built_dist ( path_dist , root ) ,
2024-04-29 14:03:17 -04:00
}
}
2024-08-23 22:19:10 -04:00
fn from_source_dist (
2024-10-01 20:15:32 -04:00
source_dist : & uv_distribution_types ::SourceDist ,
2024-08-23 22:19:10 -04:00
root : & Path ,
) -> Result < Source , LockError > {
2024-04-29 14:03:17 -04:00
match * source_dist {
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Registry ( ref reg_dist ) = > {
2024-08-24 22:41:47 -04:00
Source ::from_registry_source_dist ( reg_dist , root )
2024-04-29 14:03:17 -04:00
}
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::DirectUrl ( ref direct_dist ) = > {
2024-08-23 22:19:10 -04:00
Ok ( Source ::from_direct_source_dist ( direct_dist ) )
}
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Git ( ref git_dist ) = > {
2024-08-23 22:19:10 -04:00
Ok ( Source ::from_git_dist ( git_dist ) )
2024-04-29 14:03:17 -04:00
}
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Path ( ref path_dist ) = > {
2024-08-23 22:19:10 -04:00
Source ::from_path_source_dist ( path_dist , root )
2024-04-29 14:03:17 -04:00
}
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Directory ( ref directory ) = > {
2024-08-23 22:19:10 -04:00
Source ::from_directory_source_dist ( directory , root )
2024-05-13 10:03:14 -04:00
}
2024-04-29 14:03:17 -04:00
}
}
2024-08-24 22:41:47 -04:00
fn from_registry_built_dist (
reg_dist : & RegistryBuiltDist ,
root : & Path ,
) -> Result < Source , LockError > {
Source ::from_index_url ( & reg_dist . best_wheel ( ) . index , root )
2024-05-15 15:07:28 -04:00
}
2024-08-24 22:41:47 -04:00
fn from_registry_source_dist (
reg_dist : & RegistrySourceDist ,
root : & Path ,
) -> Result < Source , LockError > {
Source ::from_index_url ( & reg_dist . index , root )
2024-04-29 14:03:17 -04:00
}
fn from_direct_built_dist ( direct_dist : & DirectUrlBuiltDist ) -> Source {
2024-06-11 13:58:03 +02:00
Source ::Direct (
2024-09-04 22:05:39 -04:00
normalize_url ( direct_dist . url . to_url ( ) ) ,
2024-06-11 13:58:03 +02:00
DirectSource { subdirectory : None } ,
)
2024-04-29 14:03:17 -04:00
}
fn from_direct_source_dist ( direct_dist : & DirectUrlSourceDist ) -> Source {
2024-06-11 13:58:03 +02:00
Source ::Direct (
2024-09-04 22:05:39 -04:00
normalize_url ( direct_dist . url . to_url ( ) ) ,
2024-06-11 13:58:03 +02:00
DirectSource {
2024-12-20 19:28:36 -05:00
subdirectory : direct_dist . subdirectory . clone ( ) ,
2024-06-11 13:58:03 +02:00
} ,
)
2024-04-29 14:03:17 -04:00
}
2024-08-23 22:19:10 -04:00
fn from_path_built_dist ( path_dist : & PathBuiltDist , root : & Path ) -> Result < Source , LockError > {
let path = relative_to ( & path_dist . install_path , root )
2024-08-24 22:41:47 -04:00
. or_else ( | _ | std ::path ::absolute ( & path_dist . install_path ) )
2024-08-23 22:19:10 -04:00
. map_err ( LockErrorKind ::DistributionRelativePath ) ? ;
2025-03-25 17:56:06 -04:00
Ok ( Source ::Path ( path . into_boxed_path ( ) ) )
2024-04-29 14:03:17 -04:00
}
2024-08-23 22:19:10 -04:00
fn from_path_source_dist ( path_dist : & PathSourceDist , root : & Path ) -> Result < Source , LockError > {
let path = relative_to ( & path_dist . install_path , root )
2024-08-24 22:41:47 -04:00
. or_else ( | _ | std ::path ::absolute ( & path_dist . install_path ) )
2024-08-23 22:19:10 -04:00
. map_err ( LockErrorKind ::DistributionRelativePath ) ? ;
2025-03-25 17:56:06 -04:00
Ok ( Source ::Path ( path . into_boxed_path ( ) ) )
2024-04-29 14:03:17 -04:00
}
2024-08-23 22:19:10 -04:00
fn from_directory_source_dist (
directory_dist : & DirectorySourceDist ,
root : & Path ,
) -> Result < Source , LockError > {
let path = relative_to ( & directory_dist . install_path , root )
2024-08-24 22:41:47 -04:00
. or_else ( | _ | std ::path ::absolute ( & directory_dist . install_path ) )
2024-08-23 22:19:10 -04:00
. map_err ( LockErrorKind ::DistributionRelativePath ) ? ;
2024-06-11 13:58:03 +02:00
if directory_dist . editable {
2025-03-25 17:56:06 -04:00
Ok ( Source ::Editable ( path . into_boxed_path ( ) ) )
2024-08-27 21:19:05 -04:00
} else if directory_dist . r#virtual {
2025-03-25 17:56:06 -04:00
Ok ( Source ::Virtual ( path . into_boxed_path ( ) ) )
2024-06-11 13:58:03 +02:00
} else {
2025-03-25 17:56:06 -04:00
Ok ( Source ::Directory ( path . into_boxed_path ( ) ) )
2024-05-13 10:03:14 -04:00
}
}
2024-08-24 22:41:47 -04:00
fn from_index_url ( index_url : & IndexUrl , root : & Path ) -> Result < Source , LockError > {
match index_url {
IndexUrl ::Pypi ( _ ) | IndexUrl ::Url ( _ ) = > {
// Remove any sensitive credentials from the index URL.
2025-05-27 00:05:30 +02:00
let redacted = index_url . without_credentials ( ) ;
2024-08-24 22:41:47 -04:00
let source = RegistrySource ::Url ( UrlString ::from ( redacted . as_ref ( ) ) ) ;
Ok ( Source ::Registry ( source ) )
}
IndexUrl ::Path ( url ) = > {
2025-06-26 15:42:04 -04:00
let path = url
. to_file_path ( )
. map_err ( | ( ) | LockErrorKind ::UrlToPath { url : url . to_url ( ) } ) ? ;
2024-08-24 22:41:47 -04:00
let path = relative_to ( & path , root )
. or_else ( | _ | std ::path ::absolute ( & path ) )
. map_err ( LockErrorKind ::IndexRelativePath ) ? ;
2025-03-25 17:56:06 -04:00
let source = RegistrySource ::Path ( path . into_boxed_path ( ) ) ;
2024-08-24 22:41:47 -04:00
Ok ( Source ::Registry ( source ) )
}
}
2024-04-29 14:03:17 -04:00
}
fn from_git_dist ( git_dist : & GitSourceDist ) -> Source {
2024-06-11 13:58:03 +02:00
Source ::Git (
2024-08-06 08:58:09 -04:00
UrlString ::from ( locked_git_url ( git_dist ) ) ,
2024-06-11 13:58:03 +02:00
GitSource {
2024-05-17 17:23:40 -04:00
kind : GitSourceKind ::from ( git_dist . git . reference ( ) . clone ( ) ) ,
2024-08-07 10:56:15 -04:00
precise : git_dist . git . precise ( ) . unwrap_or_else ( | | {
panic! ( " Git distribution is missing a precise hash: {git_dist} " )
} ) ,
2024-12-20 19:28:36 -05:00
subdirectory : git_dist . subdirectory . clone ( ) ,
2024-06-11 13:58:03 +02:00
} ,
)
2024-04-29 14:03:17 -04:00
}
2024-06-28 09:41:25 -04:00
2024-08-15 08:51:41 -04:00
/// Returns `true` if the source should be considered immutable.
///
/// We assume that registry sources are immutable. In other words, we expect that once a
/// package-version is published to a registry, its metadata will not change.
2024-08-15 09:26:47 -04:00
///
/// We also assume that Git sources are immutable, since a Git source encodes a specific commit.
2024-08-15 08:51:41 -04:00
fn is_immutable ( & self ) -> bool {
2024-08-15 09:26:47 -04:00
matches! ( self , Self ::Registry ( .. ) | Self ::Git ( _ , _ ) )
2024-08-15 08:51:41 -04:00
}
2024-09-11 15:53:08 -04:00
/// Returns `true` if the source is that of a wheel.
fn is_wheel ( & self ) -> bool {
match & self {
Source ::Path ( path ) = > {
matches! (
DistExtension ::from_path ( path ) . ok ( ) ,
Some ( DistExtension ::Wheel )
)
}
Source ::Direct ( url , _ ) = > {
matches! (
DistExtension ::from_path ( url . as_ref ( ) ) . ok ( ) ,
Some ( DistExtension ::Wheel )
)
}
Source ::Directory ( .. ) = > false ,
Source ::Editable ( .. ) = > false ,
Source ::Virtual ( .. ) = > false ,
Source ::Git ( .. ) = > false ,
Source ::Registry ( .. ) = > false ,
}
}
2025-01-15 11:54:32 -05:00
/// Returns `true` if the source is that of a source tree.
2025-01-16 23:27:46 -05:00
fn is_source_tree ( & self ) -> bool {
2025-01-15 11:54:32 -05:00
match self {
Source ::Directory ( .. ) | Source ::Editable ( .. ) | Source ::Virtual ( .. ) = > true ,
Source ::Path ( .. ) | Source ::Git ( .. ) | Source ::Registry ( .. ) | Source ::Direct ( .. ) = > false ,
}
}
2025-01-16 23:27:46 -05:00
/// Returns the path to the source tree, if the source is a source tree.
fn as_source_tree ( & self ) -> Option < & Path > {
match self {
Source ::Directory ( path ) | Source ::Editable ( path ) | Source ::Virtual ( path ) = > Some ( path ) ,
Source ::Path ( .. ) | Source ::Git ( .. ) | Source ::Registry ( .. ) | Source ::Direct ( .. ) = > None ,
}
}
2024-06-28 09:41:25 -04:00
fn to_toml ( & self , table : & mut Table ) {
2024-06-28 11:23:06 -04:00
let mut source_table = InlineTable ::new ( ) ;
match * self {
2024-08-24 22:41:47 -04:00
Source ::Registry ( ref source ) = > match source {
RegistrySource ::Url ( url ) = > {
source_table . insert ( " registry " , Value ::from ( url . as_ref ( ) ) ) ;
}
RegistrySource ::Path ( path ) = > {
source_table . insert (
" registry " ,
Value ::from ( PortablePath ::from ( path ) . to_string ( ) ) ,
) ;
}
} ,
2024-06-28 11:23:06 -04:00
Source ::Git ( ref url , _ ) = > {
2024-08-06 08:58:09 -04:00
source_table . insert ( " git " , Value ::from ( url . as_ref ( ) ) ) ;
2024-06-11 13:58:03 +02:00
}
2024-06-28 11:23:06 -04:00
Source ::Direct ( ref url , DirectSource { ref subdirectory } ) = > {
2024-08-06 08:58:09 -04:00
source_table . insert ( " url " , Value ::from ( url . as_ref ( ) ) ) ;
2024-06-28 11:23:06 -04:00
if let Some ( ref subdirectory ) = * subdirectory {
2024-12-20 19:28:36 -05:00
source_table . insert (
" subdirectory " ,
Value ::from ( PortablePath ::from ( subdirectory ) . to_string ( ) ) ,
) ;
2024-06-28 11:23:06 -04:00
}
}
Source ::Path ( ref path ) = > {
2024-07-31 12:00:37 -04:00
source_table . insert ( " path " , Value ::from ( PortablePath ::from ( path ) . to_string ( ) ) ) ;
2024-06-28 11:23:06 -04:00
}
Source ::Directory ( ref path ) = > {
source_table . insert (
" directory " ,
2024-07-31 12:00:37 -04:00
Value ::from ( PortablePath ::from ( path ) . to_string ( ) ) ,
2024-06-28 11:23:06 -04:00
) ;
}
Source ::Editable ( ref path ) = > {
source_table . insert (
" editable " ,
2024-07-31 12:00:37 -04:00
Value ::from ( PortablePath ::from ( path ) . to_string ( ) ) ,
2024-06-28 11:23:06 -04:00
) ;
2024-06-11 13:58:03 +02:00
}
2024-08-27 21:19:05 -04:00
Source ::Virtual ( ref path ) = > {
source_table . insert ( " virtual " , Value ::from ( PortablePath ::from ( path ) . to_string ( ) ) ) ;
}
2024-04-29 14:03:17 -04:00
}
2024-06-28 11:23:06 -04:00
table . insert ( " source " , value ( source_table ) ) ;
2024-04-29 14:03:17 -04:00
}
}
2024-12-25 14:18:01 -05:00
impl Display for Source {
2024-04-29 14:03:17 -04:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
2024-06-11 13:58:03 +02:00
match self {
2024-08-24 22:41:47 -04:00
Source ::Registry ( RegistrySource ::Url ( url ) )
| Source ::Git ( url , _ )
| Source ::Direct ( url , _ ) = > {
2024-06-11 13:58:03 +02:00
write! ( f , " {}+{} " , self . name ( ) , url )
}
2024-08-24 22:41:47 -04:00
Source ::Registry ( RegistrySource ::Path ( path ) )
| Source ::Path ( path )
| Source ::Directory ( path )
2024-08-27 21:19:05 -04:00
| Source ::Editable ( path )
| Source ::Virtual ( path ) = > {
2024-07-31 12:00:37 -04:00
write! ( f , " {}+{} " , self . name ( ) , PortablePath ::from ( path ) )
2024-06-11 13:58:03 +02:00
}
}
2024-04-29 14:03:17 -04:00
}
}
2024-06-11 13:58:03 +02:00
impl Source {
2024-04-29 14:03:17 -04:00
fn name ( & self ) -> & str {
match * self {
2024-06-11 13:58:03 +02:00
Self ::Registry ( .. ) = > " registry " ,
Self ::Git ( .. ) = > " git " ,
Self ::Direct ( .. ) = > " direct " ,
Self ::Path ( .. ) = > " path " ,
Self ::Directory ( .. ) = > " directory " ,
Self ::Editable ( .. ) = > " editable " ,
2024-08-27 21:19:05 -04:00
Self ::Virtual ( .. ) = > " virtual " ,
2024-04-29 14:03:17 -04:00
}
}
2024-05-10 10:32:30 -04:00
2024-07-17 16:29:49 -04:00
/// Returns `Some(true)` to indicate that the source kind _must_ include a
/// hash.
///
/// Returns `Some(false)` to indicate that the source kind _must not_
/// include a hash.
2024-05-10 10:32:30 -04:00
///
2024-07-17 16:29:49 -04:00
/// Returns `None` to indicate that the source kind _may_ include a hash.
fn requires_hash ( & self ) -> Option < bool > {
2024-05-10 10:32:30 -04:00
match * self {
2024-07-17 16:29:49 -04:00
Self ::Registry ( .. ) = > None ,
Self ::Direct ( .. ) | Self ::Path ( .. ) = > Some ( true ) ,
2024-08-27 21:19:05 -04:00
Self ::Git ( .. ) | Self ::Directory ( .. ) | Self ::Editable ( .. ) | Self ::Virtual ( .. ) = > {
Some ( false )
}
2024-05-10 10:32:30 -04:00
}
}
2024-04-29 14:03:17 -04:00
}
2024-06-28 11:23:06 -04:00
#[ derive(Clone, Debug, serde::Deserialize) ]
2025-04-28 11:01:17 -04:00
#[ serde(untagged, rename_all = " kebab-case " ) ]
2024-06-28 11:23:06 -04:00
enum SourceWire {
Registry {
2024-12-20 19:28:36 -05:00
registry : RegistrySourceWire ,
2024-06-28 11:23:06 -04:00
} ,
Git {
git : String ,
} ,
Direct {
2024-08-06 08:58:09 -04:00
url : UrlString ,
2024-12-20 19:28:36 -05:00
subdirectory : Option < PortablePathBuf > ,
2024-06-28 11:23:06 -04:00
} ,
Path {
2024-07-31 12:00:37 -04:00
path : PortablePathBuf ,
2024-06-28 11:23:06 -04:00
} ,
Directory {
2024-07-31 12:00:37 -04:00
directory : PortablePathBuf ,
2024-06-28 11:23:06 -04:00
} ,
Editable {
2024-07-31 12:00:37 -04:00
editable : PortablePathBuf ,
2024-06-28 11:23:06 -04:00
} ,
2024-08-27 21:19:05 -04:00
Virtual {
r#virtual : PortablePathBuf ,
} ,
2024-05-17 17:32:42 -04:00
}
2024-06-28 11:23:06 -04:00
impl TryFrom < SourceWire > for Source {
type Error = LockError ;
fn try_from ( wire : SourceWire ) -> Result < Source , LockError > {
#[ allow(clippy::enum_glob_use) ]
use self ::SourceWire ::* ;
match wire {
2024-12-20 19:28:36 -05:00
Registry { registry } = > Ok ( Source ::Registry ( registry . into ( ) ) ) ,
2024-06-28 11:23:06 -04:00
Git { git } = > {
2025-05-27 00:05:30 +02:00
let url = DisplaySafeUrl ::parse ( & git )
2024-06-28 11:23:06 -04:00
. map_err ( | err | SourceParseError ::InvalidUrl {
given : git . to_string ( ) ,
err ,
} )
. map_err ( LockErrorKind ::InvalidGitSourceUrl ) ? ;
2024-08-07 11:47:48 -04:00
let git_source = GitSource ::from_url ( & url )
2024-06-28 11:23:06 -04:00
. map_err ( | err | match err {
GitSourceError ::InvalidSha = > SourceParseError ::InvalidSha {
given : git . to_string ( ) ,
} ,
GitSourceError ::MissingSha = > SourceParseError ::MissingSha {
given : git . to_string ( ) ,
} ,
} )
. map_err ( LockErrorKind ::InvalidGitSourceUrl ) ? ;
2024-08-07 11:47:48 -04:00
2024-08-06 08:58:09 -04:00
Ok ( Source ::Git ( UrlString ::from ( url ) , git_source ) )
2024-05-17 17:32:42 -04:00
}
2024-12-20 19:28:36 -05:00
Direct { url , subdirectory } = > Ok ( Source ::Direct (
url ,
DirectSource {
2025-03-25 17:56:06 -04:00
subdirectory : subdirectory . map ( Box ::< std ::path ::Path > ::from ) ,
2024-12-20 19:28:36 -05:00
} ,
) ) ,
2024-07-31 12:00:37 -04:00
Path { path } = > Ok ( Source ::Path ( path . into ( ) ) ) ,
Directory { directory } = > Ok ( Source ::Directory ( directory . into ( ) ) ) ,
Editable { editable } = > Ok ( Source ::Editable ( editable . into ( ) ) ) ,
2024-08-27 21:19:05 -04:00
Virtual { r#virtual } = > Ok ( Source ::Virtual ( r#virtual . into ( ) ) ) ,
2024-06-28 11:23:06 -04:00
}
2024-05-17 17:32:42 -04:00
}
}
2024-08-24 22:41:47 -04:00
/// The source for a registry, which could be a URL or a relative path.
#[ derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord) ]
enum RegistrySource {
/// Ex) `https://pypi.org/simple`
Url ( UrlString ) ,
/// Ex) `../path/to/local/index`
2025-03-25 17:56:06 -04:00
Path ( Box < Path > ) ,
2024-08-24 22:41:47 -04:00
}
2024-12-25 14:18:01 -05:00
impl Display for RegistrySource {
2024-08-24 22:41:47 -04:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
match self {
RegistrySource ::Url ( url ) = > write! ( f , " {url} " ) ,
RegistrySource ::Path ( path ) = > write! ( f , " {} " , path . display ( ) ) ,
}
}
}
2024-12-20 19:28:36 -05:00
#[ derive(Clone, Debug) ]
enum RegistrySourceWire {
/// Ex) `https://pypi.org/simple`
Url ( UrlString ) ,
/// Ex) `../path/to/local/index`
Path ( PortablePathBuf ) ,
}
impl < ' de > serde ::de ::Deserialize < ' de > for RegistrySourceWire {
2024-08-24 22:41:47 -04:00
fn deserialize < D > ( deserializer : D ) -> Result < Self , D ::Error >
where
D : serde ::de ::Deserializer < ' de > ,
{
struct Visitor ;
2024-12-11 10:06:19 -06:00
impl serde ::de ::Visitor < '_ > for Visitor {
2024-12-20 19:28:36 -05:00
type Value = RegistrySourceWire ;
2024-08-24 22:41:47 -04:00
fn expecting ( & self , formatter : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
formatter . write_str ( " a valid URL or a file path " )
}
fn visit_str < E > ( self , value : & str ) -> Result < Self ::Value , E >
where
E : serde ::de ::Error ,
{
if split_scheme ( value ) . is_some ( ) {
Ok (
serde ::Deserialize ::deserialize ( serde ::de ::value ::StrDeserializer ::new (
value ,
) )
2024-12-20 19:28:36 -05:00
. map ( RegistrySourceWire ::Url ) ? ,
2024-08-24 22:41:47 -04:00
)
} else {
Ok (
serde ::Deserialize ::deserialize ( serde ::de ::value ::StrDeserializer ::new (
value ,
) )
2024-12-20 19:28:36 -05:00
. map ( RegistrySourceWire ::Path ) ? ,
2024-08-24 22:41:47 -04:00
)
}
}
}
deserializer . deserialize_str ( Visitor )
}
}
2024-12-20 19:28:36 -05:00
impl From < RegistrySourceWire > for RegistrySource {
fn from ( wire : RegistrySourceWire ) -> Self {
match wire {
RegistrySourceWire ::Url ( url ) = > Self ::Url ( url ) ,
RegistrySourceWire ::Path ( path ) = > Self ::Path ( path . into ( ) ) ,
}
}
}
2024-06-28 11:23:06 -04:00
#[ derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize) ]
2025-04-28 11:01:17 -04:00
#[ serde(rename_all = " kebab-case " ) ]
2024-06-28 11:23:06 -04:00
struct DirectSource {
2025-03-25 17:56:06 -04:00
subdirectory : Option < Box < Path > > ,
2024-06-28 11:23:06 -04:00
}
2024-04-29 14:03:17 -04:00
/// NOTE: Care should be taken when adding variants to this enum. Namely, new
/// variants should be added without changing the relative ordering of other
2024-07-25 10:22:36 -04:00
/// variants. Otherwise, this could cause the lockfile to have a different
2024-08-08 17:25:06 +02:00
/// canonical ordering of package entries.
2024-05-17 17:23:40 -04:00
#[ derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord) ]
2024-06-03 15:00:35 -04:00
struct GitSource {
2025-01-21 09:15:11 -05:00
precise : GitOid ,
2025-03-25 17:56:06 -04:00
subdirectory : Option < Box < Path > > ,
2024-04-29 14:03:17 -04:00
kind : GitSourceKind ,
}
2024-05-17 17:23:40 -04:00
/// An error that occurs when a source string could not be parsed.
#[ derive(Clone, Debug, Eq, PartialEq) ]
enum GitSourceError {
InvalidSha ,
MissingSha ,
}
2024-04-29 14:03:17 -04:00
impl GitSource {
2024-08-07 11:47:48 -04:00
/// Extracts a Git source reference from the query pairs and the hash
2024-04-29 14:03:17 -04:00
/// fragment in the given URL.
2024-08-07 11:47:48 -04:00
fn from_url ( url : & Url ) -> Result < GitSource , GitSourceError > {
2024-04-29 14:03:17 -04:00
let mut kind = GitSourceKind ::DefaultBranch ;
2024-05-17 17:23:40 -04:00
let mut subdirectory = None ;
2024-04-29 14:03:17 -04:00
for ( key , val ) in url . query_pairs ( ) {
2024-05-17 17:23:40 -04:00
match & * key {
" tag " = > kind = GitSourceKind ::Tag ( val . into_owned ( ) ) ,
" branch " = > kind = GitSourceKind ::Branch ( val . into_owned ( ) ) ,
" rev " = > kind = GitSourceKind ::Rev ( val . into_owned ( ) ) ,
2024-12-20 19:28:36 -05:00
" subdirectory " = > subdirectory = Some ( PortablePathBuf ::from ( val . as_ref ( ) ) . into ( ) ) ,
2025-04-04 11:49:26 -04:00
_ = > { }
}
2024-04-29 14:03:17 -04:00
}
2025-01-21 09:15:11 -05:00
let precise = GitOid ::from_str ( url . fragment ( ) . ok_or ( GitSourceError ::MissingSha ) ? )
2024-05-17 17:23:40 -04:00
. map_err ( | _ | GitSourceError ::InvalidSha ) ? ;
Ok ( GitSource {
precise ,
subdirectory ,
kind ,
} )
2024-04-29 14:03:17 -04:00
}
}
2024-05-30 15:08:29 -04:00
#[ derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize) ]
2025-04-28 11:01:17 -04:00
#[ serde(rename_all = " kebab-case " ) ]
2024-04-29 14:03:17 -04:00
enum GitSourceKind {
Tag ( String ) ,
Branch ( String ) ,
Rev ( String ) ,
DefaultBranch ,
}
/// Inspired by: <https://discuss.python.org/t/lock-files-again-but-this-time-w-sdists/46593>
2024-07-09 17:08:27 -04:00
#[ derive(Clone, Debug, serde::Deserialize, PartialEq, Eq) ]
2025-04-28 11:01:17 -04:00
#[ serde(rename_all = " kebab-case " ) ]
2024-06-11 13:58:03 +02:00
struct SourceDistMetadata {
2024-04-29 14:03:17 -04:00
/// A hash of the source distribution.
2024-07-17 16:29:49 -04:00
hash : Option < Hash > ,
2024-05-18 22:27:05 -04:00
/// The size of the source distribution in bytes.
///
/// This is only present for source distributions that come from registries.
size : Option < u64 > ,
2025-04-20 21:58:12 -04:00
/// The upload time of the source distribution.
2025-04-28 11:01:17 -04:00
#[ serde(alias = " upload_time " ) ]
2025-04-20 21:58:12 -04:00
upload_time : Option < Timestamp > ,
2024-04-29 14:03:17 -04:00
}
2024-06-11 13:58:03 +02:00
/// A URL or file path where the source dist that was
/// locked against was found. The location does not need to exist in the
/// future, so this should be treated as only a hint to where to look
/// and/or recording where the source dist file originally came from.
2024-07-09 17:08:27 -04:00
#[ derive(Clone, Debug, serde::Deserialize, PartialEq, Eq) ]
2024-07-31 12:00:37 -04:00
#[ serde(try_from = " SourceDistWire " ) ]
2024-06-11 13:58:03 +02:00
enum SourceDist {
Url {
2024-07-19 22:11:26 -04:00
url : UrlString ,
2024-06-11 13:58:03 +02:00
#[ serde(flatten) ]
metadata : SourceDistMetadata ,
} ,
Path {
2025-03-25 17:56:06 -04:00
path : Box < Path > ,
2024-06-11 13:58:03 +02:00
#[ serde(flatten) ]
metadata : SourceDistMetadata ,
} ,
2024-12-21 09:31:28 -05:00
Metadata {
#[ serde(flatten) ]
metadata : SourceDistMetadata ,
} ,
2024-06-11 13:58:03 +02:00
}
impl SourceDist {
2024-06-12 10:01:29 -07:00
fn filename ( & self ) -> Option < Cow < str > > {
2024-06-11 13:58:03 +02:00
match self {
2024-12-21 09:31:28 -05:00
SourceDist ::Metadata { .. } = > None ,
2024-06-11 13:58:03 +02:00
SourceDist ::Url { url , .. } = > url . filename ( ) . ok ( ) ,
SourceDist ::Path { path , .. } = > {
path . file_name ( ) . map ( | filename | filename . to_string_lossy ( ) )
}
}
}
2024-07-19 22:11:26 -04:00
fn url ( & self ) -> Option < & UrlString > {
2024-06-12 10:01:29 -07:00
match & self {
2024-12-21 09:31:28 -05:00
SourceDist ::Metadata { .. } = > None ,
2024-06-12 10:01:29 -07:00
SourceDist ::Url { url , .. } = > Some ( url ) ,
SourceDist ::Path { .. } = > None ,
}
}
2025-04-21 17:21:17 -04:00
pub ( crate ) fn hash ( & self ) -> Option < & Hash > {
2024-06-11 13:58:03 +02:00
match & self {
2024-12-21 09:31:28 -05:00
SourceDist ::Metadata { metadata } = > metadata . hash . as_ref ( ) ,
2024-07-17 16:29:49 -04:00
SourceDist ::Url { metadata , .. } = > metadata . hash . as_ref ( ) ,
SourceDist ::Path { metadata , .. } = > metadata . hash . as_ref ( ) ,
2024-06-11 13:58:03 +02:00
}
}
2024-07-17 16:29:49 -04:00
2025-04-21 17:21:17 -04:00
pub ( crate ) fn size ( & self ) -> Option < u64 > {
2024-06-11 13:58:03 +02:00
match & self {
2024-12-21 09:31:28 -05:00
SourceDist ::Metadata { metadata } = > metadata . size ,
2024-06-11 13:58:03 +02:00
SourceDist ::Url { metadata , .. } = > metadata . size ,
SourceDist ::Path { metadata , .. } = > metadata . size ,
}
}
2025-04-20 21:58:12 -04:00
pub ( crate ) fn upload_time ( & self ) -> Option < Timestamp > {
match & self {
SourceDist ::Metadata { metadata } = > metadata . upload_time ,
SourceDist ::Url { metadata , .. } = > metadata . upload_time ,
SourceDist ::Path { metadata , .. } = > metadata . upload_time ,
}
}
2024-06-11 13:58:03 +02:00
}
2024-04-29 14:03:17 -04:00
impl SourceDist {
2024-05-14 19:07:24 -04:00
fn from_annotated_dist (
2024-08-08 17:25:06 +02:00
id : & PackageId ,
2024-05-14 19:07:24 -04:00
annotated_dist : & AnnotatedDist ,
) -> Result < Option < SourceDist > , LockError > {
match annotated_dist . dist {
2024-08-07 15:22:56 +02:00
// We pass empty installed packages for locking.
2024-11-13 19:06:16 -05:00
ResolvedDist ::Installed { .. } = > unreachable! ( ) ,
2025-02-24 07:11:44 -10:00
ResolvedDist ::Installable { ref dist , .. } = > SourceDist ::from_dist (
id ,
dist ,
annotated_dist . hashes . as_slice ( ) ,
annotated_dist . index ( ) ,
) ,
2024-04-29 14:03:17 -04:00
}
}
2024-06-25 14:29:49 -04:00
fn from_dist (
2024-08-08 17:25:06 +02:00
id : & PackageId ,
2024-06-25 14:29:49 -04:00
dist : & Dist ,
hashes : & [ HashDigest ] ,
2024-08-12 08:54:09 -04:00
index : Option < & IndexUrl > ,
2024-06-25 14:29:49 -04:00
) -> Result < Option < SourceDist > , LockError > {
2024-04-29 14:03:17 -04:00
match * dist {
2024-05-15 15:07:28 -04:00
Dist ::Built ( BuiltDist ::Registry ( ref built_dist ) ) = > {
let Some ( sdist ) = built_dist . sdist . as_ref ( ) else {
return Ok ( None ) ;
} ;
2024-08-12 08:54:09 -04:00
SourceDist ::from_registry_dist ( sdist , index )
2024-05-15 15:07:28 -04:00
}
2024-04-29 14:03:17 -04:00
Dist ::Built ( _ ) = > Ok ( None ) ,
2024-08-12 08:54:09 -04:00
Dist ::Source ( ref source_dist ) = > {
SourceDist ::from_source_dist ( id , source_dist , hashes , index )
}
2024-04-29 14:03:17 -04:00
}
}
fn from_source_dist (
2024-08-08 17:25:06 +02:00
id : & PackageId ,
2024-10-01 20:15:32 -04:00
source_dist : & uv_distribution_types ::SourceDist ,
2024-05-14 19:07:24 -04:00
hashes : & [ HashDigest ] ,
2024-08-12 08:54:09 -04:00
index : Option < & IndexUrl > ,
2024-06-25 14:19:51 -04:00
) -> Result < Option < SourceDist > , LockError > {
2024-04-29 14:03:17 -04:00
match * source_dist {
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Registry ( ref reg_dist ) = > {
2024-08-12 08:54:09 -04:00
SourceDist ::from_registry_dist ( reg_dist , index )
2024-04-29 14:03:17 -04:00
}
2024-12-21 09:31:28 -05:00
uv_distribution_types ::SourceDist ::DirectUrl ( _ ) = > {
SourceDist ::from_direct_dist ( id , hashes ) . map ( Some )
}
uv_distribution_types ::SourceDist ::Path ( _ ) = > {
SourceDist ::from_path_dist ( id , hashes ) . map ( Some )
2024-05-13 10:03:14 -04:00
}
2024-07-25 10:22:36 -04:00
// An actual sdist entry in the lockfile is only required when
2024-06-25 14:19:51 -04:00
// it's from a registry or a direct URL. Otherwise, it's strictly
// redundant with the information in all other kinds of `source`.
2024-10-01 20:15:32 -04:00
uv_distribution_types ::SourceDist ::Git ( _ )
| uv_distribution_types ::SourceDist ::Directory ( _ ) = > Ok ( None ) ,
2024-04-29 14:03:17 -04:00
}
}
2024-08-12 08:54:09 -04:00
fn from_registry_dist (
reg_dist : & RegistrySourceDist ,
index : Option < & IndexUrl > ,
) -> Result < Option < SourceDist > , LockError > {
// Reject distributions from registries that don't match the index URL, as can occur with
// `--find-links`.
2024-12-11 10:06:19 -06:00
if index . is_none_or ( | index | * index ! = reg_dist . index ) {
2024-08-12 08:54:09 -04:00
return Ok ( None ) ;
}
2024-08-24 22:41:47 -04:00
match & reg_dist . index {
IndexUrl ::Pypi ( _ ) | IndexUrl ::Url ( _ ) = > {
let url = normalize_file_location ( & reg_dist . file . url )
2025-01-01 12:35:30 -05:00
. map_err ( LockErrorKind ::InvalidUrl )
2024-08-24 22:41:47 -04:00
. map_err ( LockError ::from ) ? ;
let hash = reg_dist . file . hashes . iter ( ) . max ( ) . cloned ( ) . map ( Hash ::from ) ;
let size = reg_dist . file . size ;
2025-04-20 21:58:12 -04:00
let upload_time = reg_dist
. file
. upload_time_utc_ms
. map ( Timestamp ::from_millisecond )
. transpose ( )
. map_err ( LockErrorKind ::InvalidTimestamp ) ? ;
2024-08-24 22:41:47 -04:00
Ok ( Some ( SourceDist ::Url {
url ,
2025-04-20 21:58:12 -04:00
metadata : SourceDistMetadata {
hash ,
size ,
upload_time ,
} ,
2024-08-24 22:41:47 -04:00
} ) )
}
IndexUrl ::Path ( path ) = > {
2025-06-26 15:42:04 -04:00
let index_path = path
. to_file_path ( )
. map_err ( | ( ) | LockErrorKind ::UrlToPath { url : path . to_url ( ) } ) ? ;
2025-06-26 16:17:42 -04:00
let url = reg_dist
2024-08-24 22:41:47 -04:00
. file
. url
. to_url ( )
2025-06-26 15:42:04 -04:00
. map_err ( LockErrorKind ::InvalidUrl ) ? ;
2025-06-26 16:17:42 -04:00
if url . scheme ( ) = = " file " {
let reg_dist_path = url
. to_file_path ( )
. map_err ( | ( ) | LockErrorKind ::UrlToPath { url } ) ? ;
let path = relative_to ( & reg_dist_path , index_path )
. or_else ( | _ | std ::path ::absolute ( & reg_dist_path ) )
. map_err ( LockErrorKind ::DistributionRelativePath ) ?
. into_boxed_path ( ) ;
let hash = reg_dist . file . hashes . iter ( ) . max ( ) . cloned ( ) . map ( Hash ::from ) ;
let size = reg_dist . file . size ;
let upload_time = reg_dist
. file
. upload_time_utc_ms
. map ( Timestamp ::from_millisecond )
. transpose ( )
. map_err ( LockErrorKind ::InvalidTimestamp ) ? ;
Ok ( Some ( SourceDist ::Path {
path ,
metadata : SourceDistMetadata {
hash ,
size ,
upload_time ,
} ,
} ) )
} else {
let url = normalize_file_location ( & reg_dist . file . url )
. map_err ( LockErrorKind ::InvalidUrl )
. map_err ( LockError ::from ) ? ;
let hash = reg_dist . file . hashes . iter ( ) . max ( ) . cloned ( ) . map ( Hash ::from ) ;
let size = reg_dist . file . size ;
let upload_time = reg_dist
. file
. upload_time_utc_ms
. map ( Timestamp ::from_millisecond )
. transpose ( )
. map_err ( LockErrorKind ::InvalidTimestamp ) ? ;
Ok ( Some ( SourceDist ::Url {
url ,
metadata : SourceDistMetadata {
hash ,
size ,
upload_time ,
} ,
} ) )
}
2024-08-24 22:41:47 -04:00
}
}
2024-04-29 14:03:17 -04:00
}
2024-12-21 09:31:28 -05:00
fn from_direct_dist ( id : & PackageId , hashes : & [ HashDigest ] ) -> Result < SourceDist , LockError > {
2024-07-17 16:38:33 -04:00
let Some ( hash ) = hashes . iter ( ) . max ( ) . cloned ( ) . map ( Hash ::from ) else {
2024-06-25 14:29:49 -04:00
let kind = LockErrorKind ::Hash {
id : id . clone ( ) ,
artifact_type : " direct URL source distribution " ,
expected : true ,
} ;
return Err ( kind . into ( ) ) ;
} ;
2024-12-21 09:31:28 -05:00
Ok ( SourceDist ::Metadata {
metadata : SourceDistMetadata {
hash : Some ( hash ) ,
size : None ,
2025-04-20 21:58:12 -04:00
upload_time : None ,
2024-12-21 09:31:28 -05:00
} ,
} )
}
fn from_path_dist ( id : & PackageId , hashes : & [ HashDigest ] ) -> Result < SourceDist , LockError > {
let Some ( hash ) = hashes . iter ( ) . max ( ) . cloned ( ) . map ( Hash ::from ) else {
let kind = LockErrorKind ::Hash {
id : id . clone ( ) ,
artifact_type : " path source distribution " ,
expected : true ,
} ;
return Err ( kind . into ( ) ) ;
} ;
Ok ( SourceDist ::Metadata {
2024-07-17 16:29:49 -04:00
metadata : SourceDistMetadata {
hash : Some ( hash ) ,
size : None ,
2025-04-20 21:58:12 -04:00
upload_time : None ,
2024-07-17 16:29:49 -04:00
} ,
2024-06-25 14:29:49 -04:00
} )
2024-04-29 14:03:17 -04:00
}
}
2024-07-31 12:00:37 -04:00
#[ derive(Clone, Debug, serde::Deserialize) ]
2025-04-28 11:01:17 -04:00
#[ serde(untagged, rename_all = " kebab-case " ) ]
2024-07-31 12:00:37 -04:00
enum SourceDistWire {
Url {
url : UrlString ,
#[ serde(flatten) ]
metadata : SourceDistMetadata ,
} ,
Path {
path : PortablePathBuf ,
#[ serde(flatten) ]
metadata : SourceDistMetadata ,
} ,
2024-12-21 09:31:28 -05:00
Metadata {
#[ serde(flatten) ]
metadata : SourceDistMetadata ,
} ,
2024-07-31 12:00:37 -04:00
}
impl SourceDist {
/// Returns the TOML representation of this source distribution.
2024-12-24 20:24:26 -05:00
fn to_toml ( & self ) -> Result < InlineTable , toml_edit ::ser ::Error > {
2024-07-31 12:00:37 -04:00
let mut table = InlineTable ::new ( ) ;
match & self {
2024-12-21 09:31:28 -05:00
SourceDist ::Metadata { .. } = > { }
2024-07-31 12:00:37 -04:00
SourceDist ::Url { url , .. } = > {
2024-08-14 20:12:32 -04:00
table . insert ( " url " , Value ::from ( url . as_ref ( ) ) ) ;
2024-07-31 12:00:37 -04:00
}
SourceDist ::Path { path , .. } = > {
table . insert ( " path " , Value ::from ( PortablePath ::from ( path ) . to_string ( ) ) ) ;
}
}
if let Some ( hash ) = self . hash ( ) {
table . insert ( " hash " , Value ::from ( hash . to_string ( ) ) ) ;
}
if let Some ( size ) = self . size ( ) {
2024-12-24 20:24:26 -05:00
table . insert (
" size " ,
toml_edit ::ser ::ValueSerializer ::new ( ) . serialize_u64 ( size ) ? ,
) ;
2024-07-31 12:00:37 -04:00
}
2025-04-20 21:58:12 -04:00
if let Some ( upload_time ) = self . upload_time ( ) {
2025-04-28 11:01:17 -04:00
table . insert ( " upload-time " , Value ::from ( upload_time . to_string ( ) ) ) ;
2025-04-20 21:58:12 -04:00
}
2024-07-31 12:00:37 -04:00
Ok ( table )
}
}
impl TryFrom < SourceDistWire > for SourceDist {
type Error = Infallible ;
fn try_from ( wire : SourceDistWire ) -> Result < SourceDist , Infallible > {
match wire {
SourceDistWire ::Url { url , metadata } = > Ok ( SourceDist ::Url { url , metadata } ) ,
SourceDistWire ::Path { path , metadata } = > Ok ( SourceDist ::Path {
path : path . into ( ) ,
metadata ,
} ) ,
2024-12-21 09:31:28 -05:00
SourceDistWire ::Metadata { metadata } = > Ok ( SourceDist ::Metadata { metadata } ) ,
2024-07-31 12:00:37 -04:00
}
}
}
2024-05-17 17:23:40 -04:00
impl From < GitReference > for GitSourceKind {
fn from ( value : GitReference ) -> Self {
match value {
GitReference ::Branch ( branch ) = > GitSourceKind ::Branch ( branch . to_string ( ) ) ,
GitReference ::Tag ( tag ) = > GitSourceKind ::Tag ( tag . to_string ( ) ) ,
GitReference ::BranchOrTag ( rev ) = > GitSourceKind ::Rev ( rev . to_string ( ) ) ,
GitReference ::BranchOrTagOrCommit ( rev ) = > GitSourceKind ::Rev ( rev . to_string ( ) ) ,
GitReference ::NamedRef ( rev ) = > GitSourceKind ::Rev ( rev . to_string ( ) ) ,
GitReference ::DefaultBranch = > GitSourceKind ::DefaultBranch ,
}
}
}
impl From < GitSourceKind > for GitReference {
fn from ( value : GitSourceKind ) -> Self {
match value {
GitSourceKind ::Branch ( branch ) = > GitReference ::Branch ( branch ) ,
GitSourceKind ::Tag ( tag ) = > GitReference ::Tag ( tag ) ,
2024-05-18 13:26:15 -04:00
GitSourceKind ::Rev ( rev ) = > GitReference ::from_rev ( rev ) ,
2024-05-17 17:23:40 -04:00
GitSourceKind ::DefaultBranch = > GitReference ::DefaultBranch ,
}
}
}
2025-05-27 00:05:30 +02:00
/// Construct the lockfile-compatible [`DisplaySafeUrl`] for a [`GitSourceDist`].
fn locked_git_url ( git_dist : & GitSourceDist ) -> DisplaySafeUrl {
2024-05-17 17:23:40 -04:00
let mut url = git_dist . git . repository ( ) . clone ( ) ;
2025-05-27 00:05:30 +02:00
// Remove the credentials.
url . remove_credentials ( ) ;
2024-08-13 19:48:59 -04:00
2024-05-17 17:23:40 -04:00
// Clear out any existing state.
url . set_fragment ( None ) ;
url . set_query ( None ) ;
// Put the subdirectory in the query.
2024-10-30 15:37:40 -04:00
if let Some ( subdirectory ) = git_dist
. subdirectory
. as_deref ( )
. map ( PortablePath ::from )
. as_ref ( )
. map ( PortablePath ::to_string )
{
2024-05-17 17:23:40 -04:00
url . query_pairs_mut ( )
2024-10-30 15:37:40 -04:00
. append_pair ( " subdirectory " , & subdirectory ) ;
2024-05-17 17:23:40 -04:00
}
// Put the requested reference in the query.
match git_dist . git . reference ( ) {
GitReference ::Branch ( branch ) = > {
2025-01-21 14:26:58 -05:00
url . query_pairs_mut ( ) . append_pair ( " branch " , branch . as_str ( ) ) ;
2024-05-17 17:23:40 -04:00
}
GitReference ::Tag ( tag ) = > {
2025-01-21 14:26:58 -05:00
url . query_pairs_mut ( ) . append_pair ( " tag " , tag . as_str ( ) ) ;
2024-05-17 17:23:40 -04:00
}
2025-01-20 18:35:47 -05:00
GitReference ::BranchOrTag ( rev )
2024-05-17 17:23:40 -04:00
| GitReference ::BranchOrTagOrCommit ( rev )
2025-01-21 14:26:58 -05:00
| GitReference ::NamedRef ( rev ) = > {
url . query_pairs_mut ( ) . append_pair ( " rev " , rev . as_str ( ) ) ;
2024-05-17 17:23:40 -04:00
}
GitReference ::DefaultBranch = > { }
}
// Put the precise commit in the fragment.
url . set_fragment (
git_dist
. git
. precise ( )
. as_ref ( )
2025-01-21 09:15:11 -05:00
. map ( GitOid ::to_string )
2024-05-17 17:23:40 -04:00
. as_deref ( ) ,
) ;
url
}
2024-04-29 14:03:17 -04:00
/// Inspired by: <https://discuss.python.org/t/lock-files-again-but-this-time-w-sdists/46593>
2024-07-09 17:08:27 -04:00
#[ derive(Clone, Debug, serde::Deserialize, PartialEq, Eq) ]
2024-05-30 15:08:29 -04:00
#[ serde(try_from = " WheelWire " ) ]
2024-06-03 15:00:35 -04:00
struct Wheel {
2024-04-29 14:03:17 -04:00
/// A URL or file path (via `file://`) where the wheel that was locked
/// against was found. The location does not need to exist in the future,
/// so this should be treated as only a hint to where to look and/or
/// recording where the wheel file originally came from.
2024-08-09 23:57:16 +02:00
url : WheelWireSource ,
2024-05-18 22:27:05 -04:00
/// A hash of the built distribution.
2024-05-10 10:32:30 -04:00
///
/// This is only present for wheels that come from registries and direct
/// URLs. Wheels from git or path dependencies do not have hashes
/// associated with them.
hash : Option < Hash > ,
2024-05-18 22:27:05 -04:00
/// The size of the built distribution in bytes.
///
/// This is only present for wheels that come from registries.
size : Option < u64 > ,
2025-04-20 21:58:12 -04:00
/// The upload time of the built distribution.
///
/// This is only present for wheels that come from registries.
upload_time : Option < Timestamp > ,
2024-05-03 08:18:36 -04:00
/// The filename of the wheel.
///
/// This isn't part of the wire format since it's redundant with the
/// URL. But we do use it for various things, and thus compute it at
/// deserialization time. Not being able to extract a wheel filename from a
/// wheel URL is thus a deserialization error.
filename : WheelFilename ,
2024-04-29 14:03:17 -04:00
}
impl Wheel {
2024-05-15 15:07:28 -04:00
fn from_annotated_dist ( annotated_dist : & AnnotatedDist ) -> Result < Vec < Wheel > , LockError > {
2024-05-14 19:07:24 -04:00
match annotated_dist . dist {
2024-08-07 15:22:56 +02:00
// We pass empty installed packages for locking.
2024-11-13 19:06:16 -05:00
ResolvedDist ::Installed { .. } = > unreachable! ( ) ,
2025-02-24 07:11:44 -10:00
ResolvedDist ::Installable { ref dist , .. } = > Wheel ::from_dist (
dist ,
annotated_dist . hashes . as_slice ( ) ,
annotated_dist . index ( ) ,
) ,
2024-04-29 14:03:17 -04:00
}
}
2024-08-12 08:54:09 -04:00
fn from_dist (
dist : & Dist ,
hashes : & [ HashDigest ] ,
index : Option < & IndexUrl > ,
) -> Result < Vec < Wheel > , LockError > {
2024-04-29 14:03:17 -04:00
match * dist {
2024-08-12 08:54:09 -04:00
Dist ::Built ( ref built_dist ) = > Wheel ::from_built_dist ( built_dist , hashes , index ) ,
2024-10-01 20:15:32 -04:00
Dist ::Source ( uv_distribution_types ::SourceDist ::Registry ( ref source_dist ) ) = > {
source_dist
. wheels
. iter ( )
. filter ( | wheel | {
// Reject distributions from registries that don't match the index URL, as can occur with
// `--find-links`.
index . is_some_and ( | index | * index = = wheel . index )
} )
. map ( Wheel ::from_registry_wheel )
. collect ( )
}
2024-05-15 15:07:28 -04:00
Dist ::Source ( _ ) = > Ok ( vec! [ ] ) ,
2024-04-29 14:03:17 -04:00
}
}
2024-05-15 15:07:28 -04:00
fn from_built_dist (
built_dist : & BuiltDist ,
hashes : & [ HashDigest ] ,
2024-08-12 08:54:09 -04:00
index : Option < & IndexUrl > ,
2024-05-15 15:07:28 -04:00
) -> Result < Vec < Wheel > , LockError > {
2024-04-29 14:03:17 -04:00
match * built_dist {
2024-08-12 08:54:09 -04:00
BuiltDist ::Registry ( ref reg_dist ) = > Wheel ::from_registry_dist ( reg_dist , index ) ,
2024-05-14 19:07:24 -04:00
BuiltDist ::DirectUrl ( ref direct_dist ) = > {
2024-05-15 15:07:28 -04:00
Ok ( vec! [ Wheel ::from_direct_dist ( direct_dist , hashes ) ] )
2024-05-14 19:07:24 -04:00
}
2024-05-15 15:07:28 -04:00
BuiltDist ::Path ( ref path_dist ) = > Ok ( vec! [ Wheel ::from_path_dist ( path_dist , hashes ) ] ) ,
2024-04-29 14:03:17 -04:00
}
}
2024-08-12 08:54:09 -04:00
fn from_registry_dist (
reg_dist : & RegistryBuiltDist ,
index : Option < & IndexUrl > ,
) -> Result < Vec < Wheel > , LockError > {
2024-05-15 15:16:00 -04:00
reg_dist
. wheels
. iter ( )
2024-08-12 08:54:09 -04:00
. filter ( | wheel | {
// Reject distributions from registries that don't match the index URL, as can occur with
// `--find-links`.
index . is_some_and ( | index | * index = = wheel . index )
} )
2024-05-15 15:16:00 -04:00
. map ( Wheel ::from_registry_wheel )
. collect ( )
}
fn from_registry_wheel ( wheel : & RegistryBuiltWheel ) -> Result < Wheel , LockError > {
let filename = wheel . filename . clone ( ) ;
2024-08-24 22:41:47 -04:00
match & wheel . index {
IndexUrl ::Pypi ( _ ) | IndexUrl ::Url ( _ ) = > {
let url = normalize_file_location ( & wheel . file . url )
2025-01-01 12:35:30 -05:00
. map_err ( LockErrorKind ::InvalidUrl )
2024-08-24 22:41:47 -04:00
. map_err ( LockError ::from ) ? ;
let hash = wheel . file . hashes . iter ( ) . max ( ) . cloned ( ) . map ( Hash ::from ) ;
let size = wheel . file . size ;
2025-04-20 21:58:12 -04:00
let upload_time = wheel
. file
. upload_time_utc_ms
. map ( Timestamp ::from_millisecond )
. transpose ( )
. map_err ( LockErrorKind ::InvalidTimestamp ) ? ;
2024-08-24 22:41:47 -04:00
Ok ( Wheel {
url : WheelWireSource ::Url { url } ,
hash ,
size ,
filename ,
2025-04-20 21:58:12 -04:00
upload_time ,
2024-08-24 22:41:47 -04:00
} )
}
IndexUrl ::Path ( path ) = > {
2025-06-26 15:42:04 -04:00
let index_path = path
. to_file_path ( )
. map_err ( | ( ) | LockErrorKind ::UrlToPath { url : path . to_url ( ) } ) ? ;
let wheel_url = wheel . file . url . to_url ( ) . map_err ( LockErrorKind ::InvalidUrl ) ? ;
2025-06-26 16:17:42 -04:00
if wheel_url . scheme ( ) = = " file " {
let wheel_path = wheel_url
. to_file_path ( )
. map_err ( | ( ) | LockErrorKind ::UrlToPath { url : wheel_url } ) ? ;
let path = relative_to ( & wheel_path , index_path )
. or_else ( | _ | std ::path ::absolute ( & wheel_path ) )
. map_err ( LockErrorKind ::DistributionRelativePath ) ?
. into_boxed_path ( ) ;
Ok ( Wheel {
url : WheelWireSource ::Path { path } ,
hash : None ,
size : None ,
upload_time : None ,
filename ,
} )
} else {
let url = normalize_file_location ( & wheel . file . url )
. map_err ( LockErrorKind ::InvalidUrl )
. map_err ( LockError ::from ) ? ;
let hash = wheel . file . hashes . iter ( ) . max ( ) . cloned ( ) . map ( Hash ::from ) ;
let size = wheel . file . size ;
let upload_time = wheel
. file
. upload_time_utc_ms
. map ( Timestamp ::from_millisecond )
. transpose ( )
. map_err ( LockErrorKind ::InvalidTimestamp ) ? ;
Ok ( Wheel {
url : WheelWireSource ::Url { url } ,
hash ,
size ,
filename ,
upload_time ,
} )
}
2024-08-24 22:41:47 -04:00
}
}
2024-04-29 14:03:17 -04:00
}
2024-05-14 19:07:24 -04:00
fn from_direct_dist ( direct_dist : & DirectUrlBuiltDist , hashes : & [ HashDigest ] ) -> Wheel {
2024-04-29 14:03:17 -04:00
Wheel {
2024-08-09 23:57:16 +02:00
url : WheelWireSource ::Url {
2024-08-14 20:12:32 -04:00
url : normalize_url ( direct_dist . url . to_url ( ) ) ,
2024-08-09 23:57:16 +02:00
} ,
2024-07-17 16:38:33 -04:00
hash : hashes . iter ( ) . max ( ) . cloned ( ) . map ( Hash ::from ) ,
2024-05-18 22:27:05 -04:00
size : None ,
2025-04-20 21:58:12 -04:00
upload_time : None ,
2024-05-03 08:18:36 -04:00
filename : direct_dist . filename . clone ( ) ,
2024-04-29 14:03:17 -04:00
}
}
2024-05-14 19:07:24 -04:00
fn from_path_dist ( path_dist : & PathBuiltDist , hashes : & [ HashDigest ] ) -> Wheel {
2024-04-29 14:03:17 -04:00
Wheel {
2024-08-09 23:57:16 +02:00
url : WheelWireSource ::Filename {
filename : path_dist . filename . clone ( ) ,
} ,
2024-07-17 16:38:33 -04:00
hash : hashes . iter ( ) . max ( ) . cloned ( ) . map ( Hash ::from ) ,
2024-05-18 22:27:05 -04:00
size : None ,
2025-04-20 21:58:12 -04:00
upload_time : None ,
2024-05-03 08:18:36 -04:00
filename : path_dist . filename . clone ( ) ,
}
}
2024-05-15 15:07:28 -04:00
2025-04-21 18:10:30 -04:00
pub ( crate ) fn to_registry_wheel (
2024-08-24 22:41:47 -04:00
& self ,
source : & RegistrySource ,
root : & Path ,
) -> Result < RegistryBuiltWheel , LockError > {
2024-05-15 15:07:28 -04:00
let filename : WheelFilename = self . filename . clone ( ) ;
2024-08-24 22:41:47 -04:00
match source {
2025-01-01 12:35:30 -05:00
RegistrySource ::Url ( url ) = > {
2025-06-26 16:17:42 -04:00
let file_location = match & self . url {
WheelWireSource ::Url { url : file_url } = > {
FileLocation ::AbsoluteUrl ( file_url . clone ( ) )
}
2024-08-24 22:41:47 -04:00
WheelWireSource ::Path { .. } | WheelWireSource ::Filename { .. } = > {
return Err ( LockErrorKind ::MissingUrl {
name : filename . name ,
version : filename . version ,
}
2025-05-17 02:19:02 +02:00
. into ( ) ) ;
2024-08-24 22:41:47 -04:00
}
} ;
2024-10-01 20:15:32 -04:00
let file = Box ::new ( uv_distribution_types ::File {
2024-08-24 22:41:47 -04:00
dist_info_metadata : false ,
2025-02-24 21:06:57 -10:00
filename : SmallString ::from ( filename . to_string ( ) ) ,
2024-08-24 22:41:47 -04:00
hashes : self . hash . iter ( ) . map ( | h | h . 0. clone ( ) ) . collect ( ) ,
requires_python : None ,
size : self . size ,
2025-04-20 21:58:12 -04:00
upload_time_utc_ms : self . upload_time . map ( Timestamp ::as_millisecond ) ,
2025-06-26 16:17:42 -04:00
url : file_location ,
2024-08-24 22:41:47 -04:00
yanked : None ,
} ) ;
2025-01-01 12:35:30 -05:00
let index = IndexUrl ::from ( VerbatimUrl ::from_url (
url . to_url ( ) . map_err ( LockErrorKind ::InvalidUrl ) ? ,
) ) ;
2024-08-24 22:41:47 -04:00
Ok ( RegistryBuiltWheel {
filename ,
file ,
index ,
} )
2024-08-09 23:57:16 +02:00
}
2024-08-24 22:41:47 -04:00
RegistrySource ::Path ( index_path ) = > {
2025-06-26 16:17:42 -04:00
let file_location = match & self . url {
WheelWireSource ::Url { url : file_url } = > {
FileLocation ::AbsoluteUrl ( file_url . clone ( ) )
}
WheelWireSource ::Path { path : file_path } = > {
let file_path = root . join ( index_path ) . join ( file_path ) ;
let file_url =
DisplaySafeUrl ::from_file_path ( & file_path ) . map_err ( | ( ) | {
LockErrorKind ::PathToUrl {
path : file_path . into_boxed_path ( ) ,
}
} ) ? ;
FileLocation ::AbsoluteUrl ( UrlString ::from ( file_url ) )
}
WheelWireSource ::Filename { .. } = > {
2024-08-24 22:41:47 -04:00
return Err ( LockErrorKind ::MissingPath {
name : filename . name ,
version : filename . version ,
}
2025-05-17 02:19:02 +02:00
. into ( ) ) ;
2024-08-24 22:41:47 -04:00
}
} ;
2024-10-01 20:15:32 -04:00
let file = Box ::new ( uv_distribution_types ::File {
2024-08-24 22:41:47 -04:00
dist_info_metadata : false ,
2025-02-24 21:06:57 -10:00
filename : SmallString ::from ( filename . to_string ( ) ) ,
2024-08-24 22:41:47 -04:00
hashes : self . hash . iter ( ) . map ( | h | h . 0. clone ( ) ) . collect ( ) ,
requires_python : None ,
size : self . size ,
2025-04-20 21:58:12 -04:00
upload_time_utc_ms : self . upload_time . map ( Timestamp ::as_millisecond ) ,
2025-06-26 16:17:42 -04:00
url : file_location ,
2024-08-24 22:41:47 -04:00
yanked : None ,
} ) ;
2024-09-16 19:33:32 -04:00
let index = IndexUrl ::from (
2024-08-24 22:41:47 -04:00
VerbatimUrl ::from_absolute_path ( root . join ( index_path ) )
. map_err ( LockErrorKind ::RegistryVerbatimUrl ) ? ,
) ;
Ok ( RegistryBuiltWheel {
filename ,
file ,
index ,
} )
}
}
2024-05-15 15:07:28 -04:00
}
2024-05-03 08:18:36 -04:00
}
2024-05-30 15:08:29 -04:00
#[ derive(Clone, Debug, serde::Deserialize) ]
2025-04-28 11:01:17 -04:00
#[ serde(rename_all = " kebab-case " ) ]
2024-05-03 08:18:36 -04:00
struct WheelWire {
2024-08-09 23:57:16 +02:00
#[ serde(flatten) ]
url : WheelWireSource ,
2024-05-18 22:27:05 -04:00
/// A hash of the built distribution.
2024-05-10 10:32:30 -04:00
///
/// This is only present for wheels that come from registries and direct
/// URLs. Wheels from git or path dependencies do not have hashes
/// associated with them.
hash : Option < Hash > ,
2024-05-18 22:27:05 -04:00
/// The size of the built distribution in bytes.
///
/// This is only present for wheels that come from registries.
size : Option < u64 > ,
2025-04-20 21:58:12 -04:00
/// The upload time of the built distribution.
///
/// This is only present for wheels that come from registries.
2025-04-28 11:01:17 -04:00
#[ serde(alias = " upload_time " ) ]
2025-04-20 21:58:12 -04:00
upload_time : Option < Timestamp > ,
2024-05-03 08:18:36 -04:00
}
2024-08-09 23:57:16 +02:00
#[ derive(Clone, Debug, serde::Deserialize, PartialEq, Eq) ]
2025-04-28 11:01:17 -04:00
#[ serde(untagged, rename_all = " kebab-case " ) ]
2024-08-09 23:57:16 +02:00
enum WheelWireSource {
2024-08-24 22:41:47 -04:00
/// Used for all wheels that come from remote sources.
2024-08-09 23:57:16 +02:00
Url {
2024-08-24 22:41:47 -04:00
/// A URL where the wheel that was locked against was found. The location
/// does not need to exist in the future, so this should be treated as
/// only a hint to where to look and/or recording where the wheel file
/// originally came from.
2024-08-09 23:57:16 +02:00
url : UrlString ,
} ,
2024-08-24 22:41:47 -04:00
/// Used for wheels that come from local registries (like `--find-links`).
Path {
/// The path to the wheel, relative to the index.
2025-03-25 17:56:06 -04:00
path : Box < Path > ,
2024-08-24 22:41:47 -04:00
} ,
2024-08-09 23:57:16 +02:00
/// Used for path wheels.
///
/// We only store the filename for path wheel, since we can't store a relative path in the url
Filename {
/// We duplicate the filename since a lot of code relies on having the filename on the
/// wheel entry.
filename : WheelFilename ,
} ,
}
2024-05-30 15:08:29 -04:00
impl Wheel {
/// Returns the TOML representation of this wheel.
2024-12-24 20:24:26 -05:00
fn to_toml ( & self ) -> Result < InlineTable , toml_edit ::ser ::Error > {
2024-05-30 15:08:29 -04:00
let mut table = InlineTable ::new ( ) ;
2024-08-09 23:57:16 +02:00
match & self . url {
WheelWireSource ::Url { url } = > {
2024-08-14 20:12:32 -04:00
table . insert ( " url " , Value ::from ( url . as_ref ( ) ) ) ;
2024-08-09 23:57:16 +02:00
}
2024-08-24 22:41:47 -04:00
WheelWireSource ::Path { path } = > {
table . insert ( " path " , Value ::from ( PortablePath ::from ( path ) . to_string ( ) ) ) ;
}
2024-08-09 23:57:16 +02:00
WheelWireSource ::Filename { filename } = > {
table . insert ( " filename " , Value ::from ( filename . to_string ( ) ) ) ;
}
}
2024-05-30 15:08:29 -04:00
if let Some ( ref hash ) = self . hash {
table . insert ( " hash " , Value ::from ( hash . to_string ( ) ) ) ;
2024-04-29 14:03:17 -04:00
}
2024-05-30 15:08:29 -04:00
if let Some ( size ) = self . size {
2024-12-24 20:24:26 -05:00
table . insert (
" size " ,
toml_edit ::ser ::ValueSerializer ::new ( ) . serialize_u64 ( size ) ? ,
) ;
2024-05-30 15:08:29 -04:00
}
2025-04-20 21:58:12 -04:00
if let Some ( upload_time ) = self . upload_time {
2025-04-28 11:01:17 -04:00
table . insert ( " upload-time " , Value ::from ( upload_time . to_string ( ) ) ) ;
2025-04-20 21:58:12 -04:00
}
2024-05-30 15:08:29 -04:00
Ok ( table )
2024-04-29 14:03:17 -04:00
}
}
2024-05-03 08:18:36 -04:00
impl TryFrom < WheelWire > for Wheel {
type Error = String ;
fn try_from ( wire : WheelWire ) -> Result < Wheel , String > {
2024-08-09 23:57:16 +02:00
let filename = match & wire . url {
WheelWireSource ::Url { url } = > {
let filename = url . filename ( ) . map_err ( | err | err . to_string ( ) ) ? ;
filename . parse ::< WheelFilename > ( ) . map_err ( | err | {
format! ( " failed to parse ` {filename} ` as wheel filename: {err} " )
} ) ?
}
2024-08-24 22:41:47 -04:00
WheelWireSource ::Path { path } = > {
let filename = path
. file_name ( )
. and_then ( | file_name | file_name . to_str ( ) )
. ok_or_else ( | | {
format! ( " path ` {} ` has no filename component " , path . display ( ) )
} ) ? ;
filename . parse ::< WheelFilename > ( ) . map_err ( | err | {
format! ( " failed to parse ` {filename} ` as wheel filename: {err} " )
} ) ?
}
2024-08-09 23:57:16 +02:00
WheelWireSource ::Filename { filename } = > filename . clone ( ) ,
} ;
2024-05-20 09:25:31 -04:00
2024-05-03 08:18:36 -04:00
Ok ( Wheel {
2024-07-19 22:11:26 -04:00
url : wire . url ,
2024-05-03 08:18:36 -04:00
hash : wire . hash ,
2024-05-18 22:27:05 -04:00
size : wire . size ,
2025-04-20 21:58:12 -04:00
upload_time : wire . upload_time ,
2024-05-03 08:18:36 -04:00
filename ,
} )
}
}
2024-08-08 17:25:06 +02:00
/// A single dependency of a package in a lockfile.
2024-06-24 14:29:01 -04:00
#[ derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord) ]
2024-06-03 15:00:35 -04:00
struct Dependency {
2024-08-08 17:25:06 +02:00
package_id : PackageId ,
2024-07-18 20:07:49 +02:00
extra : BTreeSet < ExtraName > ,
2024-11-21 14:01:55 -05:00
/// A marker simplified from the PEP 508 marker in `complexified_marker`
/// by assuming `requires-python` is satisfied. So if
/// `requires-python = '>=3.8'`, then
2024-08-19 18:05:01 -04:00
/// `python_version >= '3.8' and python_version < '3.12'`
2024-12-24 10:23:28 -05:00
/// gets simplified to `python_version < '3.12'`.
2024-08-19 18:05:01 -04:00
///
/// Generally speaking, this marker should not be exposed to
/// anything outside this module unless it's for a specialized use
/// case. But specifically, it should never be used to evaluate
/// against a marker environment or for disjointness checks or any
/// other kind of marker algebra.
///
/// It exists because there are some cases where we do actually
/// want to compare markers in their "simplified" form. For
/// example, when collapsing the extras on duplicate dependencies.
/// Even if a dependency has different complexified markers,
/// they might have identical markers once simplified. And since
/// `requires-python` applies to the entire lock file, it's
/// acceptable to do comparisons on the simplified form.
simplified_marker : SimplifiedMarkerTree ,
2024-11-21 14:01:55 -05:00
/// The "complexified" marker is a universal marker whose PEP 508
/// marker can stand on its own independent of `requires-python`.
/// It can be safely used for any kind of marker algebra.
complexified_marker : UniversalMarker ,
2024-04-29 14:03:17 -04:00
}
impl Dependency {
2024-08-19 18:05:01 -04:00
fn new (
requires_python : & RequiresPython ,
package_id : PackageId ,
extra : BTreeSet < ExtraName > ,
2024-11-21 14:01:55 -05:00
complexified_marker : UniversalMarker ,
2024-08-19 18:05:01 -04:00
) -> Dependency {
let simplified_marker =
2024-12-10 10:57:22 -05:00
SimplifiedMarkerTree ::new ( requires_python , complexified_marker . combined ( ) ) ;
2024-08-19 18:05:01 -04:00
Dependency {
package_id ,
extra ,
simplified_marker ,
complexified_marker ,
}
}
2024-08-23 22:19:10 -04:00
fn from_annotated_dist (
2024-08-19 18:05:01 -04:00
requires_python : & RequiresPython ,
2024-08-23 22:19:10 -04:00
annotated_dist : & AnnotatedDist ,
2024-11-21 14:01:55 -05:00
complexified_marker : UniversalMarker ,
2024-08-23 22:19:10 -04:00
root : & Path ,
) -> Result < Dependency , LockError > {
let package_id = PackageId ::from_annotated_dist ( annotated_dist , root ) ? ;
2024-07-18 20:07:49 +02:00
let extra = annotated_dist . extra . iter ( ) . cloned ( ) . collect ( ) ;
2024-08-19 18:05:01 -04:00
Ok ( Dependency ::new (
requires_python ,
2024-08-08 17:25:06 +02:00
package_id ,
2024-06-07 11:28:54 -07:00
extra ,
2024-08-19 18:05:01 -04:00
complexified_marker ,
) )
2024-04-29 14:03:17 -04:00
}
2024-05-30 15:08:29 -04:00
/// Returns the TOML representation of this dependency.
2024-08-19 18:05:01 -04:00
fn to_toml (
& self ,
_requires_python : & RequiresPython ,
dist_count_by_name : & FxHashMap < PackageName , u64 > ,
) -> Table {
2024-05-30 15:08:29 -04:00
let mut table = Table ::new ( ) ;
2024-08-08 17:25:06 +02:00
self . package_id
2024-06-28 09:41:25 -04:00
. to_toml ( Some ( dist_count_by_name ) , & mut table ) ;
2024-07-18 20:07:49 +02:00
if ! self . extra . is_empty ( ) {
let extra_array = self
. extra
. iter ( )
. map ( ToString ::to_string )
. collect ::< Array > ( ) ;
table . insert ( " extra " , value ( extra_array ) ) ;
2024-05-30 15:08:29 -04:00
}
2024-08-19 18:05:01 -04:00
if let Some ( marker ) = self . simplified_marker . try_to_string ( ) {
table . insert ( " marker " , value ( marker ) ) ;
2024-06-10 05:40:51 -07:00
}
2024-05-30 15:08:29 -04:00
table
}
2024-04-29 14:03:17 -04:00
}
2024-12-25 14:18:01 -05:00
impl Display for Dependency {
2024-06-07 11:28:54 -07:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
2025-01-15 11:54:32 -05:00
match ( self . extra . is_empty ( ) , self . package_id . version . as_ref ( ) ) {
( true , Some ( version ) ) = > write! ( f , " {}=={} " , self . package_id . name , version ) ,
( true , None ) = > write! ( f , " {} " , self . package_id . name ) ,
( false , Some ( version ) ) = > write! (
2024-06-07 11:28:54 -07:00
f ,
2025-01-15 11:54:32 -05:00
" {}[{}]=={} " ,
2024-08-08 17:25:06 +02:00
self . package_id . name ,
2024-07-18 20:07:49 +02:00
self . extra . iter ( ) . join ( " , " ) ,
2025-01-15 11:54:32 -05:00
version
) ,
( false , None ) = > write! (
f ,
" {}[{}] " ,
self . package_id . name ,
self . extra . iter ( ) . join ( " , " )
) ,
2024-06-07 11:28:54 -07:00
}
}
}
2024-08-08 17:25:06 +02:00
/// A single dependency of a package in a lockfile.
2024-06-24 14:29:01 -04:00
#[ derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, serde::Deserialize) ]
2025-04-28 11:01:17 -04:00
#[ serde(rename_all = " kebab-case " ) ]
2024-06-24 14:29:01 -04:00
struct DependencyWire {
#[ serde(flatten) ]
2024-08-08 17:25:06 +02:00
package_id : PackageIdForDependency ,
2024-07-18 20:07:49 +02:00
#[ serde(default) ]
extra : BTreeSet < ExtraName > ,
2024-08-10 13:23:29 -04:00
#[ serde(default) ]
2024-08-19 18:05:01 -04:00
marker : SimplifiedMarkerTree ,
2024-06-24 14:29:01 -04:00
}
impl DependencyWire {
2024-06-24 15:03:45 -04:00
fn unwire (
self ,
2024-08-19 18:05:01 -04:00
requires_python : & RequiresPython ,
2024-08-08 17:25:06 +02:00
unambiguous_package_ids : & FxHashMap < PackageName , PackageId > ,
2024-06-24 15:03:45 -04:00
) -> Result < Dependency , LockError > {
2024-11-30 14:07:07 -05:00
let complexified_marker = self . marker . into_marker ( requires_python ) ;
2024-06-24 14:29:01 -04:00
Ok ( Dependency {
2024-08-08 17:25:06 +02:00
package_id : self . package_id . unwire ( unambiguous_package_ids ) ? ,
2024-06-24 14:29:01 -04:00
extra : self . extra ,
2024-08-19 18:05:01 -04:00
simplified_marker : self . marker ,
2024-12-10 10:57:22 -05:00
complexified_marker : UniversalMarker ::from_combined ( complexified_marker ) ,
2024-06-24 14:29:01 -04:00
} )
}
}
2024-07-25 10:22:36 -04:00
/// A single hash for a distribution artifact in a lockfile.
2024-04-29 14:03:17 -04:00
///
/// A hash is encoded as a single TOML string in the format
/// `{algorithm}:{digest}`.
2024-07-09 17:08:27 -04:00
#[ derive(Clone, Debug, PartialEq, Eq) ]
2024-06-03 15:00:35 -04:00
struct Hash ( HashDigest ) ;
2024-04-29 14:03:17 -04:00
impl From < HashDigest > for Hash {
fn from ( hd : HashDigest ) -> Hash {
Hash ( hd )
}
}
2024-12-25 14:18:01 -05:00
impl FromStr for Hash {
2024-04-29 14:03:17 -04:00
type Err = HashParseError ;
fn from_str ( s : & str ) -> Result < Hash , HashParseError > {
let ( algorithm , digest ) = s . split_once ( ':' ) . ok_or ( HashParseError (
" expected '{algorithm}:{digest}', but found no ':' in hash digest " ,
) ) ? ;
let algorithm = algorithm
. parse ( )
. map_err ( | _ | HashParseError ( " unrecognized hash algorithm " ) ) ? ;
Ok ( Hash ( HashDigest {
algorithm ,
digest : digest . into ( ) ,
} ) )
}
}
2024-12-25 14:18:01 -05:00
impl Display for Hash {
2024-04-29 14:03:17 -04:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
write! ( f , " {}:{} " , self . 0. algorithm , self . 0. digest )
}
}
impl < ' de > serde ::Deserialize < ' de > for Hash {
2025-02-25 06:28:16 -08:00
fn deserialize < D > ( deserializer : D ) -> Result < Hash , D ::Error >
2024-04-29 14:03:17 -04:00
where
D : serde ::de ::Deserializer < ' de > ,
{
2025-02-25 06:28:16 -08:00
struct Visitor ;
impl serde ::de ::Visitor < '_ > for Visitor {
type Value = Hash ;
fn expecting ( & self , f : & mut Formatter ) -> std ::fmt ::Result {
f . write_str ( " a string " )
}
fn visit_str < E : serde ::de ::Error > ( self , v : & str ) -> Result < Self ::Value , E > {
Hash ::from_str ( v ) . map_err ( serde ::de ::Error ::custom )
}
}
deserializer . deserialize_str ( Visitor )
2024-04-29 14:03:17 -04:00
}
}
2025-04-21 17:21:17 -04:00
impl From < Hash > for Hashes {
fn from ( value : Hash ) -> Self {
match value . 0. algorithm {
HashAlgorithm ::Md5 = > Hashes {
md5 : Some ( value . 0. digest ) ,
sha256 : None ,
sha384 : None ,
sha512 : None ,
2025-04-29 18:39:41 -04:00
blake2b : None ,
2025-04-21 17:21:17 -04:00
} ,
HashAlgorithm ::Sha256 = > Hashes {
md5 : None ,
sha256 : Some ( value . 0. digest ) ,
sha384 : None ,
sha512 : None ,
2025-04-29 18:39:41 -04:00
blake2b : None ,
2025-04-21 17:21:17 -04:00
} ,
HashAlgorithm ::Sha384 = > Hashes {
md5 : None ,
sha256 : None ,
sha384 : Some ( value . 0. digest ) ,
sha512 : None ,
2025-04-29 18:39:41 -04:00
blake2b : None ,
2025-04-21 17:21:17 -04:00
} ,
HashAlgorithm ::Sha512 = > Hashes {
md5 : None ,
sha256 : None ,
sha384 : None ,
sha512 : Some ( value . 0. digest ) ,
2025-04-29 18:39:41 -04:00
blake2b : None ,
} ,
HashAlgorithm ::Blake2b = > Hashes {
md5 : None ,
sha256 : None ,
sha384 : None ,
sha512 : None ,
blake2b : Some ( value . 0. digest ) ,
2025-04-21 17:21:17 -04:00
} ,
}
}
}
2024-08-14 20:12:32 -04:00
/// Convert a [`FileLocation`] into a normalized [`UrlString`].
fn normalize_file_location ( location : & FileLocation ) -> Result < UrlString , ToUrlError > {
match location {
2025-06-27 19:54:52 +02:00
FileLocation ::AbsoluteUrl ( absolute ) = > Ok ( absolute . without_fragment ( ) . into_owned ( ) ) ,
2024-08-24 07:52:43 -04:00
FileLocation ::RelativeUrl ( _ , _ ) = > Ok ( normalize_url ( location . to_url ( ) ? ) ) ,
2024-08-14 20:12:32 -04:00
}
}
2025-05-27 00:05:30 +02:00
/// Convert a [`DisplaySafeUrl`] into a normalized [`UrlString`] by removing the fragment.
fn normalize_url ( mut url : DisplaySafeUrl ) -> UrlString {
2024-08-14 20:12:32 -04:00
url . set_fragment ( None ) ;
UrlString ::from ( url )
}
2024-08-14 20:00:15 -04:00
/// Normalize a [`Requirement`], which could come from a lockfile, a `pyproject.toml`, etc.
///
/// Performs the following steps:
///
/// 1. Removes any sensitive credentials.
/// 2. Ensures that the lock and install paths are appropriately framed with respect to the
/// current [`Workspace`].
/// 3. Removes the `origin` field, which is only used in `requirements.txt`.
2025-05-26 14:17:19 -04:00
/// 4. Simplifies the markers using the provided [`RequiresPython`] instance.
2025-01-22 12:06:05 -05:00
fn normalize_requirement (
mut requirement : Requirement ,
root : & Path ,
2025-05-26 14:17:19 -04:00
requires_python : & RequiresPython ,
2025-01-22 12:06:05 -05:00
) -> Result < Requirement , LockError > {
// Sort the extras and groups for consistency.
requirement . extras . sort ( ) ;
requirement . groups . sort ( ) ;
// Normalize the requirement source.
2024-08-14 20:00:15 -04:00
match requirement . source {
RequirementSource ::Git {
2025-02-18 03:14:06 +01:00
git ,
2024-08-14 20:00:15 -04:00
subdirectory ,
2024-12-22 09:07:04 -05:00
url : _ ,
2024-08-14 20:00:15 -04:00
} = > {
2025-02-18 03:14:06 +01:00
// Reconstruct the Git URL.
let git = {
let mut repository = git . repository ( ) . clone ( ) ;
2025-05-27 00:05:30 +02:00
// Remove the credentials.
repository . remove_credentials ( ) ;
2024-08-14 20:00:15 -04:00
2025-02-18 03:14:06 +01:00
// Remove the fragment and query from the URL; they're already present in the source.
repository . set_fragment ( None ) ;
repository . set_query ( None ) ;
GitUrl ::from_fields ( repository , git . reference ( ) . clone ( ) , git . precise ( ) ) ?
} ;
2024-12-22 09:07:04 -05:00
// Reconstruct the PEP 508 URL from the underlying data.
2025-05-27 00:05:30 +02:00
let url = DisplaySafeUrl ::from ( ParsedGitUrl {
2025-02-18 03:14:06 +01:00
url : git . clone ( ) ,
2024-12-22 09:07:04 -05:00
subdirectory : subdirectory . clone ( ) ,
} ) ;
2024-08-14 20:00:15 -04:00
2024-08-16 13:24:27 -04:00
Ok ( Requirement {
2024-08-14 20:00:15 -04:00
name : requirement . name ,
extras : requirement . extras ,
2024-12-03 19:55:51 -05:00
groups : requirement . groups ,
2025-05-26 14:17:19 -04:00
marker : requires_python . simplify_markers ( requirement . marker ) ,
2024-08-14 20:00:15 -04:00
source : RequirementSource ::Git {
2025-02-18 03:14:06 +01:00
git ,
2024-08-14 20:00:15 -04:00
subdirectory ,
2024-12-22 09:07:04 -05:00
url : VerbatimUrl ::from_url ( url ) ,
2024-08-14 20:00:15 -04:00
} ,
origin : None ,
2024-08-16 13:24:27 -04:00
} )
2024-08-14 20:00:15 -04:00
}
RequirementSource ::Path {
2024-08-23 22:19:10 -04:00
install_path ,
2024-08-14 20:00:15 -04:00
ext ,
url : _ ,
} = > {
2025-03-25 17:56:06 -04:00
let install_path =
uv_fs ::normalize_path_buf ( root . join ( & install_path ) ) . into_boxed_path ( ) ;
2025-01-20 16:36:18 -05:00
let url = VerbatimUrl ::from_normalized_path ( & install_path )
2024-08-16 13:24:27 -04:00
. map_err ( LockErrorKind ::RequirementVerbatimUrl ) ? ;
Ok ( Requirement {
2024-08-14 20:00:15 -04:00
name : requirement . name ,
extras : requirement . extras ,
2024-12-03 19:55:51 -05:00
groups : requirement . groups ,
2025-05-26 14:17:19 -04:00
marker : requires_python . simplify_markers ( requirement . marker ) ,
2024-08-14 20:00:15 -04:00
source : RequirementSource ::Path {
install_path ,
ext ,
url ,
} ,
origin : None ,
2024-08-16 13:24:27 -04:00
} )
2024-08-14 20:00:15 -04:00
}
RequirementSource ::Directory {
2024-08-23 22:19:10 -04:00
install_path ,
2024-08-14 20:00:15 -04:00
editable ,
2024-08-27 21:19:05 -04:00
r#virtual ,
2024-08-14 20:00:15 -04:00
url : _ ,
} = > {
2025-03-25 17:56:06 -04:00
let install_path =
uv_fs ::normalize_path_buf ( root . join ( & install_path ) ) . into_boxed_path ( ) ;
2025-01-20 16:36:18 -05:00
let url = VerbatimUrl ::from_normalized_path ( & install_path )
2024-08-16 13:24:27 -04:00
. map_err ( LockErrorKind ::RequirementVerbatimUrl ) ? ;
Ok ( Requirement {
2024-08-14 20:00:15 -04:00
name : requirement . name ,
extras : requirement . extras ,
2024-12-03 19:55:51 -05:00
groups : requirement . groups ,
2025-05-26 14:17:19 -04:00
marker : requires_python . simplify_markers ( requirement . marker ) ,
2024-08-14 20:00:15 -04:00
source : RequirementSource ::Directory {
install_path ,
editable ,
2024-08-27 21:19:05 -04:00
r#virtual ,
2024-08-14 20:00:15 -04:00
url ,
} ,
origin : None ,
2024-08-16 13:24:27 -04:00
} )
2024-08-14 20:00:15 -04:00
}
2024-10-17 17:38:21 -04:00
RequirementSource ::Registry {
specifier ,
2025-03-24 10:15:49 -04:00
index ,
2024-11-18 20:06:25 -05:00
conflict ,
2024-10-17 17:38:21 -04:00
} = > {
2025-03-24 10:15:49 -04:00
// Round-trip the index to remove anything apart from the URL.
let index = index
. map ( | index | index . url . into_url ( ) )
. map ( | mut index | {
2025-05-27 00:05:30 +02:00
index . remove_credentials ( ) ;
2025-03-24 10:15:49 -04:00
index
} )
. map ( | index | IndexMetadata ::from ( IndexUrl ::from ( VerbatimUrl ::from_url ( index ) ) ) ) ;
2024-10-17 17:38:21 -04:00
Ok ( Requirement {
name : requirement . name ,
extras : requirement . extras ,
2024-12-03 19:55:51 -05:00
groups : requirement . groups ,
2025-05-26 14:17:19 -04:00
marker : requires_python . simplify_markers ( requirement . marker ) ,
2024-11-18 20:06:25 -05:00
source : RequirementSource ::Registry {
specifier ,
index ,
conflict ,
} ,
2024-10-17 17:38:21 -04:00
origin : None ,
} )
}
RequirementSource ::Url {
2024-12-20 19:00:50 -05:00
mut location ,
2024-10-17 17:38:21 -04:00
subdirectory ,
ext ,
2024-12-22 09:07:04 -05:00
url : _ ,
2024-12-20 19:00:50 -05:00
} = > {
2025-05-27 00:05:30 +02:00
// Remove the credentials.
location . remove_credentials ( ) ;
2024-12-20 19:00:50 -05:00
2024-12-31 12:59:26 -05:00
// Remove the fragment from the URL; it's already present in the source.
2024-12-22 09:07:04 -05:00
location . set_fragment ( None ) ;
// Reconstruct the PEP 508 URL from the underlying data.
2025-05-27 00:05:30 +02:00
let url = DisplaySafeUrl ::from ( ParsedArchiveUrl {
2024-12-22 09:07:04 -05:00
url : location . clone ( ) ,
subdirectory : subdirectory . clone ( ) ,
ext ,
} ) ;
2024-12-20 19:00:50 -05:00
Ok ( Requirement {
name : requirement . name ,
extras : requirement . extras ,
groups : requirement . groups ,
2025-05-26 14:17:19 -04:00
marker : requires_python . simplify_markers ( requirement . marker ) ,
2024-12-20 19:00:50 -05:00
source : RequirementSource ::Url {
location ,
subdirectory ,
ext ,
2024-12-22 09:07:04 -05:00
url : VerbatimUrl ::from_url ( url ) ,
2024-12-20 19:00:50 -05:00
} ,
origin : None ,
} )
}
2024-08-14 20:00:15 -04:00
}
}
2025-01-20 12:46:46 -05:00
#[ derive(Debug) ]
pub struct LockError {
kind : Box < LockErrorKind > ,
2025-04-27 12:56:50 -04:00
hint : Option < WheelTagHint > ,
2025-01-20 12:46:46 -05:00
}
impl std ::error ::Error for LockError {
fn source ( & self ) -> Option < & ( dyn Error + 'static ) > {
self . kind . source ( )
}
}
impl std ::fmt ::Display for LockError {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
write! ( f , " {} " , self . kind ) ? ;
if let Some ( hint ) = & self . hint {
write! ( f , " \n \n {hint} " ) ? ;
}
Ok ( ( ) )
}
}
2024-06-11 04:40:48 -07:00
2024-10-10 16:01:20 +02:00
impl LockError {
/// Returns true if the [`LockError`] is a resolver error.
pub fn is_resolution ( & self ) -> bool {
2025-01-20 12:46:46 -05:00
matches! ( & * self . kind , LockErrorKind ::Resolution { .. } )
2024-10-10 16:01:20 +02:00
}
}
2024-06-11 04:40:48 -07:00
impl < E > From < E > for LockError
where
LockErrorKind : From < E > ,
{
fn from ( err : E ) -> Self {
2025-01-20 12:46:46 -05:00
LockError {
kind : Box ::new ( LockErrorKind ::from ( err ) ) ,
hint : None ,
}
}
}
#[ derive(Debug, Clone, PartialEq, Eq) ]
#[ allow(clippy::enum_variant_names) ]
2025-04-27 12:56:50 -04:00
enum WheelTagHint {
2025-01-20 12:46:46 -05:00
/// None of the available wheels for a package have a compatible Python language tag (e.g.,
/// `cp310` in `cp310-abi3-manylinux_2_17_x86_64.whl`).
LanguageTags {
package : PackageName ,
version : Option < Version > ,
tags : BTreeSet < LanguageTag > ,
best : Option < LanguageTag > ,
} ,
/// None of the available wheels for a package have a compatible ABI tag (e.g., `abi3` in
/// `cp310-abi3-manylinux_2_17_x86_64.whl`).
AbiTags {
package : PackageName ,
version : Option < Version > ,
tags : BTreeSet < AbiTag > ,
best : Option < AbiTag > ,
} ,
/// None of the available wheels for a package have a compatible platform tag (e.g.,
/// `manylinux_2_17_x86_64` in `cp310-abi3-manylinux_2_17_x86_64.whl`).
PlatformTags {
package : PackageName ,
version : Option < Version > ,
tags : BTreeSet < PlatformTag > ,
best : Option < PlatformTag > ,
} ,
}
2025-04-27 12:56:50 -04:00
impl WheelTagHint {
/// Generate a [`WheelTagHint`] from the given (incompatible) wheels.
fn from_wheels (
name : & PackageName ,
version : Option < & Version > ,
filenames : & [ & WheelFilename ] ,
tags : & Tags ,
) -> Option < WheelTagHint > {
let incompatibility = filenames
. iter ( )
. map ( | filename | {
tags . compatibility (
filename . python_tags ( ) ,
filename . abi_tags ( ) ,
filename . platform_tags ( ) ,
)
} )
. max ( ) ? ;
match incompatibility {
TagCompatibility ::Incompatible ( IncompatibleTag ::Python ) = > {
let best = tags . python_tag ( ) ;
let tags = Self ::python_tags ( filenames . iter ( ) . copied ( ) ) . collect ::< BTreeSet < _ > > ( ) ;
if tags . is_empty ( ) {
None
} else {
Some ( WheelTagHint ::LanguageTags {
package : name . clone ( ) ,
version : version . cloned ( ) ,
tags ,
best ,
} )
}
}
TagCompatibility ::Incompatible ( IncompatibleTag ::Abi ) = > {
let best = tags . abi_tag ( ) ;
let tags = Self ::abi_tags ( filenames . iter ( ) . copied ( ) )
// Ignore `none`, which is universally compatible.
//
// As an example, `none` can appear here if we're solving for Python 3.13, and
// the distribution includes a wheel for `cp312-none-macosx_11_0_arm64`.
//
// In that case, the wheel isn't compatible, but when solving for Python 3.13,
// the `cp312` Python tag _can_ be compatible (e.g., for `cp312-abi3-macosx_11_0_arm64.whl`),
// so this is considered an ABI incompatibility rather than Python incompatibility.
. filter ( | tag | * tag ! = AbiTag ::None )
. collect ::< BTreeSet < _ > > ( ) ;
if tags . is_empty ( ) {
None
} else {
Some ( WheelTagHint ::AbiTags {
package : name . clone ( ) ,
version : version . cloned ( ) ,
tags ,
best ,
} )
}
}
TagCompatibility ::Incompatible ( IncompatibleTag ::Platform ) = > {
let best = tags . platform_tag ( ) . cloned ( ) ;
let tags = Self ::platform_tags ( filenames . iter ( ) . copied ( ) , tags )
. cloned ( )
. collect ::< BTreeSet < _ > > ( ) ;
if tags . is_empty ( ) {
None
} else {
Some ( WheelTagHint ::PlatformTags {
package : name . clone ( ) ,
version : version . cloned ( ) ,
tags ,
best ,
} )
}
}
_ = > None ,
}
}
/// Returns an iterator over the compatible Python tags of the available wheels.
fn python_tags < ' a > (
filenames : impl Iterator < Item = & ' a WheelFilename > + ' a ,
) -> impl Iterator < Item = LanguageTag > + ' a {
filenames
. flat_map ( uv_distribution_filename ::WheelFilename ::python_tags )
. copied ( )
}
/// Returns an iterator over the compatible Python tags of the available wheels.
fn abi_tags < ' a > (
filenames : impl Iterator < Item = & ' a WheelFilename > + ' a ,
) -> impl Iterator < Item = AbiTag > + ' a {
filenames
. flat_map ( uv_distribution_filename ::WheelFilename ::abi_tags )
. copied ( )
}
/// Returns the set of platform tags for the distribution that are ABI-compatible with the given
/// tags.
fn platform_tags < ' a > (
filenames : impl Iterator < Item = & ' a WheelFilename > + ' a ,
tags : & ' a Tags ,
) -> impl Iterator < Item = & ' a PlatformTag > + ' a {
filenames . flat_map ( move | filename | {
if filename . python_tags ( ) . iter ( ) . any ( | wheel_py | {
filename
. abi_tags ( )
. iter ( )
. any ( | wheel_abi | tags . is_compatible_abi ( * wheel_py , * wheel_abi ) )
} ) {
filename . platform_tags ( ) . iter ( )
} else {
[ ] . iter ( )
}
} )
}
}
impl std ::fmt ::Display for WheelTagHint {
2025-01-20 12:46:46 -05:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
Self ::LanguageTags {
package ,
version ,
tags ,
best ,
} = > {
if let Some ( best ) = best {
let s = if tags . len ( ) = = 1 { " " } else { " s " } ;
let best = if let Some ( pretty ) = best . pretty ( ) {
format! ( " {} (` {} `) " , pretty . cyan ( ) , best . cyan ( ) )
} else {
format! ( " {} " , best . cyan ( ) )
} ;
if let Some ( version ) = version {
write! (
f ,
" {}{} You're using {}, but `{}` ({}) only has wheels with the following Python implementation tag{s}: {} " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
best ,
package . cyan ( ) ,
format! ( " v {version} " ) . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
} else {
write! (
f ,
" {}{} You're using {}, but `{}` only has wheels with the following Python implementation tag{s}: {} " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
best ,
package . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
}
} else {
let s = if tags . len ( ) = = 1 { " " } else { " s " } ;
if let Some ( version ) = version {
write! (
f ,
" {}{} Wheels are available for `{}` ({}) with the following Python implementation tag{s}: {} " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
package . cyan ( ) ,
format! ( " v {version} " ) . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
} else {
write! (
f ,
" {}{} Wheels are available for `{}` with the following Python implementation tag{s}: {} " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
package . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
}
}
}
Self ::AbiTags {
package ,
version ,
tags ,
best ,
} = > {
if let Some ( best ) = best {
let s = if tags . len ( ) = = 1 { " " } else { " s " } ;
let best = if let Some ( pretty ) = best . pretty ( ) {
format! ( " {} (` {} `) " , pretty . cyan ( ) , best . cyan ( ) )
} else {
format! ( " {} " , best . cyan ( ) )
} ;
if let Some ( version ) = version {
write! (
f ,
2025-03-14 19:29:44 +01:00
" {}{} You're using {}, but `{}` ({}) only has wheels with the following Python ABI tag{s}: {} " ,
2025-01-20 12:46:46 -05:00
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
best ,
package . cyan ( ) ,
format! ( " v {version} " ) . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
} else {
write! (
f ,
" {}{} You're using {}, but `{}` only has wheels with the following Python ABI tag{s}: {} " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
best ,
package . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
}
} else {
let s = if tags . len ( ) = = 1 { " " } else { " s " } ;
if let Some ( version ) = version {
write! (
f ,
" {}{} Wheels are available for `{}` ({}) with the following Python ABI tag{s}: {} " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
package . cyan ( ) ,
format! ( " v {version} " ) . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
} else {
write! (
f ,
" {}{} Wheels are available for `{}` with the following Python ABI tag{s}: {} " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
package . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
}
}
}
Self ::PlatformTags {
package ,
version ,
tags ,
best ,
} = > {
let s = if tags . len ( ) = = 1 { " " } else { " s " } ;
if let Some ( best ) = best {
let best = if let Some ( pretty ) = best . pretty ( ) {
format! ( " {} (` {} `) " , pretty . cyan ( ) , best . cyan ( ) )
} else {
format! ( " ` {} ` " , best . cyan ( ) )
} ;
2025-06-06 21:15:52 +02:00
let package_ref = if let Some ( version ) = version {
format! ( " ` {} ` ( {} ) " , package . cyan ( ) , format! ( " v {version} " ) . cyan ( ) )
2025-01-20 12:46:46 -05:00
} else {
2025-06-06 21:15:52 +02:00
format! ( " ` {} ` " , package . cyan ( ) )
} ;
writeln! (
f ,
" {}{} You're on {}, but {} only has wheels for the following platform{s}: {}; consider adding your platform to `{}` to ensure uv resolves to a version with compatible wheels " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
best ,
package_ref ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
" tool.uv.required-environments " . green ( )
)
2025-01-20 12:46:46 -05:00
} else {
if let Some ( version ) = version {
write! (
f ,
" {}{} Wheels are available for `{}` ({}) on the following platform{s}: {} " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
package . cyan ( ) ,
format! ( " v {version} " ) . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
} else {
write! (
f ,
" {}{} Wheels are available for `{}` on the following platform{s}: {} " ,
" hint " . bold ( ) . cyan ( ) ,
" : " . bold ( ) ,
package . cyan ( ) ,
tags . iter ( )
. map ( | tag | format! ( " ` {} ` " , tag . cyan ( ) ) )
. join ( " , " ) ,
)
}
}
}
}
2024-06-11 04:40:48 -07:00
}
}
2024-04-29 14:03:17 -04:00
/// An error that occurs when generating a `Lock` data structure.
///
/// These errors are sometimes the result of possible programming bugs.
/// For example, if there are two or more duplicative distributions given
/// to `Lock::new`, then an error is returned. It's likely that the fault
/// is with the caller somewhere in such cases.
2024-06-11 13:58:03 +02:00
#[ derive(Debug, thiserror::Error) ]
2024-04-29 14:03:17 -04:00
enum LockErrorKind {
2024-08-08 17:25:06 +02:00
/// An error that occurs when multiple packages with the same
2024-04-29 14:03:17 -04:00
/// ID were found.
2025-01-18 13:50:20 -05:00
#[ error( " Found duplicate package `{id}` " , id = id.cyan()) ]
2024-08-08 17:25:06 +02:00
DuplicatePackage {
/// The ID of the conflicting package.
id : PackageId ,
2024-04-29 14:03:17 -04:00
} ,
/// An error that occurs when there are multiple dependencies for the
2024-08-08 17:25:06 +02:00
/// same package that have identical identifiers.
2025-01-18 13:50:20 -05:00
#[ error( " For package `{id}`, found duplicate dependency `{dependency}` " , id = id.cyan(), dependency = dependency.cyan()) ]
2024-04-29 14:03:17 -04:00
DuplicateDependency {
2024-08-08 17:25:06 +02:00
/// The ID of the package for which a duplicate dependency was
2024-04-29 14:03:17 -04:00
/// found.
2024-08-08 17:25:06 +02:00
id : PackageId ,
2024-04-29 14:03:17 -04:00
/// The ID of the conflicting dependency.
2024-06-07 11:28:54 -07:00
dependency : Dependency ,
2024-04-29 14:03:17 -04:00
} ,
2024-06-07 11:18:31 -07:00
/// An error that occurs when there are multiple dependencies for the
2024-08-08 17:25:06 +02:00
/// same package that have identical identifiers, as part of the
/// that package's optional dependencies.
2025-01-18 13:50:20 -05:00
#[ error( " For package `{id}`, found duplicate dependency `{dependency}` " , id = format!( " {id}[{extra}] " ).cyan(), dependency = dependency.cyan()) ]
2024-06-07 11:18:31 -07:00
DuplicateOptionalDependency {
2024-08-08 17:25:06 +02:00
/// The ID of the package for which a duplicate dependency was
2024-06-07 11:18:31 -07:00
/// found.
2024-08-08 17:25:06 +02:00
id : PackageId ,
2024-10-22 10:18:16 -04:00
/// The name of the extra.
2024-06-07 11:18:31 -07:00
extra : ExtraName ,
/// The ID of the conflicting dependency.
2024-06-07 11:28:54 -07:00
dependency : Dependency ,
2024-06-07 11:18:31 -07:00
} ,
/// An error that occurs when there are multiple dependencies for the
2024-08-08 17:25:06 +02:00
/// same package that have identical identifiers, as part of the
/// that package's development dependencies.
2025-01-18 13:50:20 -05:00
#[ error( " For package `{id}`, found duplicate dependency `{dependency}` " , id = format!( " {id}:{group} " ).cyan(), dependency = dependency.cyan()) ]
2024-06-07 11:18:31 -07:00
DuplicateDevDependency {
2024-08-08 17:25:06 +02:00
/// The ID of the package for which a duplicate dependency was
2024-06-07 11:18:31 -07:00
/// found.
2024-08-08 17:25:06 +02:00
id : PackageId ,
2024-06-07 11:18:31 -07:00
/// The name of the dev dependency group.
group : GroupName ,
/// The ID of the conflicting dependency.
2024-06-07 11:28:54 -07:00
dependency : Dependency ,
2024-06-07 11:18:31 -07:00
} ,
2024-04-29 14:03:17 -04:00
/// An error that occurs when the URL to a file for a wheel or
/// source dist could not be converted to a structured `url::Url`.
2025-01-01 12:35:30 -05:00
#[ error(transparent) ]
InvalidUrl (
2024-04-29 14:03:17 -04:00
/// The underlying error that occurred. This includes the
/// errant URL in its error message.
2025-01-01 12:35:30 -05:00
#[ from ]
2024-06-11 04:40:48 -07:00
ToUrlError ,
) ,
2024-08-08 21:39:47 -04:00
/// An error that occurs when the extension can't be determined
/// for a given wheel or source distribution.
2025-06-23 14:52:07 -04:00
#[ error( " Failed to parse file extension for `{id}`; expected one of: {err} " , id = id.cyan()) ]
MissingExtension {
/// The filename that was expected to have an extension.
id : PackageId ,
/// The list of valid extensions that were expected.
err : ExtensionError ,
} ,
2025-01-18 13:50:20 -05:00
/// Failed to parse a Git source URL.
#[ error( " Failed to parse Git URL " ) ]
2024-06-28 11:23:06 -04:00
InvalidGitSourceUrl (
/// The underlying error that occurred. This includes the
/// errant URL in the message.
#[ source ]
SourceParseError ,
) ,
2025-04-20 21:58:12 -04:00
#[ error( " Failed to parse timestamp " ) ]
InvalidTimestamp (
/// The underlying error that occurred. This includes the
/// errant timestamp in the message.
#[ source ]
jiff ::Error ,
) ,
2024-06-11 04:40:48 -07:00
/// An error that occurs when there's an unrecognized dependency.
///
2024-08-08 17:25:06 +02:00
/// That is, a dependency for a package that isn't in the lockfile.
2025-01-18 13:50:20 -05:00
#[ error( " For package `{id}`, found dependency `{dependency}` with no locked package " , id = id.cyan(), dependency = dependency.cyan()) ]
2024-05-03 08:18:36 -04:00
UnrecognizedDependency {
2024-08-08 17:25:06 +02:00
/// The ID of the package that has an unrecognized dependency.
id : PackageId ,
/// The ID of the dependency that doesn't have a corresponding package
2024-06-11 04:40:48 -07:00
/// entry.
dependency : Dependency ,
2024-05-03 08:18:36 -04:00
} ,
2024-05-10 10:32:30 -04:00
/// An error that occurs when a hash is expected (or not) for a particular
/// artifact, but one was not found (or was).
2025-01-18 13:50:20 -05:00
#[ error( " Since the package `{id}` comes from a {source} dependency, a hash was {expected} but one was not found for {artifact_type} " , id = id.cyan(), source = id.source.name(), expected = if *expected { " expected " } else { " not expected " }) ]
2024-05-10 10:32:30 -04:00
Hash {
2024-08-08 17:25:06 +02:00
/// The ID of the package that has a missing hash.
id : PackageId ,
/// The specific type of artifact, e.g., "source package"
2024-05-10 10:32:30 -04:00
/// or "wheel".
artifact_type : & 'static str ,
/// When true, a hash is expected to be present.
expected : bool ,
} ,
2024-08-08 17:25:06 +02:00
/// An error that occurs when a package is included with an extra name,
/// but no corresponding base package (i.e., without the extra) exists.
2025-01-18 13:50:20 -05:00
#[ error( " Found package `{id}` with extra `{extra}` but no base package " , id = id.cyan(), extra = extra.cyan()) ]
2024-06-05 21:40:17 -04:00
MissingExtraBase {
2024-08-08 17:25:06 +02:00
/// The ID of the package that has a missing base.
id : PackageId ,
2024-06-03 15:00:35 -04:00
/// The extra name that was found.
extra : ExtraName ,
} ,
2024-08-08 17:25:06 +02:00
/// An error that occurs when a package is included with a development
/// dependency group, but no corresponding base package (i.e., without
2024-06-05 21:40:17 -04:00
/// the group) exists.
2025-01-18 13:50:20 -05:00
#[ error( " Found package `{id}` with development dependency group `{group}` but no base package " , id = id.cyan()) ]
2024-06-05 21:40:17 -04:00
MissingDevBase {
2024-08-08 17:25:06 +02:00
/// The ID of the package that has a missing base.
id : PackageId ,
2024-06-05 21:40:17 -04:00
/// The development dependency group that was found.
group : GroupName ,
} ,
2024-06-10 14:44:36 +02:00
/// An error that occurs from an invalid lockfile where a wheel comes from a non-wheel source
/// such as a directory.
2024-10-11 01:50:00 +02:00
#[ error( " Wheels cannot come from {source_type} sources " ) ]
2024-06-10 14:44:36 +02:00
InvalidWheelSource {
/// The ID of the distribution that has a missing base.
2024-08-08 17:25:06 +02:00
id : PackageId ,
2024-06-10 14:44:36 +02:00
/// The kind of the invalid source.
2024-06-11 04:40:48 -07:00
source_type : & 'static str ,
2024-06-10 14:44:36 +02:00
} ,
2024-08-24 22:41:47 -04:00
/// An error that occurs when a distribution indicates that it is sourced from a remote
/// registry, but is missing a URL.
2025-01-18 13:50:20 -05:00
#[ error( " Found registry distribution `{name}` ({version}) without a valid URL " , name = name.cyan(), version = format!( " v{version} " ).cyan()) ]
2024-06-12 10:01:29 -07:00
MissingUrl {
2024-08-09 23:57:16 +02:00
/// The name of the distribution that is missing a URL.
name : PackageName ,
/// The version of the distribution that is missing a URL.
version : Version ,
2024-06-12 10:01:29 -07:00
} ,
2024-08-24 22:41:47 -04:00
/// An error that occurs when a distribution indicates that it is sourced from a local registry,
/// but is missing a path.
2025-01-18 13:50:20 -05:00
#[ error( " Found registry distribution `{name}` ({version}) without a valid path " , name = name.cyan(), version = format!( " v{version} " ).cyan()) ]
2024-08-24 22:41:47 -04:00
MissingPath {
/// The name of the distribution that is missing a path.
name : PackageName ,
/// The version of the distribution that is missing a path.
version : Version ,
} ,
2024-06-12 10:01:29 -07:00
/// An error that occurs when a distribution indicates that it is sourced from a registry, but
/// is missing a filename.
2025-01-18 13:50:20 -05:00
#[ error( " Found registry distribution `{id}` without a valid filename " , id = id.cyan()) ]
2024-06-12 10:01:29 -07:00
MissingFilename {
/// The ID of the distribution that is missing a filename.
2024-08-08 17:25:06 +02:00
id : PackageId ,
2024-06-12 10:01:29 -07:00
} ,
2024-06-11 04:40:48 -07:00
/// An error that occurs when a distribution is included with neither wheels nor a source
/// distribution.
2025-01-18 13:50:20 -05:00
#[ error( " Distribution `{id}` can't be installed because it doesn't have a source distribution or wheel for the current platform " , id = id.cyan()) ]
2024-06-10 14:44:36 +02:00
NeitherSourceDistNorWheel {
2024-09-11 15:53:08 -04:00
/// The ID of the distribution.
2024-08-08 17:25:06 +02:00
id : PackageId ,
2024-06-10 14:44:36 +02:00
} ,
2024-09-05 17:45:42 -04:00
/// An error that occurs when a distribution is marked as both `--no-binary` and `--no-build`.
2025-01-18 13:50:20 -05:00
#[ error( " Distribution `{id}` can't be installed because it is marked as both `--no-binary` and `--no-build` " , id = id.cyan()) ]
2024-09-05 17:45:42 -04:00
NoBinaryNoBuild {
/// The ID of the distribution.
id : PackageId ,
} ,
2024-09-11 15:53:08 -04:00
/// An error that occurs when a distribution is marked as `--no-binary`, but no source
2024-09-05 17:45:42 -04:00
/// distribution is available.
2025-01-18 13:50:20 -05:00
#[ error( " Distribution `{id}` can't be installed because it is marked as `--no-binary` but has no source distribution " , id = id.cyan()) ]
2024-09-05 17:45:42 -04:00
NoBinary {
/// The ID of the distribution.
id : PackageId ,
} ,
2024-09-11 15:53:08 -04:00
/// An error that occurs when a distribution is marked as `--no-build`, but no binary
2024-09-05 17:45:42 -04:00
/// distribution is available.
2025-01-18 13:50:20 -05:00
#[ error( " Distribution `{id}` can't be installed because it is marked as `--no-build` but has no binary distribution " , id = id.cyan()) ]
2024-09-05 17:45:42 -04:00
NoBuild {
/// The ID of the distribution.
id : PackageId ,
} ,
2024-09-11 15:53:08 -04:00
/// An error that occurs when a wheel-only distribution is incompatible with the current
/// platform.
2025-01-18 13:50:20 -05:00
#[ error( " Distribution `{id}` can't be installed because the binary distribution is incompatible with the current platform " , id = id.cyan()) ]
2024-09-11 15:53:08 -04:00
IncompatibleWheelOnly {
/// The ID of the distribution.
id : PackageId ,
} ,
/// An error that occurs when a wheel-only source is marked as `--no-binary`.
2025-01-18 13:50:20 -05:00
#[ error( " Distribution `{id}` can't be installed because it is marked as `--no-binary` but is itself a binary distribution " , id = id.cyan()) ]
2024-09-11 15:53:08 -04:00
NoBinaryWheelOnly {
/// The ID of the distribution.
id : PackageId ,
} ,
2024-06-11 13:58:03 +02:00
/// An error that occurs when converting between URLs and paths.
2025-01-18 13:50:20 -05:00
#[ error( " Found dependency `{id}` with no locked distribution " , id = id.cyan()) ]
2024-06-11 13:58:03 +02:00
VerbatimUrl {
/// The ID of the distribution that has a missing base.
2024-08-08 17:25:06 +02:00
id : PackageId ,
2024-06-11 13:58:03 +02:00
/// The inner error we forward.
#[ source ]
err : VerbatimUrlError ,
} ,
2024-08-23 22:19:10 -04:00
/// An error that occurs when parsing an existing requirement.
2024-10-11 01:50:00 +02:00
#[ error( " Could not compute relative path between workspace and distribution " ) ]
2024-08-23 22:19:10 -04:00
DistributionRelativePath (
/// The inner error we forward.
#[ source ]
2024-12-25 14:18:01 -05:00
io ::Error ,
2024-08-23 22:19:10 -04:00
) ,
2024-08-24 22:41:47 -04:00
/// An error that occurs when converting an index URL to a relative path
2024-10-11 01:50:00 +02:00
#[ error( " Could not compute relative path between workspace and index " ) ]
2024-08-24 22:41:47 -04:00
IndexRelativePath (
/// The inner error we forward.
#[ source ]
2024-12-25 14:18:01 -05:00
io ::Error ,
2024-08-24 22:41:47 -04:00
) ,
2024-12-12 13:49:05 -05:00
/// An error that occurs when converting a lockfile path from relative to absolute.
#[ error( " Could not compute absolute path from workspace root and lockfile path " ) ]
AbsolutePath (
/// The inner error we forward.
#[ source ]
2024-12-25 14:18:01 -05:00
io ::Error ,
2024-12-12 13:49:05 -05:00
) ,
2024-08-08 17:25:06 +02:00
/// An error that occurs when an ambiguous `package.dependency` is
2024-06-24 15:03:45 -04:00
/// missing a `version` field.
2025-01-18 13:50:20 -05:00
#[ error( " Dependency `{name}` has missing `version` field but has more than one matching package " , name = name.cyan()) ]
2024-06-24 15:03:45 -04:00
MissingDependencyVersion {
/// The name of the dependency that is missing a `version` field.
name : PackageName ,
} ,
2024-08-08 17:25:06 +02:00
/// An error that occurs when an ambiguous `package.dependency` is
2024-06-24 15:03:45 -04:00
/// missing a `source` field.
2025-01-18 13:50:20 -05:00
#[ error( " Dependency `{name}` has missing `source` field but has more than one matching package " , name = name.cyan()) ]
2024-06-24 15:03:45 -04:00
MissingDependencySource {
/// The name of the dependency that is missing a `source` field.
name : PackageName ,
} ,
2024-08-16 13:24:27 -04:00
/// An error that occurs when parsing an existing requirement.
2024-10-11 01:50:00 +02:00
#[ error( " Could not compute relative path between workspace and requirement " ) ]
2024-08-16 13:24:27 -04:00
RequirementRelativePath (
/// The inner error we forward.
#[ source ]
2024-12-25 14:18:01 -05:00
io ::Error ,
2024-08-16 13:24:27 -04:00
) ,
/// An error that occurs when parsing an existing requirement.
2024-10-11 01:50:00 +02:00
#[ error( " Could not convert between URL and path " ) ]
2024-08-16 13:24:27 -04:00
RequirementVerbatimUrl (
/// The inner error we forward.
#[ source ]
VerbatimUrlError ,
) ,
2024-08-24 22:41:47 -04:00
/// An error that occurs when parsing a registry's index URL.
2024-10-11 01:50:00 +02:00
#[ error( " Could not convert between URL and path " ) ]
2024-08-24 22:41:47 -04:00
RegistryVerbatimUrl (
/// The inner error we forward.
#[ source ]
VerbatimUrlError ,
) ,
/// An error that occurs when converting a path to a URL.
2025-06-26 15:42:04 -04:00
#[ error( " Failed to convert path to URL: {path} " , path = path.display().cyan()) ]
PathToUrl { path : Box < Path > } ,
2024-08-24 22:41:47 -04:00
/// An error that occurs when converting a URL to a path
2025-06-26 15:42:04 -04:00
#[ error( " Failed to convert URL to path: {url} " , url = url.cyan()) ]
UrlToPath { url : DisplaySafeUrl } ,
2024-09-02 17:01:50 -04:00
/// An error that occurs when multiple packages with the same
/// name were found when identifying the root packages.
2025-01-18 13:50:20 -05:00
#[ error( " Found multiple packages matching `{name}` " , name = name.cyan()) ]
2024-09-02 17:01:50 -04:00
MultipleRootPackages {
/// The ID of the package.
name : PackageName ,
} ,
/// An error that occurs when a root package can't be found.
2025-01-18 13:50:20 -05:00
#[ error( " Could not find root package `{name}` " , name = name.cyan()) ]
2024-09-02 17:01:50 -04:00
MissingRootPackage {
/// The ID of the package.
name : PackageName ,
} ,
2024-10-10 16:01:20 +02:00
/// An error that occurs when resolving metadata for a package.
2025-01-18 13:50:20 -05:00
#[ error( " Failed to generate package metadata for `{id}` " , id = id.cyan()) ]
2024-10-10 16:01:20 +02:00
Resolution {
/// The ID of the distribution that failed to resolve.
id : PackageId ,
/// The inner error we forward.
#[ source ]
err : uv_distribution ::Error ,
} ,
2025-03-17 23:33:32 +01:00
/// A package has inconsistent versions in a single entry
// Using name instead of id since the version in the id is part of the conflict.
2025-03-17 17:34:51 -05:00
#[ error( " The entry for package `{name}` v{version} has wheel `{wheel_filename}` with inconsistent version: v{wheel_version} " , name = name.cyan(), wheel_filename = wheel.filename, wheel_version = wheel.filename.version) ]
2025-03-17 23:33:32 +01:00
InconsistentVersions {
/// The name of the package with the inconsistent entry.
name : PackageName ,
2025-03-17 17:34:51 -05:00
/// The version of the package with the inconsistent entry.
version : Version ,
/// The wheel with the inconsistent version.
wheel : Wheel ,
2025-03-17 23:33:32 +01:00
} ,
2025-01-22 14:25:21 -05:00
#[ error(
" Found conflicting extras `{package1}[{extra1}]` \
and `{package2}[{extra2}]` enabled simultaneously "
) ]
ConflictingExtra {
package1 : PackageName ,
extra1 : ExtraName ,
package2 : PackageName ,
extra2 : ExtraName ,
} ,
2025-02-18 03:14:06 +01:00
#[ error(transparent) ]
GitUrlParse ( #[ from ] GitUrlParseError ) ,
2024-05-03 08:18:36 -04:00
}
2024-04-29 14:03:17 -04:00
/// An error that occurs when a source string could not be parsed.
2024-10-10 16:01:20 +02:00
#[ derive(Debug, thiserror::Error) ]
2024-06-11 04:40:48 -07:00
enum SourceParseError {
2024-04-29 14:03:17 -04:00
/// An error that occurs when the URL in the source is invalid.
2024-10-11 01:50:00 +02:00
#[ error( " Invalid URL in source `{given}` " ) ]
2024-04-29 14:03:17 -04:00
InvalidUrl {
2024-06-11 04:40:48 -07:00
/// The source string given.
given : String ,
2024-04-29 14:03:17 -04:00
/// The URL parse error.
2024-06-11 04:40:48 -07:00
#[ source ]
2024-04-29 14:03:17 -04:00
err : url ::ParseError ,
} ,
2024-05-17 17:23:40 -04:00
/// An error that occurs when a Git URL is missing a precise commit SHA.
2024-10-11 01:50:00 +02:00
#[ error( " Missing SHA in source `{given}` " ) ]
2024-06-11 04:40:48 -07:00
MissingSha {
/// The source string given.
given : String ,
} ,
2024-05-17 17:23:40 -04:00
/// An error that occurs when a Git URL has an invalid SHA.
2024-10-11 01:50:00 +02:00
#[ error( " Invalid SHA in source `{given}` " ) ]
2024-06-11 04:40:48 -07:00
InvalidSha {
/// The source string given.
given : String ,
} ,
2024-04-29 14:03:17 -04:00
}
/// An error that occurs when a hash digest could not be parsed.
#[ derive(Clone, Debug, Eq, PartialEq) ]
2024-06-03 15:00:35 -04:00
struct HashParseError ( & 'static str ) ;
2024-04-29 14:03:17 -04:00
impl std ::error ::Error for HashParseError { }
2024-12-25 14:18:01 -05:00
impl Display for HashParseError {
2024-04-29 14:03:17 -04:00
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
2024-06-11 13:58:03 +02:00
Display ::fmt ( self . 0 , f )
2024-04-29 14:03:17 -04:00
}
}
2024-05-10 10:32:30 -04:00
2024-06-27 20:06:45 +02:00
/// Format an array so that each element is on its own line and has a trailing comma.
///
/// Example:
///
/// ```toml
/// dependencies = [
/// { name = "idna" },
/// { name = "sniffio" },
/// ]
/// ```
2024-07-30 13:11:18 +02:00
fn each_element_on_its_line_array ( elements : impl Iterator < Item = impl Into < Value > > ) -> Array {
2024-06-27 20:06:45 +02:00
let mut array = elements
2024-07-30 13:11:18 +02:00
. map ( | item | {
let mut value = item . into ( ) ;
2024-06-27 20:06:45 +02:00
// Each dependency is on its own line and indented.
2024-07-30 13:11:18 +02:00
value . decor_mut ( ) . set_prefix ( " \n " ) ;
value
2024-06-27 20:06:45 +02:00
} )
. collect ::< Array > ( ) ;
// With a trailing comma, inserting another entry doesn't change the preceding line,
// reducing the diff noise.
array . set_trailing_comma ( true ) ;
// The line break between the last element's comma and the closing square bracket.
array . set_trailing ( " \n " ) ;
array
}
2024-12-10 14:58:39 -05:00
/// Returns the simplified string-ified version of each marker given.
///
2025-01-21 14:04:31 -05:00
/// Note that the marker strings returned will include conflict markers if they
/// are present.
fn simplified_universal_markers (
2024-12-10 14:58:39 -05:00
markers : & [ UniversalMarker ] ,
requires_python : & RequiresPython ,
) -> Vec < String > {
2025-01-21 14:04:31 -05:00
let mut pep508_only = vec! [ ] ;
2024-12-10 14:58:39 -05:00
let mut seen = FxHashSet ::default ( ) ;
for marker in markers {
2025-01-21 14:04:31 -05:00
let simplified =
SimplifiedMarkerTree ::new ( requires_python , marker . pep508 ( ) ) . as_simplified_marker_tree ( ) ;
if seen . insert ( simplified ) {
pep508_only . push ( simplified ) ;
2024-12-10 14:58:39 -05:00
}
}
2025-01-21 14:04:31 -05:00
let any_overlap = pep508_only
. iter ( )
. tuple_combinations ( )
. any ( | ( & marker1 , & marker2 ) | ! marker1 . is_disjoint ( marker2 ) ) ;
let markers = if ! any_overlap {
pep508_only
} else {
markers
. iter ( )
. map ( | marker | {
SimplifiedMarkerTree ::new ( requires_python , marker . combined ( ) )
. as_simplified_marker_tree ( )
} )
. collect ( )
} ;
markers
. into_iter ( )
. filter_map ( MarkerTree ::try_to_string )
. collect ( )
2024-12-10 14:58:39 -05:00
}
2024-05-10 10:32:30 -04:00
#[ cfg(test) ]
2024-11-18 15:11:46 -05:00
mod tests {
2025-01-18 13:50:20 -05:00
use uv_warnings ::anstream ;
2024-11-18 15:11:46 -05:00
use super ::* ;
2025-01-18 13:50:20 -05:00
/// Assert a given display snapshot, stripping ANSI color codes.
macro_rules ! assert_stripped_snapshot {
( $expr :expr , @ $snapshot :literal ) = > { {
let expr = format! ( " {} " , $expr ) ;
let expr = format! ( " {} " , anstream ::adapter ::strip_str ( & expr ) ) ;
insta ::assert_snapshot! ( expr , @ $snapshot ) ;
} } ;
}
2024-11-18 15:11:46 -05:00
#[ test ]
fn missing_dependency_source_unambiguous ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "a"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "b"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package.dependencies]]
name = "a"
version = "0.1.0"
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
#[ test ]
fn missing_dependency_version_unambiguous ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "a"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "b"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package.dependencies]]
name = "a"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
#[ test ]
fn missing_dependency_source_version_unambiguous ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "a"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "b"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package.dependencies]]
name = "a"
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
#[ test ]
fn missing_dependency_source_ambiguous ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "a"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "a"
version = "0.1.1"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "b"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package.dependencies]]
name = "a"
version = "0.1.0"
"# ;
2025-01-18 13:50:20 -05:00
let result = toml ::from_str ::< Lock > ( data ) . unwrap_err ( ) ;
assert_stripped_snapshot! ( result , @ " Dependency `a` has missing `source` field but has more than one matching package " ) ;
2024-11-18 15:11:46 -05:00
}
#[ test ]
fn missing_dependency_version_ambiguous ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "a"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "a"
version = "0.1.1"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "b"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package.dependencies]]
name = "a"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
"# ;
2025-01-18 13:50:20 -05:00
let result = toml ::from_str ::< Lock > ( data ) . unwrap_err ( ) ;
assert_stripped_snapshot! ( result , @ " Dependency `a` has missing `version` field but has more than one matching package " ) ;
2024-11-18 15:11:46 -05:00
}
#[ test ]
fn missing_dependency_source_version_ambiguous ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "a"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "a"
version = "0.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "b"
version = "0.1.0"
2025-02-10 15:21:02 -05:00
source = { registry = "https://pypi.org/simple" }
2024-11-18 15:11:46 -05:00
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package.dependencies]]
name = "a"
"# ;
2025-01-18 13:50:20 -05:00
let result = toml ::from_str ::< Lock > ( data ) . unwrap_err ( ) ;
2025-02-10 15:21:02 -05:00
assert_stripped_snapshot! ( result , @ " Dependency `a` has missing `source` field but has more than one matching package " ) ;
}
#[ test ]
fn missing_dependency_version_dynamic ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "a"
source = { editable = "path/to/a" }
[[package]]
name = "a"
version = "0.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package]]
name = "b"
version = "0.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 }
[[package.dependencies]]
name = "a"
source = { editable = "path/to/a" }
"# ;
let result = toml ::from_str ::< Lock > ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
2024-11-18 15:11:46 -05:00
}
#[ test ]
fn hash_optional_missing ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "anyio"
version = "4.3.0"
source = { registry = "https://pypi.org/simple" }
wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl" }]
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
#[ test ]
fn hash_optional_present ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "anyio"
version = "4.3.0"
source = { registry = "https://pypi.org/simple" }
wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" }]
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
#[ test ]
fn hash_required_present ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "anyio"
version = "4.3.0"
source = { path = "file:///foo/bar" }
wheels = [{ url = "file:///foo/bar/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" }]
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
#[ test ]
fn source_direct_no_subdir ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "anyio"
version = "4.3.0"
source = { url = "https://burntsushi.net" }
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
#[ test ]
fn source_direct_has_subdir ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "anyio"
version = "4.3.0"
source = { url = "https://burntsushi.net", subdirectory = "wat/foo/bar" }
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
#[ test ]
fn source_directory ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "anyio"
version = "4.3.0"
source = { directory = "path/to/dir" }
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
#[ test ]
fn source_editable ( ) {
let data = r # "
version = 1
requires-python = ">=3.12"
[[package]]
name = "anyio"
version = "4.3.0"
source = { editable = "path/to/dir" }
"# ;
let result : Result < Lock , _ > = toml ::from_str ( data ) ;
insta ::assert_debug_snapshot! ( result ) ;
}
}