dampen_cli/commands/
build.rs

1#![allow(clippy::print_stderr, clippy::print_stdout)]
2
3//! Build command - generates production Rust code from Dampen UI files
4
5use std::path::Path;
6
7/// Build command arguments
8#[derive(clap::Args)]
9pub struct BuildArgs {
10    /// Input directory containing .dampen files (default: ui/)
11    #[arg(short, long, default_value = "ui")]
12    input: String,
13
14    /// Output file for generated code (default: src/ui_generated.rs)
15    #[arg(short, long, default_value = "src/ui_generated.rs")]
16    output: String,
17
18    /// Model struct name (default: Model)
19    #[arg(long, default_value = "Model")]
20    model: String,
21
22    /// Message enum name (default: Message)
23    #[arg(long, default_value = "Message")]
24    message: String,
25
26    /// Verbose output
27    #[arg(short, long)]
28    verbose: bool,
29
30    /// Package to build (if workspace has multiple packages)
31    #[arg(short, long)]
32    package: Option<String>,
33
34    /// Additional features to enable (beyond codegen)
35    #[arg(long, value_delimiter = ',')]
36    features: Vec<String>,
37
38    /// Build in release mode with codegen
39    #[arg(long)]
40    release: bool,
41}
42
43/// Execute the build command
44///
45/// Builds the application:
46/// - Debug mode (default): Interpreted mode with hot-reload support
47/// - Release mode (--release): Codegen mode with full optimizations
48///
49/// # Mode Behavior
50///
51/// - **Debug Mode** (default): Interpreted mode, fast iteration for development
52/// - **Release Mode** (--release): Codegen mode with zero runtime overhead
53///
54/// # Examples
55///
56/// ```bash
57/// # Debug build (interpreted)
58/// dampen build
59///
60/// # Release build (codegen optimized)
61/// dampen build --release
62///
63/// # Build specific package
64/// dampen build -p my-app
65///
66/// # Enable additional features
67/// dampen build --features tokio,logging
68/// ```
69pub fn execute(args: &BuildArgs) -> Result<(), String> {
70    execute_production_build(args)
71}
72
73fn execute_production_build(args: &BuildArgs) -> Result<(), String> {
74    use std::process::Command;
75
76    let mode = if args.release {
77        "codegen"
78    } else {
79        "interpreted"
80    };
81
82    if args.verbose {
83        eprintln!("Running {} build...", mode);
84    }
85
86    // Check build.rs only for codegen mode
87    // If package is specified, also check in examples/ directory
88    let build_rs_exists = Path::new("build.rs").exists()
89        || args
90            .package
91            .as_ref()
92            .is_some_and(|pkg| Path::new("examples").join(pkg).join("build.rs").exists());
93
94    if args.release && !build_rs_exists {
95        return Err(
96            "build.rs not found. Codegen mode requires build.rs for code generation.\n\
97             Tip: Use 'dampen build' (without --release) for interpreted mode,\n\
98             or ensure you're in the correct project directory."
99                .to_string(),
100        );
101    }
102
103    if !Path::new("Cargo.toml").exists() {
104        return Err("Cargo.toml not found. Are you in a Rust project directory?".to_string());
105    }
106
107    let mut cmd = Command::new("cargo");
108    cmd.arg("build");
109
110    if let Some(ref package) = args.package {
111        cmd.arg("-p").arg(package);
112    }
113
114    if args.verbose {
115        cmd.arg("--verbose");
116    }
117
118    // Build features list based on mode
119    let all_features = if args.release {
120        // Release mode: codegen with optimizations
121        cmd.arg("--release");
122        cmd.arg("--no-default-features");
123        let mut features = vec!["codegen".to_string()];
124        features.extend(args.features.clone());
125        features
126    } else {
127        // Debug mode: interpreted (NEW BEHAVIOR)
128        let mut features = vec!["interpreted".to_string()];
129        features.extend(args.features.clone());
130        features
131    };
132
133    cmd.arg("--features").arg(all_features.join(","));
134
135    if args.verbose {
136        let features_str = all_features.join(",");
137        let cargo_cmd = if args.release {
138            format!(
139                "cargo build --release --no-default-features --features {}",
140                features_str
141            )
142        } else {
143            format!("cargo build --features {}", features_str)
144        };
145        eprintln!("Executing: {}", cargo_cmd);
146    }
147
148    let status = cmd
149        .status()
150        .map_err(|e| format!("Failed to execute cargo: {}", e))?;
151
152    if !status.success() {
153        return Err("Build failed".to_string());
154    }
155
156    if args.verbose {
157        let output_dir = if args.release {
158            "target/release/"
159        } else {
160            "target/debug/"
161        };
162        eprintln!("Build successful! Binary is in {}", output_dir);
163    }
164
165    if args.release {
166        eprintln!("Release build (codegen) completed successfully!");
167    } else {
168        eprintln!("Debug build (interpreted) completed successfully!");
169        eprintln!(
170            "Use 'dampen build --release' or 'dampen release' for optimized production builds."
171        );
172    }
173
174    Ok(())
175}
176
177/// Execute build command with release mode enabled
178/// This is a helper function for the release command
179pub fn execute_release_build(
180    package: Option<String>,
181    features: Vec<String>,
182    verbose: bool,
183    target_dir: Option<String>,
184) -> Result<(), String> {
185    use std::process::Command;
186
187    let mode = "codegen";
188
189    if verbose {
190        eprintln!("Running {} build...", mode);
191    }
192
193    // Check build.rs only for codegen mode
194    // If package is specified, also check in examples/ directory
195    let build_rs_exists = Path::new("build.rs").exists()
196        || package
197            .as_ref()
198            .is_some_and(|pkg| Path::new("examples").join(pkg).join("build.rs").exists());
199
200    if !build_rs_exists {
201        return Err(
202            "build.rs not found. Codegen mode requires build.rs for code generation.\n\
203             Tip: Use 'dampen build' (without --release) for interpreted mode,\n\
204             or ensure you're in the correct project directory."
205                .to_string(),
206        );
207    }
208
209    if !Path::new("Cargo.toml").exists() {
210        return Err("Cargo.toml not found. Are you in a Rust project directory?".to_string());
211    }
212
213    let mut cmd = Command::new("cargo");
214    cmd.arg("build");
215    cmd.arg("--release");
216
217    if let Some(ref pkg) = package {
218        cmd.arg("-p").arg(pkg);
219    }
220
221    if let Some(ref dir) = target_dir {
222        cmd.arg("--target-dir").arg(dir);
223    }
224
225    if verbose {
226        cmd.arg("--verbose");
227    }
228
229    // Release mode: codegen with optimizations
230    cmd.arg("--no-default-features");
231    let mut all_features = vec!["codegen".to_string()];
232    all_features.extend(features);
233    cmd.arg("--features").arg(all_features.join(","));
234
235    if verbose {
236        let features_str = all_features.join(",");
237        eprintln!(
238            "Executing: cargo build --release --no-default-features --features {}",
239            features_str
240        );
241    }
242
243    let status = cmd
244        .status()
245        .map_err(|e| format!("Failed to execute cargo: {}", e))?;
246
247    if !status.success() {
248        return Err("Build failed".to_string());
249    }
250
251    if verbose {
252        eprintln!("Build successful! Binary is in target/release/");
253    }
254
255    eprintln!("Release build (codegen) completed successfully!");
256
257    Ok(())
258}