util/
build.rs

1// Copyright (c) 2015 - 2016, Alberto Corona <ac@albertocorona.com>
2// All rights reserved. This file is part of yabs, distributed under the BSD
3// 3-Clause license. For full terms please see the LICENSE file.
4
5extern 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// A build file could have multiple `Profile`s
39#[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    // Creates a `Profiles` from a toml file. Is essentiall `BuildFile::new`
50    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}