1- use std:: collections:: { BTreeSet , HashMap } ;
1+ use std:: collections:: BTreeMap ;
2+ use std:: collections:: BTreeSet ;
3+ use std:: collections:: HashMap ;
24use std:: fs:: { self , File } ;
35use std:: io:: prelude:: * ;
46use std:: io:: SeekFrom ;
@@ -32,6 +34,7 @@ use crate::util::HumanBytes;
3234use crate :: { drop_println, ops} ;
3335use anyhow:: { bail, Context as _} ;
3436use cargo_util:: paths;
37+ use cargo_util_schemas:: messages;
3538use flate2:: { Compression , GzBuilder } ;
3639use tar:: { Builder , EntryType , Header , HeaderMode } ;
3740use tracing:: debug;
@@ -40,10 +43,38 @@ use unicase::Ascii as UncasedAscii;
4043mod vcs;
4144mod verify;
4245
46+ /// Message format for `cargo package`.
47+ ///
48+ /// Currently only affect the output of the `--list` flag.
49+ #[ derive( Debug , Clone ) ]
50+ pub enum PackageMessageFormat {
51+ Human ,
52+ Json ,
53+ }
54+
55+ impl PackageMessageFormat {
56+ pub const POSSIBLE_VALUES : [ & str ; 2 ] = [ "human" , "json" ] ;
57+
58+ pub const DEFAULT : & str = "human" ;
59+ }
60+
61+ impl std:: str:: FromStr for PackageMessageFormat {
62+ type Err = anyhow:: Error ;
63+
64+ fn from_str ( s : & str ) -> Result < PackageMessageFormat , anyhow:: Error > {
65+ match s {
66+ "human" => Ok ( PackageMessageFormat :: Human ) ,
67+ "json" => Ok ( PackageMessageFormat :: Json ) ,
68+ f => bail ! ( "unknown message format `{f}`" ) ,
69+ }
70+ }
71+ }
72+
4373#[ derive( Clone ) ]
4474pub struct PackageOpts < ' gctx > {
4575 pub gctx : & ' gctx GlobalContext ,
4676 pub list : bool ,
77+ pub fmt : PackageMessageFormat ,
4778 pub check_metadata : bool ,
4879 pub allow_dirty : bool ,
4980 pub include_lockfile : bool ,
@@ -78,9 +109,13 @@ enum FileContents {
78109
79110enum GeneratedFile {
80111 /// Generates `Cargo.toml` by rewriting the original.
81- Manifest ,
82- /// Generates `Cargo.lock` in some cases (like if there is a binary).
83- Lockfile ,
112+ ///
113+ /// Associated path is the original manifest path.
114+ Manifest ( PathBuf ) ,
115+ /// Generates `Cargo.lock`.
116+ ///
117+ /// Associated path is the path to the original lock file, if existing.
118+ Lockfile ( Option < PathBuf > ) ,
84119 /// Adds a `.cargo_vcs_info.json` file if in a git repo.
85120 VcsInfo ( vcs:: VcsInfo ) ,
86121}
@@ -236,8 +271,33 @@ fn do_package<'a>(
236271 let ar_files = prepare_archive ( ws, & pkg, & opts) ?;
237272
238273 if opts. list {
239- for ar_file in & ar_files {
240- drop_println ! ( ws. gctx( ) , "{}" , ar_file. rel_str) ;
274+ match opts. fmt {
275+ PackageMessageFormat :: Human => {
276+ // While this form is called "human",
277+ // it keeps the old file-per-line format for compatibility.
278+ for ar_file in & ar_files {
279+ drop_println ! ( ws. gctx( ) , "{}" , ar_file. rel_str) ;
280+ }
281+ }
282+ PackageMessageFormat :: Json => {
283+ let message = messages:: PackageList {
284+ id : pkg. package_id ( ) . to_spec ( ) ,
285+ files : BTreeMap :: from_iter ( ar_files. into_iter ( ) . map ( |f| {
286+ let file = match f. contents {
287+ FileContents :: OnDisk ( path) => messages:: PackageFile :: Copy { path } ,
288+ FileContents :: Generated (
289+ GeneratedFile :: Manifest ( path)
290+ | GeneratedFile :: Lockfile ( Some ( path) ) ,
291+ ) => messages:: PackageFile :: Generate { path : Some ( path) } ,
292+ FileContents :: Generated (
293+ GeneratedFile :: VcsInfo ( _) | GeneratedFile :: Lockfile ( None ) ,
294+ ) => messages:: PackageFile :: Generate { path : None } ,
295+ } ;
296+ ( f. rel_path , file)
297+ } ) ) ,
298+ } ;
299+ let _ = ws. gctx ( ) . shell ( ) . print_json ( & message) ;
300+ }
241301 }
242302 } else {
243303 let tarball = create_package ( ws, & pkg, ar_files, local_reg. as_ref ( ) ) ?;
@@ -444,7 +504,9 @@ fn build_ar_list(
444504 . push ( ArchiveFile {
445505 rel_path : PathBuf :: from ( "Cargo.toml" ) ,
446506 rel_str : "Cargo.toml" . to_string ( ) ,
447- contents : FileContents :: Generated ( GeneratedFile :: Manifest ) ,
507+ contents : FileContents :: Generated ( GeneratedFile :: Manifest (
508+ pkg. manifest_path ( ) . to_owned ( ) ,
509+ ) ) ,
448510 } ) ;
449511 } else {
450512 ws. gctx ( ) . shell ( ) . warn ( & format ! (
@@ -454,14 +516,16 @@ fn build_ar_list(
454516 }
455517
456518 if include_lockfile {
519+ let lockfile_path = ws. lock_root ( ) . as_path_unlocked ( ) . join ( LOCKFILE_NAME ) ;
520+ let lockfile_path = lockfile_path. exists ( ) . then_some ( lockfile_path) ;
457521 let rel_str = "Cargo.lock" ;
458522 result
459523 . entry ( UncasedAscii :: new ( rel_str) )
460524 . or_insert_with ( Vec :: new)
461525 . push ( ArchiveFile {
462526 rel_path : PathBuf :: from ( rel_str) ,
463527 rel_str : rel_str. to_string ( ) ,
464- contents : FileContents :: Generated ( GeneratedFile :: Lockfile ) ,
528+ contents : FileContents :: Generated ( GeneratedFile :: Lockfile ( lockfile_path ) ) ,
465529 } ) ;
466530 }
467531
@@ -780,8 +844,10 @@ fn tar(
780844 }
781845 FileContents :: Generated ( generated_kind) => {
782846 let contents = match generated_kind {
783- GeneratedFile :: Manifest => publish_pkg. manifest ( ) . to_normalized_contents ( ) ?,
784- GeneratedFile :: Lockfile => build_lock ( ws, & publish_pkg, local_reg) ?,
847+ GeneratedFile :: Manifest ( _) => {
848+ publish_pkg. manifest ( ) . to_normalized_contents ( ) ?
849+ }
850+ GeneratedFile :: Lockfile ( _) => build_lock ( ws, & publish_pkg, local_reg) ?,
785851 GeneratedFile :: VcsInfo ( ref s) => serde_json:: to_string_pretty ( s) ?,
786852 } ;
787853 header. set_entry_type ( EntryType :: file ( ) ) ;
0 commit comments