1
+ use std:: collections:: HashSet ;
2
+ use std:: fmt;
3
+ use std:: fmt:: Write ;
4
+
1
5
use cargo:: core:: registry:: PackageRegistry ;
2
6
use cargo:: core:: QueryKind ;
3
7
use cargo:: core:: Registry ;
4
8
use cargo:: core:: SourceId ;
9
+ use cargo:: ops:: Packages ;
5
10
use cargo:: util:: command_prelude:: * ;
6
11
12
+ type Record = ( String , Option < String > , String , bool ) ;
13
+
7
14
pub fn cli ( ) -> clap:: Command {
8
15
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" )
9
21
. arg (
10
22
opt (
11
23
"verbose" ,
@@ -76,14 +88,24 @@ fn config_configure(config: &mut Config, args: &ArgMatches) -> CliResult {
76
88
77
89
fn unpublished ( args : & clap:: ArgMatches , config : & mut cargo:: util:: Config ) -> cargo:: CliResult {
78
90
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
+
79
101
let mut results = Vec :: new ( ) ;
80
102
{
81
103
let mut registry = PackageRegistry :: new ( config) ?;
82
104
let _lock = config. acquire_package_cache_lock ( ) ?;
83
105
registry. lock_patches ( ) ;
84
106
let source_id = SourceId :: crates_io ( config) ?;
85
107
86
- for member in ws . members ( ) {
108
+ for member in members_to_inspect {
87
109
let name = member. name ( ) ;
88
110
let current = member. version ( ) ;
89
111
if member. publish ( ) == & Some ( vec ! [ ] ) {
@@ -92,11 +114,8 @@ fn unpublished(args: &clap::ArgMatches, config: &mut cargo::util::Config) -> car
92
114
}
93
115
94
116
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) ?;
100
119
let possibilities = loop {
101
120
// Exact to avoid returning all for path/git
102
121
match registry. query_vec ( & query, QueryKind :: Exact ) {
@@ -107,50 +126,142 @@ fn unpublished(args: &clap::ArgMatches, config: &mut cargo::util::Config) -> car
107
126
}
108
127
} ;
109
128
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
+ ) ) ;
119
136
} else {
120
- results. push ( ( name. to_string ( ) , None , current. to_string ( ) ) ) ;
137
+ results. push ( ( name. to_string ( ) , None , current. to_string ( ) , false ) ) ;
121
138
}
122
139
}
123
140
}
141
+ results. sort ( ) ;
124
142
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| {
136
179
(
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 ( ( ) ) ;
142
190
}
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?"
146
198
} else {
147
- println ! ( "{name} - {current}" ) ;
199
+ "published?"
148
200
}
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, "|" ) ?;
149
242
}
150
243
244
+ println ! ( "{out}" ) ;
245
+
151
246
Ok ( ( ) )
152
247
}
153
248
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
+
154
265
#[ test]
155
266
fn verify_cli ( ) {
156
267
cli ( ) . debug_assert ( ) ;
0 commit comments