use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::compiler_contract::contract::{BuildRequestArgs, CompilerBackend};
use crate::error::{DealerError, DealerResult};
use crate::project::ProjectRoot;
pub(crate) struct ExecutableCompilerBackend {
pub(crate) compiler_path: PathBuf,
impl ExecutableCompilerBackend {
fn run_compiler_protocol(
deps: Option<&HashMap<String, PathBuf>>,
build_args: Option<BuildRequestArgs>,
let protocol_dir = project_root
.join(crate::constants::dirs::PROJECT_DEALER_DIR)
.join(crate::constants::dirs::PROJECT_XTAZY_DIR)
.join(crate::constants::protocol::PROTOCOL_COMPILER_DIR);
let request_dir = protocol_dir.join(crate::constants::protocol::PROTOCOL_REQUEST_DIR);
let result_dir = protocol_dir.join(crate::constants::protocol::PROTOCOL_RESULT_DIR);
if request_dir.exists() {
fs::remove_dir_all(&request_dir).ok();
fs::remove_dir_all(&result_dir).ok();
fs::create_dir_all(&request_dir).map_err(|e| DealerError::io(&request_dir, e))?;
request_dir.join(crate::constants::protocol::PROTOCOL_MODE),
request_dir.join(crate::constants::protocol::PROTOCOL_MODE),
request_dir.join(crate::constants::protocol::PROTOCOL_ENTRY_FILE),
format!("{}\n", entry_file.display()),
request_dir.join(crate::constants::protocol::PROTOCOL_ENTRY_FILE),
request_dir.join(crate::constants::protocol::PROTOCOL_PROJECT_ROOT),
format!("{}\n", project_root.display()),
request_dir.join(crate::constants::protocol::PROTOCOL_PROJECT_ROOT),
request_dir.join(crate::constants::protocol::PROTOCOL_PROJECT_NAME),
format!("{}\n", project_name),
request_dir.join(crate::constants::protocol::PROTOCOL_PROJECT_NAME),
request_dir.join(crate::constants::protocol::PROTOCOL_COLOR),
request_dir.join(crate::constants::protocol::PROTOCOL_COLOR),
if let Some(deps_map) = deps {
let mut tsv_content = String::new();
// Sort keys to ensure deterministic output for testing/debugging
let mut keys: Vec<&String> = deps_map.keys().collect();
let path = &deps_map[name];
tsv_content.push_str(&format!("{}\t{}\n", name, path.display()));
request_dir.join(crate::constants::protocol::PROTOCOL_RESOLVED_PACKAGES),
request_dir.join(crate::constants::protocol::PROTOCOL_RESOLVED_PACKAGES),
if let Some(bargs) = build_args {
request_dir.join(crate::constants::protocol::PROTOCOL_RUST_OUTPUT_DIR),
format!("{}\n", bargs.rust_output_dir.display()),
request_dir.join(crate::constants::protocol::PROTOCOL_RUST_OUTPUT_DIR),
request_dir.join(crate::constants::protocol::PROTOCOL_GENERATED_PACKAGE_NAME),
format!("{}\n", bargs.generated_package_name),
request_dir.join(crate::constants::protocol::PROTOCOL_GENERATED_PACKAGE_NAME),
request_dir.join(crate::constants::protocol::PROTOCOL_RUSTTIME_PATH),
format!("{}\n", bargs.rusttime_path.display()),
request_dir.join(crate::constants::protocol::PROTOCOL_RUSTTIME_PATH),
let output = Command::new(&self.compiler_path)
.map_err(|e| DealerError::Compiler(format!("failed to start πko process: {e}")))?;
let status_file = result_dir.join(crate::constants::protocol::PROTOCOL_STATUS);
let diagnostics_file = result_dir.join(crate::constants::protocol::PROTOCOL_DIAGNOSTICS);
if !status_file.is_file() {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
return Err(DealerError::Compiler(format!(
"πko exited with status {}\nstdout:\n{}\nstderr:\n{}",
output.status, stdout, stderr
return Err(DealerError::Compiler(
"πko exited without writing result/status file".to_string(),
let status = fs::read_to_string(&status_file)
.map_err(|e| DealerError::io(&status_file, e))?
let diagnostics = if diagnostics_file.is_file() {
fs::read_to_string(&diagnostics_file).unwrap_or_default()
if !diagnostics.is_empty() {
print!("{}", diagnostics);
if status == crate::constants::protocol::STATUS_OK
|| status == crate::constants::protocol::STATUS_WARNING
} else if status == crate::constants::protocol::STATUS_ERROR {
Err(DealerError::Compiler("πko reported errors".to_string()))
Err(DealerError::Compiler(format!(
"πko returned unknown status: '{status}'"
impl CompilerBackend for ExecutableCompilerBackend {
fn check(&self, project: &ProjectRoot, deps: &HashMap<String, PathBuf>) -> DealerResult<()> {
self.run_compiler_protocol(
deps: &HashMap<String, PathBuf>,
let build_args = BuildRequestArgs {
rust_output_dir: output_dir.to_path_buf(),
generated_package_name: crate::names::sanitize_package_name(&project.project_name),
rusttime_path: rusttime_path.to_path_buf(),
self.run_compiler_protocol(
fn test(&self, project: &ProjectRoot, deps: &HashMap<String, PathBuf>) -> DealerResult<()> {
self.run_compiler_protocol(
let mode = if check { "fmt-check" } else { "fmt" };
self.run_compiler_protocol(project_root, mode, entry_file, project_name, None, None)