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