use crate::cfg::config::Config; use std::{fs::create_dir_all, path::Path, process::Command}; use num_cpus; use super::archive::ArchiveOperations; use super::types::{Build, BuildSystems, Package}; pub trait BuildOperations { fn execute_build(&self, build_meta: &Build) -> Result<(), std::io::Error>; fn build(&mut self) -> Result; } #[allow(dead_code)] impl BuildOperations for Package { /// Execute the build script for the package. /// /// This function takes the `Build` meta information as an argument and /// executes the build script accordingly. It also handles the different /// build systems supported (Make, CMake, Meson, Cargo). /// /// # Errors /// /// Returns an error if the build command fails. fn execute_build(&self, build_meta: &Build) -> Result<(), std::io::Error> { let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?; let build_dir = Path::new(&config.paths.cache_dir).join(format!("{}-{}", self.name, self.version)); // Check if build directory exists if !build_dir.exists() { return Err(std::io::Error::other(format!( "Build directory not found: {}", build_dir.display() ))); } // Prepare environment variables let mut cmd_envs: Vec<(String, String)> = Vec::new(); if let Some(ref env_vars) = build_meta.env { for env_line in env_vars.lines() { if let Some((key, value)) = env_line.split_once('=') { cmd_envs.push((key.trim().to_string(), value.trim().to_string())); } } } // Handle custom build script if provided if let Some(ref script) = build_meta.script { log::info!("Executing custom build script: {}", script); Self::validate_custom_script(script)?; let mut cmd = if script.starts_with("./") || script.contains('/') { // Assume it's a file path let script_path = build_dir.join(script); if !script_path.exists() { return Err(std::io::Error::other(format!( "Custom script file not found: {}", script_path.display() ))); } let mut inner_cmd = Command::new("/bin/sh"); inner_cmd.arg("-c"); inner_cmd.arg(script_path.to_str().unwrap()); inner_cmd } else { // Inline script let mut inner_cmd = Command::new("/bin/sh"); inner_cmd.arg("-c"); inner_cmd.arg(script); inner_cmd }; cmd.current_dir(&build_dir); for (key, value) in &cmd_envs { cmd.env(key, value); } let output = cmd .output() .map_err(|e| std::io::Error::other(format!("Custom build script failed: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); return Err(std::io::Error::other(format!( "Custom build script failed:\nStdout: {}\nStderr: {}", stdout, stderr ))); } log::info!( "Custom build script completed successfully for package: {}", self.name ); return Ok(()); } // No custom script, proceed with build system match build_meta.build_system { BuildSystems::Make => { // Check for Makefile let makefile_path = build_dir.join("Makefile"); if !makefile_path.exists() { return Err(std::io::Error::other(format!( "Makefile not found: {}", makefile_path.display() ))); } let mut cmd = Command::new("make"); cmd.current_dir(&build_dir); cmd.arg("all"); for (key, value) in &cmd_envs { cmd.env(key, value); } log::info!("Running Make build: {:?}", cmd); let output = cmd .output() .map_err(|e| std::io::Error::other(format!("Make build failed: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); return Err(std::io::Error::other(format!( "Make build failed:\nStdout: {}\nStderr: {}", stdout, stderr ))); } } BuildSystems::CMake => { // Check for CMakeLists.txt let cmake_file = build_dir.join("CMakeLists.txt"); if !cmake_file.exists() { return Err(std::io::Error::other(format!( "CMakeLists.txt not found: {}", cmake_file.display() ))); } let build_dir_build = build_dir.join("build"); create_dir_all(&build_dir_build)?; let mut config_cmd = Command::new("cmake"); config_cmd .arg("-S") .arg(&build_dir) .arg("-B") .arg(&build_dir_build) .current_dir(&build_dir); for (key, value) in &cmd_envs { config_cmd.env(key, value); } log::info!("Running CMake configuration: {:?}", config_cmd); let config_output = config_cmd .output() .map_err(|e| std::io::Error::other(format!("CMake config failed: {}", e)))?; if !config_output.status.success() { let stderr = String::from_utf8_lossy(&config_output.stderr); return Err(std::io::Error::other(format!( "CMake config failed:\n{}", stderr ))); } // Now build let mut build_cmd = Command::new("make"); build_cmd.current_dir(&build_dir_build); build_cmd.arg("-j").arg(num_cpus::get().to_string()); // Parallel build for (key, value) in &cmd_envs { build_cmd.env(key, value); } log::info!("Running CMake build: {:?}", build_cmd); let build_output = build_cmd .output() .map_err(|e| std::io::Error::other(format!("CMake build failed: {}", e)))?; if !build_output.status.success() { let stderr = String::from_utf8_lossy(&build_output.stderr); let stdout = String::from_utf8_lossy(&build_output.stdout); return Err(std::io::Error::other(format!( "CMake build failed:\nStdout: {}\nStderr: {}", stdout, stderr ))); } } BuildSystems::Meson => { // Check for meson.build let meson_file = build_dir.join("meson.build"); if !meson_file.exists() { return Err(std::io::Error::other(format!( "meson.build not found: {}", meson_file.display() ))); } let build_dir_build = build_dir.join("build"); create_dir_all(&build_dir_build)?; let mut config_cmd = Command::new("meson"); config_cmd .arg("setup") .arg(&build_dir_build) .current_dir(&build_dir); for (key, value) in &cmd_envs { config_cmd.env(key, value); } log::info!("Running Meson configuration: {:?}", config_cmd); let config_output = config_cmd .output() .map_err(|e| std::io::Error::other(format!("Meson config failed: {}", e)))?; if !config_output.status.success() { let stderr = String::from_utf8_lossy(&config_output.stderr); return Err(std::io::Error::other(format!( "Meson config failed:\n{}", stderr ))); } // Now build let mut build_cmd = Command::new("ninja"); build_cmd.current_dir(&build_dir_build); build_cmd.arg("-j").arg(num_cpus::get().to_string()); // Parallel build for (key, value) in &cmd_envs { build_cmd.env(key, value); } log::info!("Running Meson build: {:?}", build_cmd); let build_output = build_cmd .output() .map_err(|e| std::io::Error::other(format!("Meson build failed: {}", e)))?; if !build_output.status.success() { let stderr = String::from_utf8_lossy(&build_output.stderr); let stdout = String::from_utf8_lossy(&build_output.stdout); return Err(std::io::Error::other(format!( "Meson build failed:\nStdout: {}\nStderr: {}", stdout, stderr ))); } } BuildSystems::Cargo => { // Check for Cargo.toml let cargo_file = build_dir.join("Cargo.toml"); if !cargo_file.exists() { return Err(std::io::Error::other(format!( "Cargo.toml not found: {}", cargo_file.display() ))); } let mut cmd = Command::new("cargo"); cmd.arg("build").arg("--release").current_dir(&build_dir); for (key, value) in &cmd_envs { cmd.env(key, value); } log::info!("Running Cargo build: {:?}", cmd); let output = cmd .output() .map_err(|e| std::io::Error::other(format!("Cargo build failed: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); return Err(std::io::Error::other(format!( "Cargo build failed:\nStdout: {}\nStderr: {}", stdout, stderr ))); } } } log::info!("Build completed successfully for package: {}", self.name); Ok(()) } /// Builds the package according to the BUILD file in the archive. /// /// There are two strategies for building the package. If the BUILD file is empty, the package is assumed to be a binary package and the default install hook is skipped. If the BUILD file is not empty, the package is assumed to be a source package and the default build hook is skipped. /// /// If the BUILD file is empty and the INSTALL file contains a custom script, the custom script is run instead of the default install hook. /// If the BUILD file is not empty and the INSTALL file contains a custom script, the custom script is ignored. /// /// /// # Errors /// /// Returns an error if the BUILD file is invalid or if the build or install hook fails. fn build(&mut self) -> Result { let meta = Self::loadmeta(self)?; let install_meta = meta.0; // let setts_meta = meta.1; let build_meta = meta.2; // BUILD NOT EMPTY. SOURCE: -> BUILD -> INSTALL -> SETTS // BUILD EMPTY. BIN: -> INSTALL -> SETTS enum Strategies { Bin, Source, } let strategy; //default if build_meta.is_none() { log::info!("BUILD file is empty. Skipping build, preparing to install"); strategy = Strategies::Bin; } else { strategy = Strategies::Source; log::info!("BUILD file is not empty. Skipping install, preparing to build"); } match strategy { Strategies::Bin => { if install_meta.install.custom_script.is_none() { log::info!("Strategy: BIN; No custom script. Running default install hook."); } else { log::info!("Strategy: BIN; Running custom script."); let script = install_meta.install.custom_script.as_ref().unwrap(); // Validate script before execution Self::validate_custom_script(script)?; if !script.starts_with("./") { let output = std::process::Command::new(script).output().map_err(|e| { std::io::Error::other(format!("Failed to execute custom script: {}", e)) })?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(std::io::Error::other(format!( "Custom script failed:\n{}", stderr ))); } } else { let output = std::process::Command::new("/bin/sh") .arg("-c") .arg(script) .output() .map_err(|e| { std::io::Error::other(format!( "Failed to execute custom script: {}", e )) })?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(std::io::Error::other(format!( "Custom script failed:\n{}", stderr ))); } } } } Strategies::Source => { log::info!("Strategy: SOURCE; Running default build hook."); if let Err(e) = self.execute_build(&build_meta.unwrap()) { return Err(std::io::Error::other(format!("Build failed: {}", e))); } } } Ok(true) } }