Skip to content

Commit a68f140

Browse files
committed
Migrate Clippy to the orchestrator
1 parent e2aa307 commit a68f140

File tree

5 files changed

+356
-189
lines changed

5 files changed

+356
-189
lines changed

compiler/base/orchestrator/src/coordinator.rs

Lines changed: 258 additions & 0 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>,
@@ -2652,6 +2840,76 @@ mod tests {
26522840
Ok(())
26532841
}
26542842

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+
26552913
// The next set of tests are broader than the functionality of a
26562914
// single operation.
26572915

ui/src/main.rs

Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,6 @@ impl MetricsToken {
165165
enum Error {
166166
#[snafu(display("Sandbox creation failed: {}", source))]
167167
SandboxCreation { source: sandbox::Error },
168-
#[snafu(display("Linting operation failed: {}", source))]
169-
Linting { source: sandbox::Error },
170168
#[snafu(display("Expansion operation failed: {}", source))]
171169
Expansion { source: sandbox::Error },
172170
#[snafu(display("Interpreting operation failed: {}", source))]
@@ -204,11 +202,14 @@ enum Error {
204202
source: server_axum::api_orchestrator_integration_impls::ParseFormatRequestError,
205203
},
206204

205+
#[snafu(context(false))]
206+
ClippyRequest {
207+
source: server_axum::api_orchestrator_integration_impls::ParseClippyRequestError,
208+
},
209+
207210
// Remove at a later point. From here ...
208211
#[snafu(display("The value {:?} is not a valid edition", value))]
209212
InvalidEdition { value: String },
210-
#[snafu(display("The value {:?} is not a valid crate type", value))]
211-
InvalidCrateType { value: String },
212213
// ... to here
213214
#[snafu(display("No request was provided"))]
214215
RequestMissing,
@@ -242,6 +243,11 @@ enum Error {
242243
source: orchestrator::coordinator::FormatError,
243244
},
244245

246+
#[snafu(display("Unable to convert the Clippy request"))]
247+
Clippy {
248+
source: orchestrator::coordinator::ClippyError,
249+
},
250+
245251
#[snafu(display("The operation timed out"))]
246252
Timeout { source: tokio::time::error::Elapsed },
247253

@@ -356,6 +362,7 @@ struct ClippyRequest {
356362
#[derive(Debug, Clone, Serialize)]
357363
struct ClippyResponse {
358364
success: bool,
365+
exit_detail: String,
359366
stdout: String,
360367
stderr: String,
361368
}
@@ -436,28 +443,6 @@ struct EvaluateResponse {
436443
error: Option<String>,
437444
}
438445

439-
impl TryFrom<ClippyRequest> for sandbox::ClippyRequest {
440-
type Error = Error;
441-
442-
fn try_from(me: ClippyRequest) -> Result<Self> {
443-
Ok(sandbox::ClippyRequest {
444-
code: me.code,
445-
crate_type: parse_crate_type(&me.crate_type)?,
446-
edition: parse_edition(&me.edition)?,
447-
})
448-
}
449-
}
450-
451-
impl From<sandbox::ClippyResponse> for ClippyResponse {
452-
fn from(me: sandbox::ClippyResponse) -> Self {
453-
ClippyResponse {
454-
success: me.success,
455-
stdout: me.stdout,
456-
stderr: me.stderr,
457-
}
458-
}
459-
}
460-
461446
impl TryFrom<MiriRequest> for sandbox::MiriRequest {
462447
type Error = Error;
463448

@@ -546,20 +531,6 @@ fn parse_edition(s: &str) -> Result<Option<sandbox::Edition>> {
546531
})
547532
}
548533

549-
fn parse_crate_type(s: &str) -> Result<sandbox::CrateType> {
550-
use crate::sandbox::{CrateType::*, LibraryType::*};
551-
Ok(match s {
552-
"bin" => Binary,
553-
"lib" => Library(Lib),
554-
"dylib" => Library(Dylib),
555-
"rlib" => Library(Rlib),
556-
"staticlib" => Library(Staticlib),
557-
"cdylib" => Library(Cdylib),
558-
"proc-macro" => Library(ProcMacro),
559-
value => InvalidCrateTypeSnafu { value }.fail()?,
560-
})
561-
}
562-
563534
fn default_crate_type() -> String {
564535
"bin".into()
565536
}

0 commit comments

Comments
 (0)