Skip to content

Commit f14e3a8

Browse files
authored
Merge pull request #1000 from rust-lang/clippy
Migrate Clippy to the orchestrator
2 parents 31b3fcb + a68f140 commit f14e3a8

File tree

8 files changed

+379
-209
lines changed

8 files changed

+379
-209
lines changed

compiler/base/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- \
4545
--profile minimal \
4646
--default-toolchain "${channel}" \
4747
--target wasm32-unknown-unknown \
48-
--component rustfmt
48+
--component rustfmt \
49+
--component clippy
4950

5051
COPY --chown=playground entrypoint.sh /playground/tools/
5152

@@ -109,6 +110,7 @@ ARG channel
109110

110111
RUN cargo build
111112
RUN cargo build --release
113+
RUN cargo clippy
112114
RUN rm src/*.rs
113115

114116
COPY --from=modify-cargo-toml /playground/modify-cargo-toml/target/release/modify-cargo-toml /playground/.cargo/bin

compiler/base/orchestrator/src/coordinator.rs

Lines changed: 276 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,54 @@ pub struct FormatResponse {
410410
pub code: String,
411411
}
412412

413+
#[derive(Debug, Clone)]
414+
pub struct ClippyRequest {
415+
pub channel: Channel,
416+
pub crate_type: CrateType,
417+
pub edition: Edition,
418+
pub code: String,
419+
}
420+
421+
impl ClippyRequest {
422+
pub(crate) fn delete_previous_main_request(&self) -> DeleteFileRequest {
423+
delete_previous_primary_file_request(self.crate_type)
424+
}
425+
426+
pub(crate) fn write_main_request(&self) -> WriteFileRequest {
427+
write_primary_file_request(self.crate_type, &self.code)
428+
}
429+
430+
pub(crate) fn execute_cargo_request(&self) -> ExecuteCommandRequest {
431+
ExecuteCommandRequest {
432+
cmd: "cargo".to_owned(),
433+
args: vec!["clippy".to_owned()],
434+
envs: Default::default(),
435+
cwd: None,
436+
}
437+
}
438+
}
439+
440+
impl CargoTomlModifier for ClippyRequest {
441+
fn modify_cargo_toml(&self, mut cargo_toml: toml::Value) -> toml::Value {
442+
if self.edition == Edition::Rust2024 {
443+
cargo_toml = modify_cargo_toml::set_feature_edition2024(cargo_toml);
444+
}
445+
446+
cargo_toml = modify_cargo_toml::set_edition(cargo_toml, self.edition.to_cargo_toml_key());
447+
448+
if let Some(crate_type) = self.crate_type.to_library_cargo_toml_key() {
449+
cargo_toml = modify_cargo_toml::set_crate_type(cargo_toml, crate_type);
450+
}
451+
cargo_toml
452+
}
453+
}
454+
455+
#[derive(Debug, Clone)]
456+
pub struct ClippyResponse {
457+
pub success: bool,
458+
pub exit_detail: String,
459+
}
460+
413461
#[derive(Debug, Clone)]
414462
pub struct WithOutput<T> {
415463
pub response: T,
@@ -574,6 +622,33 @@ where
574622
.await
575623
}
576624

625+
pub async fn clippy(
626+
&self,
627+
request: ClippyRequest,
628+
) -> Result<WithOutput<ClippyResponse>, ClippyError> {
629+
use clippy_error::*;
630+
631+
self.select_channel(request.channel)
632+
.await
633+
.context(CouldNotStartContainerSnafu)?
634+
.clippy(request)
635+
.await
636+
}
637+
638+
pub async fn begin_clippy(
639+
&self,
640+
token: CancellationToken,
641+
request: ClippyRequest,
642+
) -> Result<ActiveClippy, ClippyError> {
643+
use clippy_error::*;
644+
645+
self.select_channel(request.channel)
646+
.await
647+
.context(CouldNotStartContainerSnafu)?
648+
.begin_clippy(token, request)
649+
.await
650+
}
651+
577652
pub async fn idle(&mut self) -> Result<()> {
578653
let Self {
579654
stable,
@@ -929,6 +1004,78 @@ impl Container {
9291004
})
9301005
}
9311006

1007+
async fn clippy(
1008+
&self,
1009+
request: ClippyRequest,
1010+
) -> Result<WithOutput<ClippyResponse>, ClippyError> {
1011+
let token = Default::default();
1012+
1013+
let ActiveClippy {
1014+
task,
1015+
stdout_rx,
1016+
stderr_rx,
1017+
} = self.begin_clippy(token, request).await?;
1018+
1019+
WithOutput::try_absorb(task, stdout_rx, stderr_rx).await
1020+
}
1021+
1022+
async fn begin_clippy(
1023+
&self,
1024+
token: CancellationToken,
1025+
request: ClippyRequest,
1026+
) -> Result<ActiveClippy, ClippyError> {
1027+
use clippy_error::*;
1028+
1029+
let delete_previous_main = request.delete_previous_main_request();
1030+
let write_main = request.write_main_request();
1031+
let execute_cargo = request.execute_cargo_request();
1032+
1033+
let delete_previous_main = self.commander.one(delete_previous_main);
1034+
let write_main = self.commander.one(write_main);
1035+
let modify_cargo_toml = self.modify_cargo_toml.modify_for(&request);
1036+
1037+
let (delete_previous_main, write_main, modify_cargo_toml) =
1038+
join!(delete_previous_main, write_main, modify_cargo_toml);
1039+
1040+
delete_previous_main.context(CouldNotDeletePreviousCodeSnafu)?;
1041+
write_main.context(CouldNotWriteCodeSnafu)?;
1042+
modify_cargo_toml.context(CouldNotModifyCargoTomlSnafu)?;
1043+
1044+
let SpawnCargo {
1045+
task,
1046+
stdin_tx,
1047+
stdout_rx,
1048+
stderr_rx,
1049+
} = self
1050+
.spawn_cargo_task(token, execute_cargo)
1051+
.await
1052+
.context(CouldNotStartCargoSnafu)?;
1053+
1054+
drop(stdin_tx);
1055+
1056+
let task = async move {
1057+
let ExecuteCommandResponse {
1058+
success,
1059+
exit_detail,
1060+
} = task
1061+
.await
1062+
.context(CargoTaskPanickedSnafu)?
1063+
.context(CargoFailedSnafu)?;
1064+
1065+
Ok(ClippyResponse {
1066+
success,
1067+
exit_detail,
1068+
})
1069+
}
1070+
.boxed();
1071+
1072+
Ok(ActiveClippy {
1073+
task,
1074+
stdout_rx,
1075+
stderr_rx,
1076+
})
1077+
}
1078+
9321079
async fn spawn_cargo_task(
9331080
&self,
9341081
token: CancellationToken,
@@ -1158,6 +1305,47 @@ pub enum FormatError {
11581305
CodeNotUtf8 { source: std::string::FromUtf8Error },
11591306
}
11601307

1308+
pub struct ActiveClippy {
1309+
pub task: BoxFuture<'static, Result<ClippyResponse, ClippyError>>,
1310+
pub stdout_rx: mpsc::Receiver<String>,
1311+
pub stderr_rx: mpsc::Receiver<String>,
1312+
}
1313+
1314+
impl fmt::Debug for ActiveClippy {
1315+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1316+
f.debug_struct("ActiveClippy")
1317+
.field("task", &"<future>")
1318+
.field("stdout_rx", &self.stdout_rx)
1319+
.field("stderr_rx", &self.stderr_rx)
1320+
.finish()
1321+
}
1322+
}
1323+
1324+
#[derive(Debug, Snafu)]
1325+
#[snafu(module)]
1326+
pub enum ClippyError {
1327+
#[snafu(display("Could not start the container"))]
1328+
CouldNotStartContainer { source: Error },
1329+
1330+
#[snafu(display("Could not modify Cargo.toml"))]
1331+
CouldNotModifyCargoToml { source: ModifyCargoTomlError },
1332+
1333+
#[snafu(display("Could not delete previous source code"))]
1334+
CouldNotDeletePreviousCode { source: CommanderError },
1335+
1336+
#[snafu(display("Could not write source code"))]
1337+
CouldNotWriteCode { source: CommanderError },
1338+
1339+
#[snafu(display("Could not start Cargo task"))]
1340+
CouldNotStartCargo { source: SpawnCargoError },
1341+
1342+
#[snafu(display("The Cargo task panicked"))]
1343+
CargoTaskPanicked { source: tokio::task::JoinError },
1344+
1345+
#[snafu(display("Cargo task failed"))]
1346+
CargoFailed { source: SpawnCargoError },
1347+
}
1348+
11611349
struct SpawnCargo {
11621350
task: JoinHandle<Result<ExecuteCommandResponse, SpawnCargoError>>,
11631351
stdin_tx: mpsc::Sender<String>,
@@ -1749,29 +1937,34 @@ mod tests {
17491937
let project_dir =
17501938
TempDir::new("playground").expect("Failed to create temporary project directory");
17511939

1752-
let output = std::process::Command::new("cargo")
1753-
.arg("init")
1754-
.args(["--name", "playground"])
1755-
.arg(project_dir.path())
1756-
.output()
1757-
.expect("Build failed");
1758-
assert!(output.status.success(), "Cargo initialization failed");
1940+
for channel in Channel::ALL {
1941+
let channel = channel.to_str();
1942+
let channel_dir = project_dir.path().join(channel);
17591943

1760-
let main = project_dir.path().join("src").join("main.rs");
1761-
std::fs::remove_file(main).expect("Could not delete main.rs");
1944+
let output = std::process::Command::new("cargo")
1945+
.arg(format!("+{channel}"))
1946+
.arg("new")
1947+
.args(["--name", "playground"])
1948+
.arg(&channel_dir)
1949+
.output()
1950+
.expect("Cargo new failed");
1951+
assert!(output.status.success(), "Cargo new failed");
1952+
1953+
let main = channel_dir.join("src").join("main.rs");
1954+
std::fs::remove_file(main).expect("Could not delete main.rs");
1955+
}
17621956

17631957
Self { project_dir }
17641958
}
17651959
}
17661960

17671961
impl Backend for TestBackend {
17681962
fn prepare_worker_command(&self, channel: Channel) -> Command {
1769-
let toolchain_file = format!(r#"[toolchain]\nchannel = "{}""#, channel.to_str());
1770-
let path = self.project_dir.path().join("rust-toolchain.toml");
1771-
std::fs::write(path, toolchain_file).expect("Couldn't write toolchain file");
1963+
let channel_dir = self.project_dir.path().join(channel.to_str());
17721964

17731965
let mut command = Command::new("./target/debug/worker");
1774-
command.arg(self.project_dir.path());
1966+
command.env("RUSTUP_TOOLCHAIN", channel.to_str());
1967+
command.arg(channel_dir);
17751968
command
17761969
}
17771970
}
@@ -2647,6 +2840,76 @@ mod tests {
26472840
Ok(())
26482841
}
26492842

2843+
const ARBITRARY_CLIPPY_REQUEST: ClippyRequest = ClippyRequest {
2844+
channel: Channel::Stable,
2845+
crate_type: CrateType::Library(LibraryType::Rlib),
2846+
edition: Edition::Rust2021,
2847+
code: String::new(),
2848+
};
2849+
2850+
#[tokio::test]
2851+
#[snafu::report]
2852+
async fn clippy() -> Result<()> {
2853+
let coordinator = new_coordinator().await;
2854+
2855+
let req = ClippyRequest {
2856+
code: r#"
2857+
fn main() {
2858+
let a = 0.0 / 0.0;
2859+
println!("NaN is {}", a);
2860+
}
2861+
"#
2862+
.into(),
2863+
..ARBITRARY_CLIPPY_REQUEST
2864+
};
2865+
2866+
let response = coordinator.clippy(req).with_timeout().await.unwrap();
2867+
2868+
assert!(!response.success, "stderr: {}", response.stderr);
2869+
assert_contains!(response.stderr, "deny(clippy::eq_op)");
2870+
assert_contains!(response.stderr, "warn(clippy::zero_divided_by_zero)");
2871+
2872+
Ok(())
2873+
}
2874+
2875+
#[tokio::test]
2876+
#[snafu::report]
2877+
async fn clippy_edition() -> Result<()> {
2878+
let cases = [(
2879+
"#![deny(clippy::single_element_loop)]
2880+
fn a() { for i in [1] { dbg!(i); } }",
2881+
[true, true, false, false],
2882+
)];
2883+
2884+
let tests = cases.into_iter().flat_map(|(code, expected_to_be_clean)| {
2885+
Edition::ALL.into_iter().zip(expected_to_be_clean).map(
2886+
move |(edition, expected_to_be_clean)| async move {
2887+
let coordinator = new_coordinator().await;
2888+
2889+
let req = ClippyRequest {
2890+
edition,
2891+
code: code.into(),
2892+
..ARBITRARY_CLIPPY_REQUEST
2893+
};
2894+
2895+
let response = coordinator.clippy(req).with_timeout().await.unwrap();
2896+
2897+
assert_eq!(
2898+
response.success, expected_to_be_clean,
2899+
"{code:?} in {edition:?}, {}",
2900+
response.stderr
2901+
);
2902+
2903+
Ok(())
2904+
},
2905+
)
2906+
});
2907+
2908+
try_join_all(tests).with_timeout().await?;
2909+
2910+
Ok(())
2911+
}
2912+
26502913
// The next set of tests are broader than the functionality of a
26512914
// single operation.
26522915

compiler/clippy/Dockerfile

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
ARG base_image=shepmaster/rust-nightly
22
FROM ${base_image}
33

4-
RUN rustup component add clippy
5-
6-
RUN touch src/lib.rs
7-
RUN cargo clippy
8-
RUN rm src/*.rs
4+
# The base image takes care of all this for now
95

106
ENTRYPOINT ["/playground/tools/entrypoint.sh"]

compiler/rustfmt/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
ARG base_image=shepmaster/rust-nightly
22
FROM ${base_image}
33

4-
RUN rustup component add rustfmt
4+
# The base image takes care of all this for now
55

66
ENTRYPOINT ["/playground/tools/entrypoint.sh"]

0 commit comments

Comments
 (0)