1
1
use std:: env;
2
2
use std:: io;
3
- use std:: path:: Path ;
3
+ use std:: path:: { Path , PathBuf } ;
4
4
use std:: process:: Command ;
5
+ use std:: str:: Utf8Error ;
5
6
use tracing:: info;
6
7
8
+ pub enum CheckDiffError {
9
+ /// Git related errors
10
+ FailedGit ( GitError ) ,
11
+ /// Error for generic commands
12
+ FailedCommand ( & ' static str ) ,
13
+ /// UTF8 related errors
14
+ FailedUtf8 ( Utf8Error ) ,
15
+ /// Error for building rustfmt from source
16
+ FailedSourceBuild ( & ' static str ) ,
17
+ /// Error when obtaining binary version
18
+ FailedBinaryVersioning ( PathBuf ) ,
19
+ /// Error when obtaining cargo version
20
+ FailedCargoVersion ( & ' static str ) ,
21
+ IO ( std:: io:: Error ) ,
22
+ }
23
+
24
+ impl From < io:: Error > for CheckDiffError {
25
+ fn from ( error : io:: Error ) -> Self {
26
+ CheckDiffError :: IO ( error)
27
+ }
28
+ }
29
+
30
+ impl From < GitError > for CheckDiffError {
31
+ fn from ( error : GitError ) -> Self {
32
+ CheckDiffError :: FailedGit ( error)
33
+ }
34
+ }
35
+
36
+ impl From < Utf8Error > for CheckDiffError {
37
+ fn from ( error : Utf8Error ) -> Self {
38
+ CheckDiffError :: FailedUtf8 ( error)
39
+ }
40
+ }
41
+
7
42
pub enum GitError {
8
43
FailedClone { stdout : Vec < u8 > , stderr : Vec < u8 > } ,
44
+ FailedRemoteAdd { stdout : Vec < u8 > , stderr : Vec < u8 > } ,
45
+ FailedFetch { stdout : Vec < u8 > , stderr : Vec < u8 > } ,
46
+ FailedSwitch { stdout : Vec < u8 > , stderr : Vec < u8 > } ,
9
47
IO ( std:: io:: Error ) ,
10
48
}
11
49
@@ -15,6 +53,35 @@ impl From<io::Error> for GitError {
15
53
}
16
54
}
17
55
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
+ }
62
+
63
+ pub struct RustfmtRunner {
64
+ ld_library_path : String ,
65
+ binary_path : PathBuf ,
66
+ }
67
+
68
+ impl RustfmtRunner {
69
+ fn get_binary_version ( & self ) -> Result < String , CheckDiffError > {
70
+ let Ok ( command) = Command :: new ( & self . binary_path )
71
+ . env ( "LD_LIBRARY_PATH" , & self . ld_library_path )
72
+ . args ( [ "--version" ] )
73
+ . output ( )
74
+ else {
75
+ return Err ( CheckDiffError :: FailedBinaryVersioning (
76
+ self . binary_path . clone ( ) ,
77
+ ) ) ;
78
+ } ;
79
+
80
+ let binary_version = std:: str:: from_utf8 ( & command. stdout ) ?. trim ( ) ;
81
+ return Ok ( binary_version. to_string ( ) ) ;
82
+ }
83
+ }
84
+
18
85
/// Clone a git repository
19
86
///
20
87
/// Parameters:
@@ -47,6 +114,62 @@ pub fn clone_git_repo(url: &str, dest: &Path) -> Result<(), GitError> {
47
114
return Ok ( ( ) ) ;
48
115
}
49
116
117
+ pub fn git_remote_add ( url : & str ) -> Result < ( ) , GitError > {
118
+ let git_cmd = Command :: new ( "git" )
119
+ . args ( [ "remote" , "add" , "feature" , url] )
120
+ . output ( ) ?;
121
+
122
+ // if the git command does not return successfully,
123
+ // any command on the repo will fail. So fail fast.
124
+ if !git_cmd. status . success ( ) {
125
+ let error = GitError :: FailedRemoteAdd {
126
+ stdout : git_cmd. stdout ,
127
+ stderr : git_cmd. stderr ,
128
+ } ;
129
+ return Err ( error) ;
130
+ }
131
+
132
+ info ! ( "Successfully added remote: {url}" ) ;
133
+ return Ok ( ( ) ) ;
134
+ }
135
+
136
+ pub fn git_fetch ( branch_name : & str ) -> Result < ( ) , GitError > {
137
+ let git_cmd = Command :: new ( "git" )
138
+ . args ( [ "fetch" , "feature" , branch_name] )
139
+ . output ( ) ?;
140
+
141
+ // if the git command does not return successfully,
142
+ // any command on the repo will fail. So fail fast.
143
+ if !git_cmd. status . success ( ) {
144
+ let error = GitError :: FailedFetch {
145
+ stdout : git_cmd. stdout ,
146
+ stderr : git_cmd. stderr ,
147
+ } ;
148
+ return Err ( error) ;
149
+ }
150
+
151
+ info ! ( "Successfully fetched: {branch_name}" ) ;
152
+ return Ok ( ( ) ) ;
153
+ }
154
+
155
+ pub fn git_switch ( git_ref : & str , should_detach : bool ) -> Result < ( ) , GitError > {
156
+ let detach_arg = if should_detach { "--detach" } else { "" } ;
157
+ let args = [ "switch" , git_ref, detach_arg] ;
158
+ let output = Command :: new ( "git" )
159
+ . args ( args. iter ( ) . filter ( |arg| !arg. is_empty ( ) ) )
160
+ . output ( ) ?;
161
+ if !output. status . success ( ) {
162
+ tracing:: error!( "Git switch failed: {output:?}" ) ;
163
+ let error = GitError :: FailedSwitch {
164
+ stdout : output. stdout ,
165
+ stderr : output. stderr ,
166
+ } ;
167
+ return Err ( error) ;
168
+ }
169
+ info ! ( "Successfully switched to {git_ref}" ) ;
170
+ return Ok ( ( ) ) ;
171
+ }
172
+
50
173
pub fn change_directory_to_path ( dest : & Path ) -> io:: Result < ( ) > {
51
174
let dest_path = Path :: new ( & dest) ;
52
175
env:: set_current_dir ( & dest_path) ?;
@@ -56,3 +179,94 @@ pub fn change_directory_to_path(dest: &Path) -> io::Result<()> {
56
179
) ;
57
180
return Ok ( ( ) ) ;
58
181
}
182
+
183
+ pub fn get_ld_library_path ( ) -> Result < String , CheckDiffError > {
184
+ let Ok ( command) = Command :: new ( "rustc" ) . args ( [ "--print" , "sysroot" ] ) . output ( ) else {
185
+ return Err ( CheckDiffError :: FailedCommand ( "Error getting sysroot" ) ) ;
186
+ } ;
187
+ let sysroot = std:: str:: from_utf8 ( & command. stdout ) ?. trim_end ( ) ;
188
+ let ld_lib_path = format ! ( "{}/lib" , sysroot) ;
189
+ return Ok ( ld_lib_path) ;
190
+ }
191
+
192
+ pub fn get_cargo_version ( ) -> Result < String , CheckDiffError > {
193
+ let Ok ( command) = Command :: new ( "cargo" ) . args ( [ "--version" ] ) . output ( ) else {
194
+ return Err ( CheckDiffError :: FailedCargoVersion (
195
+ "Failed to obtain cargo version" ,
196
+ ) ) ;
197
+ } ;
198
+
199
+ let cargo_version = std:: str:: from_utf8 ( & command. stdout ) ?. trim_end ( ) ;
200
+ return Ok ( cargo_version. to_string ( ) ) ;
201
+ }
202
+
203
+ /// Obtains the ld_lib path and then builds rustfmt from source
204
+ /// 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 > {
206
+ //Because we're building standalone binaries we need to set `LD_LIBRARY_PATH` so each
207
+ // binary can find it's runtime dependencies.
208
+ // See https://github.com/rust-lang/rustfmt/issues/5675
209
+ // This will prepend the `LD_LIBRARY_PATH` for the master rustfmt binary
210
+ let ld_lib_path = get_ld_library_path ( ) ?;
211
+
212
+ info ! ( "Building rustfmt from source" ) ;
213
+ let Ok ( _) = Command :: new ( "cargo" )
214
+ . args ( [ "build" , "-q" , "--release" , "--bin" , "rustfmt" ] )
215
+ . output ( )
216
+ else {
217
+ return Err ( CheckDiffError :: FailedSourceBuild (
218
+ "Error building rustfmt from source" ,
219
+ ) ) ;
220
+ } ;
221
+
222
+ std:: fs:: copy ( "target/release/rustfmt" , & binary_path) ?;
223
+
224
+ return Ok ( RustfmtRunner {
225
+ ld_library_path : ld_lib_path,
226
+ binary_path,
227
+ } ) ;
228
+ }
229
+
230
+ // Compiles and produces two rustfmt binaries.
231
+ // One for the current master, and another for the feature branch
232
+ // Parameters:
233
+ // dest: Directory where rustfmt will be cloned
234
+ pub fn compile_rustfmt (
235
+ dest : & Path ,
236
+ remote_repo_url : String ,
237
+ feature_branch : String ,
238
+ commit_hash : Option < String > ,
239
+ ) -> Result < CheckDiffRunners , CheckDiffError > {
240
+ const RUSTFMT_REPO : & str = "https://github.com/rust-lang/rustfmt.git" ;
241
+
242
+ clone_git_repo ( RUSTFMT_REPO , dest) ?;
243
+ change_directory_to_path ( dest) ?;
244
+ git_remote_add ( remote_repo_url. as_str ( ) ) ?;
245
+ git_fetch ( feature_branch. as_str ( ) ) ?;
246
+
247
+ let cargo_version = get_cargo_version ( ) ?;
248
+ info ! ( "Compiling with {}" , cargo_version) ;
249
+ let src_runner = build_rustfmt_from_src ( dest. join ( "src_rustfmt" ) ) ?;
250
+ let should_detach = commit_hash. is_some ( ) ;
251
+ git_switch (
252
+ commit_hash. unwrap_or ( feature_branch) . as_str ( ) ,
253
+ should_detach,
254
+ ) ?;
255
+
256
+ let feature_runner = build_rustfmt_from_src ( dest. join ( "feature_rustfmt" ) ) ?;
257
+ info ! ( "RUSFMT_BIN {}" , src_runner. get_binary_version( ) ?) ;
258
+ info ! (
259
+ "Runtime dependencies for (src) rustfmt -- LD_LIBRARY_PATH: {}" ,
260
+ src_runner. ld_library_path
261
+ ) ;
262
+ info ! ( "FEATURE_BIN {}" , feature_runner. get_binary_version( ) ?) ;
263
+ info ! (
264
+ "Runtime dependencies for (feature) rustfmt -- LD_LIBRARY_PATH: {}" ,
265
+ feature_runner. ld_library_path
266
+ ) ;
267
+
268
+ return Ok ( CheckDiffRunners {
269
+ src_runner,
270
+ feature_runner,
271
+ } ) ;
272
+ }
0 commit comments