1+ use diffy;
12use std:: env;
2- use std:: io;
3+ use std:: fmt:: { Debug , Display } ;
4+ use std:: io:: { self , Write } ;
35use std:: path:: { Path , PathBuf } ;
4- use std:: process:: Command ;
6+ use std:: process:: { Command , Stdio } ;
57use std:: str:: Utf8Error ;
68use tracing:: info;
9+ use walkdir:: WalkDir ;
710
11+ #[ derive( Debug ) ]
812pub enum CheckDiffError {
913 /// Git related errors
1014 FailedGit ( GitError ) ,
@@ -39,6 +43,7 @@ impl From<Utf8Error> for CheckDiffError {
3943 }
4044}
4145
46+ #[ derive( Debug ) ]
4247pub enum GitError {
4348 FailedClone { stdout : Vec < u8 > , stderr : Vec < u8 > } ,
4449 FailedRemoteAdd { stdout : Vec < u8 > , stderr : Vec < u8 > } ,
@@ -53,18 +58,73 @@ impl From<io::Error> for GitError {
5358 }
5459}
5560
56- // will be used in future PRs, just added to make the compiler happy
57- #[ allow( dead_code) ]
58- pub struct CheckDiffRunners {
59- feature_runner : RustfmtRunner ,
60- src_runner : RustfmtRunner ,
61+ pub struct Diff {
62+ src_format : String ,
63+ feature_format : String ,
64+ }
65+
66+ impl Display for Diff {
67+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
68+ let patch = diffy:: create_patch ( self . src_format . as_str ( ) , self . feature_format . as_str ( ) ) ;
69+ write ! ( f, "{}" , patch)
70+ }
71+ }
72+
73+ impl Diff {
74+ pub fn is_empty ( & self ) -> bool {
75+ let patch = diffy:: create_patch ( self . src_format . as_str ( ) , self . feature_format . as_str ( ) ) ;
76+ patch. hunks ( ) . is_empty ( )
77+ }
78+ }
79+
80+ pub struct CheckDiffRunners < F , S > {
81+ feature_runner : F ,
82+ src_runner : S ,
83+ }
84+
85+ pub trait CodeFormatter {
86+ fn format_code < ' a > (
87+ & self ,
88+ code : & ' a str ,
89+ config : & Option < Vec < String > > ,
90+ ) -> Result < String , CheckDiffError > ;
6191}
6292
6393pub struct RustfmtRunner {
6494 ld_library_path : String ,
6595 binary_path : PathBuf ,
6696}
6797
98+ impl < F , S > CheckDiffRunners < F , S > {
99+ pub fn new ( feature_runner : F , src_runner : S ) -> Self {
100+ Self {
101+ feature_runner,
102+ src_runner,
103+ }
104+ }
105+ }
106+
107+ impl < F , S > CheckDiffRunners < F , S >
108+ where
109+ F : CodeFormatter ,
110+ S : CodeFormatter ,
111+ {
112+ /// Creates a diff generated by running the source and feature binaries on the same file path
113+ pub fn create_diff (
114+ & self ,
115+ path : & Path ,
116+ additional_configs : & Option < Vec < String > > ,
117+ ) -> Result < Diff , CheckDiffError > {
118+ let code = std:: fs:: read_to_string ( path) ?;
119+ let src_format = self . src_runner . format_code ( & code, additional_configs) ?;
120+ let feature_format = self . feature_runner . format_code ( & code, additional_configs) ?;
121+ Ok ( Diff {
122+ src_format,
123+ feature_format,
124+ } )
125+ }
126+ }
127+
68128impl RustfmtRunner {
69129 fn get_binary_version ( & self ) -> Result < String , CheckDiffError > {
70130 let Ok ( command) = Command :: new ( & self . binary_path )
@@ -82,6 +142,58 @@ impl RustfmtRunner {
82142 }
83143}
84144
145+ impl CodeFormatter for RustfmtRunner {
146+ // Run rusfmt to see if a diff is produced. Runs on the code specified
147+ //
148+ // Parameters:
149+ // code: Code to run the binary on
150+ // config: Any additional configuration options to pass to rustfmt
151+ //
152+ fn format_code < ' a > (
153+ & self ,
154+ code : & ' a str ,
155+ config : & Option < Vec < String > > ,
156+ ) -> Result < String , CheckDiffError > {
157+ let config = create_config_arg ( config) ;
158+ let mut command = Command :: new ( & self . binary_path )
159+ . env ( "LD_LIBRARY_PATH" , & self . ld_library_path )
160+ . args ( [
161+ "--unstable-features" ,
162+ "--skip-children" ,
163+ "--emit=stdout" ,
164+ config. as_str ( ) ,
165+ ] )
166+ . stdin ( Stdio :: piped ( ) )
167+ . stdout ( Stdio :: piped ( ) )
168+ . stderr ( Stdio :: piped ( ) )
169+ . spawn ( ) ?;
170+
171+ command. stdin . as_mut ( ) . unwrap ( ) . write_all ( code. as_bytes ( ) ) ?;
172+ let output = command. wait_with_output ( ) ?;
173+ Ok ( std:: str:: from_utf8 ( & output. stdout ) ?. to_string ( ) )
174+ }
175+ }
176+
177+ /// Creates a configuration in the following form:
178+ /// <config_name>=<config_val>, <config_name>=<config_val>, ...
179+ fn create_config_arg ( config : & Option < Vec < String > > ) -> String {
180+ let config_arg: String = match config {
181+ Some ( configs) => {
182+ let mut result = String :: new ( ) ;
183+ for arg in configs. iter ( ) {
184+ result. push ( ',' ) ;
185+ result. push_str ( arg. as_str ( ) ) ;
186+ }
187+ result
188+ }
189+ None => String :: new ( ) ,
190+ } ;
191+ let config = format ! (
192+ "--config=error_on_line_overflow=false,error_on_unformatted=false{}" ,
193+ config_arg. as_str( )
194+ ) ;
195+ config
196+ }
85197/// Clone a git repository
86198///
87199/// Parameters:
@@ -180,8 +292,12 @@ pub fn change_directory_to_path(dest: &Path) -> io::Result<()> {
180292 return Ok ( ( ) ) ;
181293}
182294
183- pub fn get_ld_library_path ( ) -> Result < String , CheckDiffError > {
184- let Ok ( command) = Command :: new ( "rustc" ) . args ( [ "--print" , "sysroot" ] ) . output ( ) else {
295+ pub fn get_ld_library_path ( dir : & Path ) -> Result < String , CheckDiffError > {
296+ let Ok ( command) = Command :: new ( "rustc" )
297+ . current_dir ( dir)
298+ . args ( [ "--print" , "sysroot" ] )
299+ . output ( )
300+ else {
185301 return Err ( CheckDiffError :: FailedCommand ( "Error getting sysroot" ) ) ;
186302 } ;
187303 let sysroot = std:: str:: from_utf8 ( & command. stdout ) ?. trim_end ( ) ;
@@ -202,15 +318,19 @@ pub fn get_cargo_version() -> Result<String, CheckDiffError> {
202318
203319/// Obtains the ld_lib path and then builds rustfmt from source
204320/// If that operation succeeds, the source is then copied to the output path specified
205- pub fn build_rustfmt_from_src ( binary_path : PathBuf ) -> Result < RustfmtRunner , CheckDiffError > {
321+ pub fn build_rustfmt_from_src (
322+ binary_path : PathBuf ,
323+ dir : & Path ,
324+ ) -> Result < RustfmtRunner , CheckDiffError > {
206325 //Because we're building standalone binaries we need to set `LD_LIBRARY_PATH` so each
207326 // binary can find it's runtime dependencies.
208327 // See https://github.com/rust-lang/rustfmt/issues/5675
209328 // This will prepend the `LD_LIBRARY_PATH` for the master rustfmt binary
210- let ld_lib_path = get_ld_library_path ( ) ?;
329+ let ld_lib_path = get_ld_library_path ( & dir ) ?;
211330
212331 info ! ( "Building rustfmt from source" ) ;
213332 let Ok ( _) = Command :: new ( "cargo" )
333+ . current_dir ( dir)
214334 . args ( [ "build" , "-q" , "--release" , "--bin" , "rustfmt" ] )
215335 . output ( )
216336 else {
@@ -219,7 +339,7 @@ pub fn build_rustfmt_from_src(binary_path: PathBuf) -> Result<RustfmtRunner, Che
219339 ) ) ;
220340 } ;
221341
222- std:: fs:: copy ( "target/release/rustfmt" , & binary_path) ?;
342+ std:: fs:: copy ( dir . join ( "target/release/rustfmt" ) , & binary_path) ?;
223343
224344 return Ok ( RustfmtRunner {
225345 ld_library_path : ld_lib_path,
@@ -236,7 +356,7 @@ pub fn compile_rustfmt(
236356 remote_repo_url : String ,
237357 feature_branch : String ,
238358 commit_hash : Option < String > ,
239- ) -> Result < CheckDiffRunners , CheckDiffError > {
359+ ) -> Result < CheckDiffRunners < RustfmtRunner , RustfmtRunner > , CheckDiffError > {
240360 const RUSTFMT_REPO : & str = "https://github.com/rust-lang/rustfmt.git" ;
241361
242362 clone_git_repo ( RUSTFMT_REPO , dest) ?;
@@ -246,14 +366,14 @@ pub fn compile_rustfmt(
246366
247367 let cargo_version = get_cargo_version ( ) ?;
248368 info ! ( "Compiling with {}" , cargo_version) ;
249- let src_runner = build_rustfmt_from_src ( dest. join ( "src_rustfmt" ) ) ?;
369+ let src_runner = build_rustfmt_from_src ( dest. join ( "src_rustfmt" ) , dest ) ?;
250370 let should_detach = commit_hash. is_some ( ) ;
251371 git_switch (
252372 commit_hash. unwrap_or ( feature_branch) . as_str ( ) ,
253373 should_detach,
254374 ) ?;
255375
256- let feature_runner = build_rustfmt_from_src ( dest. join ( "feature_rustfmt" ) ) ?;
376+ let feature_runner = build_rustfmt_from_src ( dest. join ( "feature_rustfmt" ) , dest ) ?;
257377 info ! ( "RUSFMT_BIN {}" , src_runner. get_binary_version( ) ?) ;
258378 info ! (
259379 "Runtime dependencies for (src) rustfmt -- LD_LIBRARY_PATH: {}" ,
@@ -270,3 +390,48 @@ pub fn compile_rustfmt(
270390 feature_runner,
271391 } ) ;
272392}
393+
394+ /// Searches for rust files in the particular path and returns an iterator to them.
395+ pub fn search_for_rs_files ( repo : & Path ) -> impl Iterator < Item = PathBuf > {
396+ return WalkDir :: new ( repo) . into_iter ( ) . filter_map ( |e| match e. ok ( ) {
397+ Some ( entry) => {
398+ let path = entry. path ( ) ;
399+ if path. is_file ( ) && path. extension ( ) . map_or ( false , |ext| ext == "rs" ) {
400+ return Some ( entry. into_path ( ) ) ;
401+ }
402+ return None ;
403+ }
404+ None => None ,
405+ } ) ;
406+ }
407+
408+ /// Calculates the number of errors when running the compiled binary and the feature binary on the
409+ /// repo specified with the specific configs.
410+ pub fn check_diff (
411+ config : Option < Vec < String > > ,
412+ runners : CheckDiffRunners < impl CodeFormatter , impl CodeFormatter > ,
413+ repo : & Path ,
414+ ) -> i32 {
415+ let mut errors = 0 ;
416+ let iter = search_for_rs_files ( repo) ;
417+ for file in iter {
418+ match runners. create_diff ( file. as_path ( ) , & config) {
419+ Ok ( diff) => {
420+ if !diff. is_empty ( ) {
421+ eprint ! ( "{diff}" ) ;
422+ errors += 1 ;
423+ }
424+ }
425+ Err ( e) => {
426+ eprintln ! (
427+ "Error creating diff for {:?}: {:?}" ,
428+ file. as_path( ) . display( ) ,
429+ e
430+ ) ;
431+ errors += 1 ;
432+ }
433+ }
434+ }
435+
436+ return errors;
437+ }
0 commit comments