11use crate :: core:: package:: MANIFEST_PREAMBLE ;
22use crate :: core:: shell:: Verbosity ;
3- use crate :: core:: { GitReference , Package , Workspace } ;
3+ use crate :: core:: { GitReference , Package , SourceId , Workspace } ;
44use crate :: ops;
55use crate :: sources:: path:: PathSource ;
66use crate :: sources:: CRATES_IO_REGISTRY ;
@@ -9,18 +9,23 @@ use crate::util::{try_canonicalize, CargoResult, Config};
99use anyhow:: { bail, Context as _} ;
1010use cargo_util:: { paths, Sha256 } ;
1111use serde:: Serialize ;
12+ use std:: collections:: hash_map:: DefaultHasher ;
1213use std:: collections:: HashSet ;
1314use std:: collections:: { BTreeMap , BTreeSet , HashMap } ;
1415use std:: ffi:: OsStr ;
1516use std:: fs:: { self , File , OpenOptions } ;
17+ use std:: hash:: Hasher ;
1618use std:: io:: { Read , Write } ;
1719use std:: path:: { Path , PathBuf } ;
1820
21+ const SOURCES_FILE_NAME : & str = ".sources" ;
22+
1923pub struct VendorOptions < ' a > {
2024 pub no_delete : bool ,
2125 pub versioned_dirs : bool ,
2226 pub destination : & ' a Path ,
2327 pub extra : Vec < PathBuf > ,
28+ pub no_merge_sources : bool ,
2429}
2530
2631pub fn vendor ( ws : & Workspace < ' _ > , opts : & VendorOptions < ' _ > ) -> CargoResult < ( ) > {
@@ -88,8 +93,16 @@ fn sync(
8893 let canonical_destination = try_canonicalize ( opts. destination ) ;
8994 let canonical_destination = canonical_destination. as_deref ( ) . unwrap_or ( opts. destination ) ;
9095 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 ) ;
9198
9299 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+
93106 let mut to_remove = HashSet :: new ( ) ;
94107 if !opts. no_delete {
95108 for entry in canonical_destination. read_dir ( ) ? {
@@ -176,8 +189,9 @@ fn sync(
176189 let mut versions = HashMap :: new ( ) ;
177190 for id in ids. keys ( ) {
178191 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 ! (
181195 "found duplicate version of package `{} v{}` \
182196 vendored from two sources:\n \
183197 \n \
@@ -187,7 +201,8 @@ fn sync(
187201 id. version( ) ,
188202 prev,
189203 id. source_id( )
190- ) ;
204+ ) ,
205+ _ => { }
191206 }
192207 map. insert ( id. version ( ) , id. source_id ( ) ) ;
193208 }
@@ -211,7 +226,17 @@ fn sync(
211226 } ;
212227
213228 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) ;
215240 to_remove. remove ( & dst) ;
216241 let cksum = dst. join ( ".cargo-checksum.json" ) ;
217242 if dir_has_version_suffix && cksum. exists ( ) {
@@ -248,6 +273,31 @@ fn sync(
248273 }
249274 }
250275
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+
251301 // add our vendored source
252302 let mut config = BTreeMap :: new ( ) ;
253303
@@ -263,16 +313,37 @@ fn sync(
263313 source_id. without_precise ( ) . as_url ( ) . to_string ( )
264314 } ;
265315
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+
266337 let source = if source_id. is_crates_io ( ) {
267338 VendorSource :: Registry {
268339 registry : None ,
269- replace_with : merged_source_name . to_string ( ) ,
340+ replace_with : replace_name ,
270341 }
271342 } else if source_id. is_remote_registry ( ) {
272343 let registry = source_id. url ( ) . to_string ( ) ;
273344 VendorSource :: Registry {
274345 registry : Some ( registry) ,
275- replace_with : merged_source_name . to_string ( ) ,
346+ replace_with : replace_name ,
276347 }
277348 } else if source_id. is_git ( ) {
278349 let mut branch = None ;
@@ -291,7 +362,7 @@ fn sync(
291362 branch,
292363 tag,
293364 rev,
294- replace_with : merged_source_name . to_string ( ) ,
365+ replace_with : replace_name ,
295366 }
296367 } else {
297368 panic ! ( "Invalid source ID: {}" , source_id)
@@ -400,6 +471,42 @@ fn cp_sources(
400471 Ok ( ( ) )
401472}
402473
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+
403510fn copy_and_checksum < T : Read > (
404511 dst_path : & Path ,
405512 dst_opts : & mut OpenOptions ,
0 commit comments