1extern crate serde;
6extern crate toml;
7extern crate walkdir;
8extern crate ansi_term;
9
10use desc::project::*;
11use error::{YabsError, YabsErrorKind};
12use ext::{Job, PrependEach, get_assumed_filename_for_dir, run_cmd, spawn_cmd};
13
14use std::collections::BTreeSet;
15use std::env;
16use std::fs;
17use std::fs::File;
18use std::io::prelude::*;
19use std::path::{Path, PathBuf};
20use std::process::Child;
21
22pub trait Buildable<T> {
23 fn path(&self) -> PathBuf;
24}
25
26impl<T> Buildable<T> for Binary {
27 fn path(&self) -> PathBuf {
28 PathBuf::from(self.name())
29 }
30}
31
32impl<T> Buildable<T> for Library {
33 fn path(&self) -> PathBuf {
34 self.path()
35 }
36}
37
38#[derive(Debug, Default, Clone, Serialize, Deserialize)]
40pub struct BuildFile {
41 project: ProjectDesc,
42 #[serde(rename = "bin")]
43 binaries: Option<Vec<Binary>>,
44 #[serde(rename = "lib")]
45 libraries: Option<Vec<Library>>,
46}
47
48impl BuildFile {
49 pub fn from_file<T: AsRef<Path>>(filepath: &T) -> Result<BuildFile, YabsError> {
51 let mut buffer = String::new();
52 let mut file = File::open(filepath)?;
53 file.read_to_string(&mut buffer)?;
54 let mut build_file: BuildFile = toml::from_str(&buffer)?;
55 build_file.project.find_source_files()?;
56 Ok(build_file)
57 }
58
59 pub fn print_sources(&mut self) {
60 for target in self.project.file_mod_map.keys() {
61 info!("{}", target.source().display());
62 }
63 }
64
65 fn spawn_build_object(&self, target: &Target) -> Result<(String, Child), YabsError> {
66 let command = &format!("{CC} -c {CFLAGS} {INC} -o \"{OBJ}\" \"{SRC}\"",
67 CC =
68 &self.project.compiler.as_ref().unwrap_or(&String::from("gcc")),
69 CFLAGS = &self.project
70 .compiler_flags
71 .as_ref()
72 .unwrap_or(&vec![])
73 .prepend_each("-")
74 .join(" "),
75 INC = &self.project
76 .include
77 .as_ref()
78 .unwrap_or(&vec![])
79 .prepend_each("-I")
80 .join(" "),
81 OBJ = target.object().to_str().unwrap(),
82 SRC = target.source().to_str().unwrap());
83 Ok((command.to_owned(), spawn_cmd(command)?))
84 }
85
86 fn build_object_queue<T: Buildable<T>>(&self,
87 build_target: &T)
88 -> Result<Vec<Target>, YabsError> {
89 let mut queue = BTreeSet::new();
90 let target_path = build_target.path();
91 if target_path.exists() {
92 for (target, modtime) in &self.project.file_mod_map {
93 if modtime > &fs::metadata(&target_path)?.modified()? || !target.object().exists() {
94 queue.insert(target.clone());
95 }
96 }
97 } else {
98 for target in self.project.file_mod_map.keys() {
99 if !target.object().exists() {
100 queue.insert(target.clone());
101 }
102 }
103 }
104 Ok(queue.iter().cloned().collect())
105 }
106
107 fn build_all_binaries(&mut self, jobs: usize) -> Result<(), YabsError> {
108 if !&self.binaries.is_some() {
109 return Ok(());
110 }
111 for binary in self.binaries.clone().unwrap() {
112 let job_queue = self.build_object_queue(&binary)?;
113 self.run_job_queue(job_queue, jobs)?;
114 self.build_binary(&binary)?;
115 }
116 Ok(())
117 }
118
119 fn run_job_queue(&self, mut job_queue: Vec<Target>, jobs: usize) -> Result<(), YabsError> {
120 let mut job_processes: Vec<Job> = Vec::new();
121 while !job_queue.is_empty() {
122 if job_processes.len() < jobs {
123 if let Some(target) = job_queue.pop() {
124 let job = Job::new(self.spawn_build_object(&target)?);
125 info!("{}", job.command());
126 job_processes.push(job);
127 }
128 } else {
129 while !job_processes.is_empty() {
130 if let Some(mut job) = job_processes.pop() {
131 job.yield_self()?;
132 }
133 }
134 }
135 }
136 while !job_processes.is_empty() {
137 if let Some(mut job) = job_processes.pop() {
138 job.yield_self()?;
139 }
140 }
141 Ok(())
142 }
143
144 fn build_binary(&self, binary: &Binary) -> Result<(), YabsError> {
145 let object_list = if self.binaries.as_ref().unwrap().len() == 1 {
146 self.project.object_list_as_string(None)?
147 } else {
148 self.project
149 .object_list_as_string(Some(self.binaries
150 .clone()
151 .unwrap()
152 .into_iter()
153 .filter(|bin| bin.path() != binary.path())
154 .collect::<Vec<Binary>>()))?
155 };
156 Ok(run_cmd(&format!("{CC} {LFLAGS} -o {BIN} {OBJ_LIST} {LIB_DIR} {LIBS}",
157 CC = &self.project.compiler.as_ref().unwrap_or(&String::from("gcc")),
158 LFLAGS = &self.project
159 .lflags
160 .as_ref()
161 .unwrap_or(&vec![])
162 .prepend_each("-")
163 .join(" "),
164 BIN = binary.name(),
165 OBJ_LIST = object_list,
166 LIB_DIR = &self.project
167 .lib_dir
168 .as_ref()
169 .unwrap_or(&vec![])
170 .prepend_each("-L")
171 .join(" "),
172 LIBS = &self.project.libs_as_string()))?)
173 }
174
175 pub fn build_static_library(&self, library: &Library) -> Result<(), YabsError> {
176 let object_list = &self.project.object_list_as_string(None)?;
177 Ok(run_cmd(&format!("{AR} {ARFLAGS} {LIB} {OBJ_LIST}",
178 AR = &self.project.ar.as_ref().unwrap_or(&String::from("ar")),
179 ARFLAGS =
180 &self.project.arflags.as_ref().unwrap_or(&String::from("rcs")),
181 LIB = library.static_file_name().display(),
182 OBJ_LIST = object_list))?)
183 }
184
185 pub fn build_dynamic_library(&self, library: &Library) -> Result<(), YabsError> {
186 let object_list = &self.project.object_list_as_string(None)?;
187 Ok(run_cmd(&format!("{CC} -shared -o {LIB} {OBJ_LIST} {LIBS}",
188 CC = &self.project.compiler.as_ref().unwrap_or(&String::from("gcc")),
189 LIB = library.dynamic_file_name().display(),
190 OBJ_LIST = object_list,
191 LIBS = &self.project.libs_as_string()))?)
192 }
193
194 pub fn build_library(&self, library: &Library) -> Result<(), YabsError> {
195 if library.is_static() {
196 self.build_static_library(library)?;
197 }
198 if library.is_dynamic() {
199 self.build_dynamic_library(library)?;
200 }
201 Ok(())
202 }
203
204 pub fn build_library_with_name(&mut self, name: &str, jobs: usize) -> Result<(), YabsError> {
205 if let Some(libraries) = self.libraries.as_ref() {
206 if let Some(library) = libraries.into_iter()
207 .find(|&lib| {
208 lib.name() == name
209 }) {
210 let job_queue = self.build_object_queue(library)?;
211 self.run_job_queue(job_queue, jobs)?;
212 self.build_library(library)?;
213 }
214 } else {
215 bail!(YabsErrorKind::TargetNotFound("library".to_owned(), name.to_owned()))
216 }
217 Ok(())
218 }
219
220 pub fn build_binary_with_name(&mut self, name: &str, jobs: usize) -> Result<(), YabsError> {
221 if let Some(binaries) = self.binaries.as_ref() {
222 if let Some(binary) = binaries.into_iter()
223 .find(|&bin| {
224 bin.name() == name
225 }) {
226 let job_queue = self.build_object_queue(binary)?;
227 self.run_job_queue(job_queue, jobs)?;
228 self.build_binary(binary)?;
229 }
230 } else {
231 bail!(YabsErrorKind::TargetNotFound("binary".to_owned(), name.to_owned()))
232 }
233 Ok(())
234 }
235
236 pub fn build_all_libraries(&mut self, jobs: usize) -> Result<(), YabsError> {
237 if !self.libraries.is_some() {
238 return Ok(());
239 }
240 for library in self.libraries.clone().unwrap() {
241 let job_queue = self.build_object_queue(&library)?;
242 self.run_job_queue(job_queue, jobs)?;
243 self.build_library(&library)?;
244 }
245 Ok(())
246 }
247
248 pub fn build(&mut self, jobs: usize) -> Result<(), YabsError> {
249 self.project.run_script(&self.project.before_script)?;
250 self.build_all_binaries(jobs)?;
251 self.build_all_libraries(jobs)?;
252 self.project.run_script(&self.project.after_script)?;
253 Ok(())
254 }
255
256 pub fn clean(&self) -> Result<(), YabsError> {
257 for target in self.project.file_mod_map.keys() {
258 if target.object().exists() && fs::remove_file(target.object()).is_ok() {
259 info!("removed object '{}'", target.object().display());
260 }
261 }
262 if let Some(binaries) = self.binaries.clone() {
263 for binary in binaries {
264 let bin_path = PathBuf::from(binary.name());
265 if bin_path.exists() && fs::remove_file(&bin_path).is_ok() {
266 info!("removed binary '{}'", bin_path.display());
267 }
268 }
269 }
270 if let Some(libraries) = self.libraries.clone() {
271 for library in libraries {
272 if library.dynamic_file_name().exists() &&
273 fs::remove_file(library.dynamic_file_name()).is_ok() {
274 info!("removed library '{}'",
275 library.dynamic_file_name().display());
276 }
277 if library.static_file_name().exists() &&
278 fs::remove_file(library.static_file_name()).is_ok() {
279 info!("removed library '{}'", library.static_file_name().display());
280 }
281 }
282 }
283 Ok(())
284 }
285}
286
287pub fn find_build_file(dir: &mut PathBuf) -> Result<BuildFile, YabsError> {
288 let original = dir.clone();
289 loop {
290 if let Some(filepath) = check_dir(dir) {
291 env::set_current_dir(&dir)?;
292 return Ok(BuildFile::from_file(&dir.join(filepath))?);
293 } else if !dir.pop() {
294 break;
295 }
296 }
297 bail!(YabsErrorKind::NoAssumedToml(original.to_str().unwrap().to_owned()))
298}
299
300fn check_dir(dir: &PathBuf) -> Option<PathBuf> {
301 if let Some(assumed) = get_assumed_filename_for_dir(dir) {
302 if dir.join(&assumed).exists() {
303 return Some(dir.join(assumed));
304 }
305 }
306 None
307}
308
309#[test]
310#[should_panic]
311fn test_empty_buildfile() {
312 let bf = BuildFile::from_file(&"test/empty.toml").unwrap();
313 assert_eq!(bf.binaries.unwrap().len(), 0);
314}
315
316#[test]
317#[should_panic]
318fn test_non_empty_buildfile() {
319 let bf = BuildFile::from_file(&"test/test_project/test.toml").unwrap();
320 let default_proj: ProjectDesc = Default::default();
321 assert_eq!(bf.project, default_proj);
322}