uv_distribution_types/
build_requires.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use uv_cache_key::{CacheKey, CacheKeyHasher};
6use uv_normalize::PackageName;
7
8use crate::{Name, Requirement, RequirementSource, Resolution};
9
10#[derive(Debug, thiserror::Error)]
11pub enum ExtraBuildRequiresError {
12    #[error(
13        "`{0}` was declared as an extra build dependency with `match-runtime = true`, but was not found in the resolution"
14    )]
15    NotFound(PackageName),
16    #[error(
17        "Dependencies marked with `match-runtime = true` cannot include version specifiers, but found: `{0}{1}`"
18    )]
19    VersionSpecifiersNotAllowed(PackageName, Box<RequirementSource>),
20    #[error(
21        "Dependencies marked with `match-runtime = true` cannot include URL constraints, but found: `{0}{1}`"
22    )]
23    UrlNotAllowed(PackageName, Box<RequirementSource>),
24}
25
26/// Lowered extra build dependencies with source resolution applied.
27#[derive(Debug, Clone, Default)]
28pub struct ExtraBuildRequires(BTreeMap<PackageName, Vec<ExtraBuildRequirement>>);
29
30impl std::ops::Deref for ExtraBuildRequires {
31    type Target = BTreeMap<PackageName, Vec<ExtraBuildRequirement>>;
32
33    fn deref(&self) -> &Self::Target {
34        &self.0
35    }
36}
37
38impl std::ops::DerefMut for ExtraBuildRequires {
39    fn deref_mut(&mut self) -> &mut Self::Target {
40        &mut self.0
41    }
42}
43
44impl IntoIterator for ExtraBuildRequires {
45    type Item = (PackageName, Vec<ExtraBuildRequirement>);
46    type IntoIter = std::collections::btree_map::IntoIter<PackageName, Vec<ExtraBuildRequirement>>;
47
48    fn into_iter(self) -> Self::IntoIter {
49        self.0.into_iter()
50    }
51}
52
53impl FromIterator<(PackageName, Vec<ExtraBuildRequirement>)> for ExtraBuildRequires {
54    fn from_iter<T: IntoIterator<Item = (PackageName, Vec<ExtraBuildRequirement>)>>(
55        iter: T,
56    ) -> Self {
57        Self(iter.into_iter().collect())
58    }
59}
60
61#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
62pub struct ExtraBuildRequirement {
63    /// The underlying [`Requirement`] for the build requirement.
64    pub requirement: Requirement,
65    /// Whether this build requirement should match the runtime environment.
66    pub match_runtime: bool,
67}
68
69impl From<ExtraBuildRequirement> for Requirement {
70    fn from(value: ExtraBuildRequirement) -> Self {
71        value.requirement
72    }
73}
74
75impl CacheKey for ExtraBuildRequirement {
76    fn cache_key(&self, state: &mut CacheKeyHasher) {
77        self.requirement.cache_key(state);
78        self.match_runtime.cache_key(state);
79    }
80}
81
82impl ExtraBuildRequires {
83    /// Apply runtime constraints from a resolution to the extra build requirements.
84    pub fn match_runtime(self, resolution: &Resolution) -> Result<Self, ExtraBuildRequiresError> {
85        self.into_iter()
86            .filter(|(_, requirements)| !requirements.is_empty())
87            .filter(|(name, _)| resolution.distributions().any(|dist| dist.name() == name))
88            .map(|(name, requirements)| {
89                let requirements = requirements
90                    .into_iter()
91                    .map(|requirement| match requirement {
92                        ExtraBuildRequirement {
93                            requirement,
94                            match_runtime: true,
95                        } => {
96                            // Reject requirements with `match-runtime = true` that include any form
97                            // of constraint.
98                            if let RequirementSource::Registry { specifier, .. } =
99                                &requirement.source
100                            {
101                                if !specifier.is_empty() {
102                                    return Err(
103                                        ExtraBuildRequiresError::VersionSpecifiersNotAllowed(
104                                            requirement.name.clone(),
105                                            Box::new(requirement.source.clone()),
106                                        ),
107                                    );
108                                }
109                            } else {
110                                return Err(ExtraBuildRequiresError::VersionSpecifiersNotAllowed(
111                                    requirement.name.clone(),
112                                    Box::new(requirement.source.clone()),
113                                ));
114                            }
115
116                            let dist = resolution
117                                .distributions()
118                                .find(|dist| dist.name() == &requirement.name)
119                                .ok_or_else(|| {
120                                    ExtraBuildRequiresError::NotFound(requirement.name.clone())
121                                })?;
122                            let requirement = Requirement {
123                                source: RequirementSource::from(dist),
124                                ..requirement
125                            };
126                            Ok::<_, ExtraBuildRequiresError>(ExtraBuildRequirement {
127                                requirement,
128                                match_runtime: true,
129                            })
130                        }
131                        requirement => Ok(requirement),
132                    })
133                    .collect::<Result<Vec<_>, _>>()?;
134                Ok::<_, ExtraBuildRequiresError>((name, requirements))
135            })
136            .collect::<Result<Self, _>>()
137    }
138}
139
140/// A map of extra build variables, from variable name to value.
141pub type BuildVariables = BTreeMap<String, String>;
142
143/// Extra environment variables to set during builds, on a per-package basis.
144#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
145#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
146pub struct ExtraBuildVariables(BTreeMap<PackageName, BuildVariables>);
147
148impl std::ops::Deref for ExtraBuildVariables {
149    type Target = BTreeMap<PackageName, BuildVariables>;
150
151    fn deref(&self) -> &Self::Target {
152        &self.0
153    }
154}
155
156impl std::ops::DerefMut for ExtraBuildVariables {
157    fn deref_mut(&mut self) -> &mut Self::Target {
158        &mut self.0
159    }
160}
161
162impl IntoIterator for ExtraBuildVariables {
163    type Item = (PackageName, BuildVariables);
164    type IntoIter = std::collections::btree_map::IntoIter<PackageName, BuildVariables>;
165
166    fn into_iter(self) -> Self::IntoIter {
167        self.0.into_iter()
168    }
169}
170
171impl FromIterator<(PackageName, BuildVariables)> for ExtraBuildVariables {
172    fn from_iter<T: IntoIterator<Item = (PackageName, BuildVariables)>>(iter: T) -> Self {
173        Self(iter.into_iter().collect())
174    }
175}
176
177impl CacheKey for ExtraBuildVariables {
178    fn cache_key(&self, state: &mut CacheKeyHasher) {
179        for (package, vars) in &self.0 {
180            package.as_str().cache_key(state);
181            for (key, value) in vars {
182                key.cache_key(state);
183                value.cache_key(state);
184            }
185        }
186    }
187}