11use std:: env;
22use std:: io;
3- use std:: path:: Path ;
3+ use std:: path:: { Path , PathBuf } ;
44use std:: process:: Command ;
5+ use std:: str:: Utf8Error ;
56use tracing:: info;
67
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+
742pub enum GitError {
843 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 > } ,
947 IO ( std:: io:: Error ) ,
1048}
1149
@@ -15,6 +53,35 @@ impl From<io::Error> for GitError {
1553 }
1654}
1755
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+
1885/// Clone a git repository
1986///
2087/// Parameters:
@@ -47,6 +114,62 @@ pub fn clone_git_repo(url: &str, dest: &Path) -> Result<(), GitError> {
47114 return Ok ( ( ) ) ;
48115}
49116
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+
50173pub fn change_directory_to_path ( dest : & Path ) -> io:: Result < ( ) > {
51174 let dest_path = Path :: new ( & dest) ;
52175 env:: set_current_dir ( & dest_path) ?;
@@ -56,3 +179,94 @@ pub fn change_directory_to_path(dest: &Path) -> io::Result<()> {
56179 ) ;
57180 return Ok ( ( ) ) ;
58181}
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