Skip to content

Commit 054efdd

Browse files
benluiwjytmimi
authored andcommitted
add check_diff function implementation to check_diff crate
1 parent 6f7aeed commit 054efdd

File tree

5 files changed

+336
-20
lines changed

5 files changed

+336
-20
lines changed

check_diff/Cargo.lock

+49-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

check_diff/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ clap = { version = "4.4.2", features = ["derive"] }
1010
tracing = "0.1.37"
1111
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
1212
tempfile = "3"
13+
walkdir = "2.5.0"
14+
diffy = "0.4.0"

check_diff/src/lib.rs

+180-15
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
use diffy;
12
use std::env;
2-
use std::io;
3+
use std::fmt::{Debug, Display};
4+
use std::io::{self, Write};
35
use std::path::{Path, PathBuf};
4-
use std::process::Command;
6+
use std::process::{Command, Stdio};
57
use std::str::Utf8Error;
68
use tracing::info;
9+
use walkdir::WalkDir;
710

11+
#[derive(Debug)]
812
pub enum CheckDiffError {
913
/// Git related errors
1014
FailedGit(GitError),
@@ -39,6 +43,7 @@ impl From<Utf8Error> for CheckDiffError {
3943
}
4044
}
4145

46+
#[derive(Debug)]
4247
pub enum GitError {
4348
FailedClone { stdout: Vec<u8>, stderr: Vec<u8> },
4449
FailedRemoteAdd { stdout: Vec<u8>, stderr: Vec<u8> },
@@ -53,18 +58,73 @@ impl From<io::Error> for GitError {
5358
}
5459
}
5560

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+
pub struct Diff {
62+
src_format: String,
63+
feature_format: String,
64+
}
65+
66+
impl Display for Diff {
67+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68+
let patch = diffy::create_patch(self.src_format.as_str(), self.feature_format.as_str());
69+
write!(f, "{}", patch)
70+
}
71+
}
72+
73+
impl Diff {
74+
pub fn is_empty(&self) -> bool {
75+
let patch = diffy::create_patch(self.src_format.as_str(), self.feature_format.as_str());
76+
patch.hunks().is_empty()
77+
}
78+
}
79+
80+
pub struct CheckDiffRunners<F, S> {
81+
feature_runner: F,
82+
src_runner: S,
83+
}
84+
85+
pub trait CodeFormatter {
86+
fn format_code<'a>(
87+
&self,
88+
code: &'a str,
89+
config: &Option<Vec<String>>,
90+
) -> Result<String, CheckDiffError>;
6191
}
6292

6393
pub struct RustfmtRunner {
6494
ld_library_path: String,
6595
binary_path: PathBuf,
6696
}
6797

98+
impl<F, S> CheckDiffRunners<F, S> {
99+
pub fn new(feature_runner: F, src_runner: S) -> Self {
100+
Self {
101+
feature_runner,
102+
src_runner,
103+
}
104+
}
105+
}
106+
107+
impl<F, S> CheckDiffRunners<F, S>
108+
where
109+
F: CodeFormatter,
110+
S: CodeFormatter,
111+
{
112+
/// Creates a diff generated by running the source and feature binaries on the same file path
113+
pub fn create_diff(
114+
&self,
115+
path: &Path,
116+
additional_configs: &Option<Vec<String>>,
117+
) -> Result<Diff, CheckDiffError> {
118+
let code = std::fs::read_to_string(path)?;
119+
let src_format = self.src_runner.format_code(&code, additional_configs)?;
120+
let feature_format = self.feature_runner.format_code(&code, additional_configs)?;
121+
Ok(Diff {
122+
src_format,
123+
feature_format,
124+
})
125+
}
126+
}
127+
68128
impl RustfmtRunner {
69129
fn get_binary_version(&self) -> Result<String, CheckDiffError> {
70130
let Ok(command) = Command::new(&self.binary_path)
@@ -82,6 +142,58 @@ impl RustfmtRunner {
82142
}
83143
}
84144

145+
impl CodeFormatter for RustfmtRunner {
146+
// Run rusfmt to see if a diff is produced. Runs on the code specified
147+
//
148+
// Parameters:
149+
// code: Code to run the binary on
150+
// config: Any additional configuration options to pass to rustfmt
151+
//
152+
fn format_code<'a>(
153+
&self,
154+
code: &'a str,
155+
config: &Option<Vec<String>>,
156+
) -> Result<String, CheckDiffError> {
157+
let config = create_config_arg(config);
158+
let mut command = Command::new(&self.binary_path)
159+
.env("LD_LIBRARY_PATH", &self.ld_library_path)
160+
.args([
161+
"--unstable-features",
162+
"--skip-children",
163+
"--emit=stdout",
164+
config.as_str(),
165+
])
166+
.stdin(Stdio::piped())
167+
.stdout(Stdio::piped())
168+
.stderr(Stdio::piped())
169+
.spawn()?;
170+
171+
command.stdin.as_mut().unwrap().write_all(code.as_bytes())?;
172+
let output = command.wait_with_output()?;
173+
Ok(std::str::from_utf8(&output.stdout)?.to_string())
174+
}
175+
}
176+
177+
/// Creates a configuration in the following form:
178+
/// <config_name>=<config_val>, <config_name>=<config_val>, ...
179+
fn create_config_arg(config: &Option<Vec<String>>) -> String {
180+
let config_arg: String = match config {
181+
Some(configs) => {
182+
let mut result = String::new();
183+
for arg in configs.iter() {
184+
result.push(',');
185+
result.push_str(arg.as_str());
186+
}
187+
result
188+
}
189+
None => String::new(),
190+
};
191+
let config = format!(
192+
"--config=error_on_line_overflow=false,error_on_unformatted=false{}",
193+
config_arg.as_str()
194+
);
195+
config
196+
}
85197
/// Clone a git repository
86198
///
87199
/// Parameters:
@@ -180,8 +292,12 @@ pub fn change_directory_to_path(dest: &Path) -> io::Result<()> {
180292
return Ok(());
181293
}
182294

183-
pub fn get_ld_library_path() -> Result<String, CheckDiffError> {
184-
let Ok(command) = Command::new("rustc").args(["--print", "sysroot"]).output() else {
295+
pub fn get_ld_library_path(dir: &Path) -> Result<String, CheckDiffError> {
296+
let Ok(command) = Command::new("rustc")
297+
.current_dir(dir)
298+
.args(["--print", "sysroot"])
299+
.output()
300+
else {
185301
return Err(CheckDiffError::FailedCommand("Error getting sysroot"));
186302
};
187303
let sysroot = std::str::from_utf8(&command.stdout)?.trim_end();
@@ -202,15 +318,19 @@ pub fn get_cargo_version() -> Result<String, CheckDiffError> {
202318

203319
/// Obtains the ld_lib path and then builds rustfmt from source
204320
/// 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> {
321+
pub fn build_rustfmt_from_src(
322+
binary_path: PathBuf,
323+
dir: &Path,
324+
) -> Result<RustfmtRunner, CheckDiffError> {
206325
//Because we're building standalone binaries we need to set `LD_LIBRARY_PATH` so each
207326
// binary can find it's runtime dependencies.
208327
// See https://github.com/rust-lang/rustfmt/issues/5675
209328
// This will prepend the `LD_LIBRARY_PATH` for the master rustfmt binary
210-
let ld_lib_path = get_ld_library_path()?;
329+
let ld_lib_path = get_ld_library_path(&dir)?;
211330

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

222-
std::fs::copy("target/release/rustfmt", &binary_path)?;
342+
std::fs::copy(dir.join("target/release/rustfmt"), &binary_path)?;
223343

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

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

247367
let cargo_version = get_cargo_version()?;
248368
info!("Compiling with {}", cargo_version);
249-
let src_runner = build_rustfmt_from_src(dest.join("src_rustfmt"))?;
369+
let src_runner = build_rustfmt_from_src(dest.join("src_rustfmt"), dest)?;
250370
let should_detach = commit_hash.is_some();
251371
git_switch(
252372
commit_hash.unwrap_or(feature_branch).as_str(),
253373
should_detach,
254374
)?;
255375

256-
let feature_runner = build_rustfmt_from_src(dest.join("feature_rustfmt"))?;
376+
let feature_runner = build_rustfmt_from_src(dest.join("feature_rustfmt"), dest)?;
257377
info!("RUSFMT_BIN {}", src_runner.get_binary_version()?);
258378
info!(
259379
"Runtime dependencies for (src) rustfmt -- LD_LIBRARY_PATH: {}",
@@ -270,3 +390,48 @@ pub fn compile_rustfmt(
270390
feature_runner,
271391
});
272392
}
393+
394+
/// Searches for rust files in the particular path and returns an iterator to them.
395+
pub fn search_for_rs_files(repo: &Path) -> impl Iterator<Item = PathBuf> {
396+
return WalkDir::new(repo).into_iter().filter_map(|e| match e.ok() {
397+
Some(entry) => {
398+
let path = entry.path();
399+
if path.is_file() && path.extension().map_or(false, |ext| ext == "rs") {
400+
return Some(entry.into_path());
401+
}
402+
return None;
403+
}
404+
None => None,
405+
});
406+
}
407+
408+
/// Calculates the number of errors when running the compiled binary and the feature binary on the
409+
/// repo specified with the specific configs.
410+
pub fn check_diff(
411+
config: Option<Vec<String>>,
412+
runners: CheckDiffRunners<impl CodeFormatter, impl CodeFormatter>,
413+
repo: &Path,
414+
) -> i32 {
415+
let mut errors = 0;
416+
let iter = search_for_rs_files(repo);
417+
for file in iter {
418+
match runners.create_diff(file.as_path(), &config) {
419+
Ok(diff) => {
420+
if !diff.is_empty() {
421+
eprint!("{diff}");
422+
errors += 1;
423+
}
424+
}
425+
Err(e) => {
426+
eprintln!(
427+
"Error creating diff for {:?}: {:?}",
428+
file.as_path().display(),
429+
e
430+
);
431+
errors += 1;
432+
}
433+
}
434+
}
435+
436+
return errors;
437+
}

0 commit comments

Comments
 (0)