1
+ //! This module contains the part of package management that's needed by the evaluator.
2
+ //!
3
+ //! In particular, it doesn't contain any support for version resolution or
4
+ //! dependency fetching, but defines lockfiles and allows them to be used to
5
+ //! resolve package dependencies to paths.
6
+
1
7
use std:: {
2
8
collections:: HashMap ,
3
9
path:: { Path , PathBuf } ,
@@ -7,10 +13,7 @@ use std::{
7
13
use directories:: ProjectDirs ;
8
14
use serde:: { Deserialize , Serialize } ;
9
15
10
- use crate :: {
11
- cache:: { normalize_abs_path, normalize_path} ,
12
- error:: ImportError ,
13
- } ;
16
+ use crate :: { error:: ImportError , position:: TermPos } ;
14
17
15
18
const ID_LEN : usize = 20 ;
16
19
@@ -134,8 +137,16 @@ impl From<Name> for String {
134
137
/// A locked package source uniquely identifies the source of the package (with a specific version).
135
138
#[ derive( Clone , Debug , Serialize , Deserialize , PartialEq , Eq , Hash ) ]
136
139
pub enum LockedPackageSource {
137
- Git { repo : String , tree : ObjectId } ,
138
- Path { path : PathBuf } ,
140
+ Git {
141
+ repo : String ,
142
+ tree : ObjectId ,
143
+ /// Path of the package relative to the git repo root.
144
+ #[ serde( default ) ]
145
+ path : PathBuf ,
146
+ } ,
147
+ Path {
148
+ path : PathBuf ,
149
+ } ,
139
150
}
140
151
141
152
impl LockedPackageSource {
@@ -158,31 +169,6 @@ impl LockedPackageSource {
158
169
matches ! ( self , LockedPackageSource :: Path { .. } )
159
170
}
160
171
161
- fn canonicalize (
162
- self ,
163
- parent_path : & Path ,
164
- restriction : Option < & Path > ,
165
- ) -> Result < Self , ImportError > {
166
- assert ! ( parent_path. is_absolute( ) ) ;
167
- assert ! ( restriction. map_or( true , Path :: is_absolute) ) ;
168
-
169
- match self {
170
- LockedPackageSource :: Git { .. } => Ok ( self ) ,
171
- LockedPackageSource :: Path { path } => {
172
- let path = normalize_abs_path ( & parent_path. join ( path) ) ;
173
- if let Some ( restriction) = restriction {
174
- if !path. starts_with ( restriction) {
175
- return Err ( ImportError :: InvalidPath {
176
- attempted : path. to_owned ( ) ,
177
- restriction : restriction. to_owned ( ) ,
178
- } ) ;
179
- }
180
- }
181
- Ok ( LockedPackageSource :: Path { path } )
182
- }
183
- }
184
- }
185
-
186
172
/// Is this locked package available offline? If not, it needs to be fetched.
187
173
pub fn is_available_offline ( & self ) -> bool {
188
174
// We consider path-dependencies to be always available offline, even if they don't exist.
@@ -197,109 +183,70 @@ impl LockedPackageSource {
197
183
}
198
184
}
199
185
186
+ /// A lock file that's been fully resolved, including path dependencies.
200
187
#[ derive( Clone , Debug , Default ) ]
201
- pub struct LockFile {
188
+ pub struct ResolvedLockFile {
202
189
/// Absolute path to the lock file's parent directory.
203
190
///
204
- /// Path dependencies at the top-level are resolved relative to this. (Path dependencies
205
- /// of dependencies are resolved relative to the dependency that imported them.)
191
+ /// Path dependencies at the top-level are resolved relative to this.
206
192
pub path : PathBuf ,
207
- pub data : LockFileData ,
193
+ /// The inner lockfile, which is now guaranteed to have closed dependencies.
194
+ pub inner : LockFile ,
208
195
}
209
196
210
197
/// A lock file, specifying versions and names for all recursive dependencies.
198
+ ///
199
+ /// This defines the on-disk format for lock files.
211
200
#[ derive( Clone , Debug , Default , Serialize , Deserialize ) ]
212
- pub struct LockFileData {
201
+ pub struct LockFile {
213
202
/// The dependencies of the current (top-level) package.
214
203
pub dependencies : HashMap < Name , LockedPackageSource > ,
215
204
/// All packages that we know about, and the dependencies of each one.
216
205
///
217
- /// We guarantee that this package list is closed, in the sense that everything that is
218
- /// a dependency of some package here has its own entry in the map.
206
+ /// Note that the package list is not guaranteed to be closed: path dependencies
207
+ /// cannot have their dependencies resolved in the on-disk lockfile because they
208
+ /// can change at any time. *Some* path dependencies (for example, path dependencies
209
+ /// that are local to a git depencency repo) may have resolved dependencies.
219
210
pub packages : HashMap < LockedPackageSource , LockFileEntry > ,
220
211
}
221
212
222
- impl LockFileData {
213
+ impl ResolvedLockFile {
223
214
/// Are all the packages mentioned in this lockfile available offline?
224
215
pub fn is_available_offline ( & self ) -> bool {
225
- self . packages
216
+ self . inner
217
+ . packages
226
218
. keys ( )
227
219
. all ( LockedPackageSource :: is_available_offline)
228
220
}
229
- }
230
221
231
- impl LockFile {
232
- pub fn canonicalize_paths ( & mut self ) -> Result < ( ) , ImportError > {
233
- let mut new = LockFile {
234
- path : self . path . clone ( ) ,
235
- data : LockFileData :: default ( ) ,
222
+ pub fn get (
223
+ & self ,
224
+ parent : Option < & LockedPackageSource > ,
225
+ pkg : & Name ,
226
+ pos : & TermPos ,
227
+ ) -> Result < & LockedPackageSource , ImportError > {
228
+ // The parent package should have come from the lock file.
229
+ let ( deps, parent_name) = if let Some ( parent) = parent {
230
+ let parent_pkg =
231
+ self . inner
232
+ . packages
233
+ . get ( parent)
234
+ . ok_or_else ( || ImportError :: InternalError {
235
+ msg : format ! ( "unknown parent package {parent:?}" ) ,
236
+ pos : * pos,
237
+ } ) ?;
238
+
239
+ ( & parent_pkg. dependencies , Some ( & parent_pkg. name ) )
240
+ } else {
241
+ ( & self . inner . dependencies , None )
236
242
} ;
237
-
238
- struct StackEntry < ' a > {
239
- abs_parent : LockedPackageSource ,
240
- restriction : Option < PathBuf > ,
241
- entry : & ' a LockFileEntry ,
242
- }
243
- let mut stack = Vec :: new ( ) ;
244
-
245
- fn add_dep < ' stack , ' a : ' stack > (
246
- stack : & ' stack mut Vec < StackEntry < ' a > > ,
247
- source : & LockedPackageSource ,
248
- parent : & Path ,
249
- restriction : Option < & Path > ,
250
- orig_packages : & ' a HashMap < LockedPackageSource , LockFileEntry > ,
251
- ) -> Result < LockedPackageSource , ImportError > {
252
- let abs_source = source. clone ( ) . canonicalize ( parent, restriction) ?;
253
-
254
- if let Some ( entry) = orig_packages. get ( source) {
255
- let parent_path = abs_source. local_path ( ) ;
256
- stack. push ( StackEntry {
257
- restriction : if abs_source. is_path ( ) {
258
- restriction. map ( Path :: to_owned)
259
- } else {
260
- Some ( parent_path. clone ( ) )
261
- } ,
262
- abs_parent : abs_source. clone ( ) ,
263
- entry,
264
- } ) ;
265
- }
266
- Ok ( abs_source)
267
- }
268
-
269
- for ( name, source) in & self . data . dependencies {
270
- let abs_source = add_dep ( & mut stack, source, & self . path , None , & self . data . packages ) ?;
271
- new. data . dependencies . insert ( name. clone ( ) , abs_source) ;
272
- }
273
-
274
- while let Some ( StackEntry {
275
- abs_parent,
276
- restriction,
277
- entry,
278
- } ) = stack. pop ( )
279
- {
280
- let mut abs_dependencies = HashMap :: new ( ) ;
281
- for ( name, source) in & entry. dependencies {
282
- let abs_source = add_dep (
283
- & mut stack,
284
- source,
285
- & abs_parent. local_path ( ) ,
286
- restriction. as_deref ( ) ,
287
- & self . data . packages ,
288
- ) ?;
289
-
290
- // FIXME: insist that if there's overlap then everything agrees
291
- abs_dependencies. insert ( name. clone ( ) , abs_source) ;
292
- }
293
- new. data . packages . insert (
294
- abs_parent,
295
- LockFileEntry {
296
- name : entry. name . clone ( ) ,
297
- dependencies : abs_dependencies,
298
- } ,
299
- ) ;
300
- }
301
-
302
- Ok ( ( ) )
243
+ deps. get ( pkg) . ok_or_else ( || ImportError :: MissingDependency {
244
+ parent : parent
245
+ . zip ( parent_name)
246
+ . map ( |( p, n) | Box :: new ( ( n. clone ( ) , p. clone ( ) ) ) ) ,
247
+ missing : pkg. clone ( ) ,
248
+ pos : * pos,
249
+ } )
303
250
}
304
251
}
305
252
0 commit comments