rialo-build-lib 0.11.2

Shared library for Rialo program building logic
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Venus workflow build support
//!
//! This module provides build integration for Venus DSL workflows, including:
//! - WIT file detection and validation
//! - Automatic WIT file copying to output directory
//! - Build artifact management

use std::path::{Path, PathBuf};

use anyhow::{Context, Result};

/// Build a Venus workflow project
///
/// This function handles the complete build process for Venus workflows:
/// 1. Validates the project structure
/// 2. Builds the Rust code (which generates WIT files automatically)
/// 3. Locates the generated WIT file
/// 4. Optionally validates WIT syntax
/// 5. Copies WIT to output directory
pub fn build_venus_workflow(project_path: &Path, output_dir: &Path) -> Result<()> {
    println!("🔧 Building Venus workflow...");

    // Validate project path
    super::validate_program_path(project_path)?;

    // Find generated WIT file
    println!("   Step 1: Looking for WIT file...");
    let wit_file = find_wit_file(project_path)?;
    println!("   ✅ Found WIT: {}", wit_file.display());

    // Copy WIT to output directory
    println!("   Step 2: Copying WIT to output...");
    let output_wit = copy_wit_to_output(&wit_file, output_dir)?;
    println!("   ✅ Copied WIT to: {}", output_wit.display());

    println!("✅ Venus workflow build complete");

    Ok(())
}

/// Find the WIT file generated by Venus macro
///
/// Searches multiple locations in priority order:
/// 1. `wit/` directory (primary location)
/// 2. `interface.wit` (legacy location)
/// 3. `target/wit/` directory (alternative location)
fn find_wit_file(project_path: &Path) -> Result<PathBuf> {
    // Try wit/ directory first (most common)
    let wit_dir = project_path.join("wit");
    if wit_dir.is_dir() {
        for entry in std::fs::read_dir(&wit_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.extension().and_then(|s| s.to_str()) == Some("wit") {
                return Ok(path);
            }
        }
    }

    // Try interface.wit (legacy)
    let interface_wit = project_path.join("interface.wit");
    if interface_wit.exists() && interface_wit.is_file() {
        return Ok(interface_wit);
    }

    // Try target/wit/ directory
    let target_wit_dir = project_path.join("target/wit");
    if target_wit_dir.is_dir() {
        for entry in std::fs::read_dir(&target_wit_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.extension().and_then(|s| s.to_str()) == Some("wit") {
                return Ok(path);
            }
        }
    }

    anyhow::bail!(
        "No WIT file found in {}. Venus macro may not have generated it.\n\
         Ensure your workflow uses the rialo! macro and runs `cargo build` first.",
        project_path.display()
    )
}

/// Copy WIT file to output directory
///
/// Creates the output directory if it doesn't exist and copies the WIT file,
/// preserving the original filename.
fn copy_wit_to_output(wit_file: &Path, output_dir: &Path) -> Result<PathBuf> {
    // Create output directory
    std::fs::create_dir_all(output_dir).with_context(|| {
        format!(
            "Failed to create output directory: {}",
            output_dir.display()
        )
    })?;

    // Determine output path
    let wit_filename = wit_file
        .file_name()
        .ok_or_else(|| anyhow::anyhow!("WIT file has no filename"))?;
    let output_wit = output_dir.join(wit_filename);

    // Copy file
    std::fs::copy(wit_file, &output_wit)
        .with_context(|| format!("Failed to copy WIT file to {}", output_wit.display()))?;

    Ok(output_wit)
}

/// Detect if a project is a Venus workflow
///
/// Checks for indicators that a project uses Venus DSL:
/// - Depends on `rialo-venus-proc-macro`
/// - Has a `wit/` directory
/// - Uses `rialo!` macro in source files
pub fn is_venus_workflow(project_path: &Path) -> bool {
    // Check Cargo.toml for rialo-venus-proc-macro dependency
    let cargo_toml = project_path.join("Cargo.toml");
    if cargo_toml.exists() {
        if let Ok(contents) = std::fs::read_to_string(&cargo_toml) {
            if contents.contains("rialo-venus-proc-macro")
                || contents.contains("rialo_venus_proc_macro")
            {
                return true;
            }
        }
    }

    // Check for wit/ directory (generated by Venus)
    let wit_dir = project_path.join("wit");
    if wit_dir.exists() && wit_dir.is_dir() {
        return true;
    }

    // Check source files for rialo! macro usage
    let src_dir = project_path.join("src");
    if src_dir.exists() && src_dir.is_dir() {
        if let Ok(entries) = std::fs::read_dir(&src_dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                if path.extension().and_then(|s| s.to_str()) == Some("rs") {
                    if let Ok(contents) = std::fs::read_to_string(&path) {
                        if contents.contains("rialo!") {
                            return true;
                        }
                    }
                }
            }
        }
    }

    false
}

#[cfg(test)]
mod tests {
    use std::fs;

    use tempfile::TempDir;

    use super::*;

    #[test]
    fn test_find_wit_file_in_wit_dir() {
        let temp = TempDir::new().unwrap();
        let wit_dir = temp.path().join("wit");
        fs::create_dir(&wit_dir).unwrap();
        let wit_file = wit_dir.join("test.wit");
        fs::write(&wit_file, "package test;").unwrap();

        let found = find_wit_file(temp.path()).unwrap();
        assert_eq!(found, wit_file);
    }

    #[test]
    fn test_find_wit_file_not_found() {
        let temp = TempDir::new().unwrap();
        let result = find_wit_file(temp.path());
        assert!(result.is_err());
    }

    #[test]
    fn test_is_venus_workflow_with_dependency() {
        let temp = TempDir::new().unwrap();
        let cargo_toml = temp.path().join("Cargo.toml");
        fs::write(
            &cargo_toml,
            "[dependencies]\nrialo-venus-proc-macro = \"0.1\"\n",
        )
        .unwrap();

        assert!(is_venus_workflow(temp.path()));
    }

    #[test]
    fn test_is_venus_workflow_with_wit_dir() {
        let temp = TempDir::new().unwrap();
        let wit_dir = temp.path().join("wit");
        fs::create_dir(&wit_dir).unwrap();

        assert!(is_venus_workflow(temp.path()));
    }

    #[test]
    fn test_is_venus_workflow_false() {
        let temp = TempDir::new().unwrap();
        assert!(!is_venus_workflow(temp.path()));
    }
}