Skip to main content

uv_requirements/
script.rs

1use std::collections::BTreeMap;
2
3use uv_auth::CredentialsCache;
4use uv_cache::Cache;
5use uv_configuration::{NoSources, Override, PackageOverride};
6use uv_distribution::{LoweredExtraBuildDependencies, LoweredRequirement, LoweringError};
7use uv_distribution_types::{
8    ExtraBuildRequirement, ExtraBuildRequires, IndexLocations, IndexUrlError,
9};
10use uv_scripts::Pep723ItemRef;
11use uv_workspace::WorkspaceCache;
12use uv_workspace::pyproject::ExtraBuildDependency;
13
14use crate::RequirementsSpecification;
15
16/// A failure while lowering a script's requirements or extra build dependencies.
17#[derive(Debug, thiserror::Error)]
18pub enum ScriptRequirementsError {
19    #[error(transparent)]
20    Io(#[from] std::io::Error),
21
22    #[error(transparent)]
23    IndexUrl(#[from] IndexUrlError),
24
25    #[error(transparent)]
26    Lowering(#[from] Box<LoweringError>),
27}
28
29impl From<LoweringError> for ScriptRequirementsError {
30    fn from(error: LoweringError) -> Self {
31        Self::Lowering(Box::new(error))
32    }
33}
34
35/// Determine the [`RequirementsSpecification`] for a script.
36pub async fn script_specification(
37    script: Pep723ItemRef<'_>,
38    sources: &NoSources,
39    index_locations: &IndexLocations,
40    cache: &Cache,
41    workspace_cache: &WorkspaceCache,
42    credentials_cache: &CredentialsCache,
43) -> Result<Option<RequirementsSpecification>, ScriptRequirementsError> {
44    let Some(dependencies) = script.metadata().dependencies.as_ref() else {
45        return Ok(None);
46    };
47
48    let script_dir = script.directory()?;
49    let script_indexes = script
50        .indexes(sources)
51        .iter()
52        .cloned()
53        .map(|index| index.relative_to(&script_dir))
54        .collect::<Result<Vec<_>, _>>()?;
55    let script_sources = script.sources(sources);
56
57    let mut requirements = Vec::new();
58    for requirement in dependencies.iter().cloned() {
59        requirements.extend(
60            LoweredRequirement::from_non_workspace_requirement(
61                requirement,
62                script_dir.as_ref(),
63                script_sources.as_ref(),
64                &script_indexes,
65                index_locations,
66                cache,
67                workspace_cache,
68                credentials_cache,
69            )
70            .await
71            .map(|requirement| requirement.map(LoweredRequirement::into_inner))
72            .collect::<Result<Vec<_>, _>>()?,
73        );
74    }
75    let constraint_dependencies = script
76        .metadata()
77        .tool
78        .as_ref()
79        .and_then(|tool| tool.uv.as_ref())
80        .and_then(|uv| uv.constraint_dependencies.as_ref())
81        .into_iter()
82        .flatten()
83        .cloned();
84    let mut constraints = Vec::new();
85    for requirement in constraint_dependencies {
86        constraints.extend(
87            LoweredRequirement::from_non_workspace_requirement(
88                requirement,
89                script_dir.as_ref(),
90                script_sources.as_ref(),
91                &script_indexes,
92                index_locations,
93                cache,
94                workspace_cache,
95                credentials_cache,
96            )
97            .await
98            .map(|requirement| requirement.map(LoweredRequirement::into_inner))
99            .collect::<Result<Vec<_>, _>>()?,
100        );
101    }
102    let overrides = {
103        let override_entries = script
104            .metadata()
105            .tool
106            .as_ref()
107            .and_then(|tool| tool.uv.as_ref())
108            .and_then(|uv| uv.override_dependencies.as_ref())
109            .into_iter()
110            .flatten()
111            .cloned();
112        let mut overrides = Vec::new();
113        for entry in override_entries {
114            match entry {
115                Override::Requirement(requirement) => {
116                    overrides.extend(
117                        LoweredRequirement::from_non_workspace_requirement(
118                            requirement,
119                            script_dir.as_ref(),
120                            script_sources.as_ref(),
121                            &script_indexes,
122                            index_locations,
123                            cache,
124                            workspace_cache,
125                            credentials_cache,
126                        )
127                        .await
128                        .map(|requirement| {
129                            requirement
130                                .map(|requirement| Override::Requirement(requirement.into_inner()))
131                        })
132                        .collect::<Result<Vec<_>, _>>()?,
133                    );
134                }
135                Override::Package(package) => {
136                    let mut dependencies = Vec::new();
137                    for requirement in package.dependencies.into_vec() {
138                        dependencies.extend(
139                            LoweredRequirement::from_non_workspace_requirement(
140                                requirement,
141                                script_dir.as_ref(),
142                                script_sources.as_ref(),
143                                &script_indexes,
144                                index_locations,
145                                cache,
146                                workspace_cache,
147                                credentials_cache,
148                            )
149                            .await
150                            .map(|requirement| requirement.map(LoweredRequirement::into_inner))
151                            .collect::<Result<Vec<_>, _>>()?,
152                        );
153                    }
154                    overrides.push(Override::Package(PackageOverride {
155                        package: package.package,
156                        dependencies: dependencies.into_boxed_slice(),
157                    }));
158                }
159            }
160        }
161        overrides
162    };
163    let excludes = script
164        .metadata()
165        .tool
166        .as_ref()
167        .and_then(|tool| tool.uv.as_ref())
168        .and_then(|uv| uv.exclude_dependencies.as_ref())
169        .into_iter()
170        .flatten()
171        .cloned()
172        .collect::<Vec<_>>();
173
174    let mut specification =
175        RequirementsSpecification::from_excludes(requirements, constraints, Vec::new(), Vec::new());
176    specification.override_dependencies = overrides;
177    specification.excludes = excludes;
178    Ok(Some(specification))
179}
180
181/// Determine the extra build requires for a script.
182pub async fn script_extra_build_requires(
183    script: Pep723ItemRef<'_>,
184    sources: &NoSources,
185    index_locations: &IndexLocations,
186    cache: &Cache,
187    workspace_cache: &WorkspaceCache,
188    credentials_cache: &CredentialsCache,
189) -> Result<LoweredExtraBuildDependencies, ScriptRequirementsError> {
190    let script_dir = script.directory()?;
191    let script_indexes = script
192        .indexes(sources)
193        .iter()
194        .cloned()
195        .map(|index| index.relative_to(&script_dir))
196        .collect::<Result<Vec<_>, _>>()?;
197    let script_sources = script.sources(sources);
198
199    // Collect any `tool.uv.extra-build-dependencies` from the script.
200    let empty = BTreeMap::default();
201    let script_extra_build_dependencies = script
202        .metadata()
203        .tool
204        .as_ref()
205        .and_then(|tool| tool.uv.as_ref())
206        .and_then(|uv| uv.extra_build_dependencies.as_ref())
207        .unwrap_or(&empty);
208
209    // Lower the extra build dependencies.
210    let mut extra_build_requires = ExtraBuildRequires::default();
211    for (name, requirements) in script_extra_build_dependencies {
212        let mut lowered_requirements = Vec::new();
213        for ExtraBuildDependency {
214            requirement,
215            match_runtime,
216        } in requirements.iter().cloned()
217        {
218            lowered_requirements.extend(
219                LoweredRequirement::from_non_workspace_requirement(
220                    requirement,
221                    script_dir.as_ref(),
222                    script_sources.as_ref(),
223                    &script_indexes,
224                    index_locations,
225                    cache,
226                    workspace_cache,
227                    credentials_cache,
228                )
229                .await
230                .map(|requirement| {
231                    requirement.map(|requirement| ExtraBuildRequirement {
232                        requirement: requirement.into_inner(),
233                        match_runtime,
234                    })
235                })
236                .collect::<Result<Vec<_>, _>>()?,
237            );
238        }
239        extra_build_requires.insert(name.clone(), lowered_requirements);
240    }
241
242    Ok(LoweredExtraBuildDependencies::from_lowered(
243        extra_build_requires,
244    ))
245}