use anyhow::{bail, Context, Result};
use std::path::{Path, PathBuf};
use super::manifest::generate_manifest;
use super::types::{CompileOptions, CompileResult};
use super::validation::{
validate_cargo_toml, validate_cloacina_compatibility, validate_packaged_workflow_presence,
validate_rust_crate_structure, validate_rust_version_compatibility,
};
pub fn compile_workflow(
project_path: PathBuf,
output: PathBuf,
options: CompileOptions,
) -> Result<CompileResult> {
validate_rust_crate_structure(&project_path)?;
let cargo_toml = validate_cargo_toml(&project_path)?;
validate_cloacina_compatibility(&cargo_toml)?;
validate_packaged_workflow_presence(&project_path)?;
validate_rust_version_compatibility(&cargo_toml)?;
let so_path = execute_cargo_build(&project_path, &options)?;
let manifest = generate_manifest(&cargo_toml, &so_path, &options.target, &project_path)?;
copy_output_file(&so_path, &output)?;
Ok(CompileResult {
so_path: output,
manifest,
})
}
fn execute_cargo_build(project_path: &PathBuf, options: &CompileOptions) -> Result<PathBuf> {
let mut cmd = std::process::Command::new("cargo");
cmd.arg("build").arg("--lib").current_dir(project_path);
if options.profile == "release" {
cmd.arg("--release");
}
if let Some(target_triple) = &options.target {
cmd.arg("--target").arg(target_triple);
}
if let Some(jobs) = options.jobs {
cmd.arg("--jobs").arg(jobs.to_string());
}
for flag in &options.cargo_flags {
cmd.arg(flag);
}
let output = cmd
.output()
.context("Failed to execute cargo build. Is cargo installed?")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
bail!(
"Cargo build failed with exit code {:?}\n\nSTDOUT:\n{}\n\nSTDERR:\n{}",
output.status.code(),
stdout,
stderr
);
}
find_compiled_library(project_path, &options.target, &options.profile)
}
fn find_compiled_library(
project_path: &Path,
target: &Option<String>,
profile: &str,
) -> Result<PathBuf> {
let target_dir = project_path.join("target");
let build_dir = if let Some(target_triple) = target {
target_dir.join(target_triple).join(profile)
} else {
target_dir.join(profile)
};
if !build_dir.exists() {
bail!("Build directory not found: {:?}", build_dir);
}
let extensions = if cfg!(target_os = "windows") {
vec!["dll"]
} else {
vec!["so", "dylib"]
};
for extension in &extensions {
for entry in std::fs::read_dir(&build_dir)
.with_context(|| format!("Failed to read build directory: {:?}", build_dir))?
{
let entry = entry?;
let path = entry.path();
if let Some(ext) = path.extension() {
if ext == *extension {
return Ok(path);
}
}
}
}
bail!(
"No compiled library found in build directory: {:?}\n\
Expected files with extensions: {:?}",
build_dir,
extensions
);
}
fn copy_output_file(source: &PathBuf, destination: &PathBuf) -> Result<()> {
if let Some(parent) = destination.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("Failed to create output directory: {:?}", parent))?;
}
std::fs::copy(source, destination)
.with_context(|| format!("Failed to copy {:?} to {:?}", source, destination))?;
Ok(())
}