1+ use std:: collections:: HashSet ;
2+ use std:: fmt;
3+ use std:: fmt:: Write ;
4+
15use cargo:: core:: registry:: PackageRegistry ;
26use cargo:: core:: QueryKind ;
37use cargo:: core:: Registry ;
48use cargo:: core:: SourceId ;
9+ use cargo:: ops:: Packages ;
510use cargo:: util:: command_prelude:: * ;
611
12+ type Record = ( String , Option < String > , String , bool ) ;
13+
714pub fn cli ( ) -> clap:: Command {
815 clap:: Command :: new ( "xtask-unpublished" )
16+ . arg ( flag (
17+ "check-version-bump" ,
18+ "check if any version bump is needed" ,
19+ ) )
20+ . arg_package_spec_simple ( "Package to inspect the published status" )
921 . arg (
1022 opt (
1123 "verbose" ,
@@ -76,14 +88,24 @@ fn config_configure(config: &mut Config, args: &ArgMatches) -> CliResult {
7688
7789fn unpublished ( args : & clap:: ArgMatches , config : & mut cargo:: util:: Config ) -> cargo:: CliResult {
7890 let ws = args. workspace ( config) ?;
91+
92+ let members_to_inspect: HashSet < _ > = {
93+ let pkgs = args. packages_from_flags ( ) ?;
94+ if let Packages :: Packages ( _) = pkgs {
95+ HashSet :: from_iter ( pkgs. get_packages ( & ws) ?)
96+ } else {
97+ HashSet :: from_iter ( ws. members ( ) )
98+ }
99+ } ;
100+
79101 let mut results = Vec :: new ( ) ;
80102 {
81103 let mut registry = PackageRegistry :: new ( config) ?;
82104 let _lock = config. acquire_package_cache_lock ( ) ?;
83105 registry. lock_patches ( ) ;
84106 let source_id = SourceId :: crates_io ( config) ?;
85107
86- for member in ws . members ( ) {
108+ for member in members_to_inspect {
87109 let name = member. name ( ) ;
88110 let current = member. version ( ) ;
89111 if member. publish ( ) == & Some ( vec ! [ ] ) {
@@ -92,11 +114,8 @@ fn unpublished(args: &clap::ArgMatches, config: &mut cargo::util::Config) -> car
92114 }
93115
94116 let version_req = format ! ( "<={current}" ) ;
95- let query = cargo:: core:: dependency:: Dependency :: parse (
96- name,
97- Some ( & version_req) ,
98- source_id. clone ( ) ,
99- ) ?;
117+ let query =
118+ cargo:: core:: dependency:: Dependency :: parse ( name, Some ( & version_req) , source_id) ?;
100119 let possibilities = loop {
101120 // Exact to avoid returning all for path/git
102121 match registry. query_vec ( & query, QueryKind :: Exact ) {
@@ -107,50 +126,142 @@ fn unpublished(args: &clap::ArgMatches, config: &mut cargo::util::Config) -> car
107126 }
108127 } ;
109128 if let Some ( last) = possibilities. iter ( ) . map ( |s| s. version ( ) ) . max ( ) {
110- if last != current {
111- results. push ( (
112- name. to_string ( ) ,
113- Some ( last. to_string ( ) ) ,
114- current. to_string ( ) ,
115- ) ) ;
116- } else {
117- log:: trace!( "{name} {current} is published" ) ;
118- }
129+ let published = last == current;
130+ results. push ( (
131+ name. to_string ( ) ,
132+ Some ( last. to_string ( ) ) ,
133+ current. to_string ( ) ,
134+ published,
135+ ) ) ;
119136 } else {
120- results. push ( ( name. to_string ( ) , None , current. to_string ( ) ) ) ;
137+ results. push ( ( name. to_string ( ) , None , current. to_string ( ) , false ) ) ;
121138 }
122139 }
123140 }
141+ results. sort ( ) ;
124142
125- if !results. is_empty ( ) {
126- results. insert (
127- 0 ,
128- (
129- "name" . to_owned ( ) ,
130- Some ( "published" . to_owned ( ) ) ,
131- "current" . to_owned ( ) ,
132- ) ,
133- ) ;
134- results. insert (
135- 1 ,
143+ if results. is_empty ( ) {
144+ return Ok ( ( ) ) ;
145+ }
146+
147+ let check_version_bump = args. flag ( "check-version-bump" ) ;
148+
149+ if check_version_bump {
150+ output_version_bump_notice ( & results) ;
151+ }
152+
153+ output_table ( results, check_version_bump) ?;
154+
155+ Ok ( ( ) )
156+ }
157+
158+ /// Outputs a markdown table of publish status for each members.
159+ ///
160+ /// ```text
161+ /// | name | crates.io | local | published? |
162+ /// | ---- | --------- | ----- | ---------- |
163+ /// | cargo | 0.70.1 | 0.72.0 | no |
164+ /// | cargo-credential | 0.1.0 | 0.2.0 | no |
165+ /// | cargo-credential-1password | 0.1.0 | 0.2.0 | no |
166+ /// | cargo-credential-gnome-secret | 0.1.0 | 0.2.0 | no |
167+ /// | cargo-credential-macos-keychain | 0.1.0 | 0.2.0 | no |
168+ /// | cargo-credential-wincred | 0.1.0 | 0.2.0 | no |
169+ /// | cargo-platform | 0.1.2 | 0.1.3 | no |
170+ /// | cargo-util | 0.2.3 | 0.2.4 | no |
171+ /// | crates-io | 0.36.0 | 0.36.1 | no |
172+ /// | home | 0.5.5 | 0.5.6 | no |
173+ /// ```
174+ fn output_table ( results : Vec < Record > , check_version_bump : bool ) -> fmt:: Result {
175+ let mut results: Vec < _ > = results
176+ . into_iter ( )
177+ . filter ( |( .., published) | !check_version_bump || * published)
178+ . map ( |e| {
136179 (
137- "====" . to_owned ( ) ,
138- Some ( "=========" . to_owned ( ) ) ,
139- "=======" . to_owned ( ) ,
140- ) ,
141- ) ;
180+ e. 0 ,
181+ e. 1 . unwrap_or ( "-" . to_owned ( ) ) ,
182+ e. 2 ,
183+ if e. 3 { "yes" } else { "no" } . to_owned ( ) ,
184+ )
185+ } )
186+ . collect ( ) ;
187+
188+ if results. is_empty ( ) {
189+ return Ok ( ( ) ) ;
142190 }
143- for ( name, last, current) in results {
144- if let Some ( last) = last {
145- println ! ( "{name} {last} {current}" ) ;
191+
192+ let header = (
193+ "name" . to_owned ( ) ,
194+ "crates.io" . to_owned ( ) ,
195+ "local" . to_owned ( ) ,
196+ if check_version_bump {
197+ "need version bump?"
146198 } else {
147- println ! ( "{name} - {current}" ) ;
199+ "published?"
148200 }
201+ . to_owned ( ) ,
202+ ) ;
203+ let separators = (
204+ "-" . repeat ( header. 0 . len ( ) ) ,
205+ "-" . repeat ( header. 1 . len ( ) ) ,
206+ "-" . repeat ( header. 2 . len ( ) ) ,
207+ "-" . repeat ( header. 3 . len ( ) ) ,
208+ ) ;
209+ results. insert ( 0 , header) ;
210+ results. insert ( 1 , separators) ;
211+
212+ let max_col_widths = results
213+ . iter ( )
214+ . map ( |( name, last, local, bump) | ( name. len ( ) , last. len ( ) , local. len ( ) , bump. len ( ) ) )
215+ . reduce ( |( c0, c1, c2, c3) , ( f0, f1, f2, f3) | {
216+ ( c0. max ( f0) , c1. max ( f1) , c2. max ( f2) , c3. max ( f3) )
217+ } )
218+ . unwrap ( ) ;
219+
220+ let print_space = |out : & mut dyn Write , n| {
221+ for _ in 0 ..( n + 1 ) {
222+ write ! ( out, " " ) ?;
223+ }
224+ fmt:: Result :: Ok ( ( ) )
225+ } ;
226+
227+ let out = & mut String :: new ( ) ;
228+ for ( name, last, local, bump) in results {
229+ write ! ( out, "| {name}" ) ?;
230+ print_space ( out, max_col_widths. 0 - name. len ( ) ) ?;
231+
232+ write ! ( out, "| {last}" ) ?;
233+ print_space ( out, max_col_widths. 1 - last. len ( ) ) ?;
234+
235+ write ! ( out, "| {local}" ) ?;
236+ print_space ( out, max_col_widths. 2 - local. len ( ) ) ?;
237+
238+ write ! ( out, "| {bump}" ) ?;
239+ print_space ( out, max_col_widths. 3 - bump. len ( ) ) ?;
240+
241+ writeln ! ( out, "|" ) ?;
149242 }
150243
244+ println ! ( "{out}" ) ;
245+
151246 Ok ( ( ) )
152247}
153248
249+ fn output_version_bump_notice ( results : & [ Record ] ) {
250+ let pkgs_need_bump = results
251+ . iter ( )
252+ . filter_map ( |( name, .., published) | published. then_some ( name. clone ( ) ) )
253+ . collect :: < Vec < _ > > ( ) ;
254+
255+ if !pkgs_need_bump. is_empty ( ) {
256+ print ! ( "### :warning: " ) ;
257+ println ! ( "Require at least a patch version bump for each of the following packages:\n " ) ;
258+ for pkg in pkgs_need_bump {
259+ println ! ( "* {pkg}" ) ;
260+ }
261+ println ! ( )
262+ }
263+ }
264+
154265#[ test]
155266fn verify_cli ( ) {
156267 cli ( ) . debug_assert ( ) ;
0 commit comments