1
+ use diffy;
1
2
use std:: env;
2
- use std:: io;
3
+ use std:: fmt:: { Debug , Display } ;
4
+ use std:: io:: { self , Write } ;
3
5
use std:: path:: { Path , PathBuf } ;
4
- use std:: process:: Command ;
6
+ use std:: process:: { Command , Stdio } ;
5
7
use std:: str:: Utf8Error ;
6
8
use tracing:: info;
9
+ use walkdir:: WalkDir ;
7
10
11
+ #[ derive( Debug ) ]
8
12
pub enum CheckDiffError {
9
13
/// Git related errors
10
14
FailedGit ( GitError ) ,
@@ -39,6 +43,7 @@ impl From<Utf8Error> for CheckDiffError {
39
43
}
40
44
}
41
45
46
+ #[ derive( Debug ) ]
42
47
pub enum GitError {
43
48
FailedClone { stdout : Vec < u8 > , stderr : Vec < u8 > } ,
44
49
FailedRemoteAdd { stdout : Vec < u8 > , stderr : Vec < u8 > } ,
@@ -53,18 +58,73 @@ impl From<io::Error> for GitError {
53
58
}
54
59
}
55
60
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 > ;
61
91
}
62
92
63
93
pub struct RustfmtRunner {
64
94
ld_library_path : String ,
65
95
binary_path : PathBuf ,
66
96
}
67
97
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
+
68
128
impl RustfmtRunner {
69
129
fn get_binary_version ( & self ) -> Result < String , CheckDiffError > {
70
130
let Ok ( command) = Command :: new ( & self . binary_path )
@@ -82,6 +142,58 @@ impl RustfmtRunner {
82
142
}
83
143
}
84
144
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
+ }
85
197
/// Clone a git repository
86
198
///
87
199
/// Parameters:
@@ -180,8 +292,12 @@ pub fn change_directory_to_path(dest: &Path) -> io::Result<()> {
180
292
return Ok ( ( ) ) ;
181
293
}
182
294
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 {
185
301
return Err ( CheckDiffError :: FailedCommand ( "Error getting sysroot" ) ) ;
186
302
} ;
187
303
let sysroot = std:: str:: from_utf8 ( & command. stdout ) ?. trim_end ( ) ;
@@ -202,15 +318,19 @@ pub fn get_cargo_version() -> Result<String, CheckDiffError> {
202
318
203
319
/// Obtains the ld_lib path and then builds rustfmt from source
204
320
/// 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 > {
206
325
//Because we're building standalone binaries we need to set `LD_LIBRARY_PATH` so each
207
326
// binary can find it's runtime dependencies.
208
327
// See https://github.com/rust-lang/rustfmt/issues/5675
209
328
// 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 ) ?;
211
330
212
331
info ! ( "Building rustfmt from source" ) ;
213
332
let Ok ( _) = Command :: new ( "cargo" )
333
+ . current_dir ( dir)
214
334
. args ( [ "build" , "-q" , "--release" , "--bin" , "rustfmt" ] )
215
335
. output ( )
216
336
else {
@@ -219,7 +339,7 @@ pub fn build_rustfmt_from_src(binary_path: PathBuf) -> Result<RustfmtRunner, Che
219
339
) ) ;
220
340
} ;
221
341
222
- std:: fs:: copy ( "target/release/rustfmt" , & binary_path) ?;
342
+ std:: fs:: copy ( dir . join ( "target/release/rustfmt" ) , & binary_path) ?;
223
343
224
344
return Ok ( RustfmtRunner {
225
345
ld_library_path : ld_lib_path,
@@ -236,7 +356,7 @@ pub fn compile_rustfmt(
236
356
remote_repo_url : String ,
237
357
feature_branch : String ,
238
358
commit_hash : Option < String > ,
239
- ) -> Result < CheckDiffRunners , CheckDiffError > {
359
+ ) -> Result < CheckDiffRunners < RustfmtRunner , RustfmtRunner > , CheckDiffError > {
240
360
const RUSTFMT_REPO : & str = "https://github.com/rust-lang/rustfmt.git" ;
241
361
242
362
clone_git_repo ( RUSTFMT_REPO , dest) ?;
@@ -246,14 +366,14 @@ pub fn compile_rustfmt(
246
366
247
367
let cargo_version = get_cargo_version ( ) ?;
248
368
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 ) ?;
250
370
let should_detach = commit_hash. is_some ( ) ;
251
371
git_switch (
252
372
commit_hash. unwrap_or ( feature_branch) . as_str ( ) ,
253
373
should_detach,
254
374
) ?;
255
375
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 ) ?;
257
377
info ! ( "RUSFMT_BIN {}" , src_runner. get_binary_version( ) ?) ;
258
378
info ! (
259
379
"Runtime dependencies for (src) rustfmt -- LD_LIBRARY_PATH: {}" ,
@@ -270,3 +390,48 @@ pub fn compile_rustfmt(
270
390
feature_runner,
271
391
} ) ;
272
392
}
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