Skip to content

Commit affb464

Browse files
authored
compile rustfmt in check_diff crate (#6275)
1 parent a7e0c15 commit affb464

File tree

3 files changed

+230
-3
lines changed

3 files changed

+230
-3
lines changed

check_diff/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,4 @@ edition = "2021"
99
clap = { version = "4.4.2", features = ["derive"] }
1010
tracing = "0.1.37"
1111
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
12-
[dev-dependencies]
1312
tempfile = "3"

check_diff/src/lib.rs

Lines changed: 215 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
11
use std::env;
22
use std::io;
3-
use std::path::Path;
3+
use std::path::{Path, PathBuf};
44
use std::process::Command;
5+
use std::str::Utf8Error;
56
use tracing::info;
67

8+
pub enum CheckDiffError {
9+
/// Git related errors
10+
FailedGit(GitError),
11+
/// Error for generic commands
12+
FailedCommand(&'static str),
13+
/// UTF8 related errors
14+
FailedUtf8(Utf8Error),
15+
/// Error for building rustfmt from source
16+
FailedSourceBuild(&'static str),
17+
/// Error when obtaining binary version
18+
FailedBinaryVersioning(PathBuf),
19+
/// Error when obtaining cargo version
20+
FailedCargoVersion(&'static str),
21+
IO(std::io::Error),
22+
}
23+
24+
impl From<io::Error> for CheckDiffError {
25+
fn from(error: io::Error) -> Self {
26+
CheckDiffError::IO(error)
27+
}
28+
}
29+
30+
impl From<GitError> for CheckDiffError {
31+
fn from(error: GitError) -> Self {
32+
CheckDiffError::FailedGit(error)
33+
}
34+
}
35+
36+
impl From<Utf8Error> for CheckDiffError {
37+
fn from(error: Utf8Error) -> Self {
38+
CheckDiffError::FailedUtf8(error)
39+
}
40+
}
41+
742
pub enum GitError {
843
FailedClone { stdout: Vec<u8>, stderr: Vec<u8> },
44+
FailedRemoteAdd { stdout: Vec<u8>, stderr: Vec<u8> },
45+
FailedFetch { stdout: Vec<u8>, stderr: Vec<u8> },
46+
FailedSwitch { stdout: Vec<u8>, stderr: Vec<u8> },
947
IO(std::io::Error),
1048
}
1149

@@ -15,6 +53,35 @@ impl From<io::Error> for GitError {
1553
}
1654
}
1755

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+
}
62+
63+
pub struct RustfmtRunner {
64+
ld_library_path: String,
65+
binary_path: PathBuf,
66+
}
67+
68+
impl RustfmtRunner {
69+
fn get_binary_version(&self) -> Result<String, CheckDiffError> {
70+
let Ok(command) = Command::new(&self.binary_path)
71+
.env("LD_LIBRARY_PATH", &self.ld_library_path)
72+
.args(["--version"])
73+
.output()
74+
else {
75+
return Err(CheckDiffError::FailedBinaryVersioning(
76+
self.binary_path.clone(),
77+
));
78+
};
79+
80+
let binary_version = std::str::from_utf8(&command.stdout)?.trim();
81+
return Ok(binary_version.to_string());
82+
}
83+
}
84+
1885
/// Clone a git repository
1986
///
2087
/// Parameters:
@@ -47,6 +114,62 @@ pub fn clone_git_repo(url: &str, dest: &Path) -> Result<(), GitError> {
47114
return Ok(());
48115
}
49116

117+
pub fn git_remote_add(url: &str) -> Result<(), GitError> {
118+
let git_cmd = Command::new("git")
119+
.args(["remote", "add", "feature", url])
120+
.output()?;
121+
122+
// if the git command does not return successfully,
123+
// any command on the repo will fail. So fail fast.
124+
if !git_cmd.status.success() {
125+
let error = GitError::FailedRemoteAdd {
126+
stdout: git_cmd.stdout,
127+
stderr: git_cmd.stderr,
128+
};
129+
return Err(error);
130+
}
131+
132+
info!("Successfully added remote: {url}");
133+
return Ok(());
134+
}
135+
136+
pub fn git_fetch(branch_name: &str) -> Result<(), GitError> {
137+
let git_cmd = Command::new("git")
138+
.args(["fetch", "feature", branch_name])
139+
.output()?;
140+
141+
// if the git command does not return successfully,
142+
// any command on the repo will fail. So fail fast.
143+
if !git_cmd.status.success() {
144+
let error = GitError::FailedFetch {
145+
stdout: git_cmd.stdout,
146+
stderr: git_cmd.stderr,
147+
};
148+
return Err(error);
149+
}
150+
151+
info!("Successfully fetched: {branch_name}");
152+
return Ok(());
153+
}
154+
155+
pub fn git_switch(git_ref: &str, should_detach: bool) -> Result<(), GitError> {
156+
let detach_arg = if should_detach { "--detach" } else { "" };
157+
let args = ["switch", git_ref, detach_arg];
158+
let output = Command::new("git")
159+
.args(args.iter().filter(|arg| !arg.is_empty()))
160+
.output()?;
161+
if !output.status.success() {
162+
tracing::error!("Git switch failed: {output:?}");
163+
let error = GitError::FailedSwitch {
164+
stdout: output.stdout,
165+
stderr: output.stderr,
166+
};
167+
return Err(error);
168+
}
169+
info!("Successfully switched to {git_ref}");
170+
return Ok(());
171+
}
172+
50173
pub fn change_directory_to_path(dest: &Path) -> io::Result<()> {
51174
let dest_path = Path::new(&dest);
52175
env::set_current_dir(&dest_path)?;
@@ -56,3 +179,94 @@ pub fn change_directory_to_path(dest: &Path) -> io::Result<()> {
56179
);
57180
return Ok(());
58181
}
182+
183+
pub fn get_ld_library_path() -> Result<String, CheckDiffError> {
184+
let Ok(command) = Command::new("rustc").args(["--print", "sysroot"]).output() else {
185+
return Err(CheckDiffError::FailedCommand("Error getting sysroot"));
186+
};
187+
let sysroot = std::str::from_utf8(&command.stdout)?.trim_end();
188+
let ld_lib_path = format!("{}/lib", sysroot);
189+
return Ok(ld_lib_path);
190+
}
191+
192+
pub fn get_cargo_version() -> Result<String, CheckDiffError> {
193+
let Ok(command) = Command::new("cargo").args(["--version"]).output() else {
194+
return Err(CheckDiffError::FailedCargoVersion(
195+
"Failed to obtain cargo version",
196+
));
197+
};
198+
199+
let cargo_version = std::str::from_utf8(&command.stdout)?.trim_end();
200+
return Ok(cargo_version.to_string());
201+
}
202+
203+
/// Obtains the ld_lib path and then builds rustfmt from source
204+
/// 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> {
206+
//Because we're building standalone binaries we need to set `LD_LIBRARY_PATH` so each
207+
// binary can find it's runtime dependencies.
208+
// See https://github.com/rust-lang/rustfmt/issues/5675
209+
// This will prepend the `LD_LIBRARY_PATH` for the master rustfmt binary
210+
let ld_lib_path = get_ld_library_path()?;
211+
212+
info!("Building rustfmt from source");
213+
let Ok(_) = Command::new("cargo")
214+
.args(["build", "-q", "--release", "--bin", "rustfmt"])
215+
.output()
216+
else {
217+
return Err(CheckDiffError::FailedSourceBuild(
218+
"Error building rustfmt from source",
219+
));
220+
};
221+
222+
std::fs::copy("target/release/rustfmt", &binary_path)?;
223+
224+
return Ok(RustfmtRunner {
225+
ld_library_path: ld_lib_path,
226+
binary_path,
227+
});
228+
}
229+
230+
// Compiles and produces two rustfmt binaries.
231+
// One for the current master, and another for the feature branch
232+
// Parameters:
233+
// dest: Directory where rustfmt will be cloned
234+
pub fn compile_rustfmt(
235+
dest: &Path,
236+
remote_repo_url: String,
237+
feature_branch: String,
238+
commit_hash: Option<String>,
239+
) -> Result<CheckDiffRunners, CheckDiffError> {
240+
const RUSTFMT_REPO: &str = "https://github.com/rust-lang/rustfmt.git";
241+
242+
clone_git_repo(RUSTFMT_REPO, dest)?;
243+
change_directory_to_path(dest)?;
244+
git_remote_add(remote_repo_url.as_str())?;
245+
git_fetch(feature_branch.as_str())?;
246+
247+
let cargo_version = get_cargo_version()?;
248+
info!("Compiling with {}", cargo_version);
249+
let src_runner = build_rustfmt_from_src(dest.join("src_rustfmt"))?;
250+
let should_detach = commit_hash.is_some();
251+
git_switch(
252+
commit_hash.unwrap_or(feature_branch).as_str(),
253+
should_detach,
254+
)?;
255+
256+
let feature_runner = build_rustfmt_from_src(dest.join("feature_rustfmt"))?;
257+
info!("RUSFMT_BIN {}", src_runner.get_binary_version()?);
258+
info!(
259+
"Runtime dependencies for (src) rustfmt -- LD_LIBRARY_PATH: {}",
260+
src_runner.ld_library_path
261+
);
262+
info!("FEATURE_BIN {}", feature_runner.get_binary_version()?);
263+
info!(
264+
"Runtime dependencies for (feature) rustfmt -- LD_LIBRARY_PATH: {}",
265+
feature_runner.ld_library_path
266+
);
267+
268+
return Ok(CheckDiffRunners {
269+
src_runner,
270+
feature_runner,
271+
});
272+
}

check_diff/src/main.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use check_diff::compile_rustfmt;
12
use clap::Parser;
3+
use tempfile::Builder;
4+
use tracing::info;
25

36
/// Inputs for the check_diff script
47
#[derive(Parser)]
@@ -17,5 +20,16 @@ struct CliInputs {
1720
}
1821

1922
fn main() {
20-
let _args = CliInputs::parse();
23+
tracing_subscriber::fmt()
24+
.with_env_filter(tracing_subscriber::EnvFilter::from_env("CHECK_DIFF_LOG"))
25+
.init();
26+
let args = CliInputs::parse();
27+
let tmp_dir = Builder::new().tempdir_in("").unwrap();
28+
info!("Created tmp_dir {:?}", tmp_dir);
29+
let _ = compile_rustfmt(
30+
tmp_dir.path(),
31+
args.remote_repo_url,
32+
args.feature_branch,
33+
args.commit_hash,
34+
);
2135
}

0 commit comments

Comments
 (0)