extern crate serde; extern crate toml; extern crate walkdir; extern crate regex; use error::YabsError; use ext::*; use regex::Regex; use std::collections::{BTreeMap, BTreeSet}; use std::fs::metadata; use std::path::PathBuf; use std::time::SystemTime; use walkdir::{DirEntry, WalkDir, WalkDirIterator}; #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] pub struct Binary { // Name of target binary name: String, // Path to entry point path: PathBuf, } impl Binary { pub fn name(&self) -> String { self.name.clone() } pub fn path(&self) -> PathBuf { self.path.clone() } } #[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct Target { source: PathBuf, object: PathBuf, } impl Target { pub fn new(src: PathBuf, obj: PathBuf) -> Target { Target { source: src, object: obj, } } pub fn source(&self) -> PathBuf { self.source.clone() } pub fn object(&self) -> PathBuf { self.object.clone() } } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Ord, Eq, PartialOrd)] enum LibType { #[serde(rename = "static")] Static, #[serde(rename = "dynamic")] Dynamic, } #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] pub struct Library { name: String, #[serde(skip_serializing)] #[serde(skip_deserializing)] path: PathBuf, #[serde(rename = "types")] lib_types: BTreeSet, } impl Library { pub fn name(&self) -> String { self.name.clone() } pub fn path(&self) -> PathBuf { self.path.clone() } pub fn is_static(&self) -> bool { self.lib_types.contains(&LibType::Static) } pub fn is_dynamic(&self) -> bool { self.lib_types.contains(&LibType::Dynamic) } #[cfg(target_os = "windows")] pub fn static_file_name(&self) -> PathBuf { PathBuf::from(self.name() + ".lib") } #[cfg(not(target_os = "windows"))] pub fn static_file_name(&self) -> PathBuf { PathBuf::from(self.name() + ".a") } #[cfg(target_os = "windows")] pub fn dynamic_file_name(&self) -> PathBuf { PathBuf::from(self.name() + ".dll") } #[cfg(not(target_os = "windows"))] pub fn dynamic_file_name(&self) -> PathBuf { PathBuf::from(self.name() + ".so") } } #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] pub struct ProjectDesc { pub name: Option, #[serde(rename = "file-extensions")] pub file_exts: Vec, pub version: Option, pub compiler: Option, pub src: Option>, #[serde(rename = "libraries")] pub libs: Option>, #[serde(rename = "library-directories")] pub lib_dir: Option>, pub include: Option>, #[serde(rename = "compiler-flags")] pub compiler_flags: Option>, #[serde(rename = "explicit-cflags")] pub explicit_cflags: Option, #[serde(rename = "linker-flags")] pub lflags: Option>, pub ignore: Option>, #[serde(rename = "before-script")] pub before_script: Option>, #[serde(rename = "after-script")] pub after_script: Option>, pub ar: Option, pub arflags: Option, // Serde, please don't touch this, ty #[serde(skip_serializing)] #[serde(skip_deserializing)] pub object_list: Vec, #[serde(skip_serializing)] #[serde(skip_deserializing)] pub file_mod_map: BTreeMap, } impl ProjectDesc { // TODO: Maybe have some kind of regex here to allow '*' and friends? pub fn is_in_ignore(&self, entry: &DirEntry) -> bool { if let Some(ignore) = self.ignore.as_ref() { for path in ignore { if let Some(entry_str) = entry.path().to_str() { if entry_str.find(path).is_some() { return true; } } } } false } // We have to do this as we regard entries that start with '`' as commands // and don't want to prepend them pub fn libs_as_string(&self) -> String { let mut lib_list: Vec = Vec::new(); let mut cmd_list: Vec = Vec::new(); if let Some(libs) = self.libs.as_ref() { for lib in libs { if self.is_command(lib) { cmd_list.push(lib.clone()); } else { lib_list.push(lib.clone()); } } } lib_list = lib_list.prepend_each("-l"); lib_list.extend(cmd_list); lib_list.join(" ") } // We don't care if a path is invalid UTF-8. // ! Potentially spoopy ! // We can optionally exclude an object with source path `exclude` pub fn object_list_as_string(&self, exclude: Option>) -> Result { let mut obj_str_list: Vec = Vec::new(); for target in self.file_mod_map.keys() { if let Some(ref exclude) = exclude { for bin in exclude { if target.source.canonicalize()? != bin.path().canonicalize()? { if let Some(obj_str) = target.object.to_str() { obj_str_list.push(format!("\"{}\"", obj_str.to_owned())); } } } } else if let Some(obj_str) = target.object.to_str() { obj_str_list.push(format!("\"{}\"", obj_str.to_owned())); } } Ok(obj_str_list.join(" ")) } // Sources are found with a regular expression "(.*)\.[EXTENSIONS]+$" // where extensions would be the joined list given in 'file-extensions' in // a configuration file. The filename is then stored in the "(.*)" group pub fn find_source_files(&mut self) -> Result<(), YabsError> { // If sources are listed don't walk the current directory for files. if self.src.is_some() { let regex = Regex::new(&format!("(.*)\\.[{}]+$", self.file_exts.join("|")))?; for entry in self.src.clone().unwrap() { if let Some(src_str) = entry.clone().to_str() { self.file_mod_map .insert(Target::new(entry.clone(), PathBuf::from(String::from(regex.replace(src_str, "${1}.o")))), metadata(&entry)?.modified()?); } } } else { self.walk_current_dir()?; } Ok(()) } // Same regex used as `find_source_files` fn walk_current_dir(&mut self) -> Result<(), YabsError> { let regex = Regex::new(&format!("(.*)\\.[{}]+$", self.file_exts.join("|")))?; let mut sources = Vec::new(); let mut file_mod_map = BTreeMap::new(); let walk_dir = WalkDir::new(".").into_iter(); for entry in walk_dir.filter_entry(|e| !&self.is_in_ignore(e)) { let entry = entry?; if entry.path().is_file() { if let Some(filename_str) = entry.path().to_str() { if regex.is_match(filename_str) { file_mod_map.insert(Target::new(entry.path().to_path_buf(), PathBuf::from(String::from(regex.replace(filename_str, "${1}.o")))), metadata(entry.path())?.modified()?); } } } } sources.sort(); self.src = Some(sources); self.file_mod_map = file_mod_map; Ok(()) } // Any string that starts and ends with the character "`" is regarded as a // command pub fn is_command(&self, string: &str) -> bool { string.starts_with('`') && string.ends_with('`') } pub fn run_script(&self, script: &Option>) -> Result<(), YabsError> { if let Some(script) = script.as_ref() { for cmd in script { run_cmd(cmd)?; } } Ok(()) } }