1
1
use crate :: core:: package:: MANIFEST_PREAMBLE ;
2
2
use crate :: core:: shell:: Verbosity ;
3
- use crate :: core:: { GitReference , Package , Workspace } ;
3
+ use crate :: core:: { GitReference , Package , SourceId , Workspace } ;
4
4
use crate :: ops;
5
5
use crate :: sources:: path:: PathSource ;
6
6
use crate :: sources:: CRATES_IO_REGISTRY ;
@@ -9,18 +9,23 @@ use crate::util::{try_canonicalize, CargoResult, GlobalContext};
9
9
use anyhow:: { bail, Context as _} ;
10
10
use cargo_util:: { paths, Sha256 } ;
11
11
use serde:: Serialize ;
12
+ use std:: collections:: hash_map:: DefaultHasher ;
12
13
use std:: collections:: HashSet ;
13
14
use std:: collections:: { BTreeMap , BTreeSet , HashMap } ;
14
15
use std:: ffi:: OsStr ;
15
16
use std:: fs:: { self , File , OpenOptions } ;
17
+ use std:: hash:: Hasher ;
16
18
use std:: io:: { Read , Write } ;
17
19
use std:: path:: { Path , PathBuf } ;
18
20
21
+ const SOURCES_FILE_NAME : & str = ".sources" ;
22
+
19
23
pub struct VendorOptions < ' a > {
20
24
pub no_delete : bool ,
21
25
pub versioned_dirs : bool ,
22
26
pub destination : & ' a Path ,
23
27
pub extra : Vec < PathBuf > ,
28
+ pub no_merge_sources : bool ,
24
29
}
25
30
26
31
pub fn vendor ( ws : & Workspace < ' _ > , opts : & VendorOptions < ' _ > ) -> CargoResult < ( ) > {
@@ -84,8 +89,16 @@ fn sync(
84
89
let canonical_destination = try_canonicalize ( opts. destination ) ;
85
90
let canonical_destination = canonical_destination. as_deref ( ) . unwrap_or ( opts. destination ) ;
86
91
let dest_dir_already_exists = canonical_destination. exists ( ) ;
92
+ let merge_sources = !opts. no_merge_sources ;
93
+ let sources_file = canonical_destination. join ( SOURCES_FILE_NAME ) ;
87
94
88
95
paths:: create_dir_all ( & canonical_destination) ?;
96
+
97
+ if !merge_sources {
98
+ let mut file = File :: create ( sources_file) ?;
99
+ file. write_all ( serde_json:: json!( [ ] ) . to_string ( ) . as_bytes ( ) ) ?;
100
+ }
101
+
89
102
let mut to_remove = HashSet :: new ( ) ;
90
103
if !opts. no_delete {
91
104
for entry in canonical_destination. read_dir ( ) ? {
@@ -172,8 +185,9 @@ fn sync(
172
185
let mut versions = HashMap :: new ( ) ;
173
186
for id in ids. keys ( ) {
174
187
let map = versions. entry ( id. name ( ) ) . or_insert_with ( BTreeMap :: default) ;
175
- if let Some ( prev) = map. get ( & id. version ( ) ) {
176
- bail ! (
188
+
189
+ match map. get ( & id. version ( ) ) {
190
+ Some ( prev) if merge_sources => bail ! (
177
191
"found duplicate version of package `{} v{}` \
178
192
vendored from two sources:\n \
179
193
\n \
@@ -183,7 +197,8 @@ fn sync(
183
197
id. version( ) ,
184
198
prev,
185
199
id. source_id( )
186
- ) ;
200
+ ) ,
201
+ _ => { }
187
202
}
188
203
map. insert ( id. version ( ) , id. source_id ( ) ) ;
189
204
}
@@ -207,7 +222,17 @@ fn sync(
207
222
} ;
208
223
209
224
sources. insert ( id. source_id ( ) ) ;
210
- let dst = canonical_destination. join ( & dst_name) ;
225
+ let source_dir = if merge_sources {
226
+ PathBuf :: from ( canonical_destination) . clone ( )
227
+ } else {
228
+ PathBuf :: from ( canonical_destination) . join ( source_id_to_dir_name ( id. source_id ( ) ) )
229
+ } ;
230
+ if sources. insert ( id. source_id ( ) ) && !merge_sources {
231
+ if fs:: create_dir_all ( & source_dir) . is_err ( ) {
232
+ panic ! ( "failed to create: `{}`" , source_dir. display( ) )
233
+ }
234
+ }
235
+ let dst = source_dir. join ( & dst_name) ;
211
236
to_remove. remove ( & dst) ;
212
237
let cksum = dst. join ( ".cargo-checksum.json" ) ;
213
238
if dir_has_version_suffix && cksum. exists ( ) {
@@ -244,6 +269,31 @@ fn sync(
244
269
}
245
270
}
246
271
272
+ if !merge_sources {
273
+ let sources_file = PathBuf :: from ( canonical_destination) . join ( SOURCES_FILE_NAME ) ;
274
+ let file = File :: open ( & sources_file) ?;
275
+ let mut new_sources: BTreeSet < String > = sources
276
+ . iter ( )
277
+ . map ( |src_id| source_id_to_dir_name ( * src_id) )
278
+ . collect ( ) ;
279
+ let old_sources: BTreeSet < String > = serde_json:: from_reader :: < _ , BTreeSet < String > > ( file) ?
280
+ . difference ( & new_sources)
281
+ . map ( |e| e. clone ( ) )
282
+ . collect ( ) ;
283
+ for dir_name in old_sources {
284
+ let path = PathBuf :: from ( canonical_destination) . join ( dir_name. clone ( ) ) ;
285
+ if path. is_dir ( ) {
286
+ if path. read_dir ( ) ?. next ( ) . is_none ( ) {
287
+ fs:: remove_dir ( path) ?;
288
+ } else {
289
+ new_sources. insert ( dir_name. clone ( ) ) ;
290
+ }
291
+ }
292
+ }
293
+ let file = File :: create ( sources_file) ?;
294
+ serde_json:: to_writer ( file, & new_sources) ?;
295
+ }
296
+
247
297
// add our vendored source
248
298
let mut config = BTreeMap :: new ( ) ;
249
299
@@ -259,16 +309,32 @@ fn sync(
259
309
source_id. without_precise ( ) . as_url ( ) . to_string ( )
260
310
} ;
261
311
312
+ let replace_name = if !merge_sources {
313
+ format ! ( "vendor+{}" , name)
314
+ } else {
315
+ merged_source_name. to_string ( )
316
+ } ;
317
+
318
+ if !merge_sources {
319
+ let src_id_string = source_id_to_dir_name ( source_id) ;
320
+ let src_dir = PathBuf :: from ( canonical_destination) . join ( src_id_string. clone ( ) ) ;
321
+ let string = src_dir. to_str ( ) . unwrap ( ) . to_string ( ) ;
322
+ config. insert (
323
+ replace_name. clone ( ) ,
324
+ VendorSource :: Directory { directory : string } ,
325
+ ) ;
326
+ }
327
+
262
328
let source = if source_id. is_crates_io ( ) {
263
329
VendorSource :: Registry {
264
330
registry : None ,
265
- replace_with : merged_source_name . to_string ( ) ,
331
+ replace_with : replace_name ,
266
332
}
267
333
} else if source_id. is_remote_registry ( ) {
268
334
let registry = source_id. url ( ) . to_string ( ) ;
269
335
VendorSource :: Registry {
270
336
registry : Some ( registry) ,
271
- replace_with : merged_source_name . to_string ( ) ,
337
+ replace_with : replace_name ,
272
338
}
273
339
} else if source_id. is_git ( ) {
274
340
let mut branch = None ;
@@ -287,7 +353,7 @@ fn sync(
287
353
branch,
288
354
tag,
289
355
rev,
290
- replace_with : merged_source_name . to_string ( ) ,
356
+ replace_with : replace_name ,
291
357
}
292
358
} else {
293
359
panic ! ( "Invalid source ID: {}" , source_id)
@@ -396,6 +462,42 @@ fn cp_sources(
396
462
Ok ( ( ) )
397
463
}
398
464
465
+ fn source_id_to_dir_name ( src_id : SourceId ) -> String {
466
+ let src_type = if src_id. is_registry ( ) {
467
+ "registry"
468
+ } else if src_id. is_git ( ) {
469
+ "git"
470
+ } else {
471
+ panic ! ( )
472
+ } ;
473
+ let mut hasher = DefaultHasher :: new ( ) ;
474
+ src_id. stable_hash ( Path :: new ( "" ) , & mut hasher) ;
475
+ let src_hash = hasher. finish ( ) ;
476
+ let mut bytes = [ 0 ; 8 ] ;
477
+ for i in 0 ..7 {
478
+ bytes[ i] = ( src_hash >> i * 8 ) as u8
479
+ }
480
+ format ! ( "{}-{}" , src_type, hex( & bytes) )
481
+ }
482
+
483
+ fn hex ( bytes : & [ u8 ] ) -> String {
484
+ let mut s = String :: with_capacity ( bytes. len ( ) * 2 ) ;
485
+ for & byte in bytes {
486
+ s. push ( hex ( ( byte >> 4 ) & 0xf ) ) ;
487
+ s. push ( hex ( ( byte >> 0 ) & 0xf ) ) ;
488
+ }
489
+
490
+ return s;
491
+
492
+ fn hex ( b : u8 ) -> char {
493
+ if b < 10 {
494
+ ( b'0' + b) as char
495
+ } else {
496
+ ( b'a' + b - 10 ) as char
497
+ }
498
+ }
499
+ }
500
+
399
501
fn copy_and_checksum < T : Read > (
400
502
dst_path : & Path ,
401
503
dst_opts : & mut OpenOptions ,
0 commit comments