uv_distribution/metadata/
build_requires.rs1use std::collections::BTreeMap;
2use std::path::Path;
3use uv_auth::CredentialsCache;
4use uv_configuration::SourceStrategy;
5use uv_distribution_types::{
6 ExtraBuildRequirement, ExtraBuildRequires, IndexLocations, Requirement,
7};
8use uv_normalize::PackageName;
9use uv_workspace::pyproject::{ExtraBuildDependencies, ExtraBuildDependency, ToolUvSources};
10use uv_workspace::{
11 DiscoveryOptions, MemberDiscovery, ProjectWorkspace, Workspace, WorkspaceCache,
12};
13
14use crate::metadata::{LoweredRequirement, MetadataError};
15
16#[derive(Debug, Clone)]
18pub struct BuildRequires {
19 pub name: Option<PackageName>,
20 pub requires_dist: Vec<Requirement>,
21}
22
23impl BuildRequires {
24 pub fn from_metadata23(metadata: uv_pypi_types::BuildRequires) -> Self {
27 Self {
28 name: metadata.name,
29 requires_dist: metadata
30 .requires_dist
31 .into_iter()
32 .map(Requirement::from)
33 .collect(),
34 }
35 }
36
37 pub async fn from_project_maybe_workspace(
40 metadata: uv_pypi_types::BuildRequires,
41 install_path: &Path,
42 locations: &IndexLocations,
43 sources: SourceStrategy,
44 cache: &WorkspaceCache,
45 credentials_cache: &CredentialsCache,
46 ) -> Result<Self, MetadataError> {
47 let discovery = match sources {
48 SourceStrategy::Enabled => DiscoveryOptions::default(),
49 SourceStrategy::Disabled => DiscoveryOptions {
50 members: MemberDiscovery::None,
51 ..Default::default()
52 },
53 };
54 let Some(project_workspace) =
55 ProjectWorkspace::from_maybe_project_root(install_path, &discovery, cache).await?
56 else {
57 return Ok(Self::from_metadata23(metadata));
58 };
59
60 Self::from_project_workspace(
61 metadata,
62 &project_workspace,
63 locations,
64 sources,
65 credentials_cache,
66 )
67 }
68
69 pub fn from_project_workspace(
71 metadata: uv_pypi_types::BuildRequires,
72 project_workspace: &ProjectWorkspace,
73 locations: &IndexLocations,
74 source_strategy: SourceStrategy,
75 credentials_cache: &CredentialsCache,
76 ) -> Result<Self, MetadataError> {
77 let empty = vec![];
79 let project_indexes = match source_strategy {
80 SourceStrategy::Enabled => project_workspace
81 .current_project()
82 .pyproject_toml()
83 .tool
84 .as_ref()
85 .and_then(|tool| tool.uv.as_ref())
86 .and_then(|uv| uv.index.as_deref())
87 .unwrap_or(&empty),
88 SourceStrategy::Disabled => &empty,
89 };
90
91 let empty = BTreeMap::default();
93 let project_sources = match source_strategy {
94 SourceStrategy::Enabled => project_workspace
95 .current_project()
96 .pyproject_toml()
97 .tool
98 .as_ref()
99 .and_then(|tool| tool.uv.as_ref())
100 .and_then(|uv| uv.sources.as_ref())
101 .map(ToolUvSources::inner)
102 .unwrap_or(&empty),
103 SourceStrategy::Disabled => &empty,
104 };
105
106 let requires_dist = metadata.requires_dist.into_iter();
108 let requires_dist = match source_strategy {
109 SourceStrategy::Enabled => requires_dist
110 .flat_map(|requirement| {
111 let requirement_name = requirement.name.clone();
112 let extra = requirement.marker.top_level_extra_name();
113 let group = None;
114 LoweredRequirement::from_requirement(
115 requirement,
116 metadata.name.as_ref(),
117 project_workspace.project_root(),
118 project_sources,
119 project_indexes,
120 extra.as_deref(),
121 group,
122 locations,
123 project_workspace.workspace(),
124 None,
125 credentials_cache,
126 )
127 .map(move |requirement| match requirement {
128 Ok(requirement) => Ok(requirement.into_inner()),
129 Err(err) => Err(MetadataError::LoweringError(
130 requirement_name.clone(),
131 Box::new(err),
132 )),
133 })
134 })
135 .collect::<Result<Vec<_>, _>>()?,
136 SourceStrategy::Disabled => requires_dist.into_iter().map(Requirement::from).collect(),
137 };
138
139 Ok(Self {
140 name: metadata.name,
141 requires_dist,
142 })
143 }
144
145 pub fn from_workspace(
147 metadata: uv_pypi_types::BuildRequires,
148 workspace: &Workspace,
149 locations: &IndexLocations,
150 source_strategy: SourceStrategy,
151 credentials_cache: &CredentialsCache,
152 ) -> Result<Self, MetadataError> {
153 let empty = vec![];
155 let project_indexes = match source_strategy {
156 SourceStrategy::Enabled => workspace
157 .pyproject_toml()
158 .tool
159 .as_ref()
160 .and_then(|tool| tool.uv.as_ref())
161 .and_then(|uv| uv.index.as_deref())
162 .unwrap_or(&empty),
163 SourceStrategy::Disabled => &empty,
164 };
165
166 let empty = BTreeMap::default();
168 let project_sources = match source_strategy {
169 SourceStrategy::Enabled => workspace
170 .pyproject_toml()
171 .tool
172 .as_ref()
173 .and_then(|tool| tool.uv.as_ref())
174 .and_then(|uv| uv.sources.as_ref())
175 .map(ToolUvSources::inner)
176 .unwrap_or(&empty),
177 SourceStrategy::Disabled => &empty,
178 };
179
180 let requires_dist = metadata.requires_dist.into_iter();
182 let requires_dist = match source_strategy {
183 SourceStrategy::Enabled => requires_dist
184 .flat_map(|requirement| {
185 let requirement_name = requirement.name.clone();
186 let extra = requirement.marker.top_level_extra_name();
187 let group = None;
188 LoweredRequirement::from_requirement(
189 requirement,
190 None,
191 workspace.install_path(),
192 project_sources,
193 project_indexes,
194 extra.as_deref(),
195 group,
196 locations,
197 workspace,
198 None,
199 credentials_cache,
200 )
201 .map(move |requirement| match requirement {
202 Ok(requirement) => Ok(requirement.into_inner()),
203 Err(err) => Err(MetadataError::LoweringError(
204 requirement_name.clone(),
205 Box::new(err),
206 )),
207 })
208 })
209 .collect::<Result<Vec<_>, _>>()?,
210 SourceStrategy::Disabled => requires_dist.into_iter().map(Requirement::from).collect(),
211 };
212
213 Ok(Self {
214 name: metadata.name,
215 requires_dist,
216 })
217 }
218}
219
220#[derive(Debug, Clone, Default)]
225pub struct LoweredExtraBuildDependencies(ExtraBuildRequires);
226
227impl LoweredExtraBuildDependencies {
228 pub fn into_inner(self) -> ExtraBuildRequires {
230 self.0
231 }
232
233 pub fn from_workspace(
235 extra_build_dependencies: ExtraBuildDependencies,
236 workspace: &Workspace,
237 index_locations: &IndexLocations,
238 source_strategy: SourceStrategy,
239 credentials_cache: &CredentialsCache,
240 ) -> Result<Self, MetadataError> {
241 match source_strategy {
242 SourceStrategy::Enabled => {
243 let project_indexes = workspace
245 .pyproject_toml()
246 .tool
247 .as_ref()
248 .and_then(|tool| tool.uv.as_ref())
249 .and_then(|uv| uv.index.as_deref())
250 .unwrap_or(&[]);
251
252 let empty_sources = BTreeMap::default();
253 let project_sources = workspace
254 .pyproject_toml()
255 .tool
256 .as_ref()
257 .and_then(|tool| tool.uv.as_ref())
258 .and_then(|uv| uv.sources.as_ref())
259 .map(ToolUvSources::inner)
260 .unwrap_or(&empty_sources);
261
262 let mut build_requires = ExtraBuildRequires::default();
264 for (package_name, requirements) in extra_build_dependencies {
265 let lowered: Vec<ExtraBuildRequirement> = requirements
266 .into_iter()
267 .flat_map(
268 |ExtraBuildDependency {
269 requirement,
270 match_runtime,
271 }| {
272 let requirement_name = requirement.name.clone();
273 let extra = requirement.marker.top_level_extra_name();
274 let group = None;
275 LoweredRequirement::from_requirement(
276 requirement,
277 None,
278 workspace.install_path(),
279 project_sources,
280 project_indexes,
281 extra.as_deref(),
282 group,
283 index_locations,
284 workspace,
285 None,
286 credentials_cache,
287 )
288 .map(move |requirement| {
289 match requirement {
290 Ok(requirement) => Ok(ExtraBuildRequirement {
291 requirement: requirement.into_inner(),
292 match_runtime,
293 }),
294 Err(err) => Err(MetadataError::LoweringError(
295 requirement_name.clone(),
296 Box::new(err),
297 )),
298 }
299 })
300 },
301 )
302 .collect::<Result<Vec<_>, _>>()?;
303 build_requires.insert(package_name, lowered);
304 }
305 Ok(Self(build_requires))
306 }
307 SourceStrategy::Disabled => Ok(Self::from_non_lowered(extra_build_dependencies)),
308 }
309 }
310
311 pub fn from_lowered(extra_build_dependencies: ExtraBuildRequires) -> Self {
313 Self(extra_build_dependencies)
314 }
315
316 pub fn from_non_lowered(extra_build_dependencies: ExtraBuildDependencies) -> Self {
318 Self(
319 extra_build_dependencies
320 .into_iter()
321 .map(|(name, requirements)| {
322 (
323 name,
324 requirements
325 .into_iter()
326 .map(
327 |ExtraBuildDependency {
328 requirement,
329 match_runtime,
330 }| {
331 ExtraBuildRequirement {
332 requirement: requirement.into(),
333 match_runtime,
334 }
335 },
336 )
337 .collect::<Vec<_>>(),
338 )
339 })
340 .collect(),
341 )
342 }
343}