1
1
use std:: collections:: BTreeMap ;
2
2
use std:: path:: { Path , PathBuf } ;
3
3
4
+ use itertools:: Either ;
5
+
4
6
use uv_configuration:: { LowerBound , SourceStrategy } ;
7
+ use uv_distribution:: LoweredRequirement ;
5
8
use uv_distribution_types:: IndexLocations ;
6
9
use uv_normalize:: PackageName ;
7
10
use uv_pep508:: RequirementOrigin ;
8
11
use uv_pypi_types:: { Conflicts , Requirement , SupportedEnvironments , VerbatimParsedUrl } ;
9
12
use uv_resolver:: { Lock , LockVersion , RequiresPython , VERSION } ;
13
+ use uv_scripts:: Pep723Script ;
10
14
use uv_workspace:: dependency_groups:: DependencyGroupError ;
11
15
use uv_workspace:: { Workspace , WorkspaceMember } ;
12
16
@@ -16,6 +20,7 @@ use crate::commands::project::{find_requires_python, ProjectError};
16
20
#[ derive( Debug , Copy , Clone ) ]
17
21
pub ( crate ) enum LockTarget < ' lock > {
18
22
Workspace ( & ' lock Workspace ) ,
23
+ Script ( & ' lock Pep723Script ) ,
19
24
}
20
25
21
26
impl < ' lock > From < & ' lock Workspace > for LockTarget < ' lock > {
@@ -24,6 +29,12 @@ impl<'lock> From<&'lock Workspace> for LockTarget<'lock> {
24
29
}
25
30
}
26
31
32
+ impl < ' lock > From < & ' lock Pep723Script > for LockTarget < ' lock > {
33
+ fn from ( script : & ' lock Pep723Script ) -> Self {
34
+ LockTarget :: Script ( script)
35
+ }
36
+ }
37
+
27
38
impl < ' lock > LockTarget < ' lock > {
28
39
/// Returns any requirements that are exclusive to the workspace root, i.e., not included in
29
40
/// any of the workspace members.
@@ -32,20 +43,41 @@ impl<'lock> LockTarget<'lock> {
32
43
) -> Result < Vec < uv_pep508:: Requirement < VerbatimParsedUrl > > , DependencyGroupError > {
33
44
match self {
34
45
Self :: Workspace ( workspace) => workspace. non_project_requirements ( ) ,
46
+ Self :: Script ( script) => Ok ( script. metadata . dependencies . clone ( ) . unwrap_or_default ( ) ) ,
35
47
}
36
48
}
37
49
38
50
/// Returns the set of overrides for the [`LockTarget`].
39
51
pub ( crate ) fn overrides ( self ) -> Vec < uv_pep508:: Requirement < VerbatimParsedUrl > > {
40
52
match self {
41
53
Self :: Workspace ( workspace) => workspace. overrides ( ) ,
54
+ Self :: Script ( script) => script
55
+ . metadata
56
+ . tool
57
+ . as_ref ( )
58
+ . and_then ( |tool| tool. uv . as_ref ( ) )
59
+ . and_then ( |uv| uv. override_dependencies . as_ref ( ) )
60
+ . into_iter ( )
61
+ . flatten ( )
62
+ . cloned ( )
63
+ . collect ( ) ,
42
64
}
43
65
}
44
66
45
67
/// Returns the set of constraints for the [`LockTarget`].
46
68
pub ( crate ) fn constraints ( self ) -> Vec < uv_pep508:: Requirement < VerbatimParsedUrl > > {
47
69
match self {
48
70
Self :: Workspace ( workspace) => workspace. constraints ( ) ,
71
+ Self :: Script ( script) => script
72
+ . metadata
73
+ . tool
74
+ . as_ref ( )
75
+ . and_then ( |tool| tool. uv . as_ref ( ) )
76
+ . and_then ( |uv| uv. constraint_dependencies . as_ref ( ) )
77
+ . into_iter ( )
78
+ . flatten ( )
79
+ . cloned ( )
80
+ . collect ( ) ,
49
81
}
50
82
}
51
83
@@ -83,6 +115,55 @@ impl<'lock> LockTarget<'lock> {
83
115
. map ( |requirement| requirement. with_origin ( RequirementOrigin :: Workspace ) )
84
116
. collect :: < Vec < _ > > ( ) )
85
117
}
118
+ Self :: Script ( script) => {
119
+ // Collect any `tool.uv.index` from the script.
120
+ let empty = Vec :: default ( ) ;
121
+ let indexes = match sources {
122
+ SourceStrategy :: Enabled => script
123
+ . metadata
124
+ . tool
125
+ . as_ref ( )
126
+ . and_then ( |tool| tool. uv . as_ref ( ) )
127
+ . and_then ( |uv| uv. top_level . index . as_deref ( ) )
128
+ . unwrap_or ( & empty) ,
129
+ SourceStrategy :: Disabled => & empty,
130
+ } ;
131
+
132
+ // Collect any `tool.uv.sources` from the script.
133
+ let empty = BTreeMap :: default ( ) ;
134
+ let sources = match sources {
135
+ SourceStrategy :: Enabled => script
136
+ . metadata
137
+ . tool
138
+ . as_ref ( )
139
+ . and_then ( |tool| tool. uv . as_ref ( ) )
140
+ . and_then ( |uv| uv. sources . as_ref ( ) )
141
+ . unwrap_or ( & empty) ,
142
+ SourceStrategy :: Disabled => & empty,
143
+ } ;
144
+
145
+ Ok ( requirements
146
+ . into_iter ( )
147
+ . flat_map ( |requirement| {
148
+ let requirement_name = requirement. name . clone ( ) ;
149
+ LoweredRequirement :: from_non_workspace_requirement (
150
+ requirement,
151
+ script. path . parent ( ) . unwrap ( ) ,
152
+ sources,
153
+ indexes,
154
+ locations,
155
+ LowerBound :: Allow ,
156
+ )
157
+ . map ( move |requirement| match requirement {
158
+ Ok ( requirement) => Ok ( requirement. into_inner ( ) ) ,
159
+ Err ( err) => Err ( uv_distribution:: MetadataError :: LoweringError (
160
+ requirement_name. clone ( ) ,
161
+ Box :: new ( err) ,
162
+ ) ) ,
163
+ } )
164
+ } )
165
+ . collect :: < Result < _ , _ > > ( ) ?)
166
+ }
86
167
}
87
168
}
88
169
@@ -102,62 +183,90 @@ impl<'lock> LockTarget<'lock> {
102
183
103
184
members
104
185
}
186
+ Self :: Script ( _) => Vec :: new ( ) ,
105
187
}
106
188
}
107
189
108
190
/// Return the list of packages.
109
191
pub ( crate ) fn packages ( self ) -> & ' lock BTreeMap < PackageName , WorkspaceMember > {
110
192
match self {
111
193
Self :: Workspace ( workspace) => workspace. packages ( ) ,
194
+ Self :: Script ( _) => {
195
+ static EMPTY : BTreeMap < PackageName , WorkspaceMember > = BTreeMap :: new ( ) ;
196
+ & EMPTY
197
+ }
112
198
}
113
199
}
114
200
115
201
/// Returns the set of supported environments for the [`LockTarget`].
116
202
pub ( crate ) fn environments ( self ) -> Option < & ' lock SupportedEnvironments > {
117
203
match self {
118
204
Self :: Workspace ( workspace) => workspace. environments ( ) ,
205
+ Self :: Script ( _) => {
206
+ // TODO(charlie): Add support for environments in scripts.
207
+ None
208
+ }
119
209
}
120
210
}
121
211
122
212
/// Returns the set of conflicts for the [`LockTarget`].
123
213
pub ( crate ) fn conflicts ( self ) -> Conflicts {
124
214
match self {
125
215
Self :: Workspace ( workspace) => workspace. conflicts ( ) ,
216
+ Self :: Script ( _) => Conflicts :: empty ( ) ,
126
217
}
127
218
}
128
219
129
220
/// Return the `Requires-Python` bound for the [`LockTarget`].
130
221
pub ( crate ) fn requires_python ( self ) -> Option < RequiresPython > {
131
222
match self {
132
223
Self :: Workspace ( workspace) => find_requires_python ( workspace) ,
224
+ Self :: Script ( script) => script
225
+ . metadata
226
+ . requires_python
227
+ . as_ref ( )
228
+ . map ( RequiresPython :: from_specifiers) ,
133
229
}
134
230
}
135
231
136
232
/// Returns the set of requirements that include all packages in the workspace.
137
233
pub ( crate ) fn members_requirements ( self ) -> impl Iterator < Item = Requirement > + ' lock {
138
234
match self {
139
- Self :: Workspace ( workspace) => workspace. members_requirements ( ) ,
235
+ Self :: Workspace ( workspace) => Either :: Left ( workspace. members_requirements ( ) ) ,
236
+ Self :: Script ( _) => Either :: Right ( std:: iter:: empty ( ) ) ,
140
237
}
141
238
}
142
239
143
240
/// Returns the set of requirements that include all packages in the workspace.
144
241
pub ( crate ) fn group_requirements ( self ) -> impl Iterator < Item = Requirement > + ' lock {
145
242
match self {
146
- Self :: Workspace ( workspace) => workspace. group_requirements ( ) ,
243
+ Self :: Workspace ( workspace) => Either :: Left ( workspace. group_requirements ( ) ) ,
244
+ Self :: Script ( _) => Either :: Right ( std:: iter:: empty ( ) ) ,
147
245
}
148
246
}
149
247
150
248
/// Return the path to the lock root.
151
249
pub ( crate ) fn install_path ( self ) -> & ' lock Path {
152
250
match self {
153
251
Self :: Workspace ( workspace) => workspace. install_path ( ) ,
252
+ Self :: Script ( script) => script. path . parent ( ) . unwrap ( ) ,
154
253
}
155
254
}
156
255
157
256
/// Return the path to the lockfile.
158
257
fn lock_path ( self ) -> PathBuf {
159
258
match self {
259
+ // `uv.lock`
160
260
Self :: Workspace ( workspace) => workspace. install_path ( ) . join ( "uv.lock" ) ,
261
+ // `script.py.lock`
262
+ Self :: Script ( script) => {
263
+ let mut file_name = match script. path . file_name ( ) {
264
+ Some ( f) => f. to_os_string ( ) ,
265
+ None => panic ! ( "Script path has no file name" ) ,
266
+ } ;
267
+ file_name. push ( ".lock" ) ;
268
+ script. path . with_file_name ( file_name)
269
+ }
161
270
}
162
271
}
163
272
0 commit comments