Skip to content

Commit 2ca99e3

Browse files
committed
Migrate code formatting to the orchestrator
1 parent 056dfbe commit 2ca99e3

File tree

5 files changed

+388
-182
lines changed

5 files changed

+388
-182
lines changed

compiler/base/orchestrator/src/coordinator.rs

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ pub enum Channel {
7070
}
7171

7272
impl Channel {
73+
#[cfg(test)]
74+
pub(crate) const ALL: [Self; 3] = [Self::Stable, Self::Beta, Self::Nightly];
75+
7376
#[cfg(test)]
7477
pub(crate) fn to_str(self) -> &'static str {
7578
match self {
@@ -358,6 +361,55 @@ pub struct CompileResponse {
358361
pub code: String,
359362
}
360363

364+
#[derive(Debug, Clone)]
365+
pub struct FormatRequest {
366+
pub channel: Channel,
367+
pub crate_type: CrateType,
368+
pub edition: Edition,
369+
pub code: String,
370+
}
371+
372+
impl FormatRequest {
373+
pub(crate) fn delete_previous_main_request(&self) -> DeleteFileRequest {
374+
delete_previous_primary_file_request(self.crate_type)
375+
}
376+
377+
pub(crate) fn write_main_request(&self) -> WriteFileRequest {
378+
write_primary_file_request(self.crate_type, &self.code)
379+
}
380+
381+
pub(crate) fn execute_cargo_request(&self) -> ExecuteCommandRequest {
382+
ExecuteCommandRequest {
383+
cmd: "cargo".to_owned(),
384+
args: vec!["fmt".to_owned()],
385+
envs: Default::default(),
386+
cwd: None,
387+
}
388+
}
389+
}
390+
391+
impl CargoTomlModifier for FormatRequest {
392+
fn modify_cargo_toml(&self, mut cargo_toml: toml::Value) -> toml::Value {
393+
if self.edition == Edition::Rust2024 {
394+
cargo_toml = modify_cargo_toml::set_feature_edition2024(cargo_toml);
395+
}
396+
397+
cargo_toml = modify_cargo_toml::set_edition(cargo_toml, self.edition.to_cargo_toml_key());
398+
399+
if let Some(crate_type) = self.crate_type.to_library_cargo_toml_key() {
400+
cargo_toml = modify_cargo_toml::set_crate_type(cargo_toml, crate_type);
401+
}
402+
cargo_toml
403+
}
404+
}
405+
406+
#[derive(Debug, Clone)]
407+
pub struct FormatResponse {
408+
pub success: bool,
409+
pub exit_detail: String,
410+
pub code: String,
411+
}
412+
361413
#[derive(Debug, Clone)]
362414
pub struct WithOutput<T> {
363415
pub response: T,
@@ -495,6 +547,33 @@ where
495547
.await
496548
}
497549

550+
pub async fn format(
551+
&self,
552+
request: FormatRequest,
553+
) -> Result<WithOutput<FormatResponse>, FormatError> {
554+
use format_error::*;
555+
556+
self.select_channel(request.channel)
557+
.await
558+
.context(CouldNotStartContainerSnafu)?
559+
.format(request)
560+
.await
561+
}
562+
563+
pub async fn begin_format(
564+
&self,
565+
token: CancellationToken,
566+
request: FormatRequest,
567+
) -> Result<ActiveFormatting, FormatError> {
568+
use format_error::*;
569+
570+
self.select_channel(request.channel)
571+
.await
572+
.context(CouldNotStartContainerSnafu)?
573+
.begin_format(token, request)
574+
.await
575+
}
576+
498577
pub async fn idle(&mut self) -> Result<()> {
499578
let Self {
500579
stable,
@@ -767,6 +846,89 @@ impl Container {
767846
})
768847
}
769848

849+
async fn format(
850+
&self,
851+
request: FormatRequest,
852+
) -> Result<WithOutput<FormatResponse>, FormatError> {
853+
let token = Default::default();
854+
855+
let ActiveFormatting {
856+
task,
857+
stdout_rx,
858+
stderr_rx,
859+
} = self.begin_format(token, request).await?;
860+
861+
WithOutput::try_absorb(task, stdout_rx, stderr_rx).await
862+
}
863+
864+
async fn begin_format(
865+
&self,
866+
token: CancellationToken,
867+
request: FormatRequest,
868+
) -> Result<ActiveFormatting, FormatError> {
869+
use format_error::*;
870+
871+
let delete_previous_main = request.delete_previous_main_request();
872+
let write_main = request.write_main_request();
873+
let execute_cargo = request.execute_cargo_request();
874+
let read_output = ReadFileRequest {
875+
path: request.crate_type.primary_path().to_owned(),
876+
};
877+
878+
let delete_previous_main = self.commander.one(delete_previous_main);
879+
let write_main = self.commander.one(write_main);
880+
let modify_cargo_toml = self.modify_cargo_toml.modify_for(&request);
881+
882+
let (delete_previous_main, write_main, modify_cargo_toml) =
883+
join!(delete_previous_main, write_main, modify_cargo_toml);
884+
885+
delete_previous_main.context(CouldNotDeletePreviousCodeSnafu)?;
886+
write_main.context(CouldNotWriteCodeSnafu)?;
887+
modify_cargo_toml.context(CouldNotModifyCargoTomlSnafu)?;
888+
889+
let SpawnCargo {
890+
task,
891+
stdin_tx,
892+
stdout_rx,
893+
stderr_rx,
894+
} = self
895+
.spawn_cargo_task(token, execute_cargo)
896+
.await
897+
.context(CouldNotStartCargoSnafu)?;
898+
899+
drop(stdin_tx);
900+
901+
let commander = self.commander.clone();
902+
let task = async move {
903+
let ExecuteCommandResponse {
904+
success,
905+
exit_detail,
906+
} = task
907+
.await
908+
.context(CargoTaskPanickedSnafu)?
909+
.context(CargoFailedSnafu)?;
910+
911+
let file = commander
912+
.one(read_output)
913+
.await
914+
.context(CouldNotReadCodeSnafu)?;
915+
let code = String::from_utf8(file.0).context(CodeNotUtf8Snafu)?;
916+
917+
Ok(FormatResponse {
918+
success,
919+
exit_detail,
920+
code,
921+
})
922+
}
923+
.boxed();
924+
925+
Ok(ActiveFormatting {
926+
task,
927+
stdout_rx,
928+
stderr_rx,
929+
})
930+
}
931+
770932
async fn spawn_cargo_task(
771933
&self,
772934
token: CancellationToken,
@@ -949,6 +1111,53 @@ pub enum CompileError {
9491111
CodeNotUtf8 { source: std::string::FromUtf8Error },
9501112
}
9511113

1114+
pub struct ActiveFormatting {
1115+
pub task: BoxFuture<'static, Result<FormatResponse, FormatError>>,
1116+
pub stdout_rx: mpsc::Receiver<String>,
1117+
pub stderr_rx: mpsc::Receiver<String>,
1118+
}
1119+
1120+
impl fmt::Debug for ActiveFormatting {
1121+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1122+
f.debug_struct("ActiveFormatting")
1123+
.field("task", &"<future>")
1124+
.field("stdout_rx", &self.stdout_rx)
1125+
.field("stderr_rx", &self.stderr_rx)
1126+
.finish()
1127+
}
1128+
}
1129+
1130+
#[derive(Debug, Snafu)]
1131+
#[snafu(module)]
1132+
pub enum FormatError {
1133+
#[snafu(display("Could not start the container"))]
1134+
CouldNotStartContainer { source: Error },
1135+
1136+
#[snafu(display("Could not modify Cargo.toml"))]
1137+
CouldNotModifyCargoToml { source: ModifyCargoTomlError },
1138+
1139+
#[snafu(display("Could not delete previous source code"))]
1140+
CouldNotDeletePreviousCode { source: CommanderError },
1141+
1142+
#[snafu(display("Could not write source code"))]
1143+
CouldNotWriteCode { source: CommanderError },
1144+
1145+
#[snafu(display("Could not start Cargo task"))]
1146+
CouldNotStartCargo { source: SpawnCargoError },
1147+
1148+
#[snafu(display("The Cargo task panicked"))]
1149+
CargoTaskPanicked { source: tokio::task::JoinError },
1150+
1151+
#[snafu(display("Cargo task failed"))]
1152+
CargoFailed { source: SpawnCargoError },
1153+
1154+
#[snafu(display("Could not read the compilation output"))]
1155+
CouldNotReadCode { source: CommanderError },
1156+
1157+
#[snafu(display("The compilation output was not UTF-8"))]
1158+
CodeNotUtf8 { source: std::string::FromUtf8Error },
1159+
}
1160+
9521161
struct SpawnCargo {
9531162
task: JoinHandle<Result<ExecuteCommandResponse, SpawnCargoError>>,
9541163
stdin_tx: mpsc::Sender<String>,
@@ -2282,6 +2491,93 @@ mod tests {
22822491
Ok(())
22832492
}
22842493

2494+
const ARBITRARY_FORMAT_REQUEST: FormatRequest = FormatRequest {
2495+
channel: Channel::Stable,
2496+
crate_type: CrateType::Binary,
2497+
edition: Edition::Rust2015,
2498+
code: String::new(),
2499+
};
2500+
2501+
const ARBITRARY_FORMAT_INPUT: &str = "fn main(){1+1;}";
2502+
#[rustfmt::skip]
2503+
const ARBITRARY_FORMAT_OUTPUT: &[&str] = &[
2504+
"fn main() {",
2505+
" 1 + 1;",
2506+
"}"
2507+
];
2508+
2509+
#[tokio::test]
2510+
#[snafu::report]
2511+
async fn format() -> Result<()> {
2512+
let coordinator = new_coordinator().await;
2513+
2514+
let req = FormatRequest {
2515+
code: ARBITRARY_FORMAT_INPUT.into(),
2516+
..ARBITRARY_FORMAT_REQUEST
2517+
};
2518+
2519+
let response = coordinator.format(req).with_timeout().await.unwrap();
2520+
2521+
assert!(response.success, "stderr: {}", response.stderr);
2522+
let lines = response.code.lines().collect::<Vec<_>>();
2523+
assert_eq!(ARBITRARY_FORMAT_OUTPUT, lines);
2524+
2525+
Ok(())
2526+
}
2527+
2528+
#[tokio::test]
2529+
#[snafu::report]
2530+
async fn format_channel() -> Result<()> {
2531+
for channel in Channel::ALL {
2532+
let coordinator = new_coordinator().await;
2533+
2534+
let req = FormatRequest {
2535+
channel,
2536+
code: ARBITRARY_FORMAT_INPUT.into(),
2537+
..ARBITRARY_FORMAT_REQUEST
2538+
};
2539+
2540+
let response = coordinator.format(req).with_timeout().await.unwrap();
2541+
2542+
assert!(response.success, "stderr: {}", response.stderr);
2543+
let lines = response.code.lines().collect::<Vec<_>>();
2544+
assert_eq!(ARBITRARY_FORMAT_OUTPUT, lines);
2545+
}
2546+
2547+
Ok(())
2548+
}
2549+
2550+
#[tokio::test]
2551+
#[snafu::report]
2552+
async fn format_edition() -> Result<()> {
2553+
let cases = [
2554+
("fn main() { async { 1 } }", [false, true, true, true]),
2555+
("fn main() { gen { 1 } }", [false, false, false, true]),
2556+
];
2557+
2558+
for (code, works_in) in cases {
2559+
let coordinator = new_coordinator().await;
2560+
2561+
for (edition, works) in Edition::ALL.into_iter().zip(works_in) {
2562+
let req = FormatRequest {
2563+
edition,
2564+
code: code.into(),
2565+
channel: Channel::Nightly, // To allow 2024 while it is unstable
2566+
..ARBITRARY_FORMAT_REQUEST
2567+
};
2568+
2569+
let response = coordinator.format(req).with_timeout().await.unwrap();
2570+
2571+
assert_eq!(response.success, works, "{code} in {edition:?}");
2572+
}
2573+
}
2574+
2575+
Ok(())
2576+
}
2577+
2578+
// The next set of tests are broader than the functionality of a
2579+
// single operation.
2580+
22852581
#[tokio::test]
22862582
#[snafu::report]
22872583
async fn compile_clears_old_main_rs() -> Result<()> {

0 commit comments

Comments
 (0)