use super::super::super::*;
pub(super) struct CompileExecRequest<'a> {
pub(super) state_arc: &'a Arc<SharedState>,
pub(super) compiler: &'a NormalizedPath,
pub(super) effective_args: &'a [String],
pub(super) cwd: &'a Path,
pub(super) cwd_path: &'a NormalizedPath,
pub(super) source_path: &'a NormalizedPath,
pub(super) output_path: &'a NormalizedPath,
pub(super) compilation: &'a crate::compiler::CacheableCompilation,
pub(super) dep_flags: &'a UserDepFlags,
pub(super) rustc_args_opt: Option<&'a crate::depgraph::RustcParsedArgs>,
pub(super) rustc_extern_paths: &'a [NormalizedPath],
pub(super) is_rustc: bool,
pub(super) client_env: &'a Option<Vec<(String, String)>>,
pub(super) lineage: &'a crate::daemon::lineage::Lineage,
pub(super) compile_start: std::time::Instant,
pub(super) snap_clock: Clock,
}
pub(super) struct CompileExecOutcome {
pub(super) exit_code: i32,
pub(super) stdout: Arc<Vec<u8>>,
pub(super) stderr: Arc<Vec<u8>>,
pub(super) depfile_strategy: DepfileStrategy,
pub(super) show_includes_scan: Option<crate::depgraph::ScanResult>,
pub(super) pre_hash_task: Option<tokio::task::JoinHandle<HashMap<NormalizedPath, ContentHash>>>,
pub(super) compiler_priority_decision: crate::daemon::process::CompilePriorityDecision,
pub(super) pre_exec_ns: u64,
pub(super) break_outputs_ns: u64,
pub(super) compiler_process_ns: u64,
pub(super) compiler_exec_ns: u64,
pub(super) compiler_prep_ns: u64,
pub(super) post_exec_ns: u64,
}
pub(super) enum CompileExecResult {
Ok(CompileExecOutcome),
Error(Response),
}
pub(super) async fn run_compile_exec(req: CompileExecRequest<'_>) -> CompileExecResult {
let CompileExecRequest {
state_arc,
compiler,
effective_args,
cwd,
cwd_path,
source_path,
output_path,
compilation,
dep_flags,
rustc_args_opt,
rustc_extern_paths,
is_rustc,
client_env,
lineage,
compile_start,
snap_clock,
} = req;
let state = state_arc.as_ref();
let pre_exec_ns = compile_start.elapsed().as_nanos() as u64;
let t_exec = std::time::Instant::now();
let supports_depfile = compilation.family.supports_depfile();
let (mut extra_args, mut depfile_strategy) = crate::depgraph::depfile::prepare_depfile(
supports_depfile,
dep_flags,
output_path,
&state.depfile_tmpdir,
);
if compilation.family == crate::compiler::CompilerFamily::Msvc
&& depfile_strategy == DepfileStrategy::Unsupported
{
if !dep_flags.has_md {
extra_args.push("/showIncludes".to_string());
}
depfile_strategy = DepfileStrategy::ShowIncludes;
}
let combined_args;
let rsp_args: &[String] = if extra_args.is_empty() {
effective_args
} else {
combined_args = [effective_args, extra_args.as_slice()].concat();
&combined_args
};
let _rsp_guard = match crate::compiler::response_file::write_response_file_if_needed(
rsp_args,
&state.depfile_tmpdir,
compilation.family,
) {
Ok(guard) => guard,
Err(e) => {
return CompileExecResult::Error(Response::Error {
message: format!("failed to write response file: {e}"),
});
}
};
let output_paths = if let Some(rustc_args) = rustc_args_opt {
rustc_expected_output_paths(rustc_args, output_path, cwd_path)
} else {
vec![output_path.clone()]
};
let t_break_outputs = std::time::Instant::now();
for path in &output_paths {
if let Err(e) = break_output_hardlink_before_compile(path) {
return CompileExecResult::Error(Response::Error {
message: format!(
"failed to detach hardlinked output before compile {}: {e}",
path.display()
),
});
}
}
let break_outputs_ns = t_break_outputs.elapsed().as_nanos() as u64;
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(effective_args).current_dir(cwd);
if !extra_args.is_empty() {
cmd.args(&extra_args);
}
}
apply_client_env(&mut cmd, client_env, lineage);
let t_compiler_process = std::time::Instant::now();
let is_link_like = rustc_args_opt
.is_some_and(|rustc_args| rustc_args.emit_types.iter().any(|emit| emit == "link"));
let compiler_priority =
CompilePriority::from_client_env_for_link_like(client_env.as_deref(), is_link_like);
let compiler_priority_decision = compiler_priority.resolve_for_current_load();
let pre_hash_task: Option<tokio::task::JoinHandle<HashMap<NormalizedPath, ContentHash>>> =
if is_rustc && !rustc_extern_paths.is_empty() {
let pre_state = Arc::clone(state_arc);
let pre_source = source_path.clone();
let pre_externs: Vec<NormalizedPath> = rustc_extern_paths.to_vec();
let pre_clock = snap_clock;
Some(tokio::task::spawn_blocking(move || {
use rayon::prelude::*;
let all_paths: Vec<&NormalizedPath> = std::iter::once(&pre_source)
.chain(pre_externs.iter())
.collect();
all_paths
.par_iter()
.filter_map(|path| {
let hash_path = resolve_pch_source(path, &pre_state.pch_source_map)
.unwrap_or_else(|| (*path).clone());
hash_file(&pre_state.cache_system, &hash_path, pre_clock)
.ok()
.map(|h| ((*path).clone(), h))
})
.collect()
}))
} else {
None
};
let result = crate::daemon::process::tokio_command_output_with_priority(
&mut cmd,
compiler_priority_decision.effective,
)
.await;
let compiler_process_ns = t_compiler_process.elapsed().as_nanos() as u64;
let output = match result {
Ok(o) => o,
Err(e) => {
return CompileExecResult::Error(Response::Error {
message: format!("failed to run compiler: {e}"),
});
}
};
let compiler_exec_ns = t_exec.elapsed().as_nanos() as u64;
let compiler_prep_ns = compiler_exec_ns.saturating_sub(compiler_process_ns);
let t_post_exec = std::time::Instant::now();
let exit_code = output.status.code().unwrap_or(-1);
let stdout = Arc::new(output.stdout);
let (show_includes_scan, stderr_bytes) = if depfile_strategy == DepfileStrategy::ShowIncludes {
let (scan, filtered) = crate::depgraph::show_includes::parse_show_includes(
&output.stderr,
source_path,
cwd_path,
);
(Some(scan), filtered)
} else {
(None, output.stderr)
};
let stderr = Arc::new(stderr_bytes);
let post_exec_ns = t_post_exec.elapsed().as_nanos() as u64;
drop(_rsp_guard);
CompileExecResult::Ok(CompileExecOutcome {
exit_code,
stdout,
stderr,
depfile_strategy,
show_includes_scan,
pre_hash_task,
compiler_priority_decision,
pre_exec_ns,
break_outputs_ns,
compiler_process_ns,
compiler_exec_ns,
compiler_prep_ns,
post_exec_ns,
})
}