use crate::project::Project;
use std::cell::RefCell;
use std::io::Write;
use std::path::Path;
use std::process::Command;
use std::time::SystemTime;
use walkdir::WalkDir;
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum BuildMode {
Debug,
Release,
Normal,
Example,
Test,
}
impl BuildMode {
pub fn is_normal(&self) -> bool {
self == &BuildMode::Debug || self == &BuildMode::Release || self == &BuildMode::Normal
}
}
pub struct Program {
name: String,
sources: Box<RefCell<Vec<String>>>,
dependencies: Vec<String>,
include: Vec<String>,
program_type: String,
standard: String,
}
impl Program {
pub fn build(
&mut self,
path: &str,
mode: BuildMode,
) -> std::result::Result<(), std::io::Error> {
let mut extra_args = Vec::new();
if mode == BuildMode::Release {
extra_args.push("-O3".to_string());
} else if mode == BuildMode::Debug {
extra_args.push("-g".to_string());
} else if mode == BuildMode::Example {
extra_args.push(format!("{}/target/lib{}.so", path, self.name));
self.program_type = "bin".to_string();
}
if self.program_type == "lib" {
extra_args.push("-c".to_string());
extra_args.push("-o".to_string());
extra_args.push(format!("{}/target/lib{}.so", path, self.name));
} else {
extra_args.push("-o".to_string());
extra_args.push(format!("{}/target/{}", path, self.name));
}
if self.standard == "nostd"{
extra_args.push("-nostd".to_string());
}else{
extra_args.push(format!("-std={}", self.standard.clone()));
}
extra_args.push("-static".to_string());
let op = Command::new("g++")
.args(&["-Iheaders/"])
.args(extra_args.iter())
.args(self.sources.get_mut())
.args(self.dependencies.iter())
.args(self.include.iter())
.output()?;
std::io::stdout().write_all(&op.stdout)?;
std::io::stderr().write_all(&op.stderr)?;
if mode == BuildMode::Example{
self.program_type = "lib".to_string();
}
Ok(())
}
pub fn run(&mut self, path: &str, mode: BuildMode) -> std::result::Result<(), std::io::Error> {
if self.program_type != "bin"
&& (mode == BuildMode::Normal || mode == BuildMode::Release || mode == BuildMode::Debug)
{
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"not an executable file",
));
}
if self.program_type == "bin" && mode == BuildMode::Example {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"binary projects are not allowed to have examples",
));
}
if self.program_type == "bin" && mode == BuildMode::Test {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"binary projects are not allowed to have tests",
));
}
let pa = if self.program_type == "bin" {
format!("{}/target/{}", path, self.name)
} else {
format!("{}/target/{}.o", path, self.name)
};
let p = Path::new(pa.as_str());
if !p.exists() {
self.build(path, mode)?;
}
if last_modified(path, std::fs::metadata(p)?.modified()?)? || mode == BuildMode::Release {
self.build(path, mode)?;
}
if mode != BuildMode::Debug {
let mut command = Command::new(pa.as_str());
if let Ok(mut child) = command.spawn() {
child.wait().expect("command wasn't running");
} else {
println!("command didn't start");
}
} else {
let mut command = Command::new("gdb");
if let Ok(mut child) = command
.arg(pa.as_str())
.spawn()
{
child.wait().expect("command wasn't running");
} else {
println!("command didn't start");
}
}
Ok(())
}
fn create(project: &Project, path: &str, mode: BuildMode, file: Option<&str>) -> Self {
let sources; let mut dependencies = Vec::new();
let mut include = Vec::new();
match &project.get_dependencies() {
Some(dep) => {
for depend in dep.iter() {
let mut config = pkg_config::Config::new();
if depend.get_version() != "*" {
config.exactly_version(&depend.get_version());
}
match config.probe(depend.get_name().as_str()) {
Ok(lib) => {
for l in lib.libs {
let li = format!("{}{}", "-l", l);
dependencies.push(li);
}
for i in lib.include_paths {
let inc = format!(
"{}{}",
"-I",
i.into_os_string().into_string().unwrap()
);
include.push(inc);
}
}
Err(e) => println!("error: {}", e),
}
}
}
None => (),
}
if mode.is_normal() {
let walk: RefCell<walkdir::IntoIter> =
RefCell::new(WalkDir::new(format!("{}/src", path).as_str()).into_iter());
let build: RefCell<Vec<String>> = RefCell::new(Vec::new());
sources = add_file(walk, build);
} else {
sources = Box::new(RefCell::new(vec![format!("{}/{}", path, file.unwrap())]));
}
Self {
name: project.get_package().get_name(),
sources,
dependencies,
include,
program_type: project.get_type(),
standard: project.get_standard(),
}
}
pub fn example(project: &Project, path: &str, file: &str) -> Self {
Self::create(project, path, BuildMode::Example, Some(file))
}
pub fn test(project: &Project, path: &str, file: &str) -> Self {
Self::create(project, path, BuildMode::Test, Some(file))
}
pub fn new(project: &Project, path: &str) -> Program {
Self::create(project, path, BuildMode::Normal, None)
}
pub fn get_flags(&mut self) -> String {
format!(
"include paths: {:?}\n libraries: {:?}\n source files: {:?}",
self.include,
self.dependencies,
self.sources.get_mut()
)
}
}
pub fn add_file(
mut walk: RefCell<walkdir::IntoIter>,
mut build: RefCell<Vec<String>>,
) -> Box<RefCell<Vec<String>>> {
let temppath = walk.get_mut().next().unwrap().unwrap();
let path = temppath.path();
if path.is_file() {
build.get_mut().push(path.to_str().unwrap().to_string());
return Box::new(build);
}
add_file(walk, build)
}
pub fn last_modified(path: &str, time: SystemTime) -> std::result::Result<bool, std::io::Error> {
for file in WalkDir::new(path).into_iter() {
let file = file?;
let metadata = std::fs::metadata(file.path()).unwrap();
if metadata.modified().unwrap() > time {
return Ok(true);
}
}
Ok(false)
}