uv_distribution/metadata/
mod.rs1use std::collections::BTreeMap;
2use std::path::{Path, PathBuf};
3
4use thiserror::Error;
5use uv_auth::CredentialsCache;
6use uv_configuration::SourceStrategy;
7use uv_distribution_types::{GitSourceUrl, IndexLocations, Requirement};
8use uv_normalize::{ExtraName, GroupName, PackageName};
9use uv_pep440::{Version, VersionSpecifiers};
10use uv_pypi_types::{HashDigests, ResolutionMetadata};
11use uv_workspace::dependency_groups::DependencyGroupError;
12use uv_workspace::{WorkspaceCache, WorkspaceError};
13
14pub use crate::metadata::build_requires::{BuildRequires, LoweredExtraBuildDependencies};
15pub use crate::metadata::dependency_groups::SourcedDependencyGroups;
16pub use crate::metadata::lowering::LoweredRequirement;
17pub use crate::metadata::lowering::LoweringError;
18pub use crate::metadata::requires_dist::{FlatRequiresDist, RequiresDist};
19
20mod build_requires;
21mod dependency_groups;
22mod lowering;
23mod requires_dist;
24
25#[derive(Debug, Error)]
26pub enum MetadataError {
27 #[error(transparent)]
28 Workspace(#[from] WorkspaceError),
29 #[error(transparent)]
30 DependencyGroup(#[from] DependencyGroupError),
31 #[error("No pyproject.toml found at: {0}")]
32 MissingPyprojectToml(PathBuf),
33 #[error("Failed to parse entry: `{0}`")]
34 LoweringError(PackageName, #[source] Box<LoweringError>),
35 #[error("Failed to parse entry in group `{0}`: `{1}`")]
36 GroupLoweringError(GroupName, PackageName, #[source] Box<LoweringError>),
37 #[error(
38 "Source entry for `{0}` only applies to extra `{1}`, but the `{1}` extra does not exist. When an extra is present on a source (e.g., `extra = \"{1}\"`), the relevant package must be included in the `project.optional-dependencies` section for that extra (e.g., `project.optional-dependencies = {{ \"{1}\" = [\"{0}\"] }}`)."
39 )]
40 MissingSourceExtra(PackageName, ExtraName),
41 #[error(
42 "Source entry for `{0}` only applies to extra `{1}`, but `{0}` was not found under the `project.optional-dependencies` section for that extra. When an extra is present on a source (e.g., `extra = \"{1}\"`), the relevant package must be included in the `project.optional-dependencies` section for that extra (e.g., `project.optional-dependencies = {{ \"{1}\" = [\"{0}\"] }}`)."
43 )]
44 IncompleteSourceExtra(PackageName, ExtraName),
45 #[error(
46 "Source entry for `{0}` only applies to dependency group `{1}`, but the `{1}` group does not exist. When a group is present on a source (e.g., `group = \"{1}\"`), the relevant package must be included in the `dependency-groups` section for that extra (e.g., `dependency-groups = {{ \"{1}\" = [\"{0}\"] }}`)."
47 )]
48 MissingSourceGroup(PackageName, GroupName),
49 #[error(
50 "Source entry for `{0}` only applies to dependency group `{1}`, but `{0}` was not found under the `dependency-groups` section for that group. When a group is present on a source (e.g., `group = \"{1}\"`), the relevant package must be included in the `dependency-groups` section for that extra (e.g., `dependency-groups = {{ \"{1}\" = [\"{0}\"] }}`)."
51 )]
52 IncompleteSourceGroup(PackageName, GroupName),
53}
54
55#[derive(Debug, Clone)]
56pub struct Metadata {
57 pub name: PackageName,
59 pub version: Version,
60 pub requires_dist: Box<[Requirement]>,
62 pub requires_python: Option<VersionSpecifiers>,
63 pub provides_extra: Box<[ExtraName]>,
64 pub dependency_groups: BTreeMap<GroupName, Box<[Requirement]>>,
65 pub dynamic: bool,
66}
67
68impl Metadata {
69 pub fn from_metadata23(metadata: ResolutionMetadata) -> Self {
72 Self {
73 name: metadata.name,
74 version: metadata.version,
75 requires_dist: Box::into_iter(metadata.requires_dist)
76 .map(Requirement::from)
77 .collect(),
78 requires_python: metadata.requires_python,
79 provides_extra: metadata.provides_extra,
80 dependency_groups: BTreeMap::default(),
81 dynamic: metadata.dynamic,
82 }
83 }
84
85 pub async fn from_workspace(
88 metadata: ResolutionMetadata,
89 install_path: &Path,
90 git_source: Option<&GitWorkspaceMember<'_>>,
91 locations: &IndexLocations,
92 sources: SourceStrategy,
93 cache: &WorkspaceCache,
94 credentials_cache: &CredentialsCache,
95 ) -> Result<Self, MetadataError> {
96 let requires_dist = uv_pypi_types::RequiresDist {
98 name: metadata.name,
99 requires_dist: metadata.requires_dist,
100 provides_extra: metadata.provides_extra,
101 dynamic: metadata.dynamic,
102 };
103 let RequiresDist {
104 name,
105 requires_dist,
106 provides_extra,
107 dependency_groups,
108 dynamic,
109 } = RequiresDist::from_project_maybe_workspace(
110 requires_dist,
111 install_path,
112 git_source,
113 locations,
114 sources,
115 cache,
116 credentials_cache,
117 )
118 .await?;
119
120 Ok(Self {
122 name,
123 version: metadata.version,
124 requires_dist,
125 requires_python: metadata.requires_python,
126 provides_extra,
127 dependency_groups,
128 dynamic,
129 })
130 }
131}
132
133#[derive(Debug, Clone)]
135pub struct ArchiveMetadata {
136 pub metadata: Metadata,
138 pub hashes: HashDigests,
140}
141
142impl ArchiveMetadata {
143 pub fn from_metadata23(metadata: ResolutionMetadata) -> Self {
146 Self {
147 metadata: Metadata::from_metadata23(metadata),
148 hashes: HashDigests::empty(),
149 }
150 }
151}
152
153impl From<Metadata> for ArchiveMetadata {
154 fn from(metadata: Metadata) -> Self {
155 Self {
156 metadata,
157 hashes: HashDigests::empty(),
158 }
159 }
160}
161
162#[derive(Debug, Clone)]
164pub struct GitWorkspaceMember<'a> {
165 pub fetch_root: &'a Path,
168 pub git_source: &'a GitSourceUrl<'a>,
169}