Skip to content

Commit

Permalink
add check_diff function implementation to check_diff crate
Browse files Browse the repository at this point in the history
  • Loading branch information
benluiwj committed Jan 31, 2025
1 parent 6f7aeed commit 9ab590b
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 20 deletions.
50 changes: 49 additions & 1 deletion check_diff/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions check_diff/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ clap = { version = "4.4.2", features = ["derive"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tempfile = "3"
walkdir = "2.5.0"
diffy = "0.4.0"
195 changes: 180 additions & 15 deletions check_diff/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use diffy;
use std::env;
use std::io;
use std::fmt::{Debug, Display};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::process::{Command, Stdio};
use std::str::Utf8Error;
use tracing::info;
use walkdir::WalkDir;

#[derive(Debug)]
pub enum CheckDiffError {
/// Git related errors
FailedGit(GitError),
Expand Down Expand Up @@ -39,6 +43,7 @@ impl From<Utf8Error> for CheckDiffError {
}
}

#[derive(Debug)]
pub enum GitError {
FailedClone { stdout: Vec<u8>, stderr: Vec<u8> },
FailedRemoteAdd { stdout: Vec<u8>, stderr: Vec<u8> },
Expand All @@ -53,18 +58,73 @@ 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 Diff {
src_format: String,
feature_format: String,
}

impl Display for Diff {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let patch = diffy::create_patch(self.src_format.as_str(), self.feature_format.as_str());
write!(f, "{}", patch)
}
}

impl Diff {
pub fn is_empty(&self) -> bool {
let patch = diffy::create_patch(self.src_format.as_str(), self.feature_format.as_str());
patch.hunks().is_empty()
}
}

pub struct CheckDiffRunners<F, S> {
feature_runner: F,
src_runner: S,
}

pub trait CodeFormatter {
fn format_code<'a>(
&self,
code: &'a str,
config: &Option<Vec<String>>,
) -> Result<String, CheckDiffError>;
}

pub struct RustfmtRunner {
ld_library_path: String,
binary_path: PathBuf,
}

impl<F, S> CheckDiffRunners<F, S> {
pub fn new(feature_runner: F, src_runner: S) -> Self {
Self {
feature_runner,
src_runner,
}
}
}

impl<F, S> CheckDiffRunners<F, S>
where
F: CodeFormatter,
S: CodeFormatter,
{
/// Creates a diff generated by running the source and feature binaries on the same file path
pub fn create_diff(
&self,
path: &Path,
additional_configs: &Option<Vec<String>>,
) -> Result<Diff, CheckDiffError> {
let code = std::fs::read_to_string(path)?;
let src_format = self.src_runner.format_code(&code, additional_configs)?;
let feature_format = self.feature_runner.format_code(&code, additional_configs)?;
Ok(Diff {
src_format,
feature_format,
})
}
}

impl RustfmtRunner {
fn get_binary_version(&self) -> Result<String, CheckDiffError> {
let Ok(command) = Command::new(&self.binary_path)
Expand All @@ -82,6 +142,58 @@ impl RustfmtRunner {
}
}

impl CodeFormatter for RustfmtRunner {
// Run rusfmt to see if a diff is produced. Runs on the code specified
//
// Parameters:
// code: Code to run the binary on
// config: Any additional configuration options to pass to rustfmt
//
fn format_code<'a>(
&self,
code: &'a str,
config: &Option<Vec<String>>,
) -> Result<String, CheckDiffError> {
let config = create_config_arg(config);
let mut command = Command::new(&self.binary_path)
.env("LD_LIBRARY_PATH", &self.ld_library_path)
.args([
"--unstable-features",
"--skip-children",
"--emit=stdout",
config.as_str(),
])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;

command.stdin.as_mut().unwrap().write_all(code.as_bytes())?;
let output = command.wait_with_output()?;
Ok(std::str::from_utf8(&output.stdout)?.to_string())
}
}

/// Creates a configuration in the following form:
/// <config_name>=<config_val>, <config_name>=<config_val>, ...
fn create_config_arg(config: &Option<Vec<String>>) -> String {
let config_arg: String = match config {
Some(configs) => {
let mut result = String::new();
for arg in configs.iter() {
result.push(',');
result.push_str(arg.as_str());
}
result
}
None => String::new(),
};
let config = format!(
"--config=error_on_line_overflow=false,error_on_unformatted=false{}",
config_arg.as_str()
);
config
}
/// Clone a git repository
///
/// Parameters:
Expand Down Expand Up @@ -180,8 +292,12 @@ 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 {
pub fn get_ld_library_path(dir: &Path) -> Result<String, CheckDiffError> {
let Ok(command) = Command::new("rustc")
.current_dir(dir)
.args(["--print", "sysroot"])
.output()
else {
return Err(CheckDiffError::FailedCommand("Error getting sysroot"));
};
let sysroot = std::str::from_utf8(&command.stdout)?.trim_end();
Expand All @@ -202,15 +318,19 @@ pub fn get_cargo_version() -> Result<String, CheckDiffError> {

/// 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> {
pub fn build_rustfmt_from_src(
binary_path: PathBuf,
dir: &Path,
) -> 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()?;
let ld_lib_path = get_ld_library_path(&dir)?;

info!("Building rustfmt from source");
let Ok(_) = Command::new("cargo")
.current_dir(dir)
.args(["build", "-q", "--release", "--bin", "rustfmt"])
.output()
else {
Expand All @@ -219,7 +339,7 @@ pub fn build_rustfmt_from_src(binary_path: PathBuf) -> Result<RustfmtRunner, Che
));
};

std::fs::copy("target/release/rustfmt", &binary_path)?;
std::fs::copy(dir.join("target/release/rustfmt"), &binary_path)?;

return Ok(RustfmtRunner {
ld_library_path: ld_lib_path,
Expand All @@ -236,7 +356,7 @@ pub fn compile_rustfmt(
remote_repo_url: String,
feature_branch: String,
commit_hash: Option<String>,
) -> Result<CheckDiffRunners, CheckDiffError> {
) -> Result<CheckDiffRunners<RustfmtRunner, RustfmtRunner>, CheckDiffError> {
const RUSTFMT_REPO: &str = "https://github.com/rust-lang/rustfmt.git";

clone_git_repo(RUSTFMT_REPO, dest)?;
Expand All @@ -246,14 +366,14 @@ pub fn compile_rustfmt(

let cargo_version = get_cargo_version()?;
info!("Compiling with {}", cargo_version);
let src_runner = build_rustfmt_from_src(dest.join("src_rustfmt"))?;
let src_runner = build_rustfmt_from_src(dest.join("src_rustfmt"), dest)?;
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"))?;
let feature_runner = build_rustfmt_from_src(dest.join("feature_rustfmt"), dest)?;
info!("RUSFMT_BIN {}", src_runner.get_binary_version()?);
info!(
"Runtime dependencies for (src) rustfmt -- LD_LIBRARY_PATH: {}",
Expand All @@ -270,3 +390,48 @@ pub fn compile_rustfmt(
feature_runner,
});
}

/// Searches for rust files in the particular path and returns an iterator to them.
pub fn search_for_rs_files(repo: &Path) -> impl Iterator<Item = PathBuf> {
return WalkDir::new(repo).into_iter().filter_map(|e| match e.ok() {
Some(entry) => {
let path = entry.path();
if path.is_file() && path.extension().map_or(false, |ext| ext == "rs") {
return Some(entry.into_path());
}
return None;
}
None => None,
});
}

/// Calculates the number of errors when running the compiled binary and the feature binary on the
/// repo specified with the specific configs.
pub fn check_diff(
config: Option<Vec<String>>,
runners: CheckDiffRunners<impl CodeFormatter, impl CodeFormatter>,
repo: &Path,
) -> i32 {
let mut errors = 0;
let iter = search_for_rs_files(repo);
for file in iter {
match runners.create_diff(file.as_path(), &config) {
Ok(diff) => {
if !diff.is_empty() {
eprint!("{diff}");
errors += 1;
}
}
Err(e) => {
eprintln!(
"Error creating diff for {:?}: {:?}",
file.as_path().display(),
e
);
errors += 1;
}
}
}

return errors;
}
Loading

0 comments on commit 9ab590b

Please sign in to comment.