use std::{env, fs, io};
use std::fs::OpenOptions;
use std::io::{Error, ErrorKind, Write};
use std::path::PathBuf;
use std::process::Command;
use toml::Value;
#[derive(Default, Debug, Clone)]
pub struct ProjectSettings {
compilation_target: Option<String>,
features: Option<Vec<String>>,
output_path: Option<PathBuf>,
release: bool,
is_lib: bool,
no_default_features: bool,
project_path: PathBuf,
cargo_toml_path: PathBuf,
target: Option<String>
}
impl ProjectSettings {
pub fn new(project_path: impl Into<PathBuf>, output_path: Option<impl Into<PathBuf>>, target: Option<String>,
is_lib: bool) -> Self {
let project_path = project_path.into();
let cargo_toml = project_path.clone().join("Cargo.toml");
Self {
project_path,
release: false,
output_path: output_path.map(Into::into),
cargo_toml_path: cargo_toml,
is_lib,
target,
..Default::default()
}
}
pub fn get_features(&self) -> io::Result<Vec<String>> {
let cargo_content = fs::read_to_string(&self.cargo_toml_path)?;
let parsed_toml: Value = cargo_content.parse().map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
if let Some(features) = parsed_toml.get("features").and_then(|f| f.as_table()) {
Ok(features.keys().cloned().collect())
} else {
Ok(vec![])
}
}
pub fn set_release(&mut self) {
self.release = true;
}
pub fn add_feature(&mut self, feature: String) {
self.features.get_or_insert_with(Vec::new).push(feature)
}
pub fn set_target(&mut self, target: String) {
self.target = Some(target)
}
pub fn set_output_path(&mut self, path: PathBuf) {
self.output_path = Some(path)
}
}
#[derive(Default, Debug)]
pub struct Builder {
cargo_path: PathBuf,
project_settings: ProjectSettings,
thread_count: usize,
log_path: Option<PathBuf>,
verbose_build: bool,
additional_flags: Vec<String>
}
impl Builder {
fn get_cargo_path() -> io::Result<PathBuf> {
env::var_os("CARGO")
.map(PathBuf::from)
.ok_or_else(|| Error::new(ErrorKind::NotFound, "CARGO environment variable not found"))
}
pub fn new(project_settings: ProjectSettings, thread_count: usize, log_path:
Option<impl Into<PathBuf>>) ->
io::Result<Builder> {
let cargo_path = Builder::get_cargo_path()?;
Ok(Self {
cargo_path,
project_settings,
thread_count,
log_path: log_path.map(Into::into),
..Default::default()
})
}
pub fn set_verbose(&mut self) {
self.verbose_build = true;
}
pub fn add_rustc_flag(&mut self, flag: String) {
self.additional_flags.push(flag);
}
pub fn build(&self) -> io::Result<()> {
let mut command = Command::new(self.cargo_path.clone());
command.arg("build");
if self.verbose_build {
command.arg("--verbose");
}
if self.project_settings.release {
command.arg("--release");
}
if self.thread_count > 0 {
command.arg("--jobs").arg(self.thread_count.to_string());
}
if let Some(output_path) = &self.project_settings.output_path {
command.env("CARGO_TARGET_DIR", output_path);
}
if !self.additional_flags.is_empty() {
command.env("RUSTFLAGS", self.additional_flags.join(" "));
}
if let Some(ref target) = self.project_settings.compilation_target {
command.arg("--target").arg(target);
}
if let Some(features) = &self.project_settings.features {
command.arg("--features");
features.iter().for_each(|f| { command.arg(f); });
}
if self.project_settings.no_default_features {
command.arg("--no-default-features");
}
if let Some(target) = &self.project_settings.target {
command.arg(if self.project_settings.is_lib { "--lib" } else { "--bin" }).arg(target);
}
let output = command.current_dir(&self.project_settings.project_path).output()?;
if let Some(output_log) = &self.log_path {
let mut output_file = OpenOptions::new().create(true).append(true).open(output_log)?;
output_file.write_all(&output.stdout)?;
output_file.write_all(&output.stderr)?;
}
if output.status.success() {
Ok(())
} else {
Err(Error::new(ErrorKind::Other, format!("Failed to compile project: {}", output.status)))
}
}
}