summaryrefslogtreecommitdiff
path: root/src/pkgtoolkit/build.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkgtoolkit/build.rs')
-rw-r--r--src/pkgtoolkit/build.rs349
1 files changed, 349 insertions, 0 deletions
diff --git a/src/pkgtoolkit/build.rs b/src/pkgtoolkit/build.rs
new file mode 100644
index 0000000..a323521
--- /dev/null
+++ b/src/pkgtoolkit/build.rs
@@ -0,0 +1,349 @@
+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<bool, std::io::Error>;
+}
+
+#[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<bool, std::io::Error> {
+ 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)
+ }
+}