From affb464bc1171608f7c9a589930a79e04296eaf8 Mon Sep 17 00:00:00 2001 From: benluiwj <67570985+benluiwj@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:05:59 +0800 Subject: [PATCH] compile rustfmt in `check_diff` crate (#6275) --- check_diff/Cargo.toml | 1 - check_diff/src/lib.rs | 216 ++++++++++++++++++++++++++++++++++++++++- check_diff/src/main.rs | 16 ++- 3 files changed, 230 insertions(+), 3 deletions(-) diff --git a/check_diff/Cargo.toml b/check_diff/Cargo.toml index 4ae8a5f1f3a..6c277f4af64 100644 --- a/check_diff/Cargo.toml +++ b/check_diff/Cargo.toml @@ -9,5 +9,4 @@ edition = "2021" clap = { version = "4.4.2", features = ["derive"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } -[dev-dependencies] tempfile = "3" diff --git a/check_diff/src/lib.rs b/check_diff/src/lib.rs index b83d67c8b6e..072b2f5d5c1 100644 --- a/check_diff/src/lib.rs +++ b/check_diff/src/lib.rs @@ -1,11 +1,49 @@ use std::env; use std::io; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; +use std::str::Utf8Error; use tracing::info; +pub enum CheckDiffError { + /// Git related errors + FailedGit(GitError), + /// Error for generic commands + FailedCommand(&'static str), + /// UTF8 related errors + FailedUtf8(Utf8Error), + /// Error for building rustfmt from source + FailedSourceBuild(&'static str), + /// Error when obtaining binary version + FailedBinaryVersioning(PathBuf), + /// Error when obtaining cargo version + FailedCargoVersion(&'static str), + IO(std::io::Error), +} + +impl From for CheckDiffError { + fn from(error: io::Error) -> Self { + CheckDiffError::IO(error) + } +} + +impl From for CheckDiffError { + fn from(error: GitError) -> Self { + CheckDiffError::FailedGit(error) + } +} + +impl From for CheckDiffError { + fn from(error: Utf8Error) -> Self { + CheckDiffError::FailedUtf8(error) + } +} + pub enum GitError { FailedClone { stdout: Vec, stderr: Vec }, + FailedRemoteAdd { stdout: Vec, stderr: Vec }, + FailedFetch { stdout: Vec, stderr: Vec }, + FailedSwitch { stdout: Vec, stderr: Vec }, IO(std::io::Error), } @@ -15,6 +53,35 @@ impl From for GitError { } } +// will be used in future PRs, just added to make the compiler happy +#[allow(dead_code)] +pub struct CheckDiffRunners { + feature_runner: RustfmtRunner, + src_runner: RustfmtRunner, +} + +pub struct RustfmtRunner { + ld_library_path: String, + binary_path: PathBuf, +} + +impl RustfmtRunner { + fn get_binary_version(&self) -> Result { + let Ok(command) = Command::new(&self.binary_path) + .env("LD_LIBRARY_PATH", &self.ld_library_path) + .args(["--version"]) + .output() + else { + return Err(CheckDiffError::FailedBinaryVersioning( + self.binary_path.clone(), + )); + }; + + let binary_version = std::str::from_utf8(&command.stdout)?.trim(); + return Ok(binary_version.to_string()); + } +} + /// Clone a git repository /// /// Parameters: @@ -47,6 +114,62 @@ pub fn clone_git_repo(url: &str, dest: &Path) -> Result<(), GitError> { return Ok(()); } +pub fn git_remote_add(url: &str) -> Result<(), GitError> { + let git_cmd = Command::new("git") + .args(["remote", "add", "feature", url]) + .output()?; + + // if the git command does not return successfully, + // any command on the repo will fail. So fail fast. + if !git_cmd.status.success() { + let error = GitError::FailedRemoteAdd { + stdout: git_cmd.stdout, + stderr: git_cmd.stderr, + }; + return Err(error); + } + + info!("Successfully added remote: {url}"); + return Ok(()); +} + +pub fn git_fetch(branch_name: &str) -> Result<(), GitError> { + let git_cmd = Command::new("git") + .args(["fetch", "feature", branch_name]) + .output()?; + + // if the git command does not return successfully, + // any command on the repo will fail. So fail fast. + if !git_cmd.status.success() { + let error = GitError::FailedFetch { + stdout: git_cmd.stdout, + stderr: git_cmd.stderr, + }; + return Err(error); + } + + info!("Successfully fetched: {branch_name}"); + return Ok(()); +} + +pub fn git_switch(git_ref: &str, should_detach: bool) -> Result<(), GitError> { + let detach_arg = if should_detach { "--detach" } else { "" }; + let args = ["switch", git_ref, detach_arg]; + let output = Command::new("git") + .args(args.iter().filter(|arg| !arg.is_empty())) + .output()?; + if !output.status.success() { + tracing::error!("Git switch failed: {output:?}"); + let error = GitError::FailedSwitch { + stdout: output.stdout, + stderr: output.stderr, + }; + return Err(error); + } + info!("Successfully switched to {git_ref}"); + return Ok(()); +} + pub fn change_directory_to_path(dest: &Path) -> io::Result<()> { let dest_path = Path::new(&dest); env::set_current_dir(&dest_path)?; @@ -56,3 +179,94 @@ pub fn change_directory_to_path(dest: &Path) -> io::Result<()> { ); return Ok(()); } + +pub fn get_ld_library_path() -> Result { + let Ok(command) = Command::new("rustc").args(["--print", "sysroot"]).output() else { + return Err(CheckDiffError::FailedCommand("Error getting sysroot")); + }; + let sysroot = std::str::from_utf8(&command.stdout)?.trim_end(); + let ld_lib_path = format!("{}/lib", sysroot); + return Ok(ld_lib_path); +} + +pub fn get_cargo_version() -> Result { + let Ok(command) = Command::new("cargo").args(["--version"]).output() else { + return Err(CheckDiffError::FailedCargoVersion( + "Failed to obtain cargo version", + )); + }; + + let cargo_version = std::str::from_utf8(&command.stdout)?.trim_end(); + return Ok(cargo_version.to_string()); +} + +/// Obtains the ld_lib path and then builds rustfmt from source +/// If that operation succeeds, the source is then copied to the output path specified +pub fn build_rustfmt_from_src(binary_path: PathBuf) -> Result { + //Because we're building standalone binaries we need to set `LD_LIBRARY_PATH` so each + // binary can find it's runtime dependencies. + // See https://github.com/rust-lang/rustfmt/issues/5675 + // This will prepend the `LD_LIBRARY_PATH` for the master rustfmt binary + let ld_lib_path = get_ld_library_path()?; + + info!("Building rustfmt from source"); + let Ok(_) = Command::new("cargo") + .args(["build", "-q", "--release", "--bin", "rustfmt"]) + .output() + else { + return Err(CheckDiffError::FailedSourceBuild( + "Error building rustfmt from source", + )); + }; + + std::fs::copy("target/release/rustfmt", &binary_path)?; + + return Ok(RustfmtRunner { + ld_library_path: ld_lib_path, + binary_path, + }); +} + +// Compiles and produces two rustfmt binaries. +// One for the current master, and another for the feature branch +// Parameters: +// dest: Directory where rustfmt will be cloned +pub fn compile_rustfmt( + dest: &Path, + remote_repo_url: String, + feature_branch: String, + commit_hash: Option, +) -> Result { + const RUSTFMT_REPO: &str = "https://github.com/rust-lang/rustfmt.git"; + + clone_git_repo(RUSTFMT_REPO, dest)?; + change_directory_to_path(dest)?; + git_remote_add(remote_repo_url.as_str())?; + git_fetch(feature_branch.as_str())?; + + let cargo_version = get_cargo_version()?; + info!("Compiling with {}", cargo_version); + let src_runner = build_rustfmt_from_src(dest.join("src_rustfmt"))?; + let should_detach = commit_hash.is_some(); + git_switch( + commit_hash.unwrap_or(feature_branch).as_str(), + should_detach, + )?; + + let feature_runner = build_rustfmt_from_src(dest.join("feature_rustfmt"))?; + info!("RUSFMT_BIN {}", src_runner.get_binary_version()?); + info!( + "Runtime dependencies for (src) rustfmt -- LD_LIBRARY_PATH: {}", + src_runner.ld_library_path + ); + info!("FEATURE_BIN {}", feature_runner.get_binary_version()?); + info!( + "Runtime dependencies for (feature) rustfmt -- LD_LIBRARY_PATH: {}", + feature_runner.ld_library_path + ); + + return Ok(CheckDiffRunners { + src_runner, + feature_runner, + }); +} diff --git a/check_diff/src/main.rs b/check_diff/src/main.rs index 01c5926c490..e70ae628da7 100644 --- a/check_diff/src/main.rs +++ b/check_diff/src/main.rs @@ -1,4 +1,7 @@ +use check_diff::compile_rustfmt; use clap::Parser; +use tempfile::Builder; +use tracing::info; /// Inputs for the check_diff script #[derive(Parser)] @@ -17,5 +20,16 @@ struct CliInputs { } fn main() { - let _args = CliInputs::parse(); + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_env("CHECK_DIFF_LOG")) + .init(); + let args = CliInputs::parse(); + let tmp_dir = Builder::new().tempdir_in("").unwrap(); + info!("Created tmp_dir {:?}", tmp_dir); + let _ = compile_rustfmt( + tmp_dir.path(), + args.remote_repo_url, + args.feature_branch, + args.commit_hash, + ); }