@@ -17,7 +17,7 @@ extern crate getopts;
1717extern crate serde_json as json;
1818
1919use std:: env;
20- use std:: io:: Write ;
20+ use std:: io:: { self , Write } ;
2121use std:: path:: PathBuf ;
2222use std:: process:: { Command , ExitStatus } ;
2323use std:: str;
@@ -126,7 +126,7 @@ pub enum Verbosity {
126126fn format_crate (
127127 verbosity : Verbosity ,
128128 workspace_hitlist : & WorkspaceHitlist ,
129- ) -> Result < ExitStatus , std :: io:: Error > {
129+ ) -> Result < ExitStatus , io:: Error > {
130130 let targets = get_targets ( workspace_hitlist) ?;
131131
132132 // Currently only bin and lib files get formatted
@@ -180,6 +180,29 @@ pub struct Target {
180180 kind : TargetKind ,
181181}
182182
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+
183206#[ derive( Debug , PartialEq , Eq ) ]
184207pub enum WorkspaceHitlist {
185208 All ,
@@ -205,121 +228,154 @@ impl WorkspaceHitlist {
205228 }
206229}
207230
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" ) ?;
210253 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+ }
222259
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 ;
224301 }
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 ) ;
229306 }
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 > {
234323 let output = Command :: new ( "cargo" )
235- . arg ( "metadata" )
236- . arg ( "--no-deps" )
324+ . args ( & [ "metadata" , "--no-deps" , "--format-version=1" ] )
237325 . output ( ) ?;
238326 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) ?) ;
270335 }
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+ ) )
276353 }
277354 }
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+ ) )
304360 }
305361}
306362
307363fn format_files (
308364 files : & [ PathBuf ] ,
309365 fmt_args : & [ String ] ,
310366 verbosity : Verbosity ,
311- ) -> Result < ExitStatus , std :: io:: Error > {
367+ ) -> Result < ExitStatus , io:: Error > {
312368 let stdout = if verbosity == Verbosity :: Quiet {
313369 std:: process:: Stdio :: null ( )
314370 } else {
315371 std:: process:: Stdio :: inherit ( )
316372 } ;
317373 if verbosity == Verbosity :: Verbose {
318374 print ! ( "rustfmt" ) ;
319- for a in fmt_args. iter ( ) {
375+ for a in fmt_args {
320376 print ! ( " {}" , a) ;
321377 }
322- for f in files. iter ( ) {
378+ for f in files {
323379 print ! ( " {}" , f. display( ) ) ;
324380 }
325381 println ! ( "" ) ;
@@ -330,8 +386,8 @@ fn format_files(
330386 . args ( fmt_args)
331387 . spawn ( )
332388 . 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 ,
335391 "Could not run rustfmt, please make sure it is in your PATH." ,
336392 ) ,
337393 _ => e,
0 commit comments