uv_distribution/metadata/
build_requires.rs1use std::collections::BTreeMap;
2use std::path::Path;
3
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 ) -> Result<Self, MetadataError> {
46 let discovery = match sources {
47 SourceStrategy::Enabled => DiscoveryOptions::default(),
48 SourceStrategy::Disabled => DiscoveryOptions {
49 members: MemberDiscovery::None,
50 ..Default::default()
51 },
52 };
53 let Some(project_workspace) =
54 ProjectWorkspace::from_maybe_project_root(install_path, &discovery, cache).await?
55 else {
56 return Ok(Self::from_metadata23(metadata));
57 };
58
59 Self::from_project_workspace(metadata, &project_workspace, locations, sources)
60 }
61
62 pub fn from_project_workspace(
64 metadata: uv_pypi_types::BuildRequires,
65 project_workspace: &ProjectWorkspace,
66 locations: &IndexLocations,
67 source_strategy: SourceStrategy,
68 ) -> Result<Self, MetadataError> {
69 let empty = vec![];
71 let project_indexes = match source_strategy {
72 SourceStrategy::Enabled => project_workspace
73 .current_project()
74 .pyproject_toml()
75 .tool
76 .as_ref()
77 .and_then(|tool| tool.uv.as_ref())
78 .and_then(|uv| uv.index.as_deref())
79 .unwrap_or(&empty),
80 SourceStrategy::Disabled => &empty,
81 };
82
83 let empty = BTreeMap::default();
85 let project_sources = match source_strategy {
86 SourceStrategy::Enabled => project_workspace
87 .current_project()
88 .pyproject_toml()
89 .tool
90 .as_ref()
91 .and_then(|tool| tool.uv.as_ref())
92 .and_then(|uv| uv.sources.as_ref())
93 .map(ToolUvSources::inner)
94 .unwrap_or(&empty),
95 SourceStrategy::Disabled => &empty,
96 };
97
98 let requires_dist = metadata.requires_dist.into_iter();
100 let requires_dist = match source_strategy {
101 SourceStrategy::Enabled => requires_dist
102 .flat_map(|requirement| {
103 let requirement_name = requirement.name.clone();
104 let extra = requirement.marker.top_level_extra_name();
105 let group = None;
106 LoweredRequirement::from_requirement(
107 requirement,
108 metadata.name.as_ref(),
109 project_workspace.project_root(),
110 project_sources,
111 project_indexes,
112 extra.as_deref(),
113 group,
114 locations,
115 project_workspace.workspace(),
116 None,
117 )
118 .map(move |requirement| match requirement {
119 Ok(requirement) => Ok(requirement.into_inner()),
120 Err(err) => Err(MetadataError::LoweringError(
121 requirement_name.clone(),
122 Box::new(err),
123 )),
124 })
125 })
126 .collect::<Result<Vec<_>, _>>()?,
127 SourceStrategy::Disabled => requires_dist.into_iter().map(Requirement::from).collect(),
128 };
129
130 Ok(Self {
131 name: metadata.name,
132 requires_dist,
133 })
134 }
135
136 pub fn from_workspace(
138 metadata: uv_pypi_types::BuildRequires,
139 workspace: &Workspace,
140 locations: &IndexLocations,
141 source_strategy: SourceStrategy,
142 ) -> Result<Self, MetadataError> {
143 let empty = vec![];
145 let project_indexes = match source_strategy {
146 SourceStrategy::Enabled => workspace
147 .pyproject_toml()
148 .tool
149 .as_ref()
150 .and_then(|tool| tool.uv.as_ref())
151 .and_then(|uv| uv.index.as_deref())
152 .unwrap_or(&empty),
153 SourceStrategy::Disabled => &empty,
154 };
155
156 let empty = BTreeMap::default();
158 let project_sources = match source_strategy {
159 SourceStrategy::Enabled => workspace
160 .pyproject_toml()
161 .tool
162 .as_ref()
163 .and_then(|tool| tool.uv.as_ref())
164 .and_then(|uv| uv.sources.as_ref())
165 .map(ToolUvSources::inner)
166 .unwrap_or(&empty),
167 SourceStrategy::Disabled => &empty,
168 };
169
170 let requires_dist = metadata.requires_dist.into_iter();
172 let requires_dist = match source_strategy {
173 SourceStrategy::Enabled => requires_dist
174 .flat_map(|requirement| {
175 let requirement_name = requirement.name.clone();
176 let extra = requirement.marker.top_level_extra_name();
177 let group = None;
178 LoweredRequirement::from_requirement(
179 requirement,
180 None,
181 workspace.install_path(),
182 project_sources,
183 project_indexes,
184 extra.as_deref(),
185 group,
186 locations,
187 workspace,
188 None,
189 )
190 .map(move |requirement| match requirement {
191 Ok(requirement) => Ok(requirement.into_inner()),
192 Err(err) => Err(MetadataError::LoweringError(
193 requirement_name.clone(),
194 Box::new(err),
195 )),
196 })
197 })
198 .collect::<Result<Vec<_>, _>>()?,
199 SourceStrategy::Disabled => requires_dist.into_iter().map(Requirement::from).collect(),
200 };
201
202 Ok(Self {
203 name: metadata.name,
204 requires_dist,
205 })
206 }
207}
208
209#[derive(Debug, Clone, Default)]
214pub struct LoweredExtraBuildDependencies(ExtraBuildRequires);
215
216impl LoweredExtraBuildDependencies {
217 pub fn into_inner(self) -> ExtraBuildRequires {
219 self.0
220 }
221
222 pub fn from_workspace(
224 extra_build_dependencies: ExtraBuildDependencies,
225 workspace: &Workspace,
226 index_locations: &IndexLocations,
227 source_strategy: SourceStrategy,
228 ) -> Result<Self, MetadataError> {
229 match source_strategy {
230 SourceStrategy::Enabled => {
231 let project_indexes = workspace
233 .pyproject_toml()
234 .tool
235 .as_ref()
236 .and_then(|tool| tool.uv.as_ref())
237 .and_then(|uv| uv.index.as_deref())
238 .unwrap_or(&[]);
239
240 let empty_sources = BTreeMap::default();
241 let project_sources = workspace
242 .pyproject_toml()
243 .tool
244 .as_ref()
245 .and_then(|tool| tool.uv.as_ref())
246 .and_then(|uv| uv.sources.as_ref())
247 .map(ToolUvSources::inner)
248 .unwrap_or(&empty_sources);
249
250 let mut build_requires = ExtraBuildRequires::default();
252 for (package_name, requirements) in extra_build_dependencies {
253 let lowered: Vec<ExtraBuildRequirement> = requirements
254 .into_iter()
255 .flat_map(
256 |ExtraBuildDependency {
257 requirement,
258 match_runtime,
259 }| {
260 let requirement_name = requirement.name.clone();
261 let extra = requirement.marker.top_level_extra_name();
262 let group = None;
263 LoweredRequirement::from_requirement(
264 requirement,
265 None,
266 workspace.install_path(),
267 project_sources,
268 project_indexes,
269 extra.as_deref(),
270 group,
271 index_locations,
272 workspace,
273 None,
274 )
275 .map(move |requirement| {
276 match requirement {
277 Ok(requirement) => Ok(ExtraBuildRequirement {
278 requirement: requirement.into_inner(),
279 match_runtime,
280 }),
281 Err(err) => Err(MetadataError::LoweringError(
282 requirement_name.clone(),
283 Box::new(err),
284 )),
285 }
286 })
287 },
288 )
289 .collect::<Result<Vec<_>, _>>()?;
290 build_requires.insert(package_name, lowered);
291 }
292 Ok(Self(build_requires))
293 }
294 SourceStrategy::Disabled => Ok(Self::from_non_lowered(extra_build_dependencies)),
295 }
296 }
297
298 pub fn from_lowered(extra_build_dependencies: ExtraBuildRequires) -> Self {
300 Self(extra_build_dependencies)
301 }
302
303 pub fn from_non_lowered(extra_build_dependencies: ExtraBuildDependencies) -> Self {
305 Self(
306 extra_build_dependencies
307 .into_iter()
308 .map(|(name, requirements)| {
309 (
310 name,
311 requirements
312 .into_iter()
313 .map(
314 |ExtraBuildDependency {
315 requirement,
316 match_runtime,
317 }| {
318 ExtraBuildRequirement {
319 requirement: requirement.into(),
320 match_runtime,
321 }
322 },
323 )
324 .collect::<Vec<_>>(),
325 )
326 })
327 .collect(),
328 )
329 }
330}