Skip to content

Commit

Permalink
compile rustfmt in check_diff crate (#6275)
Browse files Browse the repository at this point in the history
  • Loading branch information
benluiwj authored Oct 22, 2024
1 parent a7e0c15 commit affb464
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 3 deletions.
1 change: 0 additions & 1 deletion check_diff/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
216 changes: 215 additions & 1 deletion check_diff/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<io::Error> for CheckDiffError {
fn from(error: io::Error) -> Self {
CheckDiffError::IO(error)
}
}

impl From<GitError> for CheckDiffError {
fn from(error: GitError) -> Self {
CheckDiffError::FailedGit(error)
}
}

impl From<Utf8Error> for CheckDiffError {
fn from(error: Utf8Error) -> Self {
CheckDiffError::FailedUtf8(error)
}
}

pub enum GitError {
FailedClone { stdout: Vec<u8>, stderr: Vec<u8> },
FailedRemoteAdd { stdout: Vec<u8>, stderr: Vec<u8> },
FailedFetch { stdout: Vec<u8>, stderr: Vec<u8> },
FailedSwitch { stdout: Vec<u8>, stderr: Vec<u8> },
IO(std::io::Error),
}

Expand All @@ -15,6 +53,35 @@ impl From<io::Error> 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<String, CheckDiffError> {
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:
Expand Down Expand Up @@ -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)?;
Expand All @@ -56,3 +179,94 @@ pub fn change_directory_to_path(dest: &Path) -> io::Result<()> {
);
return Ok(());
}

pub fn get_ld_library_path() -> Result<String, CheckDiffError> {
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<String, CheckDiffError> {
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<RustfmtRunner, CheckDiffError> {
//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<String>,
) -> Result<CheckDiffRunners, CheckDiffError> {
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,
});
}
16 changes: 15 additions & 1 deletion check_diff/src/main.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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,
);
}

0 comments on commit affb464

Please sign in to comment.