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, Config};
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 < ( ) > {
@@ -88,8 +93,16 @@ fn sync(
88
93
let canonical_destination = try_canonicalize ( opts. destination ) ;
89
94
let canonical_destination = canonical_destination. as_deref ( ) . unwrap_or ( opts. destination ) ;
90
95
let dest_dir_already_exists = canonical_destination. exists ( ) ;
96
+ let merge_sources = !opts. no_merge_sources ;
97
+ let sources_file = canonical_destination. join ( SOURCES_FILE_NAME ) ;
91
98
92
99
paths:: create_dir_all ( & canonical_destination) ?;
100
+
101
+ if !merge_sources {
102
+ let mut file = File :: create ( sources_file) ?;
103
+ file. write_all ( serde_json:: json!( [ ] ) . to_string ( ) . as_bytes ( ) ) ?;
104
+ }
105
+
93
106
let mut to_remove = HashSet :: new ( ) ;
94
107
if !opts. no_delete {
95
108
for entry in canonical_destination. read_dir ( ) ? {
@@ -176,8 +189,9 @@ fn sync(
176
189
let mut versions = HashMap :: new ( ) ;
177
190
for id in ids. keys ( ) {
178
191
let map = versions. entry ( id. name ( ) ) . or_insert_with ( BTreeMap :: default) ;
179
- if let Some ( prev) = map. get ( & id. version ( ) ) {
180
- bail ! (
192
+
193
+ match map. get ( & id. version ( ) ) {
194
+ Some ( prev) if merge_sources => bail ! (
181
195
"found duplicate version of package `{} v{}` \
182
196
vendored from two sources:\n \
183
197
\n \
@@ -187,7 +201,8 @@ fn sync(
187
201
id. version( ) ,
188
202
prev,
189
203
id. source_id( )
190
- ) ;
204
+ ) ,
205
+ _ => { }
191
206
}
192
207
map. insert ( id. version ( ) , id. source_id ( ) ) ;
193
208
}
@@ -211,7 +226,17 @@ fn sync(
211
226
} ;
212
227
213
228
sources. insert ( id. source_id ( ) ) ;
214
- let dst = canonical_destination. join ( & dst_name) ;
229
+ let source_dir = if merge_sources {
230
+ PathBuf :: from ( canonical_destination) . clone ( )
231
+ } else {
232
+ PathBuf :: from ( canonical_destination) . join ( source_id_to_dir_name ( id. source_id ( ) ) )
233
+ } ;
234
+ if sources. insert ( id. source_id ( ) ) && !merge_sources {
235
+ if fs:: create_dir_all ( & source_dir) . is_err ( ) {
236
+ panic ! ( "failed to create: `{}`" , source_dir. display( ) )
237
+ }
238
+ }
239
+ let dst = source_dir. join ( & dst_name) ;
215
240
to_remove. remove ( & dst) ;
216
241
let cksum = dst. join ( ".cargo-checksum.json" ) ;
217
242
if dir_has_version_suffix && cksum. exists ( ) {
@@ -248,6 +273,31 @@ fn sync(
248
273
}
249
274
}
250
275
276
+ if !merge_sources {
277
+ let sources_file = PathBuf :: from ( canonical_destination) . join ( SOURCES_FILE_NAME ) ;
278
+ let file = File :: open ( & sources_file) ?;
279
+ let mut new_sources: BTreeSet < String > = sources
280
+ . iter ( )
281
+ . map ( |src_id| source_id_to_dir_name ( * src_id) )
282
+ . collect ( ) ;
283
+ let old_sources: BTreeSet < String > = serde_json:: from_reader :: < _ , BTreeSet < String > > ( file) ?
284
+ . difference ( & new_sources)
285
+ . map ( |e| e. clone ( ) )
286
+ . collect ( ) ;
287
+ for dir_name in old_sources {
288
+ let path = PathBuf :: from ( canonical_destination) . join ( dir_name. clone ( ) ) ;
289
+ if path. is_dir ( ) {
290
+ if path. read_dir ( ) ?. next ( ) . is_none ( ) {
291
+ fs:: remove_dir ( path) ?;
292
+ } else {
293
+ new_sources. insert ( dir_name. clone ( ) ) ;
294
+ }
295
+ }
296
+ }
297
+ let file = File :: create ( sources_file) ?;
298
+ serde_json:: to_writer ( file, & new_sources) ?;
299
+ }
300
+
251
301
// add our vendored source
252
302
let mut config = BTreeMap :: new ( ) ;
253
303
@@ -263,16 +313,37 @@ fn sync(
263
313
source_id. without_precise ( ) . as_url ( ) . to_string ( )
264
314
} ;
265
315
316
+ let replace_name = if !merge_sources {
317
+ format ! ( "vendor+{}" , name)
318
+ } else {
319
+ merged_source_name. to_string ( )
320
+ } ;
321
+
322
+ if !merge_sources {
323
+ let src_id_string = source_id_to_dir_name ( source_id) ;
324
+ let src_dir = PathBuf :: from ( canonical_destination) . join ( src_id_string. clone ( ) ) ;
325
+ let string = src_dir. to_str ( ) . unwrap ( ) . to_string ( ) ;
326
+ config. insert (
327
+ replace_name. clone ( ) ,
328
+ VendorSource :: Directory { directory : string } ,
329
+ ) ;
330
+ }
331
+
332
+ // if source id is a path, skip the source replacement
333
+ if source_id. is_path ( ) {
334
+ continue ;
335
+ }
336
+
266
337
let source = if source_id. is_crates_io ( ) {
267
338
VendorSource :: Registry {
268
339
registry : None ,
269
- replace_with : merged_source_name . to_string ( ) ,
340
+ replace_with : replace_name ,
270
341
}
271
342
} else if source_id. is_remote_registry ( ) {
272
343
let registry = source_id. url ( ) . to_string ( ) ;
273
344
VendorSource :: Registry {
274
345
registry : Some ( registry) ,
275
- replace_with : merged_source_name . to_string ( ) ,
346
+ replace_with : replace_name ,
276
347
}
277
348
} else if source_id. is_git ( ) {
278
349
let mut branch = None ;
@@ -291,7 +362,7 @@ fn sync(
291
362
branch,
292
363
tag,
293
364
rev,
294
- replace_with : merged_source_name . to_string ( ) ,
365
+ replace_with : replace_name ,
295
366
}
296
367
} else {
297
368
panic ! ( "Invalid source ID: {}" , source_id)
@@ -400,6 +471,42 @@ fn cp_sources(
400
471
Ok ( ( ) )
401
472
}
402
473
474
+ fn source_id_to_dir_name ( src_id : SourceId ) -> String {
475
+ let src_type = if src_id. is_registry ( ) {
476
+ "registry"
477
+ } else if src_id. is_git ( ) {
478
+ "git"
479
+ } else {
480
+ panic ! ( )
481
+ } ;
482
+ let mut hasher = DefaultHasher :: new ( ) ;
483
+ src_id. stable_hash ( Path :: new ( "" ) , & mut hasher) ;
484
+ let src_hash = hasher. finish ( ) ;
485
+ let mut bytes = [ 0 ; 8 ] ;
486
+ for i in 0 ..7 {
487
+ bytes[ i] = ( src_hash >> i * 8 ) as u8
488
+ }
489
+ format ! ( "{}-{}" , src_type, hex( & bytes) )
490
+ }
491
+
492
+ fn hex ( bytes : & [ u8 ] ) -> String {
493
+ let mut s = String :: with_capacity ( bytes. len ( ) * 2 ) ;
494
+ for & byte in bytes {
495
+ s. push ( hex ( ( byte >> 4 ) & 0xf ) ) ;
496
+ s. push ( hex ( ( byte >> 0 ) & 0xf ) ) ;
497
+ }
498
+
499
+ return s;
500
+
501
+ fn hex ( b : u8 ) -> char {
502
+ if b < 10 {
503
+ ( b'0' + b) as char
504
+ } else {
505
+ ( b'a' + b - 10 ) as char
506
+ }
507
+ }
508
+ }
509
+
403
510
fn copy_and_checksum < T : Read > (
404
511
dst_path : & Path ,
405
512
dst_opts : & mut OpenOptions ,
0 commit comments