robuild/
robuild.rs

1use std::{
2    env,
3    result,
4    io::ErrorKind,
5    time::SystemTime,
6    default::Default,
7    hash::{Hash, Hasher},
8    collections::VecDeque,
9    path::{Path, PathBuf},
10    fmt::{Display, Formatter},
11    process::{exit, Command, Output, Stdio, Child},
12    fs::{rename, metadata, read_dir, remove_file, create_dir_all, remove_dir_all}
13};
14
15pub const C_COMPILER: &str = if cfg!(feature = "gcc")     {"gcc"}
16else if cfg!(feature = "clang")   {"clang"}
17else if cfg!(feature = "mingw32") {"x86_64-w64-mingw32-gcc"}
18else if cfg!(windows)             {"cl.exe"}
19else                              {"cc"};
20
21pub const CXX_COMPILER: &str = if cfg!(feature = "gxx")     {"g++"}
22else if cfg!(feature = "clangxx") {"clang++"}
23else if cfg!(feature = "mingw32") {"x86_64-w64-mingw32-g++"}
24else if cfg!(windows)             {"cl.exe"}
25else                              {"c++"};
26
27pub const CC:       &str = C_COMPILER;
28pub const CXXC:     &str = CXX_COMPILER;
29
30pub const DELIM:    &str   = if cfg!(windows) {"\\"} else {"/"};
31pub const DELIM_CHAR: char = if cfg!(windows) {'\\'} else {'/'};
32
33pub const CMD_ARG:  &str = if cfg!(windows) {"cmd"} else {"sh"};
34pub const CMD_ARG2: &str = if cfg!(windows) {"/C"} else {"-c"};
35
36/// Special symbol that you can put at the end of your slice while passing it
37/// to the `append` function to perform `append` and move `acp ptr` simultaneously.
38/// But I recommend you to use the `append_mv` function instead, it does the same but,
39/// you don't need to put anything special at the end of the slice.
40pub const MOVE_ACP_PTR_SYMBOL: &str = ".n";
41pub const MAP: &str = MOVE_ACP_PTR_SYMBOL;
42
43pub type IoError = std::io::Error;
44pub type IoResult<T> = std::io::Result::<T>;
45
46/// Call dis macro in your build recipe, and the program will
47/// rebuild itself, freeing you from the need to rebuilding your
48/// build recipe file
49#[macro_export]
50macro_rules! go_rebuild_yourself {
51    () => {{
52        let source_path = file!();
53        Rob::go_rebuild_yourself(&source_path).unwrap();
54    }};
55    (?) => {{
56        let source_path = file!();
57        Rob::go_rebuild_yourself(&source_path)?;
58    }}
59}
60
61/// You can log things just like we do in Rob functions,
62/// pass the `LogLevel` enum variant and then format your output like you'r
63/// using the `println`! macro. For instance:
64/// ```
65/// let what = "logging";
66/// log!(INFO, "This is how you can do {what}");
67/// ```
68/// This will print:
69/// ```
70/// [INFO] This is how you can do logging
71/// ```
72#[macro_export]
73macro_rules! log {
74    ($log_level: tt, $($args: tt)*) => {{
75        #[allow(unused)]
76        use LogLevel::*;
77        Rob::log($log_level, &format!($($args)*));
78    }}
79}
80
81/// Macro similar to the vec! macro, but produces
82/// `std::path::Pathbuf` instead of `std::vec::Vec`
83#[macro_export]
84macro_rules! pathbuf {
85    ($($p: expr), *) => {{
86        let mut path = std::path::PathBuf::new();
87        $(path.push($p);)*
88        path
89    }}
90}
91
92/// Macro similar to the vec! macro, but produces
93/// `std::path::Pathbuf` instead of `std::vec::Vec`
94#[macro_export]
95macro_rules! path {
96    ($($p: expr), *) => {{
97        let path = [$($p), *];
98        path.join(DELIM)
99    }}
100}
101
102/// Just pass strs and it will create directories,
103/// whether it nested or not. For instance:
104/// ```
105/// mkdirs!("just", "for", "example");
106/// ```
107/// Regardless of the target os will create `just` directory having in it
108/// `for` and `example` directories.
109#[macro_export]
110macro_rules! mkdirs {
111    ($($dir: expr), *) => {{
112        let p = pathbuf![$($dir), *];
113        Rob::mkdir(p)
114    }}
115}
116
117macro_rules! colored {
118    ($f: expr, r.$str: expr)  => { write!($f, "\x1b[91m{}\x1b[0m", $str) };
119    ($f: expr, y.$str: expr)  => { write!($f, "\x1b[93m{}\x1b[0m", $str) };
120    ($f: expr, br.$str: expr) => { write!($f, "\x1b[31m{}\x1b[0m", $str) };
121}
122
123/// Structure for convenient work with directories.
124#[derive(Debug)]
125pub struct DirRec {
126    stack: VecDeque::<PathBuf>,
127}
128
129impl DirRec {
130    /// Takes path to a directory and returns
131    /// instance of the iterable struct `DirRec`.
132    ///
133    /// `DirRec` iterates using the BFS algorithm,
134    ///
135    /// if current element is file `DirRec` returns it,
136    /// otherwise it iterates that directory and checkes for other files.
137    pub fn new<P>(root: P) -> DirRec
138    where
139        P: Into::<PathBuf>
140    {
141        let mut stack = VecDeque::new();
142        stack.push_back(root.into());
143        DirRec {stack}
144    }
145}
146
147impl Iterator for DirRec {
148    type Item = PathBuf;
149
150    fn next(&mut self) -> Option<Self::Item> {
151        while let Some(p) = self.stack.pop_front() {
152            if p.is_file() { return Some(p) }
153
154            match read_dir(&p) {
155                Ok(es) => es.filter_map(Result::ok).for_each(|e| {
156                    self.stack.push_back(e.path())
157                }),
158                Err(e) => eprintln!("ERROR: {e}")
159            }
160        } None
161    }
162}
163
164/// Structure for convenient non-recursive work with directories.
165#[derive(Debug)]
166pub struct Dir {
167    paths: Vec::<PathBuf>,
168}
169
170impl Dir {
171    /// Takes path to a directory and returns
172    /// instance of the iterable struct `Dir`.
173    ///
174    /// `Dir` iterates using the BFS algorithm,
175    ///
176    /// Unlike `DirRec`, `Dir` iterates only thru each ! File ! in specified directory,
177    /// and does not iterates all of nested directories recursively.
178    pub fn new<P>(root: P) -> Dir
179    where
180        P: Into::<PathBuf>
181    {
182        Dir {
183            paths: if let Ok(entries) = read_dir(root.into()) {
184                entries.into_iter()
185                    .filter_map(Result::ok)
186                    .filter(|e| e.path().is_file())
187                    .map(|e| e.path())
188                    .collect()
189            } else {
190                Vec::new()
191            }
192        }
193    }
194}
195
196impl IntoIterator for Dir {
197    type Item = PathBuf;
198    type IntoIter = std::vec::IntoIter<Self::Item>;
199
200    fn into_iter(self) -> Self::IntoIter {
201        self.paths.into_iter()
202    }
203}
204
205#[derive(Debug)]
206pub enum LogLevel {
207    CMD,
208    INFO,
209    WARN,
210    ERROR,
211    PANIC
212}
213
214impl Display for LogLevel {
215    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
216        use self::LogLevel::*;
217        match self {
218            CMD   => write!(f, "[CMD]")?,
219            INFO  => write!(f, "[INFO]")?,
220            WARN  => colored!(f, y."[WARN]")?,
221            ERROR => colored!(f, br."[ERROR]")?,
222            PANIC => colored!(f, r."[PANIC]")?
223        } Ok(())
224    }
225}
226
227/// Structure for executing commands (actually just keeping them, but it's just for now)
228#[derive(Eq, Debug, Clone, PartialEq)]
229pub struct RobCommand {
230    lines: Vec::<Vec::<String>>,
231    acp: usize, // append command pointer
232    ecp: usize, // execution command pointer
233    pub cfg: Config,
234    output_stack: VecDeque::<Output>,
235}
236
237impl Hash for RobCommand {
238    fn hash<H: Hasher>(&self, state: &mut H) {
239        self.lines.hash(state);
240        self.acp.hash(state);
241        self.ecp.hash(state);
242        self.cfg.hash(state);
243    }
244}
245
246impl From<Config> for RobCommand {
247    fn from(cfg: Config) -> Self {
248        Self {
249            cfg, ..Self::default()
250        }
251    }
252}
253
254impl From<Vec::<Vec::<String>>> for RobCommand {
255    fn from(lines: Vec::<Vec::<String>>) -> Self {
256        Self {
257            lines,
258            ..Self::default()
259        }
260    }
261}
262
263impl Default for RobCommand {
264    fn default() -> Self {
265        Self {
266            lines: vec![Vec::new()],
267            acp: 0, ecp: 0,
268            cfg: Config::default(),
269            output_stack: VecDeque::new()
270        }
271    }
272}
273
274impl RobCommand {
275    pub fn new() -> RobCommand {
276        RobCommand::default()
277    }
278
279    /// Appends arguments to the last line in cmd,
280    /// ```
281    /// let p = pathbuf!["dummy", "rakivo", "dummy.cpp"];
282    /// Rob::new()
283    ///     .append(&["clang++", "-o", "output", p.to_str().unwrap()])
284    ///     .execute()
285    /// ```
286    /// It Outputs:
287    /// ```
288    /// [CMD] clang++ -o output test/test1/test.cpp
289    /// ```
290    pub fn append<S>(&mut self, xs: &[S]) -> &mut Self
291    where
292        S: ToString + PartialEq::<&'static str>
293    {
294        // check for the `move_acp_ptr` symbol
295        if matches!(xs.last(), Some(last) if last == &MAP) {
296            let args = xs[0..xs.len() - 1].into_iter().map(S::to_string).collect::<Vec::<_>>();
297            self.lines[self.acp].extend(args);
298            self.move_acp_ptr();
299        } else {
300            let args = xs.into_iter().map(S::to_string).collect::<Vec::<_>>();
301            self.lines[self.acp].extend(args);
302        } self
303    }
304
305    /// Performs append and moves append ptr forward
306    pub fn append_mv<S>(&mut self, xs: &[S]) -> &mut Self
307    where
308        S: ToString + PartialEq::<&'static str>
309    {
310        self.append(xs);
311        self.move_acp_ptr();
312        self
313    }
314
315    #[inline]
316    pub fn move_acp_ptr(&mut self) -> &mut Self {
317        self.acp += 1;
318        self.lines.push(Vec::new());
319        self
320    }
321
322    pub fn execute(&mut self) -> IoResult::<&mut Self> {
323        let out = self.execute_sync()?;
324        if let Some(last) = self.lines.last_mut() {
325            *last = Vec::new();
326        }
327        self.output_stack.push_back(out);
328        Ok(self)
329    }
330
331    #[inline]
332    pub fn execute_sync(&mut self) -> IoResult::<Output> {
333        self.execute_sync_exit(true)
334    }
335
336    #[inline]
337    pub fn execute_sync_dont_exit(&mut self) -> IoResult::<Output> {
338        self.execute_sync_exit(false)
339    }
340
341    fn execute_sync_exit(&mut self, exit_: bool) -> IoResult::<Output> {
342        let Some(args) = self.get_args()
343        else {
344            let err = IoError::new(ErrorKind::Other, "No arguments to process");
345            return Err(err)
346        };
347
348        if self.cfg.echo { log!(CMD, "{args}"); }
349        let mut cmd = Command::new(CMD_ARG);
350        cmd.arg(CMD_ARG2).arg(args);
351
352        if !self.cfg.echo {
353            cmd.stdout(Stdio::null())
354                .stderr(Stdio::null());
355        }
356
357        let out = cmd.output()?;
358
359        if !self.cfg.keepgoing && !out.status.success() {
360            let code = out.status.code()
361                .expect("Process terminated by signal");
362
363            let stderr = String::from_utf8_lossy(&out.stderr);
364            log!(ERROR, "{stderr}");
365            log!(ERROR, "Compilation exited abnormally with code: {code}");
366
367            if exit_ {
368                exit(1);
369            }
370        }
371        Ok(out)
372    }
373
374    #[inline]
375    pub fn execute_all_sync(&mut self) -> RobResult::<Vec::<Output>> {
376        self.execute_all_sync_exit(true)
377    }
378
379    #[inline]
380    pub fn execute_all_sync_dont_exit(&mut self) -> RobResult::<Vec::<Output>> {
381        self.execute_all_sync_exit(false)
382    }
383
384    /// Returns vector of child which you can turn into vector of the outputs using Rob::wait_for_children.
385    pub fn execute_all_sync_exit(&mut self, exit_: bool) -> RobResult::<Vec::<Output>> {
386        let mut outs = Vec::new();
387        for idx in self.ecp..self.lines.len() {
388            let line = &self.lines[idx];
389            let args = line.join(" ");
390            if args.is_empty() { continue }
391
392            if self.cfg.echo { log!(CMD, "{args}"); }
393            let mut cmd = Command::new(CMD_ARG);
394            cmd.arg(CMD_ARG2).arg(args);
395
396            if !self.cfg.echo {
397                cmd.stdout(Stdio::null())
398                   .stderr(Stdio::null());
399            }
400
401            let out = cmd.output().map_err(|err| {
402                RobError::FailedToGetOutput(err)
403            })?;
404
405            if !self.cfg.keepgoing && !out.status.success() {
406                let code = out.status.code()
407                    .expect("Process terminated by signal");
408
409                let stderr = String::from_utf8_lossy(&out.stderr);
410                log!(ERROR, "{stderr}");
411                log!(ERROR, "Compilation exited abnormally with code: {code}");
412
413                if exit_ {
414                    exit(1);
415                }
416            }
417
418            self.ecp += 1;
419            outs.push(out);
420        }
421        Ok(outs)
422    }
423
424    #[inline]
425    fn get_args(&self) -> Option::<String> {
426        if let Some(args) = self.lines.last() {
427            if args.is_empty() { None }
428            else               { Some(args.join(" ")) }
429        } else { None }
430    }
431
432    /// Function for receiving output of the last executed command.
433    /// ```
434    /// let mut rob = Rob::new();
435    ///
436    /// rob
437    ///     .append(&["echo hello"])
438    ///     .execute()?
439    ///     .append(&[CC, "-o build/output", "./test/main.c"])
440    ///     .execute()?
441    ///     .append(&[CXXC, "-o build/outputpp", "./test/main.cpp"])
442    ///     .execute()?
443    ///     .append(&["echo byebye"])
444    ///     .execute()?;
445    ///
446    /// while let Some(out) = rob.output() {
447    ///     println!("{out:?}");
448    /// }
449    /// ```
450    /// Will print:
451    /// ```
452    /// [CMD] echo hello
453    /// [INFO] hello
454    /// [CMD] clang -o build/output ./test/main.c
455    /// [CMD] clang++ -o build/outputpp ./test/main.cpp
456    /// [CMD] echo byebye
457    /// [INFO] byebye
458    /// Output { status: ExitStatus(unix_wait_status(0)), stdout: "hello\n", stderr: "" }
459    /// Output { status: ExitStatus(unix_wait_status(0)), stdout: "", stderr: "" }
460    /// Output { status: ExitStatus(unix_wait_status(0)), stdout: "", stderr: "" }
461    /// Output { status: ExitStatus(unix_wait_status(0)), stdout: "byebye\n", stderr: "" }
462    /// ```
463    /// As you can see, you receiving outputs in the reversed order, i think this is the best way of doing that.
464    #[inline]
465    pub fn output(&mut self) -> Option::<Output> {
466        self.output_stack.pop_front()
467    }
468
469    #[inline]
470    pub fn outputs_refs(&self) -> VecDeque::<&Output> {
471        self.output_stack.iter().collect()
472    }
473
474    #[inline]
475    pub fn outputs(self) -> VecDeque::<Output> {
476        self.output_stack.into_iter().collect()
477    }
478
479    /// Returns vector of child which you can turn into vector of the outputs using Rob::wait_for_children.
480    pub fn execute_all_async(&mut self) -> RobResult::<Vec::<Child>> {
481        let mut children = Vec::new();
482        for idx in self.ecp..self.lines.len() {
483            let line = &self.lines[idx];
484            let args = line.join(" ");
485            if args.is_empty() { continue }
486
487            if self.cfg.echo { log!(CMD, "{args}"); }
488            let mut cmd = Command::new(CMD_ARG);
489            cmd.arg(CMD_ARG2).arg(&args);
490
491            let child = Command::new(CMD_ARG)
492                .arg(CMD_ARG2)
493                .arg(args)
494                .stdout(Stdio::piped())
495                .stderr(Stdio::piped())
496                .spawn()
497                .map_err(|err| RobError::FailedToSpawnChild(err))?;
498
499            self.ecp += 1;
500            children.push(child);
501        }
502        Ok(children)
503    }
504
505    fn format_out(out: &str) -> &str {
506        if out.ends_with('\n') {
507            &out[0..out.len() - 1]
508        } else {
509            &out[0..]
510        }
511    }
512
513    #[inline]
514    pub fn execute_all_async_and_wait(&mut self) -> RobResult::<Vec::<Output>> {
515        self.execute_all_async_and_wait_exit(true)
516    }
517
518    #[inline]
519    pub fn execute_all_async_and_wait_dont_exit(&mut self) -> RobResult::<Vec::<Output>> {
520        self.execute_all_async_and_wait_exit(false)
521    }
522
523    /// Returns vector of child which you can turn into vector of the outputs using Rob::wait_for_children.
524    pub fn execute_all_async_and_wait_exit(&mut self, exit: bool) -> RobResult::<Vec::<Output>> {
525        let children = self.execute_all_async()?;
526        Self::wait_for_children_deq(children.into(), &self.cfg, exit)
527    }
528
529    /// Blocks the main thread and waits for all of the children.
530    pub fn wait_for_children_deq(mut children: VecDeque::<Child>, cfg: &Config, exit_: bool) -> RobResult::<Vec::<Output>> {
531        let mut ret = Vec::new();
532        while let Some(child) = children.pop_front() {
533            let out = Self::wait_for_child(child).map_err(|err| {
534                RobError::FailedToGetOutput(err)
535            })?;
536
537            if out.status.success() {
538                let stdout = String::from_utf8_lossy(&out.stdout);
539                if !stdout.is_empty() && cfg.echo {
540                    let formatted = Self::format_out(&stdout);
541                    log!(INFO, "{formatted}");
542                }
543            } else {
544                let stderr = String::from_utf8_lossy(&out.stderr);
545                if !stderr.is_empty() && cfg.echo {
546                    let formatted = Self::format_out(&stderr);
547                    log!(ERROR, "{formatted}");
548                    if !cfg.keepgoing {
549                        let code = out.status.code().expect("Process terminated by signal");
550                        log!(ERROR, "Compilation exited abnormally with code: {code}");
551                        if exit_ {
552                            exit(1);
553                        }
554                    }
555                }
556            }
557
558            ret.push(out);
559        } Ok(ret)
560    }
561
562    /// Blocks the main thread and waits for the child.
563    #[inline]
564    pub fn wait_for_child(child: Child) -> IoResult::<Output> {
565        child.wait_with_output()
566    }
567
568    #[inline]
569    pub fn echo(&mut self, echo: bool) -> &mut Self {
570        self.cfg.echo(echo);
571        self
572    }
573
574    #[inline]
575    pub fn keepgoing(&mut self, keepgoing: bool) -> &mut Self {
576        self.cfg.keepgoing(keepgoing);
577        self
578    }
579}
580
581#[derive(Hash, Eq, Debug, Clone, PartialEq)]
582pub struct Config {
583    pub echo: bool,
584    pub keepgoing: bool,
585}
586
587impl Default for Config {
588    fn default() -> Self {
589        Self{echo: true, keepgoing: false}
590    }
591}
592
593impl Config {
594    #[inline]
595    pub fn echo(&mut self, echo: bool) -> &mut Self {
596        self.echo = echo;
597        self
598    }
599
600    #[inline]
601    pub fn keepgoing(&mut self, keepgoing: bool) -> &mut Self {
602        self.keepgoing = keepgoing;
603        self
604    }
605}
606
607#[derive(Hash, Eq, Debug, Clone, Default, PartialEq)]
608pub struct Job {
609    target: String,
610    phony: bool,
611    deps: Vec::<String>,
612    cmd: RobCommand,
613    reusable_cmd: bool
614}
615
616#[derive(Debug)]
617pub enum RobError {
618    NotFound(String),
619    FailedToGetOutput(IoError),
620    FailedToSpawnChild(IoError)
621}
622
623impl Display for RobError {
624    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
625        use RobError::*;
626        match self {
627            NotFound(file_path)     => write!(f, "File not found: {file_path}"),
628            FailedToGetOutput(err)  => write!(f, "Failed to get output: {err}", err = err.to_string()),
629            FailedToSpawnChild(err) => write!(f, "Failed to spawn child: {err}", err = err.to_string()),
630        }
631    }
632}
633
634pub type RobResult<T> = result::Result::<T, RobError>;
635
636impl Job {
637    pub fn new<S>(target: &str, deps: Vec::<S>, cmd: RobCommand) -> Job
638    where
639        S: Into::<String>
640    {
641        let target = target.to_owned();
642        let deps = deps.into_iter().map(Into::into).collect::<Vec::<_>>();
643        Job {target, deps, cmd, ..Self::default()}
644    }
645
646    #[inline(always)]
647    pub fn target(&self) -> &String {
648        &self.target
649    }
650
651    #[inline(always)]
652    pub fn deps(&self) -> &Vec::<String> {
653        &self.deps
654    }
655
656    #[inline(always)]
657    pub fn cmd(&self) -> &RobCommand {
658        &self.cmd
659    }
660
661    #[inline(always)]
662    pub fn cfg(&self) -> &Config {
663        &self.cmd.cfg
664    }
665
666    #[inline(always)]
667    pub fn cfg_mut(&mut self) -> &mut Config {
668        &mut self.cmd.cfg
669    }
670
671    #[inline(always)]
672    pub fn reusable_cmd(&mut self, reusable_cmd: bool) -> &mut Self {
673        self.reusable_cmd = reusable_cmd;
674        self
675    }
676
677    #[inline(always)]
678    pub fn phony(&mut self, phony: bool) -> &mut Self {
679        self.phony = phony;
680        self
681    }
682
683    #[inline(always)]
684    pub fn keepgoing(&mut self, keepgoing: bool) -> &mut Self {
685        self.cmd.keepgoing(keepgoing);
686        self
687    }
688
689    #[inline(always)]
690    pub fn echo(&mut self, echo: bool) -> &mut Self {
691        self.cmd.echo(echo);
692        self
693    }
694
695    #[inline]
696    pub fn needs_rebuild(&self) -> RobResult::<bool> {
697        if self.phony {
698            Ok(true)
699        } else {
700            Rob::needs_rebuild_many(&self.target, &self.deps)
701        }
702    }
703
704    fn execute(&mut self, sync: bool, exit_: bool, check: bool) -> RobResult::<Vec::<Output>> {
705        if !check || self.needs_rebuild()? {
706            let cmd = if self.reusable_cmd {
707                &mut self.cmd.clone()
708            } else {
709                &mut self.cmd
710            };
711            if sync {
712                return if exit_ {
713                    cmd.execute_all_sync()
714                } else {
715                    cmd.execute_all_sync_dont_exit()
716                }
717            } else {
718                return if exit_ {
719                    cmd.execute_all_async_and_wait()
720                } else {
721                    cmd.execute_all_async_and_wait_dont_exit()
722                }
723            }
724        } else {
725            log!(INFO, "Nothing to be done for '{target}'.", target = self.target);
726            Ok(Vec::new())
727        }
728    }
729
730    #[inline(always)]
731    pub fn execute_async_dont_exit(&mut self) -> RobResult::<Vec::<Output>> {
732        self.execute(false, false, true)
733    }
734
735    #[inline(always)]
736    pub fn execute_async(&mut self) -> RobResult::<Vec::<Output>> {
737        self.execute(false, true, true)
738    }
739
740    #[inline(always)]
741    pub fn execute_sync_dont_exit(&mut self) -> RobResult::<Vec::<Output>> {
742        self.execute(true, false, true)
743    }
744
745    #[inline(always)]
746    pub fn execute_sync(&mut self) -> RobResult::<Vec::<Output>> {
747        self.execute(true, true, true)
748    }
749
750    #[inline(always)]
751    pub fn execute_async_dont_exit_unchecked(&mut self) -> RobResult::<Vec::<Output>> {
752        self.execute(false, false, false)
753    }
754
755    #[inline(always)]
756    pub fn execute_async_unchecked(&mut self) -> RobResult::<Vec::<Output>> {
757        self.execute(false, true, false)
758    }
759
760    #[inline(always)]
761    pub fn execute_sync_dont_exit_unchecked(&mut self) -> RobResult::<Vec::<Output>> {
762        self.execute(true, false, false)
763    }
764
765    #[inline(always)]
766    pub fn execute_sync_unchecked(&mut self) -> RobResult::<Vec::<Output>> {
767        self.execute(true, true, false)
768    }
769}
770
771/// The main `Rob` structure.
772#[derive(Hash, Eq, Debug, Clone, Default, PartialEq)]
773pub struct Rob {
774    pub cfg: Config,
775    cmd: RobCommand,
776    jobs: Vec::<Job>,
777}
778
779impl Rob {
780    pub const MAX_DIR_LVL: usize = 4;
781
782    pub fn new() -> Rob {
783        Rob::default()
784    }
785
786    /// Checks if src file needs rebuild
787    pub fn needs_rebuild(bin: &str, src: &str) -> IoResult::<bool> {
788        if !Rob::path_exists(bin) { return Ok(true) }
789        let bin_mod_time = Rob::get_last_modification_time(bin).map_err(|err| {
790            log!(ERROR, "{err}: {bin}"); err
791        })?;
792        let src_mod_time = Rob::get_last_modification_time(src).map_err(|err| {
793            log!(ERROR, "{err}: {bin}"); err
794        })?;
795        Ok(src_mod_time > bin_mod_time)
796    }
797
798    pub fn needs_rebuild_many(bin: &str, srcs: &Vec::<String>) -> RobResult::<bool> {
799        // I collect times on purpose to check if all of the src files exist,
800        // to catch unexisting dependencies at `compile time` whether bin path exists or not.
801
802        let mut times = Vec::new();
803        for src in srcs.iter() {
804            match Rob::get_last_modification_time(src) {
805                Ok(time) => times.push(time),
806                Err(_) => return Err(RobError::NotFound(src.to_owned()))
807            }
808        }
809
810        if !Rob::path_exists(bin) { return Ok(true) }
811
812        let bin_mod_time = match Rob::get_last_modification_time(bin) {
813            Ok(time) => time,
814            Err(_) => return Err(RobError::NotFound(bin.to_owned()))
815        };
816
817        Ok(times.into_iter().any(|src_mod_time| src_mod_time > bin_mod_time))
818    }
819
820    // The implementation idea is stolen from https://github.com/tsoding/nobuild,
821    // which is also stolen from https://github.com/zhiayang/nabs
822    /// Modified text from: `https://github.com/tsoding/nobuild`
823    /// `Go Rebuild Urself™ Technology`
824    ///
825    /// How to use it:
826    /// ```
827    /// fn main() {
828    ///     go_rebuild_yourself!();
829    ///     // actual work
830    /// }
831    /// ```
832    ///
833    /// After your added this macro every time you run `./rob` it will detect
834    /// that you modified its original source code and will try to rebuild itself
835    /// before doing any actual work. So you only need to bootstrap your build system
836    /// once.
837    ///
838    /// The modification is detected by comparing the last modified times of the executable
839    /// and its source code. The same way the make utility usually does it.
840    pub fn go_rebuild_yourself(source_path: &str) -> IoResult::<()> {
841        let args = env::args().collect::<Vec::<_>>();
842        assert!(args.len() >= 1);
843        let binary_pathbuf = std::env::current_exe()?;
844        let binary_path = binary_pathbuf.to_str().to_owned().unwrap();
845        let rebuild_is_needed = Rob::needs_rebuild(&binary_path, source_path)?;
846        if rebuild_is_needed {
847            let old_bin_path = format!("{binary_path}.old");
848            log!(INFO, "RENAMING: {binary_path} -> {old_bin_path}");
849            Rob::rename(binary_path, &old_bin_path)?;
850
851            let self_compilation_output = Rob::new()
852                .append(&["rustc -o", binary_path, source_path])
853                .execute_sync();
854
855            match self_compilation_output {
856                Ok(out) => if !out.status.success() {
857                    let stderr = String::from_utf8_lossy(&out.stderr);
858                    log!(ERROR, "{stderr}");
859                    let code = out.status.code()
860                        .expect("Process terminated by signal");
861                    log!(ERROR, "Compilation exited abnormally with code: {code}");
862                    Rob::rename(old_bin_path.as_str(), binary_path)?;
863                    exit(1);
864                }
865                Err(err) => {
866                    log!(ERROR, "Failed to rename file: {old_bin_path}: {err}");
867                    Rob::rename(old_bin_path.as_str(), binary_path)?;
868                    exit(1);
869                }
870            }
871
872            match Rob::new()
873                .append(&args)
874                .execute_sync()
875            {
876                Ok(_) => {
877                    log!(INFO, "Removing: {old_bin_path}");
878                    Rob::rm_if_exists(old_bin_path);
879                    exit(0);
880                }
881                Err(err) => {
882                    log!(ERROR, "Failed to restart rob from file: {binary_path}: {err}");
883                    exit(1);
884                }
885            }
886        } Ok(())
887    }
888
889    #[inline]
890    pub fn get_last_modification_time<P>(p: P) -> IoResult::<SystemTime>
891    where
892        P: AsRef::<Path>
893    {
894        metadata(p)?.modified()
895    }
896
897    /// Takes path and returns it without the file extension
898    #[inline]
899    pub fn noext(p: &str) -> String {
900        p.chars().take_while(|x| *x != '.').collect()
901    }
902
903    #[inline]
904    pub fn is_file<P>(p: P) -> bool
905    where
906        P: Into::<PathBuf>
907    {
908        p.into().is_file()
909    }
910
911    #[inline]
912    pub fn is_dir<P>(p: P) -> bool
913    where
914        P: Into::<PathBuf>
915    {
916        p.into().is_dir()
917    }
918
919    #[inline]
920    pub fn path_exists<P>(p: P) -> bool
921    where
922        P: Into::<PathBuf>
923    {
924        p.into().exists()
925    }
926
927    #[inline]
928    pub fn rename<P>(from: P, to: P) -> IoResult<()>
929    where
930        P: AsRef::<Path>
931    {
932        rename(from, to)
933    }
934
935    #[inline]
936    pub fn mkdir<P>(p: P) -> IoResult::<()>
937    where
938        P: Into::<PathBuf>
939    {
940        create_dir_all(p.into())
941    }
942
943    pub fn rm_if_exists<P>(p: P)
944    where
945        P: Into::<PathBuf> + ToOwned::<Owned = String>
946    {
947        if Rob::is_dir(p.to_owned()) {
948            remove_dir_all(p.into()).expect("Failed to remove directory")
949        } else if Rob::is_file(p.to_owned()) {
950            remove_file(p.into()).expect("Failed to remove file")
951        }
952    }
953
954    pub fn rm<P>(p: P) -> IoResult::<()>
955    where
956        P: Into::<PathBuf> + ToOwned::<Owned = String>
957    {
958        if !Rob::path_exists(p.to_owned()) {
959            return Err(ErrorKind::NotFound.into())
960        } else if Rob::is_dir(p.to_owned()) {
961            remove_dir_all(p.into())
962        } else if Rob::is_file(p.to_owned()) {
963            remove_file(p.into())
964        } else {
965            Err(ErrorKind::InvalidData.into())
966        }
967    }
968
969    #[track_caller]
970    pub fn log(lvl: LogLevel, out: &str) {
971        use LogLevel::*;
972        match lvl {
973            PANIC => panic!("{lvl} {out}"),
974            _     => println!("{lvl} {out}")
975        }
976    }
977
978    #[inline]
979    pub fn echo(&mut self, echo: bool) -> &mut Self {
980        self.cfg.echo(echo);
981        self
982    }
983
984    #[inline]
985    pub fn keepgoing(&mut self, keepgoing: bool) -> &mut Self {
986        self.cfg.keepgoing(keepgoing);
987        self
988    }
989
990    pub fn append<S>(&mut self, xs: &[S]) -> &mut Self
991    where
992        S: ToString + PartialEq::<&'static str>
993    {
994        self.cmd.append(xs);
995        self
996    }
997
998    pub fn append_mv<S>(&mut self, xs: &[S]) -> &mut Self
999    where
1000        S: ToString + PartialEq::<&'static str>
1001    {
1002        self.cmd.append(xs);
1003        self.cmd.move_acp_ptr();
1004        self
1005    }
1006
1007    fn execute_jobs(&mut self, sync: bool) -> RobResult::<Vec::<Vec::<Output>>> {
1008        let mut outss = Vec::new();
1009        for job in self.jobs.iter_mut() {
1010            let outs = if sync {
1011                job.execute_sync()
1012            } else {
1013                job.execute_async()
1014            }?;
1015            outss.push(outs);
1016        } Ok(outss)
1017    }
1018
1019    #[inline]
1020    pub fn execute_jobs_sync(&mut self) -> RobResult::<Vec::<Vec::<Output>>> {
1021        self.execute_jobs(true)
1022    }
1023
1024    #[inline]
1025    pub fn execute_jobs_async(&mut self) -> RobResult::<Vec::<Vec::<Output>>> {
1026        self.execute_jobs(false)
1027    }
1028
1029    #[inline]
1030    pub fn append_job_job(&mut self, job: Job) -> &mut Self {
1031        self.jobs.push(job);
1032        self
1033    }
1034
1035    #[inline]
1036    pub fn append_job<S>(&mut self, target: &str, deps: Vec::<S>, cmd: RobCommand) -> &mut Self
1037    where
1038        S: Into::<String>
1039    {
1040        let job = Job::new(target, deps, cmd);
1041        self.jobs.push(job);
1042        self
1043    }
1044
1045    #[inline]
1046    pub fn execute(&mut self) -> IoResult::<&mut Self> {
1047        self.cmd.execute()?;
1048        Ok(self)
1049    }
1050
1051    #[inline]
1052    pub fn execute_sync(&mut self) -> IoResult::<Output> {
1053        self.cmd.execute_sync()
1054    }
1055
1056    #[inline]
1057    pub fn execute_all_sync(&mut self) -> RobResult::<Vec::<Output>> {
1058        self.cmd.execute_all_sync()
1059    }
1060
1061    #[inline]
1062    pub fn output(&mut self) -> Option::<Output> {
1063        self.cmd.output()
1064    }
1065
1066    #[inline]
1067    pub fn outputs_refs(&self) -> VecDeque::<&Output> {
1068        self.cmd.outputs_refs()
1069    }
1070
1071    #[inline]
1072    pub fn outputs(self) -> VecDeque::<Output> {
1073        self.cmd.outputs()
1074    }
1075}
1076
1077/*
1078More important TODOs:
1079    (#2): Parse env::args and do something with them
1080
1081    (#3): Rob clean feature
1082
1083Less important TODOs:
1084    README;
1085    Examples;
1086    Other Nob features;
1087    Documentation;
1088*/