use std::io::Write;
use std::os::unix::process::ExitStatusExt;
use std::path::Path;
use std::process::Command;
use cairo_lang_starknet_classes::contract_class::ContractClass;
use tempfile::NamedTempFile;
use crate::errors::CompilationUtilError;
use crate::resource_limits::ResourceLimits;
#[cfg(test)]
#[path = "compiler_utils_test.rs"]
pub mod test;
pub fn compile_with_args(
compiler_binary_path: &Path,
contract_class: ContractClass,
additional_args: &[&str],
resource_limits: ResourceLimits,
) -> Result<Vec<u8>, CompilationUtilError> {
let serialized_contract_class = serde_json::to_string(&contract_class)?;
let mut temp_file = NamedTempFile::new()?;
temp_file.write_all(serialized_contract_class.as_bytes())?;
let temp_file_path = temp_file.path().to_str().ok_or(CompilationUtilError::UnexpectedError(
"Failed to get temporary file path".to_owned(),
))?;
let mut command = Command::new(compiler_binary_path.as_os_str());
command.arg(temp_file_path).args(additional_args);
resource_limits.apply(&mut command);
let compile_output = command.output()?;
if !compile_output.status.success() {
let stderr_output = String::from_utf8(compile_output.stderr)
.unwrap_or_else(|_| "Failed to decode stderr output".to_string());
let error_message = format_compiler_error(&stderr_output, &compile_output.status);
return Err(CompilationUtilError::CompilationError(error_message));
}
Ok(compile_output.stdout)
}
fn extract_error_from_stderr(stderr: &str) -> String {
let meaningful_lines: Vec<&str> = stderr
.lines()
.filter(|line| {
let trimmed = line.trim();
if trimmed.starts_with("Setting Resource::") {
return false;
}
if trimmed.eq_ignore_ascii_case("stack backtrace:")
|| trimmed.starts_with("Signal info:")
{
return false;
}
if let Some(rest) = trimmed.strip_prefix(|c: char| c.is_ascii_digit()) {
let rest = rest.trim_start_matches(|c: char| c.is_ascii_digit());
if rest.starts_with(':') {
return false;
}
}
!trimmed.is_empty()
})
.collect();
if meaningful_lines.is_empty() {
return "Compilation failed with no meaningful error output".to_string();
}
meaningful_lines.join("\n")
}
pub(crate) fn format_compiler_error(stderr: &str, status: &std::process::ExitStatus) -> String {
let error_detail = extract_error_from_stderr(stderr);
let signal_description = match status.signal() {
Some(9) => Some(
"process was killed (SIGKILL), possibly due to exceeding CPU or memory limits"
.to_string(),
),
Some(25) => Some("file size limit exceeded (SIGXFSZ)".to_string()),
Some(signal) => Some(format!("process terminated by signal {signal}")),
None => None,
};
match signal_description {
Some(signal) => format!("{error_detail} ({signal})"),
None => error_detail,
}
}