Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

check_diff function rewrite #6390

Merged
merged 1 commit into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
benluiwj marked this conversation as resolved.
Show resolved Hide resolved
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> {
benluiwj marked this conversation as resolved.
Show resolved Hide resolved
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
Loading