1
- use std:: collections:: HashMap ;
1
+ use std:: collections:: { BTreeSet , HashMap } ;
2
2
use std:: fs:: { self , File } ;
3
3
use std:: io:: prelude:: * ;
4
4
use std:: io:: SeekFrom ;
5
5
use std:: path:: { self , Path , PathBuf } ;
6
6
use std:: sync:: Arc ;
7
7
8
- use flate2:: read:: GzDecoder ;
9
8
use flate2:: { Compression , GzBuilder } ;
10
9
use log:: debug;
11
10
use serde_json:: { self , json} ;
12
- use tar:: { Archive , Builder , EntryType , Header } ;
11
+ use tar:: { Builder , EntryType , Header } ;
13
12
14
13
use crate :: core:: compiler:: { BuildConfig , CompileMode , DefaultExecutor , Executor } ;
15
- use crate :: core:: { Package , Source , SourceId , Workspace } ;
14
+ use crate :: core:: resolver:: Method ;
15
+ use crate :: core:: { Package , PackageId , PackageIdSpec , Resolve , Source , SourceId , Workspace } ;
16
16
use crate :: ops;
17
17
use crate :: sources:: PathSource ;
18
18
use crate :: util:: errors:: { CargoResult , CargoResultExt } ;
@@ -35,7 +35,6 @@ pub struct PackageOpts<'cfg> {
35
35
static VCS_INFO_FILE : & ' static str = ".cargo_vcs_info.json" ;
36
36
37
37
pub fn package ( ws : & Workspace < ' _ > , opts : & PackageOpts < ' _ > ) -> CargoResult < Option < FileLock > > {
38
- ops:: resolve_ws ( ws) ?;
39
38
let pkg = ws. current ( ) ?;
40
39
let config = ws. config ( ) ;
41
40
@@ -100,7 +99,7 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult<Option
100
99
. shell ( )
101
100
. status ( "Packaging" , pkg. package_id ( ) . to_string ( ) ) ?;
102
101
dst. file ( ) . set_len ( 0 ) ?;
103
- tar ( ws, & src_files, vcs_info. as_ref ( ) , dst. file ( ) , & filename)
102
+ tar ( ws, & src_files, vcs_info. as_ref ( ) , & dst, & filename)
104
103
. chain_err ( || failure:: format_err!( "failed to prepare local package for uploading" ) ) ?;
105
104
if opts. verify {
106
105
dst. seek ( SeekFrom :: Start ( 0 ) ) ?;
@@ -281,35 +280,43 @@ fn tar(
281
280
ws : & Workspace < ' _ > ,
282
281
src_files : & [ PathBuf ] ,
283
282
vcs_info : Option < & serde_json:: Value > ,
284
- dst : & File ,
283
+ dst : & FileLock ,
285
284
filename : & str ,
286
285
) -> CargoResult < ( ) > {
287
286
// Prepare the encoder and its header.
288
287
let filename = Path :: new ( filename) ;
289
288
let encoder = GzBuilder :: new ( )
290
289
. filename ( util:: path2bytes ( filename) ?)
291
- . write ( dst, Compression :: best ( ) ) ;
290
+ . write ( dst. file ( ) , Compression :: best ( ) ) ;
292
291
293
292
// Put all package files into a compressed archive.
294
293
let mut ar = Builder :: new ( encoder) ;
295
294
let pkg = ws. current ( ) ?;
296
295
let config = ws. config ( ) ;
297
296
let root = pkg. root ( ) ;
298
- for file in src_files. iter ( ) {
299
- let relative = file. strip_prefix ( root) ?;
297
+ // While creating the tar file, also copy to the output directory.
298
+ let dest_copy_root = dst
299
+ . parent ( )
300
+ . join ( format ! ( "{}-{}" , pkg. name( ) , pkg. version( ) ) ) ;
301
+ if dest_copy_root. exists ( ) {
302
+ paths:: remove_dir_all ( & dest_copy_root) ?;
303
+ }
304
+ fs:: create_dir_all ( & dest_copy_root) ?;
305
+ for src_file in src_files {
306
+ let relative = src_file. strip_prefix ( root) ?;
300
307
check_filename ( relative) ?;
301
- let relative = relative. to_str ( ) . ok_or_else ( || {
308
+ let relative_str = relative. to_str ( ) . ok_or_else ( || {
302
309
failure:: format_err!( "non-utf8 path in source directory: {}" , relative. display( ) )
303
310
} ) ?;
304
311
config
305
312
. shell ( )
306
- . verbose ( |shell| shell. status ( "Archiving" , & relative ) ) ?;
313
+ . verbose ( |shell| shell. status ( "Archiving" , & relative_str ) ) ?;
307
314
let path = format ! (
308
315
"{}-{}{}{}" ,
309
316
pkg. name( ) ,
310
317
pkg. version( ) ,
311
318
path:: MAIN_SEPARATOR ,
312
- relative
319
+ relative_str
313
320
) ;
314
321
315
322
// The `tar::Builder` type by default will build GNU archives, but
@@ -333,20 +340,21 @@ fn tar(
333
340
let mut header = Header :: new_ustar ( ) ;
334
341
header
335
342
. set_path ( & path)
336
- . chain_err ( || format ! ( "failed to add to archive: `{}`" , relative ) ) ?;
337
- let mut file = File :: open ( file )
338
- . chain_err ( || format ! ( "failed to open for archiving: `{}`" , file . display( ) ) ) ?;
343
+ . chain_err ( || format ! ( "failed to add to archive: `{}`" , relative_str ) ) ?;
344
+ let mut file = File :: open ( src_file )
345
+ . chain_err ( || format ! ( "failed to open for archiving: `{}`" , src_file . display( ) ) ) ?;
339
346
let metadata = file
340
347
. metadata ( )
341
- . chain_err ( || format ! ( "could not learn metadata for: `{}`" , relative ) ) ?;
348
+ . chain_err ( || format ! ( "could not learn metadata for: `{}`" , relative_str ) ) ?;
342
349
header. set_metadata ( & metadata) ;
343
350
344
- if relative == "Cargo.toml" {
351
+ if relative_str == "Cargo.toml" {
345
352
let orig = Path :: new ( & path) . with_file_name ( "Cargo.toml.orig" ) ;
346
353
header. set_path ( & orig) ?;
347
354
header. set_cksum ( ) ;
348
- ar. append ( & header, & mut file)
349
- . chain_err ( || internal ( format ! ( "could not archive source file `{}`" , relative) ) ) ?;
355
+ ar. append ( & header, & mut file) . chain_err ( || {
356
+ internal ( format ! ( "could not archive source file `{}`" , relative_str) )
357
+ } ) ?;
350
358
351
359
let mut header = Header :: new_ustar ( ) ;
352
360
let toml = pkg. to_registry_toml ( ws. config ( ) ) ?;
@@ -355,12 +363,22 @@ fn tar(
355
363
header. set_mode ( 0o644 ) ;
356
364
header. set_size ( toml. len ( ) as u64 ) ;
357
365
header. set_cksum ( ) ;
358
- ar. append ( & header, toml. as_bytes ( ) )
359
- . chain_err ( || internal ( format ! ( "could not archive source file `{}`" , relative) ) ) ?;
366
+ ar. append ( & header, toml. as_bytes ( ) ) . chain_err ( || {
367
+ internal ( format ! ( "could not archive source file `{}`" , relative_str) )
368
+ } ) ?;
369
+ fs:: write ( dest_copy_root. join ( relative) , toml) ?;
370
+ fs:: copy ( src_file, dest_copy_root. join ( "Cargo.toml.orig" ) ) ?;
360
371
} else {
361
372
header. set_cksum ( ) ;
362
- ar. append ( & header, & mut file)
363
- . chain_err ( || internal ( format ! ( "could not archive source file `{}`" , relative) ) ) ?;
373
+ ar. append ( & header, & mut file) . chain_err ( || {
374
+ internal ( format ! ( "could not archive source file `{}`" , relative_str) )
375
+ } ) ?;
376
+ let dest = dest_copy_root. join ( relative) ;
377
+ let parent = dest. parent ( ) . unwrap ( ) ;
378
+ if !parent. exists ( ) {
379
+ fs:: create_dir_all ( parent) ?;
380
+ }
381
+ fs:: copy ( src_file, dest) ?;
364
382
}
365
383
}
366
384
@@ -394,7 +412,27 @@ fn tar(
394
412
}
395
413
396
414
if include_lockfile ( pkg) {
397
- let toml = paths:: read ( & ws. root ( ) . join ( "Cargo.lock" ) ) ?;
415
+ let orig_lock_path = ws. root ( ) . join ( "Cargo.lock" ) ;
416
+ let new_lock_path = dest_copy_root. join ( "Cargo.lock" ) ;
417
+ if orig_lock_path. exists ( ) {
418
+ fs:: copy ( & orig_lock_path, & new_lock_path) ?;
419
+ }
420
+
421
+ // Regenerate Cargo.lock using the old one as a guide.
422
+ let orig_resolve = ops:: load_pkg_lockfile ( ws) ?;
423
+ let id = SourceId :: for_path ( & dest_copy_root) ?;
424
+ let mut src = PathSource :: new ( & dest_copy_root, id, ws. config ( ) ) ;
425
+ let new_pkg = src. root_package ( ) ?;
426
+ let specs = vec ! [ PackageIdSpec :: from_package_id( new_pkg. package_id( ) ) ] ;
427
+ let tmp_ws = Workspace :: ephemeral ( new_pkg, config, None , true ) ?;
428
+ let new_resolve = ops:: resolve_ws_with_method ( & tmp_ws, None , Method :: Everything , & specs) ?. 1 ;
429
+ // resolve_ws_with_method won't save for ephemeral, do it manually.
430
+ ops:: write_pkg_lockfile ( & tmp_ws, & new_resolve) ?;
431
+ if let Some ( orig_resolve) = orig_resolve {
432
+ compare_resolve ( config, tmp_ws. current ( ) ?, & orig_resolve, & new_resolve) ?;
433
+ }
434
+
435
+ let toml = paths:: read ( & new_lock_path) ?;
398
436
let path = format ! (
399
437
"{}-{}{}Cargo.lock" ,
400
438
pkg. name( ) ,
@@ -416,24 +454,97 @@ fn tar(
416
454
Ok ( ( ) )
417
455
}
418
456
457
+ /// Generate warnings when packaging Cargo.lock, and the resolve have changed.
458
+ fn compare_resolve (
459
+ config : & Config ,
460
+ current_pkg : & Package ,
461
+ orig_resolve : & Resolve ,
462
+ new_resolve : & Resolve ,
463
+ ) -> CargoResult < ( ) > {
464
+ let new_set: BTreeSet < PackageId > = new_resolve. iter ( ) . collect ( ) ;
465
+ let orig_set: BTreeSet < PackageId > = orig_resolve. iter ( ) . collect ( ) ;
466
+ let added = new_set. difference ( & orig_set) ;
467
+ // Removed entries are ignored, this is used to quickly find hints for why
468
+ // an entry changed.
469
+ let removed: Vec < & PackageId > = orig_set. difference ( & new_set) . collect ( ) ;
470
+ for pkg_id in added {
471
+ if pkg_id. name ( ) == current_pkg. name ( ) && pkg_id. version ( ) == current_pkg. version ( ) {
472
+ // Skip the package that is being created, since its SourceId
473
+ // (directory) changes.
474
+ continue ;
475
+ }
476
+ // Check for candidates where the source has changed (such as [patch]
477
+ // or a dependency with multiple sources like path/version).
478
+ let removed_candidates: Vec < & PackageId > = removed
479
+ . iter ( )
480
+ . filter ( |orig_pkg_id| {
481
+ orig_pkg_id. name ( ) == pkg_id. name ( ) && orig_pkg_id. version ( ) == pkg_id. version ( )
482
+ } )
483
+ . cloned ( )
484
+ . collect ( ) ;
485
+ let extra = match removed_candidates. len ( ) {
486
+ 0 => {
487
+ // This can happen if the original was out of date.
488
+ let previous_versions: Vec < & PackageId > = removed
489
+ . iter ( )
490
+ . filter ( |orig_pkg_id| orig_pkg_id. name ( ) == pkg_id. name ( ) )
491
+ . cloned ( )
492
+ . collect ( ) ;
493
+ match previous_versions. len ( ) {
494
+ 0 => String :: new ( ) ,
495
+ 1 => format ! (
496
+ ", previous version was `{}`" ,
497
+ previous_versions[ 0 ] . version( )
498
+ ) ,
499
+ _ => format ! (
500
+ ", previous versions were: {}" ,
501
+ previous_versions
502
+ . iter( )
503
+ . map( |pkg_id| format!( "`{}`" , pkg_id. version( ) ) )
504
+ . collect:: <Vec <_>>( )
505
+ . join( ", " )
506
+ ) ,
507
+ }
508
+ }
509
+ 1 => {
510
+ // This can happen for multi-sourced dependencies like
511
+ // `{path="...", version="..."}` or `[patch]` replacement.
512
+ // `[replace]` is not captured in Cargo.lock.
513
+ format ! (
514
+ ", was originally sourced from `{}`" ,
515
+ removed_candidates[ 0 ] . source_id( )
516
+ )
517
+ }
518
+ _ => {
519
+ // I don't know if there is a way to actually trigger this,
520
+ // but handle it just in case.
521
+ let comma_list = removed_candidates
522
+ . iter ( )
523
+ . map ( |pkg_id| format ! ( "`{}`" , pkg_id. source_id( ) ) )
524
+ . collect :: < Vec < _ > > ( )
525
+ . join ( ", " ) ;
526
+ format ! (
527
+ ", was originally sourced from one of these sources: {}" ,
528
+ comma_list
529
+ )
530
+ }
531
+ } ;
532
+ config
533
+ . shell ( )
534
+ . warn ( format ! ( "package `{}` added to Cargo.lock{}" , pkg_id, extra) ) ?;
535
+ }
536
+ Ok ( ( ) )
537
+ }
538
+
419
539
fn run_verify ( ws : & Workspace < ' _ > , tar : & FileLock , opts : & PackageOpts < ' _ > ) -> CargoResult < ( ) > {
420
540
let config = ws. config ( ) ;
421
541
let pkg = ws. current ( ) ?;
422
542
423
543
config. shell ( ) . status ( "Verifying" , pkg) ?;
424
544
425
- let f = GzDecoder :: new ( tar. file ( ) ) ;
426
545
let dst = tar
427
546
. parent ( )
428
547
. join ( & format ! ( "{}-{}" , pkg. name( ) , pkg. version( ) ) ) ;
429
- if dst. exists ( ) {
430
- paths:: remove_dir_all ( & dst) ?;
431
- }
432
- let mut archive = Archive :: new ( f) ;
433
- // We don't need to set the Modified Time, as it's not relevant to verification
434
- // and it errors on filesystems that don't support setting a modified timestamp
435
- archive. set_preserve_mtime ( false ) ;
436
- archive. unpack ( dst. parent ( ) . unwrap ( ) ) ?;
437
548
438
549
// Manufacture an ephemeral workspace to ensure that even if the top-level
439
550
// package has a workspace we can still build our new crate.
0 commit comments