Skip to main content

batuta/pipeline/stages/
build.rs

1//! Build stage - compiles to final binary.
2
3use anyhow::{Context as AnyhowContext, Result};
4
5#[cfg(feature = "native")]
6use tracing::info;
7
8// Stub macros for WASM build
9#[cfg(not(feature = "native"))]
10macro_rules! info {
11    ($($arg:tt)*) => {{}};
12}
13
14use crate::pipeline::types::{PipelineContext, PipelineStage, ValidationResult};
15
16/// Build stage - compiles to final binary
17pub struct BuildStage {
18    pub(crate) release: bool,
19    pub(crate) target: Option<String>,
20    pub(crate) wasm: bool,
21}
22
23impl BuildStage {
24    pub fn new(release: bool, target: Option<String>, wasm: bool) -> Self {
25        Self { release, target, wasm }
26    }
27}
28
29#[async_trait::async_trait]
30impl PipelineStage for BuildStage {
31    fn name(&self) -> &'static str {
32        "Build"
33    }
34
35    async fn execute(&self, mut ctx: PipelineContext) -> Result<PipelineContext> {
36        info!("Building Rust project (release: {})", self.release);
37
38        // Check if Cargo.toml exists in output directory
39        let cargo_toml = ctx.output_path.join("Cargo.toml");
40        if !cargo_toml.exists() {
41            anyhow::bail!("No Cargo.toml found in output directory");
42        }
43
44        // Build cargo arguments
45        let mut args = vec!["build"];
46
47        if self.release {
48            args.push("--release");
49        }
50
51        if let Some(target) = &self.target {
52            args.push("--target");
53            args.push(target);
54        }
55
56        // For WASM, use special target
57        if self.wasm {
58            args.push("--target");
59            args.push("wasm32-unknown-unknown");
60        }
61
62        // Run cargo build
63        let output = std::process::Command::new("cargo")
64            .args(&args)
65            .current_dir(&ctx.output_path)
66            .output()
67            .context("Failed to run cargo build")?;
68
69        if !output.status.success() {
70            let stderr = String::from_utf8_lossy(&output.stderr);
71            anyhow::bail!("Cargo build failed: {}", stderr);
72        }
73
74        ctx.metadata.insert(
75            "build_mode".to_string(),
76            serde_json::json!(if self.release { "release" } else { "debug" }),
77        );
78
79        if self.wasm {
80            ctx.metadata.insert("wasm_build".to_string(), serde_json::json!(true));
81        }
82
83        Ok(ctx)
84    }
85
86    fn validate(&self, ctx: &PipelineContext) -> Result<ValidationResult> {
87        // Check that build artifacts exist
88        let build_dir = if self.release {
89            ctx.output_path.join("target/release")
90        } else {
91            ctx.output_path.join("target/debug")
92        };
93
94        let passed = build_dir.exists();
95
96        Ok(ValidationResult {
97            stage: self.name().to_string(),
98            passed,
99            message: if passed {
100                "Build artifacts found".to_string()
101            } else {
102                "Build directory not found".to_string()
103            },
104            details: None,
105        })
106    }
107}