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