build_utils/build/
mod.rs

1use crate::source::BuildSource;
2use std::ops::{Deref};
3use std::path::PathBuf;
4
5mod meson;
6pub use meson::*;
7use crate::util::{TemporaryPath, create_temporary_path, install_prefix, build_library_type, BuildLibraryTypeError};
8use std::cell::RefCell;
9use std::collections::hash_map::DefaultHasher;
10use std::hash::{Hash, Hasher};
11
12#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
13pub enum LibraryType {
14    Static,
15    Shared
16}
17
18impl ToString for LibraryType {
19    fn to_string(&self) -> String {
20        match self {
21            LibraryType::Shared => "dylib",
22            LibraryType::Static => "static"
23        }.to_owned()
24    }
25}
26
27#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
28pub enum LinkSearchKind {
29    Dependency,
30    Crate,
31    Native,
32    Framework,
33    All
34}
35
36impl ToString for LinkSearchKind {
37    fn to_string(&self) -> String {
38        match self {
39            LinkSearchKind::Dependency => "dependency",
40            LinkSearchKind::Crate => "crate",
41            LinkSearchKind::Native => "native",
42            LinkSearchKind::Framework => "framework",
43            LinkSearchKind::All => "all"
44        }.to_owned()
45    }
46}
47
48#[derive(Debug)]
49pub enum BuildCreateError {
50    Unknown,
51    MissingName,
52    MissingSource,
53    Missing(String),
54    FailedToCreateBuildDirectory(std::io::Error),
55    InvalidEnvLibraryType(String),
56}
57
58/*
59    struct MesonBuildOptions {}
60    struct MesonBuild {
61        options: BuildOptions<MesonBuildOptions>
62    }
63    impl BuildConstructor for MesonBuild {}
64    impl Build for MesonBuild {}
65
66    let build: MesonBuild = BuildBuilder::<MesonBuild>::new()
67        .name("libnice")
68        .source(box BuildSourceDirectory::new("./libnice").expect("failed to create build source"))
69        .apply_special_options(|options| {})
70        .build();
71 */
72
73#[derive(Debug)]
74pub struct BuildError {
75    step: String,
76    error: BuildStepError
77}
78
79impl BuildError {
80    pub fn pretty_format(&self) -> String {
81        let mut result = String::with_capacity(self.error.stdout.len() + self.error.stderr.len() + self.step.len() + self.error.detail.len() + 200);
82
83        result.push_str(format!("Build step \"{}\" errored: {}\n", &self.step, &self.error.detail).as_ref());
84        if !self.error.stdout.is_empty() {
85            result.push_str("----------------- Stdout -----------------\n");
86            result.push_str(&self.error.stdout);
87        }
88
89        if !self.error.stderr.is_empty() {
90            result.push_str("----------------- Stderr -----------------\n");
91            result.push_str(&self.error.stderr);
92        }
93
94        result
95    }
96}
97
98#[derive(Debug)]
99pub struct BuildStepError {
100    detail: String,
101
102    stdout: String,
103    stderr: String
104}
105
106impl BuildStepError {
107    pub fn new_simple<S>(detail: S) -> Self
108        where S: Into<String>
109    {
110        Self::new(detail.into(), String::new(), String::new())
111    }
112
113    pub fn new_io<S>(detail: S, error: std::io::Error) -> Self
114        where S: Into<String>
115    {
116        Self::new(detail.into(), String::new(), format!("IOError: {}", error.to_string()))
117    }
118
119    pub fn new(detail: String, stdout: String, stderr: String) -> Self {
120        BuildStepError{
121            detail,
122
123            stdout,
124            stderr
125        }
126    }
127
128    pub fn stdout(&self) -> &str {
129        &self.stdout
130    }
131
132    pub fn stderr(&self) -> &str {
133        &self.stderr
134    }
135}
136
137pub struct BuildLibrary {
138    name: String,
139    kind: Option<LibraryType>
140}
141
142impl ToString for BuildLibrary {
143    fn to_string(&self) -> String {
144        if let Some(kind) = &self.kind {
145            format!("{}={}", kind.to_string(), self.name).to_owned()
146        } else {
147            self.name.clone()
148        }
149    }
150}
151
152pub struct BuildLibraryPath {
153    path: PathBuf,
154    kind: LinkSearchKind
155}
156
157impl ToString for BuildLibraryPath {
158    fn to_string(&self) -> String {
159        if self.kind == LinkSearchKind::All {
160            self.path.to_string_lossy().into_owned()
161        } else {
162            format!("{}={}", self.kind.to_string(), self.path.to_string_lossy().into_owned()).to_owned()
163        }
164    }
165}
166
167pub struct BuildResult {
168    libraries: Vec<BuildLibrary>,
169    library_paths: Vec<BuildLibraryPath>,
170    custom_compiler_emits: Vec<String>
171}
172
173impl BuildResult {
174    pub fn new() -> Self {
175        BuildResult{
176            libraries: Vec::new(),
177            library_paths: Vec::new(),
178            custom_compiler_emits: Vec::new()
179        }
180    }
181
182    pub fn add_library(&mut self, name: String, kind: Option<LibraryType>) -> &mut Self {
183        self.libraries.push(BuildLibrary{ name, kind });
184        self
185    }
186
187    pub fn libraries(&self) -> &Vec<BuildLibrary> {
188        &self.libraries
189    }
190
191    pub fn add_library_path(&mut self, path: PathBuf, kind: Option<LinkSearchKind>) -> &mut Self {
192        /* FIXME: Remove duplicated paths */
193        self.library_paths.push(BuildLibraryPath{ path, kind: kind.unwrap_or(LinkSearchKind::All) });
194        self
195    }
196
197    pub fn library_paths(&self) -> &Vec<BuildLibraryPath> {
198        &self.library_paths
199    }
200
201    pub fn add_emit(&mut self, line: String) -> &mut Self {
202        self.custom_compiler_emits.push(line);
203        self
204    }
205
206    pub fn emit_cargo(&self) {
207        self.library_paths.iter().for_each(|path| {
208            println!("cargo:rustc-link-search={}", path.to_string());
209        });
210
211        self.libraries.iter().for_each(|path| {
212            println!("cargo:rustc-link-search={}", path.to_string());
213        });
214
215        self.custom_compiler_emits.iter().for_each(|emit| {
216            println!("cargo:{}", emit);
217        });
218    }
219}
220
221pub trait BuildStep {
222    fn name(&self) -> &str;
223
224    /// Generate a hash which uniquely identifies the build options
225    fn hash(&self, hasher: &mut Box<dyn Hasher>);
226
227    /* some generic function */
228    fn execute(&mut self, build: &Build, result: &mut BuildResult) -> Result<(), BuildStepError>;
229}
230
231pub struct Build {
232    name: String,
233    source: Box<dyn BuildSource>,
234    build_hash: u64,
235
236    steps: Vec<RefCell<Box<dyn BuildStep>>>,
237
238    library_type: LibraryType,
239
240    build_path: TemporaryPath,
241    install_prefix: Option<PathBuf>,
242}
243
244impl Build {
245    /// Create a new build builder
246    pub fn builder() -> BuildBuilder {
247        BuildBuilder::new()
248    }
249
250    /// Get the name of the target build
251    pub fn name(&self) -> &str {
252        &self.name
253    }
254
255    /// The target library tyoe which should be build
256    pub fn library_type(&self) -> LibraryType {
257        self.library_type
258    }
259
260    /// Get the target install prefix where the library should be installed into.
261    /// This might be unset.
262    pub fn install_prefix(&self) -> &Option<PathBuf> {
263        &self.install_prefix
264    }
265
266    /// Get the temporary build path where you should build into
267    pub fn build_path(&self) -> &PathBuf {
268        &self.build_path.deref()
269    }
270
271    pub fn source(&self) -> &Box<dyn BuildSource> {
272        &self.source
273    }
274
275    pub fn build_hash(&self) -> u64 {
276        self.build_hash
277    }
278
279    /// Execute the build and all its steps
280    pub fn execute(&mut self) -> Result<BuildResult, BuildError> {
281        if let Err(error) = self.source.setup() {
282            return Err(BuildError{
283                step: "source setup".to_owned(),
284                error
285            });
286        }
287
288        let mut result = BuildResult::new();
289        for step in self.steps.iter() {
290            let mut step = RefCell::borrow_mut(step);
291
292            if let Err(err) = step.execute(self, &mut result) {
293                return Err(BuildError{
294                    step: step.name().to_owned(),
295                    error: err
296                })
297            }
298        }
299        Ok(result)
300    }
301}
302
303pub struct BuildBuilder {
304    name: Option<String>,
305    source: Option<Box<dyn BuildSource>>,
306
307    steps: Vec<RefCell<Box<dyn BuildStep>>>,
308    library_type: Option<LibraryType>,
309
310    install_prefix: Option<PathBuf>,
311    build_path: Option<PathBuf>,
312
313    /* TODO: Make this variable environment editable */
314    remove_build_dir: bool,
315    /* TODO: Env */
316}
317
318impl BuildBuilder {
319    fn new() -> Self {
320        BuildBuilder {
321            name: None,
322            source: None,
323
324            steps: Vec::new(),
325            library_type: None,
326
327            install_prefix: None,
328            build_path: None,
329
330            remove_build_dir: true
331        }
332    }
333
334    pub fn build(self) -> Result<Box<Build>, BuildCreateError> {
335        let name = if let Some(name) = self.name { name } else {
336            return Err(BuildCreateError::MissingName);
337        };
338        let source = if let Some(source) = self.source { source } else {
339            return Err(BuildCreateError::MissingSource);
340        };
341
342        let install_prefix = if let Some(prefix) = self.install_prefix {
343            Some(prefix)
344        } else if let Some(prefix) = install_prefix(&name) {
345            Some(prefix)
346        } else {
347            None
348        };
349
350        let library_type = if let Some(ltype) = self.library_type {
351            ltype
352        }  else {
353            match build_library_type(&name) {
354                Ok(ltype) => ltype,
355                Err(BuildLibraryTypeError::InvalidValue(value)) => return Err(BuildCreateError::InvalidEnvLibraryType(value)),
356                Err(BuildLibraryTypeError::NotPresent) => LibraryType::Shared
357            }
358        };
359
360        let build_hash = {
361            let mut hash: Box<dyn Hasher> = Box::new(DefaultHasher::new());
362            name.hash(&mut hash);
363            source.hash(&mut hash);
364            install_prefix.hash(&mut hash);
365            library_type.hash(&mut hash);
366            self.steps.iter().enumerate().for_each(|(index, step)| {
367                let step = RefCell::borrow(step);
368                index.hash(&mut hash);
369                step.name().hash(&mut hash);
370                step.hash(&mut hash);
371            });
372            hash.finish()
373        };
374
375        let hash_str = base64::encode(build_hash.to_be_bytes()).replace("/", "_");
376        let build_path = match create_temporary_path(format!("build_{}_{}", &name, hash_str).as_ref(), self.build_path) {
377            Ok(path) => path,
378            Err(err) => return Err(BuildCreateError::FailedToCreateBuildDirectory(err))
379        };
380
381        if !self.remove_build_dir {
382            build_path.release();
383        }
384
385        Ok(Box::new(Build{
386            name,
387            source,
388            build_hash,
389
390            steps: self.steps,
391            library_type,
392
393            build_path,
394            install_prefix
395        }))
396    }
397
398    pub fn name<V>(mut self, value: V) -> Self
399        where V: Into<String>
400    {
401        self.name = Some(value.into());
402        self
403    }
404
405    pub fn source(mut self, source: Box<dyn BuildSource>) -> Self {
406        self.source = Some(source);
407        self
408    }
409
410    pub fn build_path(mut self, path: PathBuf) -> Self {
411        self.build_path = Some(path);
412        self
413    }
414
415    pub fn install_prefix(mut self, path: PathBuf) -> Self {
416        self.install_prefix = Some(path);
417        self
418    }
419
420    pub fn library_type(mut self, ltype: LibraryType) -> Self {
421        self.library_type = Some(ltype);
422        self
423    }
424
425    pub fn remove_build_dir(mut self, enabled: bool) -> Self {
426        self.remove_build_dir = enabled;
427        self
428    }
429
430    pub fn add_step(mut self, step: Box<dyn BuildStep>) -> Self {
431        self.steps.push(RefCell::new(step));
432        self
433    }
434}
435
436
437#[cfg(test)]
438mod test {
439    use crate::BuildStep;
440    use crate::build::{Build, BuildResult, BuildStepError};
441    use crate::source::{BuildSource};
442    use std::path::PathBuf;
443    use std::hash::Hasher;
444
445    struct DummyBuildStep { }
446    impl BuildStep for DummyBuildStep {
447        fn name(&self) -> &str {
448            "dummy"
449        }
450
451        fn hash(&self, _state: &mut Box<dyn Hasher>) { }
452
453        fn execute(&mut self, _build: &Build, _result: &mut BuildResult) -> Result<(), BuildStepError> {
454            Ok(())
455        }
456    }
457
458    struct DummyBuildSource {}
459    impl BuildSource for DummyBuildSource {
460        fn name(&self) -> &str {
461            "dummy"
462        }
463
464        fn hash(&self, _state: &mut Box<dyn Hasher>) { }
465
466        fn setup(&mut self) -> Result<(), BuildStepError> {
467            Ok(())
468        }
469
470        fn local_directory(&self) -> &PathBuf {
471            unimplemented!()
472        }
473
474        fn cleanup(&mut self) { }
475    }
476
477    #[test]
478    fn test_builder() {
479        let mut build = Build::builder()
480            .name("test")
481            .source(Box::new(DummyBuildSource{}))
482            .add_step(Box::new(DummyBuildStep{}))
483            .build().expect("failed to create dummy build");
484        build.execute().expect("build should have succeeded");
485    }
486}