@@ -17,7 +17,7 @@ extern crate getopts;
17
17
extern crate serde_json as json;
18
18
19
19
use std:: env;
20
- use std:: io:: Write ;
20
+ use std:: io:: { self , Write } ;
21
21
use std:: path:: PathBuf ;
22
22
use std:: process:: { Command , ExitStatus } ;
23
23
use std:: str;
@@ -126,7 +126,7 @@ pub enum Verbosity {
126
126
fn format_crate (
127
127
verbosity : Verbosity ,
128
128
workspace_hitlist : & WorkspaceHitlist ,
129
- ) -> Result < ExitStatus , std :: io:: Error > {
129
+ ) -> Result < ExitStatus , io:: Error > {
130
130
let targets = get_targets ( workspace_hitlist) ?;
131
131
132
132
// Currently only bin and lib files get formatted
@@ -180,6 +180,29 @@ pub struct Target {
180
180
kind : TargetKind ,
181
181
}
182
182
183
+ impl Target {
184
+ pub fn from_json ( json_val : & Value ) -> Option < Self > {
185
+ let jtarget = json_val. as_object ( ) ?;
186
+ let path = PathBuf :: from ( jtarget. get ( "src_path" ) ?. as_str ( ) ?) ;
187
+ let kinds = jtarget. get ( "kind" ) ?. as_array ( ) ?;
188
+ let kind = match kinds[ 0 ] . as_str ( ) ? {
189
+ "bin" => TargetKind :: Bin ,
190
+ "lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind :: Lib ,
191
+ "test" => TargetKind :: Test ,
192
+ "example" => TargetKind :: Example ,
193
+ "bench" => TargetKind :: Bench ,
194
+ "custom-build" => TargetKind :: CustomBuild ,
195
+ "proc-macro" => TargetKind :: ProcMacro ,
196
+ _ => TargetKind :: Other ,
197
+ } ;
198
+
199
+ Some ( Target {
200
+ path : path,
201
+ kind : kind,
202
+ } )
203
+ }
204
+ }
205
+
183
206
#[ derive( Debug , PartialEq , Eq ) ]
184
207
pub enum WorkspaceHitlist {
185
208
All ,
@@ -205,121 +228,154 @@ impl WorkspaceHitlist {
205
228
}
206
229
}
207
230
208
- // Returns a vector of all compile targets of a crate
209
- fn get_targets ( workspace_hitlist : & WorkspaceHitlist ) -> Result < Vec < Target > , std:: io:: Error > {
231
+ fn get_cargo_metadata_from_utf8 ( v : & [ u8 ] ) -> Option < Value > {
232
+ json:: from_str ( str:: from_utf8 ( v) . ok ( ) ?) . ok ( )
233
+ }
234
+
235
+ fn get_json_array_with < ' a > ( v : & ' a Value , key : & str ) -> Option < & ' a Vec < Value > > {
236
+ v. as_object ( ) ?. get ( key) ?. as_array ( )
237
+ }
238
+
239
+ // `cargo metadata --no-deps | jq '.["packages"]'`
240
+ fn get_packages ( v : & [ u8 ] ) -> Result < Vec < Value > , io:: Error > {
241
+ let e = io:: Error :: new (
242
+ io:: ErrorKind :: NotFound ,
243
+ String :: from ( "`cargo metadata` returned json without a 'packages' key" ) ,
244
+ ) ;
245
+ match get_cargo_metadata_from_utf8 ( v) {
246
+ Some ( ref json_obj) => get_json_array_with ( json_obj, "packages" ) . cloned ( ) . ok_or ( e) ,
247
+ None => Err ( e) ,
248
+ }
249
+ }
250
+
251
+ fn extract_target_from_package ( package : & Value ) -> Option < Vec < Target > > {
252
+ let jtargets = get_json_array_with ( package, "targets" ) ?;
210
253
let mut targets: Vec < Target > = vec ! [ ] ;
211
- if * workspace_hitlist == WorkspaceHitlist :: None {
212
- let output = Command :: new ( "cargo" ) . arg ( "read-manifest" ) . output ( ) ?;
213
- if output. status . success ( ) {
214
- // None of the unwraps should fail if output of `cargo read-manifest` is correct
215
- let data = & String :: from_utf8 ( output. stdout ) . unwrap ( ) ;
216
- let json: Value = json:: from_str ( data) . unwrap ( ) ;
217
- let json_obj = json. as_object ( ) . unwrap ( ) ;
218
- let jtargets = json_obj. get ( "targets" ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
219
- for jtarget in jtargets {
220
- targets. push ( target_from_json ( jtarget) ) ;
221
- }
254
+ for jtarget in jtargets {
255
+ targets. push ( Target :: from_json ( & jtarget) ?) ;
256
+ }
257
+ Some ( targets)
258
+ }
222
259
223
- return Ok ( targets) ;
260
+ fn filter_packages_with_hitlist < ' a > (
261
+ packages : Vec < Value > ,
262
+ workspace_hitlist : & ' a WorkspaceHitlist ,
263
+ ) -> Result < Vec < Value > , & ' a String > {
264
+ if * workspace_hitlist == WorkspaceHitlist :: All {
265
+ return Ok ( packages) ;
266
+ }
267
+ let mut hitlist: HashSet < & String > = workspace_hitlist
268
+ . get_some ( )
269
+ . map_or ( HashSet :: new ( ) , HashSet :: from_iter) ;
270
+ let members: Vec < Value > = packages
271
+ . into_iter ( )
272
+ . filter ( |member| {
273
+ member
274
+ . as_object ( )
275
+ . and_then ( |member_obj| {
276
+ member_obj
277
+ . get ( "name" )
278
+ . and_then ( Value :: as_str)
279
+ . map ( |member_name| {
280
+ hitlist. take ( & member_name. to_string ( ) ) . is_some ( )
281
+ } )
282
+ } )
283
+ . unwrap_or ( false )
284
+ } )
285
+ . collect ( ) ;
286
+ if hitlist. is_empty ( ) {
287
+ Ok ( members)
288
+ } else {
289
+ Err ( hitlist. into_iter ( ) . next ( ) . unwrap ( ) )
290
+ }
291
+ }
292
+
293
+ fn get_dependencies_from_package ( package : & Value ) -> Option < Vec < PathBuf > > {
294
+ let jdependencies = get_json_array_with ( package, "dependencies" ) ?;
295
+ let root_path = env:: current_dir ( ) . ok ( ) ?;
296
+ let mut dependencies: Vec < PathBuf > = vec ! [ ] ;
297
+ for jdep in jdependencies {
298
+ let jdependency = jdep. as_object ( ) ?;
299
+ if !jdependency. get ( "source" ) ?. is_null ( ) {
300
+ continue ;
224
301
}
225
- return Err ( std :: io :: Error :: new (
226
- std :: io :: ErrorKind :: NotFound ,
227
- str :: from_utf8 ( & output . stderr ) . unwrap ( ) ,
228
- ) ) ;
302
+ let name = jdependency . get ( "name" ) ? . as_str ( ) ? ;
303
+ let mut path = root_path . clone ( ) ;
304
+ path . push ( & name ) ;
305
+ dependencies . push ( path ) ;
229
306
}
230
- // This happens when cargo-fmt is not used inside a crate or
231
- // is used inside a workspace.
232
- // To ensure backward compatability, we only use `cargo metadata` for workspaces.
233
- // TODO: Is it possible only use metadata or read-manifest
307
+ Some ( dependencies)
308
+ }
309
+
310
+ // Returns a vector of local dependencies under this crate
311
+ fn get_path_to_local_dependencies ( packages : & [ Value ] ) -> Vec < PathBuf > {
312
+ let mut local_dependencies: Vec < PathBuf > = vec ! [ ] ;
313
+ for package in packages {
314
+ if let Some ( mut d) = get_dependencies_from_package ( package) {
315
+ local_dependencies. append ( & mut d) ;
316
+ }
317
+ }
318
+ local_dependencies
319
+ }
320
+
321
+ // Returns a vector of all compile targets of a crate
322
+ fn get_targets ( workspace_hitlist : & WorkspaceHitlist ) -> Result < Vec < Target > , io:: Error > {
234
323
let output = Command :: new ( "cargo" )
235
- . arg ( "metadata" )
236
- . arg ( "--no-deps" )
324
+ . args ( & [ "metadata" , "--no-deps" , "--format-version=1" ] )
237
325
. output ( ) ?;
238
326
if output. status . success ( ) {
239
- let data = & String :: from_utf8 ( output. stdout ) . unwrap ( ) ;
240
- let json: Value = json:: from_str ( data) . unwrap ( ) ;
241
- let json_obj = json. as_object ( ) . unwrap ( ) ;
242
- let mut hitlist: HashSet < & String > = if * workspace_hitlist != WorkspaceHitlist :: All {
243
- HashSet :: from_iter ( workspace_hitlist. get_some ( ) . unwrap ( ) )
244
- } else {
245
- HashSet :: new ( ) // Unused
246
- } ;
247
- let members: Vec < & Value > = json_obj
248
- . get ( "packages" )
249
- . unwrap ( )
250
- . as_array ( )
251
- . unwrap ( )
252
- . into_iter ( )
253
- . filter ( |member| if * workspace_hitlist == WorkspaceHitlist :: All {
254
- true
255
- } else {
256
- let member_obj = member. as_object ( ) . unwrap ( ) ;
257
- let member_name = member_obj. get ( "name" ) . unwrap ( ) . as_str ( ) . unwrap ( ) ;
258
- hitlist. take ( & member_name. to_string ( ) ) . is_some ( )
259
- } )
260
- . collect ( ) ;
261
- if !hitlist. is_empty ( ) {
262
- // Mimick cargo of only outputting one <package> spec.
263
- return Err ( std:: io:: Error :: new (
264
- std:: io:: ErrorKind :: InvalidInput ,
265
- format ! (
266
- "package `{}` is not a member of the workspace" ,
267
- hitlist. iter( ) . next( ) . unwrap( )
268
- ) ,
269
- ) ) ;
327
+ let cur_dir = env:: current_dir ( ) ?;
328
+ let mut targets: Vec < Target > = vec ! [ ] ;
329
+ let packages = get_packages ( & output. stdout ) ?;
330
+
331
+ // If we can find any local dependencies, we will try to get targets from those as well.
332
+ for path in get_path_to_local_dependencies ( & packages) {
333
+ env:: set_current_dir ( path) ?;
334
+ targets. append ( & mut get_targets ( workspace_hitlist) ?) ;
270
335
}
271
- for member in members {
272
- let member_obj = member. as_object ( ) . unwrap ( ) ;
273
- let jtargets = member_obj. get ( "targets" ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
274
- for jtarget in jtargets {
275
- targets. push ( target_from_json ( jtarget) ) ;
336
+
337
+ env:: set_current_dir ( cur_dir) ?;
338
+ match filter_packages_with_hitlist ( packages, workspace_hitlist) {
339
+ Ok ( packages) => {
340
+ for package in packages {
341
+ if let Some ( mut target) = extract_target_from_package ( & package) {
342
+ targets. append ( & mut target) ;
343
+ }
344
+ }
345
+ Ok ( targets)
346
+ }
347
+ Err ( package) => {
348
+ // Mimick cargo of only outputting one <package> spec.
349
+ Err ( io:: Error :: new (
350
+ io:: ErrorKind :: InvalidInput ,
351
+ format ! ( "package `{}` is not a member of the workspace" , package) ,
352
+ ) )
276
353
}
277
354
}
278
- return Ok ( targets) ;
279
- }
280
- Err ( std:: io:: Error :: new (
281
- std:: io:: ErrorKind :: NotFound ,
282
- str:: from_utf8 ( & output. stderr ) . unwrap ( ) ,
283
- ) )
284
- }
285
-
286
- fn target_from_json ( jtarget : & Value ) -> Target {
287
- let jtarget = jtarget. as_object ( ) . unwrap ( ) ;
288
- let path = PathBuf :: from ( jtarget. get ( "src_path" ) . unwrap ( ) . as_str ( ) . unwrap ( ) ) ;
289
- let kinds = jtarget. get ( "kind" ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
290
- let kind = match kinds[ 0 ] . as_str ( ) . unwrap ( ) {
291
- "bin" => TargetKind :: Bin ,
292
- "lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind :: Lib ,
293
- "test" => TargetKind :: Test ,
294
- "example" => TargetKind :: Example ,
295
- "bench" => TargetKind :: Bench ,
296
- "custom-build" => TargetKind :: CustomBuild ,
297
- "proc-macro" => TargetKind :: ProcMacro ,
298
- _ => TargetKind :: Other ,
299
- } ;
300
-
301
- Target {
302
- path : path,
303
- kind : kind,
355
+ } else {
356
+ Err ( io:: Error :: new (
357
+ io:: ErrorKind :: NotFound ,
358
+ str:: from_utf8 ( & output. stderr ) . unwrap ( ) ,
359
+ ) )
304
360
}
305
361
}
306
362
307
363
fn format_files (
308
364
files : & [ PathBuf ] ,
309
365
fmt_args : & [ String ] ,
310
366
verbosity : Verbosity ,
311
- ) -> Result < ExitStatus , std :: io:: Error > {
367
+ ) -> Result < ExitStatus , io:: Error > {
312
368
let stdout = if verbosity == Verbosity :: Quiet {
313
369
std:: process:: Stdio :: null ( )
314
370
} else {
315
371
std:: process:: Stdio :: inherit ( )
316
372
} ;
317
373
if verbosity == Verbosity :: Verbose {
318
374
print ! ( "rustfmt" ) ;
319
- for a in fmt_args. iter ( ) {
375
+ for a in fmt_args {
320
376
print ! ( " {}" , a) ;
321
377
}
322
- for f in files. iter ( ) {
378
+ for f in files {
323
379
print ! ( " {}" , f. display( ) ) ;
324
380
}
325
381
println ! ( "" ) ;
@@ -330,8 +386,8 @@ fn format_files(
330
386
. args ( fmt_args)
331
387
. spawn ( )
332
388
. map_err ( |e| match e. kind ( ) {
333
- std :: io:: ErrorKind :: NotFound => std :: io:: Error :: new (
334
- std :: io:: ErrorKind :: Other ,
389
+ io:: ErrorKind :: NotFound => io:: Error :: new (
390
+ io:: ErrorKind :: Other ,
335
391
"Could not run rustfmt, please make sure it is in your PATH." ,
336
392
) ,
337
393
_ => e,
0 commit comments