diff options
Diffstat (limited to 'src/pkgtoolkit/build.rs')
| -rw-r--r-- | src/pkgtoolkit/build.rs | 335 |
1 files changed, 201 insertions, 134 deletions
diff --git a/src/pkgtoolkit/build.rs b/src/pkgtoolkit/build.rs index 326be1e..a37cb05 100644 --- a/src/pkgtoolkit/build.rs +++ b/src/pkgtoolkit/build.rs @@ -1,6 +1,11 @@ use crate::cfg::config::Config; -use std::{fs::create_dir_all, path::Path, process::Command}; +use std::{ + fs::create_dir_all, + path::{Path, PathBuf}, + process::Command, +}; +use glob::glob; use num_cpus; use super::archive::ArchiveOperations; @@ -9,10 +14,144 @@ 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<bool, std::io::Error>; + fn find_makefile( + &self, + build_meta: &Build, + search_dir: &Path, + ) -> std::io::Result<Option<PathBuf>>; + fn run_command(cmd: std::process::Command, context: &str) -> Result<(), std::io::Error>; + + fn run_build_system( + source_dir: &Path, + required_file: &str, + configure_cmd: &[&str], + build_cmd: &[&str], + work_dir: &Path, + envs: &[(String, String)], + context: &str, + ) -> Result<(), std::io::Error>; } #[allow(dead_code)] impl BuildOperations for Package { + /// Runs a command and checks if it was successful. + /// If the command fails, it returns an error with the command's + /// stdout and stderr. + /// + /// # Arguments + /// + /// * `cmd`: The command to run. + /// * `context`: A string to prefix the error message with if the command fails. + fn run_command(mut cmd: std::process::Command, context: &str) -> Result<(), std::io::Error> { + let output = cmd + .output() + .map_err(|e| std::io::Error::other(format!("{} failed: {}", context, 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!( + "{} failed:\nStdout: {}\nStderr: {}", + context, stdout, stderr + ))); + } + Ok(()) + } + + /// Runs a build system given the following parameters: + /// + /// `source_dir`: The directory containing the source code + /// `required_file`: The file that must exist in the source directory + /// `configure_cmd`: The command to run for configure step + /// `build_cmd`: The command to run for build step + /// `work_dir`: The directory where the build process will take place + /// `envs`: A list of environment variables to set during the build process + /// `context`: A string context to provide for error messages + fn run_build_system( + source_dir: &Path, + required_file: &str, + configure_cmd: &[&str], + build_cmd: &[&str], + work_dir: &Path, + envs: &[(String, String)], + context: &str, + ) -> Result<(), std::io::Error> { + let required_path = source_dir.join(required_file); + if !required_path.exists() { + return Err(std::io::Error::other(format!( + "{} file not found: {}", + context, + required_path.display() + ))); + } + + if !work_dir.exists() { + create_dir_all(work_dir)?; + } + + // Configure step + let mut config_cmd = std::process::Command::new(configure_cmd[0]); + config_cmd.args(&configure_cmd[1..]).current_dir(source_dir); + for (key, value) in envs { + config_cmd.env(key, value); + } + log::info!("Running {} configuration: {:?}", context, config_cmd); + Self::run_command(config_cmd, &format!("{} config", context))?; + + // Build step + let mut build_cmd_inner = std::process::Command::new(build_cmd[0]); + build_cmd_inner.args(&build_cmd[1..]).current_dir(work_dir); + for (key, value) in envs { + build_cmd_inner.env(key, value); + } + log::info!("Running {} build: {:?}", context, build_cmd_inner); + Self::run_command(build_cmd_inner, &format!("{} build", context))?; + + Ok(()) + } + + + /// Finds the build system file (e.g. Makefile, meson.build, CMakeLists.txt, Cargo.toml) + /// based on the build system specified in the build metadata. + /// + /// # Arguments + /// + /// * `build_meta`: The build metadata containing the build system type. + /// * `search_dir`: The directory to search in for the build system file. + /// + /// # Returns + /// + /// A `Result` containing an `Option<PathBuf>` which is `Some` if the build + /// system file is found, and `None` otherwise. + fn find_makefile(&self, build_meta: &Build, search_dir: &Path) -> std::io::Result<Option<PathBuf>> { + let (patterns, recursive) = match build_meta.build_system { + BuildSystems::Make => (vec!["Makefile", "makefile", "GNUmakefile"], false), + BuildSystems::Meson => (vec!["meson.build", "meson_options.txt"], true), + BuildSystems::CMake => (vec!["CMakeLists.txt"], true), + BuildSystems::Cargo => (vec!["Cargo.toml"], false), + _ => return Ok(None), + }; + + for pattern in &patterns { + + let glob_pattern = if recursive { + search_dir.join("**").join(pattern).to_string_lossy().into_owned() + } else { + search_dir.join(pattern).to_string_lossy().into_owned() + }; + + let entries = glob(&glob_pattern) + .map_err(|e| std::io::Error::other(format!("Invalid glob pattern: {}", e)))?; + + for entry in entries { + let path = entry.map_err(|e| std::io::Error::other(format!("Glob error: {}", e)))?; + return Ok(Some(path)); + } + } + + Ok(None) +} + /// Execute the build script for the package. /// /// This function takes the `Build` meta information as an argument and @@ -56,17 +195,20 @@ impl BuildOperations for Package { let mut inner_cmd = Command::new("/bin/sh"); inner_cmd.arg("-c"); inner_cmd.arg(script_path.to_str().unwrap()); + let _ = inner_cmd.output(); inner_cmd } else { let mut inner_cmd = Command::new("/bin/sh"); inner_cmd.arg("-c"); inner_cmd.arg(script); + let _ = inner_cmd.output(); 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)))?; @@ -88,14 +230,20 @@ impl BuildOperations for Package { // 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() { + let found = self + .find_makefile(&build_meta, &build_dir) + .map_err(|e| { + std::io::Error::other(format!("Failed to search for Makefile: {}", e)) + })? + .ok_or_else(|| std::io::Error::other("Makefile not found"))?; + + if !found.exists() { return Err(std::io::Error::other(format!( "Makefile not found: {}", - makefile_path.display() + found.display() ))); } + let mut cmd = Command::new("make"); cmd.current_dir(&build_dir); cmd.arg("all"); @@ -103,145 +251,53 @@ impl BuildOperations for Package { 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 - ))); - } + Self::run_command(cmd, "Make build")?; } BuildSystems::CMake => { - 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 - ))); - } + let found = self + .find_makefile(&build_meta, &build_dir) + .map_err(|e| { + std::io::Error::other(format!("Failed to search for CMakeLists: {}", e)) + })? + .ok_or_else(|| std::io::Error::other("Makefile not found"))?; - 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); + if !found.exists() { return Err(std::io::Error::other(format!( - "CMake build failed:\nStdout: {}\nStderr: {}", - stdout, stderr + "Makefile not found: {}", + found.display() ))); } + Self::run_build_system( + &build_dir, + "CMakeLists.txt", + &["cmake", "-S", ".", "-B", "build"], + &["make", "-j", &num_cpus::get().to_string()], + &build_dir.join("build"), + &cmd_envs, + "CMake", + )?; } BuildSystems::Meson => { - 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 - ))); - } + Self::run_build_system( + &build_dir, + "meson.build", + &["meson", "setup", "build"], + &["ninja", "-j", &num_cpus::get().to_string()], + &build_dir.join("build"), + &cmd_envs, + "Meson", + )?; } 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 - ))); - } + Self::run_build_system( + &build_dir, + "Cargo.toml", + &["cargo", "build", "--release"], + &[], + &build_dir, + &cmd_envs, + "Cargo", + )?; } } @@ -294,6 +350,16 @@ impl BuildOperations for Package { // Validate script before execution Self::validate_custom_script(script)?; + if self.arch.as_str().to_lowercase() + != install_meta.package.arch.as_str().to_lowercase() + { + return Err(std::io::Error::other(format!( + "Package arch mismatch. Expected: {}, Actual: {}", + install_meta.package.arch.as_str(), + self.arch.as_str().to_lowercase() + ))); + } + 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)) @@ -328,6 +394,7 @@ impl BuildOperations for Package { } } } + Strategies::Source => { log::info!("Strategy: SOURCE; Running default build hook."); if let Err(e) = self.execute_build(&build_meta.unwrap()) { |
