1
1
use crate :: core:: shell:: Verbosity ;
2
- use crate :: core:: SourceId ;
3
- use crate :: core:: { GitReference , Package , Workspace } ;
2
+ use crate :: core:: { GitReference , Package , SourceId , Workspace } ;
4
3
use crate :: ops;
5
4
use crate :: sources:: path:: PathSource ;
6
5
use crate :: sources:: PathEntry ;
@@ -11,19 +10,24 @@ use crate::util::{try_canonicalize, CargoResult, GlobalContext};
11
10
use anyhow:: { bail, Context as _} ;
12
11
use cargo_util:: { paths, Sha256 } ;
13
12
use serde:: Serialize ;
13
+ use std:: collections:: hash_map:: DefaultHasher ;
14
14
use std:: collections:: HashSet ;
15
15
use std:: collections:: { BTreeMap , BTreeSet , HashMap } ;
16
16
use std:: ffi:: OsStr ;
17
17
use std:: fs:: { self , File , OpenOptions } ;
18
+ use std:: hash:: Hasher ;
18
19
use std:: io:: { Read , Write } ;
19
20
use std:: path:: { Path , PathBuf } ;
20
21
22
+ const SOURCES_FILE_NAME : & str = ".sources" ;
23
+
21
24
pub struct VendorOptions < ' a > {
22
25
pub no_delete : bool ,
23
26
pub versioned_dirs : bool ,
24
27
pub destination : & ' a Path ,
25
28
pub extra : Vec < PathBuf > ,
26
29
pub respect_source_config : bool ,
30
+ pub no_merge_sources : bool ,
27
31
}
28
32
29
33
pub fn vendor ( ws : & Workspace < ' _ > , opts : & VendorOptions < ' _ > ) -> CargoResult < ( ) > {
@@ -114,8 +118,16 @@ fn sync(
114
118
let canonical_destination = try_canonicalize ( opts. destination ) ;
115
119
let canonical_destination = canonical_destination. as_deref ( ) . unwrap_or ( opts. destination ) ;
116
120
let dest_dir_already_exists = canonical_destination. exists ( ) ;
121
+ let merge_sources = !opts. no_merge_sources ;
122
+ let sources_file = canonical_destination. join ( SOURCES_FILE_NAME ) ;
117
123
118
124
paths:: create_dir_all ( & canonical_destination) ?;
125
+
126
+ if !merge_sources {
127
+ let mut file = File :: create ( sources_file) ?;
128
+ file. write_all ( serde_json:: json!( [ ] ) . to_string ( ) . as_bytes ( ) ) ?;
129
+ }
130
+
119
131
let mut to_remove = HashSet :: new ( ) ;
120
132
if !opts. no_delete {
121
133
for entry in canonical_destination. read_dir ( ) ? {
@@ -215,8 +227,9 @@ fn sync(
215
227
let mut versions = HashMap :: new ( ) ;
216
228
for id in ids. keys ( ) {
217
229
let map = versions. entry ( id. name ( ) ) . or_insert_with ( BTreeMap :: default) ;
218
- if let Some ( prev) = map. get ( & id. version ( ) ) {
219
- bail ! (
230
+
231
+ match map. get ( & id. version ( ) ) {
232
+ Some ( prev) if merge_sources => bail ! (
220
233
"found duplicate version of package `{} v{}` \
221
234
vendored from two sources:\n \
222
235
\n \
@@ -226,7 +239,8 @@ fn sync(
226
239
id. version( ) ,
227
240
prev,
228
241
id. source_id( )
229
- ) ;
242
+ ) ,
243
+ _ => { }
230
244
}
231
245
map. insert ( id. version ( ) , id. source_id ( ) ) ;
232
246
}
@@ -247,7 +261,17 @@ fn sync(
247
261
} ;
248
262
249
263
sources. insert ( id. source_id ( ) ) ;
250
- let dst = canonical_destination. join ( & dst_name) ;
264
+ let source_dir = if merge_sources {
265
+ PathBuf :: from ( canonical_destination) . clone ( )
266
+ } else {
267
+ PathBuf :: from ( canonical_destination) . join ( source_id_to_dir_name ( id. source_id ( ) ) )
268
+ } ;
269
+ if sources. insert ( id. source_id ( ) ) && !merge_sources {
270
+ if fs:: create_dir_all ( & source_dir) . is_err ( ) {
271
+ panic ! ( "failed to create: `{}`" , source_dir. display( ) )
272
+ }
273
+ }
274
+ let dst = source_dir. join ( & dst_name) ;
251
275
to_remove. remove ( & dst) ;
252
276
let cksum = dst. join ( ".cargo-checksum.json" ) ;
253
277
// Registries are the only immutable sources,
@@ -286,6 +310,31 @@ fn sync(
286
310
}
287
311
}
288
312
313
+ if !merge_sources {
314
+ let sources_file = PathBuf :: from ( canonical_destination) . join ( SOURCES_FILE_NAME ) ;
315
+ let file = File :: open ( & sources_file) ?;
316
+ let mut new_sources: BTreeSet < String > = sources
317
+ . iter ( )
318
+ . map ( |src_id| source_id_to_dir_name ( * src_id) )
319
+ . collect ( ) ;
320
+ let old_sources: BTreeSet < String > = serde_json:: from_reader :: < _ , BTreeSet < String > > ( file) ?
321
+ . difference ( & new_sources)
322
+ . map ( |e| e. clone ( ) )
323
+ . collect ( ) ;
324
+ for dir_name in old_sources {
325
+ let path = PathBuf :: from ( canonical_destination) . join ( dir_name. clone ( ) ) ;
326
+ if path. is_dir ( ) {
327
+ if path. read_dir ( ) ?. next ( ) . is_none ( ) {
328
+ fs:: remove_dir ( path) ?;
329
+ } else {
330
+ new_sources. insert ( dir_name. clone ( ) ) ;
331
+ }
332
+ }
333
+ }
334
+ let file = File :: create ( sources_file) ?;
335
+ serde_json:: to_writer ( file, & new_sources) ?;
336
+ }
337
+
289
338
// add our vendored source
290
339
let mut config = BTreeMap :: new ( ) ;
291
340
@@ -301,16 +350,32 @@ fn sync(
301
350
source_id. without_precise ( ) . as_url ( ) . to_string ( )
302
351
} ;
303
352
353
+ let replace_name = if !merge_sources {
354
+ format ! ( "vendor+{}" , name)
355
+ } else {
356
+ merged_source_name. to_string ( )
357
+ } ;
358
+
359
+ if !merge_sources {
360
+ let src_id_string = source_id_to_dir_name ( source_id) ;
361
+ let src_dir = PathBuf :: from ( canonical_destination) . join ( src_id_string. clone ( ) ) ;
362
+ let string = src_dir. to_str ( ) . unwrap ( ) . to_string ( ) ;
363
+ config. insert (
364
+ replace_name. clone ( ) ,
365
+ VendorSource :: Directory { directory : string } ,
366
+ ) ;
367
+ }
368
+
304
369
let source = if source_id. is_crates_io ( ) {
305
370
VendorSource :: Registry {
306
371
registry : None ,
307
- replace_with : merged_source_name . to_string ( ) ,
372
+ replace_with : replace_name ,
308
373
}
309
374
} else if source_id. is_remote_registry ( ) {
310
375
let registry = source_id. url ( ) . to_string ( ) ;
311
376
VendorSource :: Registry {
312
377
registry : Some ( registry) ,
313
- replace_with : merged_source_name . to_string ( ) ,
378
+ replace_with : replace_name ,
314
379
}
315
380
} else if source_id. is_git ( ) {
316
381
let mut branch = None ;
@@ -329,7 +394,7 @@ fn sync(
329
394
branch,
330
395
tag,
331
396
rev,
332
- replace_with : merged_source_name . to_string ( ) ,
397
+ replace_with : replace_name ,
333
398
}
334
399
} else {
335
400
panic ! ( "Invalid source ID: {}" , source_id)
@@ -558,6 +623,42 @@ fn prepare_toml_for_vendor(
558
623
Ok ( me)
559
624
}
560
625
626
+ fn source_id_to_dir_name ( src_id : SourceId ) -> String {
627
+ let src_type = if src_id. is_registry ( ) {
628
+ "registry"
629
+ } else if src_id. is_git ( ) {
630
+ "git"
631
+ } else {
632
+ panic ! ( )
633
+ } ;
634
+ let mut hasher = DefaultHasher :: new ( ) ;
635
+ src_id. stable_hash ( Path :: new ( "" ) , & mut hasher) ;
636
+ let src_hash = hasher. finish ( ) ;
637
+ let mut bytes = [ 0 ; 8 ] ;
638
+ for i in 0 ..7 {
639
+ bytes[ i] = ( src_hash >> i * 8 ) as u8
640
+ }
641
+ format ! ( "{}-{}" , src_type, hex( & bytes) )
642
+ }
643
+
644
+ fn hex ( bytes : & [ u8 ] ) -> String {
645
+ let mut s = String :: with_capacity ( bytes. len ( ) * 2 ) ;
646
+ for & byte in bytes {
647
+ s. push ( hex ( ( byte >> 4 ) & 0xf ) ) ;
648
+ s. push ( hex ( ( byte >> 0 ) & 0xf ) ) ;
649
+ }
650
+
651
+ return s;
652
+
653
+ fn hex ( b : u8 ) -> char {
654
+ if b < 10 {
655
+ ( b'0' + b) as char
656
+ } else {
657
+ ( b'a' + b - 10 ) as char
658
+ }
659
+ }
660
+ }
661
+
561
662
fn copy_and_checksum < T : Read > (
562
663
dst_path : & Path ,
563
664
dst_opts : & mut OpenOptions ,
0 commit comments