1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! Single-roundtrip ephemeral compile dispatch + direct (uncached) compiler
//! invocation.
//!
//! `handle_compile_ephemeral` is the wrapper-mode fast path: it inlines
//! session start, compile, and session end into one IPC roundtrip.
//!
//! `run_compiler_direct` is the bypass for non-cacheable invocations — it
//! shells out to the compiler with no caching, while still tracking lineage
//! and replaying the client's env.
use super::*;
/// Handle a single-roundtrip ephemeral compile: session start + compile + session end.
/// Avoids 3 IPC roundtrips for drop-in wrapper mode.
#[allow(clippy::too_many_arguments)] // Single dispatch hop; ergonomic refactor unblocked once we stop adding new client-side fields.
pub(super) async fn handle_compile_ephemeral(
state: &Arc<SharedState>,
client_pid: u32,
working_dir: &Path,
compiler: &Path,
args: &[String],
cwd: &Path,
env: Option<Vec<(String, String)>>,
stdin: Vec<u8>,
) -> Response {
// 1. Start ephemeral session (inline, no IPC roundtrip)
state.stats.record_session();
let session_resp = handle_session_start(
state,
SessionStartArgs {
client_pid,
working_dir,
log_file: None,
track_stats: false,
journal_path: None,
profile: false,
private_daemon: None,
},
)
.await;
let session_id = match session_resp {
Response::SessionStarted { session_id, .. } => session_id,
Response::Error { message } => return Response::Error { message },
other => {
return Response::Error {
message: format!("unexpected session start response: {other:?}"),
};
}
};
// 2. Compile — pass the compiler from the ephemeral request
let result = handle_compile(state, &session_id, args, cwd, compiler, env, stdin).await;
// 3. End session (best-effort, no response needed)
if let Ok(sid) = session_id.parse::<SessionId>() {
state.session_worktree_roots.remove(&sid);
state.sessions.end(&sid);
}
result
}
/// Run the compiler directly without caching.
///
/// `tmp_dir` is where the synthesized Windows response file lands when the
/// command line exceeds the OS limit. Production callers pass the daemon's
/// `state.depfile_tmpdir` (under the cache root) so the contents are
/// covered by the wrapper's Defender exclusion — see issue #275.
#[allow(clippy::too_many_arguments)] // Mirrors handle_compile's surface — refactor parked.
pub(super) async fn run_compiler_direct(
compiler: &NormalizedPath,
args: &[String],
cwd: &Path,
sessions: &SessionManager,
sid: &SessionId,
client_env: &Option<Vec<(String, String)>>,
stdin_bytes: &[u8],
tmp_dir: &Path,
) -> Response {
// Family hint controls response-file dialect (#634): rustc reads
// response files line-by-line with no quote handling, so the
// GCC-style single-quote wrapping mangles structured args like
// `--check-cfg cfg(feature, values("a","b"))`. Detect from the
// compiler path; `detect_family` falls back to Gcc for unknown
// names, which matches the historical behaviour.
let family_hint = crate::compiler::detect_family(&compiler.to_string_lossy());
let _rsp_guard = match crate::compiler::response_file::write_response_file_if_needed(
args,
tmp_dir,
family_hint,
) {
Ok(guard) => guard,
Err(e) => {
return Response::Error {
message: format!("failed to write response file: {e}"),
};
}
};
let lineage = super::super::lineage::Lineage::current(
sessions.get(sid).map(|s| s.client_pid),
Some(sid.to_string()),
);
let mut cmd = tokio::process::Command::new(compiler);
if let Some(ref rsp) = _rsp_guard {
cmd.arg(rsp.at_arg()).current_dir(cwd);
} else {
cmd.args(args).current_dir(cwd);
}
apply_client_env(&mut cmd, client_env, &lineage);
let compiler_priority = CompilePriority::from_client_env(client_env.as_deref());
let result = super::super::process::tokio_command_output_with_priority_stdin(
&mut cmd,
compiler_priority,
if stdin_bytes.is_empty() {
None
} else {
Some(stdin_bytes)
},
)
.await;
match result {
Ok(output) => {
let exit_code = output.status.code().unwrap_or(-1);
write_session_log(sessions, sid, &format!("[DIRECT] exit_code={exit_code}"));
Response::CompileResult {
exit_code,
stdout: Arc::new(output.stdout),
stderr: Arc::new(output.stderr),
cached: false,
}
}
Err(e) => Response::Error {
message: format!("failed to run compiler: {e}"),
},
}
}