Skip to main content

turtle_build/
compile.rs

1mod context;
2mod error;
3mod global_state;
4mod module_state;
5
6pub use self::error::CompileError;
7use self::{context::Context, global_state::GlobalState, module_state::ModuleState};
8use crate::{
9    ast,
10    ir::{Build, Config, DynamicBuild, DynamicConfig, HeaderDependency, Pool, Rule},
11    module_dependency::ModuleDependencyMap,
12};
13use alloc::{borrow::Cow, sync::Arc};
14use core::num::NonZeroUsize;
15use once_cell::sync::Lazy;
16use regex::{Captures, Regex};
17use std::{
18    collections::{HashMap, hash_map::Entry},
19    path::{Path, PathBuf},
20};
21use train_map::TrainMap;
22
23const PHONY_RULE: &str = "phony";
24const CONSOLE_POOL: &str = "console";
25const BUILD_DIRECTORY_VARIABLE: &str = "builddir";
26const COMMAND_VARIABLE: &str = "command";
27const DESCRIPTION_VARIABLE: &str = "description";
28const DEPFILE_VARIABLE: &str = "depfile";
29const DEPS_VARIABLE: &str = "deps";
30const DYNAMIC_MODULE_VARIABLE: &str = "dyndep";
31const POOL_VARIABLE: &str = "pool";
32const SOURCE_VARIABLE_NAME: &str = "srcdep";
33const MSVC_DEPS_PREFIX_VARIABLE: &str = "msvc_deps_prefix";
34// Matches the default prefix of `cl.exe`'s own `/showIncludes` output.
35const DEFAULT_MSVC_DEPS_PREFIX: &str = "Note: including file: ";
36
37static VARIABLE_PATTERN: Lazy<Regex> =
38    Lazy::new(|| Regex::new(r"\$(\$|\{([[:alnum:]_.-]+)\}|[[:alpha:]_][[:alnum:]_]*)").unwrap());
39
40/// Compiles modules.
41// TODO Use a string pool for paths.
42pub fn compile(
43    modules: &HashMap<PathBuf, ast::Module>,
44    dependencies: &ModuleDependencyMap,
45    root_module_path: &Path,
46) -> Result<Config, CompileError> {
47    let context = Context::new(modules, dependencies);
48
49    let mut global_state = GlobalState {
50        outputs: Default::default(),
51        default_outputs: Default::default(),
52        source_map: Default::default(),
53        pools: [(CONSOLE_POOL.into(), None)].into(),
54    };
55    let mut module_state = ModuleState {
56        rules: TrainMap::new(),
57        variables: TrainMap::new(),
58    };
59
60    compile_module(
61        &context,
62        &mut global_state,
63        &mut module_state,
64        root_module_path,
65    )?;
66
67    let default_outputs = if global_state.default_outputs.is_empty() {
68        global_state.outputs.keys().cloned().collect()
69    } else {
70        global_state.default_outputs
71    };
72
73    Ok(Config::new(
74        global_state.outputs,
75        default_outputs,
76        global_state.source_map,
77        global_state
78            .pools
79            .into_iter()
80            .filter_map(|(name, depth)| Some((name, depth?)))
81            .collect(),
82        module_state
83            .variables
84            .get(BUILD_DIRECTORY_VARIABLE)
85            .cloned(),
86    ))
87}
88
89fn compile_module<'a>(
90    context: &'a Context,
91    global_state: &mut GlobalState,
92    module_state: &mut ModuleState<'a, '_>,
93    path: &Path,
94) -> Result<(), CompileError> {
95    let module = &context
96        .modules()
97        .get(path)
98        .ok_or_else(|| CompileError::ModuleNotFound(path.into()))?;
99
100    for statement in module.statements() {
101        match statement {
102            ast::Statement::Build(build) => {
103                let mut variables = module_state.variables.fork();
104
105                if build.rule() != PHONY_RULE {
106                    variables.extend(
107                        module_state
108                            .rules
109                            .get(build.rule())
110                            .ok_or_else(|| CompileError::RuleNotFound(build.rule().into()))?
111                            .iter()
112                            .cloned(),
113                    );
114                }
115
116                variables.extend(
117                    build
118                        .variable_definitions()
119                        .iter()
120                        .map(|definition| (definition.name(), definition.value().into())),
121                );
122
123                let pool = compile_pool(&global_state.pools, &variables)?;
124
125                let outputs = interpolate_strings(build.outputs(), &variables);
126                let implicit_outputs = interpolate_strings(build.implicit_outputs(), &variables);
127                let inputs = interpolate_strings(build.inputs(), &variables);
128                let implicit_inputs = interpolate_strings(build.implicit_inputs(), &variables);
129                let order_only_inputs = interpolate_strings(build.order_only_inputs(), &variables);
130
131                variables.extend([
132                    ("in", inputs.join(" ").into()),
133                    ("in_newline", inputs.join("\n").into()),
134                    ("out", outputs.join(" ").into()),
135                ]);
136
137                let ir = Arc::new(Build::new(
138                    outputs,
139                    implicit_outputs,
140                    if build.rule() == PHONY_RULE {
141                        None
142                    } else {
143                        Some(
144                            Rule::new(
145                                resolve_variable(COMMAND_VARIABLE, &variables).unwrap_or_default(),
146                                resolve_variable(DESCRIPTION_VARIABLE, &variables),
147                            )
148                            .with_pool(pool)
149                            .with_header_dependency(
150                                compile_header_dependency(build.rule(), &variables)?,
151                            ),
152                        )
153                    },
154                    inputs.into_iter().chain(implicit_inputs).collect(),
155                    order_only_inputs,
156                    resolve_variable(DYNAMIC_MODULE_VARIABLE, &variables).map(Into::into),
157                ));
158
159                let outputs = || ir.outputs().iter().chain(ir.implicit_outputs());
160
161                global_state
162                    .outputs
163                    .extend(outputs().map(|output| (output.clone(), ir.clone())));
164
165                if let Some(source) = variables.get(SOURCE_VARIABLE_NAME) {
166                    global_state
167                        .source_map
168                        .extend(outputs().map(|output| (output.clone(), source.clone())));
169                }
170            }
171            ast::Statement::Default(default) => {
172                global_state.default_outputs.extend(interpolate_strings(
173                    default.outputs(),
174                    &module_state.variables,
175                ));
176            }
177            ast::Statement::Include(include) => {
178                compile_module(
179                    context,
180                    global_state,
181                    module_state,
182                    resolve_dependency(context, path, include.path())?,
183                )?;
184            }
185            ast::Statement::Pool(pool) => {
186                let Entry::Vacant(entry) = global_state.pools.entry(pool.name().into()) else {
187                    return Err(CompileError::DuplicatePool(pool.name().into()));
188                };
189                let depth = interpolate_variables(pool.depth(), &module_state.variables);
190
191                entry.insert(NonZeroUsize::new(depth.trim().parse().map_err(|_| {
192                    CompileError::InvalidPoolDepth(pool.name().into(), depth.as_ref().into())
193                })?));
194            }
195            ast::Statement::Rule(rule) => {
196                module_state.rules.insert(
197                    rule.name(),
198                    rule.variable_definitions()
199                        .iter()
200                        .map(|definition| (definition.name(), definition.value().into()))
201                        .collect(),
202                );
203            }
204            ast::Statement::Submodule(submodule) => {
205                compile_module(
206                    context,
207                    global_state,
208                    &mut module_state.fork(),
209                    resolve_dependency(context, path, submodule.path())?,
210                )?;
211            }
212            ast::Statement::VariableDefinition(definition) => {
213                module_state.variables.insert(
214                    definition.name(),
215                    interpolate_variables(definition.value(), &module_state.variables).into(),
216                );
217            }
218        }
219    }
220
221    Ok(())
222}
223
224fn compile_header_dependency(
225    rule: &str,
226    variables: &TrainMap<&str, Arc<str>>,
227) -> Result<Option<HeaderDependency>, CompileError> {
228    Ok(
229        match (
230            resolve_variable(DEPS_VARIABLE, variables).as_deref(),
231            resolve_variable(DEPFILE_VARIABLE, variables),
232        ) {
233            (None, None) => None,
234            (None, Some(path)) => Some(HeaderDependency::Make { path }),
235            (Some("gcc"), Some(path)) => Some(HeaderDependency::Gcc { path }),
236            (Some("gcc"), None) => return Err(CompileError::MissingDepfile(rule.into())),
237            (Some("msvc"), _) => Some(HeaderDependency::Msvc {
238                prefix: resolve_variable(MSVC_DEPS_PREFIX_VARIABLE, variables)
239                    .unwrap_or_else(|| DEFAULT_MSVC_DEPS_PREFIX.into()),
240            }),
241            (Some(deps), _) => return Err(CompileError::InvalidDependencyStyle(deps.into())),
242        },
243    )
244}
245
246fn compile_pool(
247    pools: &HashMap<Arc<str>, Option<NonZeroUsize>>,
248    variables: &TrainMap<&str, Arc<str>>,
249) -> Result<Option<Pool>, CompileError> {
250    let Some(name) = resolve_variable(POOL_VARIABLE, variables) else {
251        return Ok(None);
252    };
253    let (name, depth) = pools
254        .get_key_value(name.as_str())
255        .ok_or(CompileError::PoolNotFound(name))?;
256
257    Ok(if name.as_ref() == CONSOLE_POOL {
258        Some(Pool::Console)
259    } else if depth.is_some() {
260        Some(Pool::Limited(name.clone()))
261    } else {
262        None
263    })
264}
265
266pub fn compile_dynamic(module: &ast::DynamicModule) -> Result<DynamicConfig, CompileError> {
267    Ok(DynamicConfig::new(
268        module
269            .builds()
270            .iter()
271            .map(|build| {
272                (
273                    build.output().into(),
274                    DynamicBuild::new(
275                        build
276                            .implicit_inputs()
277                            .iter()
278                            .map(|string| string.as_str().into())
279                            .collect(),
280                    ),
281                )
282            })
283            .collect(),
284    ))
285}
286
287fn resolve_dependency<'a>(
288    context: &'a Context,
289    module_path: &Path,
290    submodule_path: &str,
291) -> Result<&'a Path, CompileError> {
292    Ok(context
293        .dependencies()
294        .get(module_path)
295        .ok_or_else(|| CompileError::ModuleNotFound(module_path.into()))?
296        .get(submodule_path)
297        .ok_or_else(|| CompileError::ModuleNotFound(submodule_path.into()))?)
298}
299
300fn resolve_variable(name: &str, variables: &TrainMap<&str, Arc<str>>) -> Option<String> {
301    variables
302        .get(name)
303        .map(|value| interpolate_variables(value, variables).into_owned())
304        .filter(|value| !value.is_empty())
305}
306
307fn interpolate_strings(strings: &[String], variables: &TrainMap<&str, Arc<str>>) -> Vec<Arc<str>> {
308    strings
309        .iter()
310        .map(|string| interpolate_variables(string, variables).into())
311        .collect()
312}
313
314fn interpolate_variables<'a>(
315    template: &'a str,
316    variables: &TrainMap<&str, Arc<str>>,
317) -> Cow<'a, str> {
318    VARIABLE_PATTERN.replace_all(template, |captures: &Captures| match &captures[1] {
319        "$" => "$",
320        name => variables
321            .get(captures.get(2).map_or(name, |name| name.as_str()))
322            .map(|string| string.as_ref())
323            .unwrap_or_default(),
324    })
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330    use crate::ast;
331    use once_cell::sync::Lazy;
332    use pretty_assertions::assert_eq;
333    use std::collections::HashSet;
334
335    static ROOT_MODULE_PATH: Lazy<PathBuf> = Lazy::new(|| PathBuf::from("build.ninja"));
336    static DEFAULT_DEPENDENCIES: Lazy<ModuleDependencyMap> = Lazy::new(|| {
337        [(ROOT_MODULE_PATH.clone(), Default::default())]
338            .into_iter()
339            .collect()
340    });
341
342    fn ast_explicit_build(
343        outputs: Vec<String>,
344        rule: String,
345        inputs: Vec<String>,
346        variable_definitions: Vec<ast::VariableDefinition>,
347    ) -> ast::Build {
348        ast::Build::new(
349            outputs,
350            vec![],
351            rule,
352            inputs,
353            vec![],
354            vec![],
355            variable_definitions,
356        )
357    }
358
359    fn ast_rule(name: &str, variable_definitions: &[(&str, &str)]) -> ast::Rule {
360        ast::Rule::new(
361            name.into(),
362            variable_definitions
363                .iter()
364                .map(|&(name, value)| ast::VariableDefinition::new(name.into(), value.into()))
365                .collect(),
366        )
367    }
368
369    fn ir_explicit_build(outputs: Vec<Arc<str>>, rule: Rule, inputs: Vec<Arc<str>>) -> Build {
370        Build::new(outputs, vec![], rule.into(), inputs, vec![], None)
371    }
372
373    fn create_simple_config(
374        outputs: HashMap<Arc<str>, Arc<Build>>,
375        default_outputs: HashSet<Arc<str>>,
376    ) -> Config {
377        Config::new(
378            outputs,
379            default_outputs,
380            Default::default(),
381            Default::default(),
382            None,
383        )
384    }
385
386    fn compile_root_module(statements: Vec<ast::Statement>) -> Result<Config, CompileError> {
387        compile(
388            &[(ROOT_MODULE_PATH.clone(), ast::Module::new(statements))]
389                .into_iter()
390                .collect(),
391            &DEFAULT_DEPENDENCIES,
392            &ROOT_MODULE_PATH,
393        )
394    }
395
396    fn create_pool_config(pool: Option<Pool>, pools: &[(&str, usize)]) -> Config {
397        Config::new(
398            [(
399                "bar".into(),
400                ir_explicit_build(
401                    vec!["bar".into()],
402                    Rule::new("baz".into(), None).with_pool(pool),
403                    vec![],
404                )
405                .into(),
406            )]
407            .into_iter()
408            .collect(),
409            ["bar".into()].into_iter().collect(),
410            Default::default(),
411            pools
412                .iter()
413                .map(|&(name, depth)| (name.into(), NonZeroUsize::new(depth).unwrap()))
414                .collect(),
415            None,
416        )
417    }
418
419    fn limited_pool(name: &str) -> Option<Pool> {
420        Some(Pool::Limited(name.into()))
421    }
422
423    #[test]
424    fn compile_empty_module() {
425        assert_eq!(
426            compile(
427                &[(ROOT_MODULE_PATH.clone(), ast::Module::new(vec![]))]
428                    .into_iter()
429                    .collect(),
430                &DEFAULT_DEPENDENCIES,
431                &ROOT_MODULE_PATH
432            )
433            .unwrap(),
434            create_simple_config(Default::default(), Default::default())
435        );
436    }
437
438    #[test]
439    fn interpolate_variable_in_command() {
440        assert_eq!(
441            compile(
442                &[(
443                    ROOT_MODULE_PATH.clone(),
444                    ast::Module::new(vec![
445                        ast::VariableDefinition::new("x".into(), "42".into()).into(),
446                        ast_rule("foo", &[("command", "$x")]).into(),
447                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
448                    ])
449                )]
450                .into_iter()
451                .collect(),
452                &DEFAULT_DEPENDENCIES,
453                &ROOT_MODULE_PATH
454            )
455            .unwrap(),
456            create_simple_config(
457                [(
458                    "bar".into(),
459                    ir_explicit_build(vec!["bar".into()], Rule::new("42".into(), None), vec![])
460                        .into()
461                )]
462                .into_iter()
463                .collect(),
464                ["bar".into()].into_iter().collect()
465            )
466        );
467    }
468
469    #[test]
470    fn interpolate_two_variables_in_command() {
471        assert_eq!(
472            compile(
473                &[(
474                    ROOT_MODULE_PATH.clone(),
475                    ast::Module::new(vec![
476                        ast::VariableDefinition::new("x".into(), "1".into()).into(),
477                        ast::VariableDefinition::new("y".into(), "2".into()).into(),
478                        ast_rule("foo", &[("command", "$x $y")]).into(),
479                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
480                    ])
481                )]
482                .into_iter()
483                .collect(),
484                &DEFAULT_DEPENDENCIES,
485                &ROOT_MODULE_PATH
486            )
487            .unwrap(),
488            create_simple_config(
489                [(
490                    "bar".into(),
491                    ir_explicit_build(vec!["bar".into()], Rule::new("1 2".into(), None), vec![])
492                        .into()
493                )]
494                .into_iter()
495                .collect(),
496                ["bar".into()].into_iter().collect()
497            )
498        );
499    }
500
501    #[test]
502    fn interpolate_variable_with_underscore_in_command() {
503        assert_eq!(
504            compile(
505                &[(
506                    ROOT_MODULE_PATH.clone(),
507                    ast::Module::new(vec![
508                        ast::VariableDefinition::new("x_y".into(), "42".into()).into(),
509                        ast_rule("foo", &[("command", "$x_y")]).into(),
510                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
511                    ])
512                )]
513                .into_iter()
514                .collect(),
515                &DEFAULT_DEPENDENCIES,
516                &ROOT_MODULE_PATH
517            )
518            .unwrap(),
519            create_simple_config(
520                [(
521                    "bar".into(),
522                    ir_explicit_build(vec!["bar".into()], Rule::new("42".into(), None), vec![])
523                        .into()
524                )]
525                .into_iter()
526                .collect(),
527                ["bar".into()].into_iter().collect()
528            )
529        );
530    }
531
532    #[test]
533    fn interpolate_variable_with_braces_in_command() {
534        assert_eq!(
535            compile(
536                &[(
537                    ROOT_MODULE_PATH.clone(),
538                    ast::Module::new(vec![
539                        ast::VariableDefinition::new("x.y".into(), "42".into()).into(),
540                        ast_rule("foo", &[("command", "${x.y}")]).into(),
541                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
542                    ])
543                )]
544                .into_iter()
545                .collect(),
546                &DEFAULT_DEPENDENCIES,
547                &ROOT_MODULE_PATH
548            )
549            .unwrap(),
550            create_simple_config(
551                [(
552                    "bar".into(),
553                    ir_explicit_build(vec!["bar".into()], Rule::new("42".into(), None), vec![])
554                        .into()
555                )]
556                .into_iter()
557                .collect(),
558                ["bar".into()].into_iter().collect()
559            )
560        );
561    }
562
563    #[test]
564    fn interpolate_dollar_sign_in_command() {
565        assert_eq!(
566            compile(
567                &[(
568                    ROOT_MODULE_PATH.clone(),
569                    ast::Module::new(vec![
570                        ast_rule("foo", &[("command", "$$")]).into(),
571                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into()
572                    ])
573                )]
574                .into_iter()
575                .collect(),
576                &DEFAULT_DEPENDENCIES,
577                &ROOT_MODULE_PATH
578            )
579            .unwrap(),
580            create_simple_config(
581                [(
582                    "bar".into(),
583                    ir_explicit_build(vec!["bar".into()], Rule::new("$".into(), None), vec![])
584                        .into()
585                )]
586                .into_iter()
587                .collect(),
588                ["bar".into()].into_iter().collect()
589            )
590        );
591    }
592
593    #[test]
594    fn interpolate_escaped_variable_in_command() {
595        assert_eq!(
596            compile(
597                &[(
598                    ROOT_MODULE_PATH.clone(),
599                    ast::Module::new(vec![
600                        ast::VariableDefinition::new("x".into(), "42".into()).into(),
601                        ast_rule("foo", &[("command", "$$x")]).into(),
602                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
603                    ])
604                )]
605                .into_iter()
606                .collect(),
607                &DEFAULT_DEPENDENCIES,
608                &ROOT_MODULE_PATH
609            )
610            .unwrap(),
611            create_simple_config(
612                [(
613                    "bar".into(),
614                    ir_explicit_build(vec!["bar".into()], Rule::new("$x".into(), None), vec![])
615                        .into()
616                )]
617                .into_iter()
618                .collect(),
619                ["bar".into()].into_iter().collect()
620            )
621        );
622    }
623
624    #[test]
625    fn interpolate_nested_variable_in_command() {
626        assert_eq!(
627            compile(
628                &[(
629                    ROOT_MODULE_PATH.clone(),
630                    ast::Module::new(vec![
631                        ast::VariableDefinition::new("x".into(), "42".into()).into(),
632                        ast::VariableDefinition::new("y".into(), "$x".into()).into(),
633                        ast_rule("foo", &[("command", "$y")]).into(),
634                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
635                    ])
636                )]
637                .into_iter()
638                .collect(),
639                &DEFAULT_DEPENDENCIES,
640                &ROOT_MODULE_PATH
641            )
642            .unwrap(),
643            create_simple_config(
644                [(
645                    "bar".into(),
646                    ir_explicit_build(vec!["bar".into()], Rule::new("42".into(), None), vec![])
647                        .into()
648                )]
649                .into_iter()
650                .collect(),
651                ["bar".into()].into_iter().collect()
652            )
653        );
654    }
655
656    #[test]
657    fn expand_variable_on_definition() {
658        assert_eq!(
659            compile(
660                &[(
661                    ROOT_MODULE_PATH.clone(),
662                    ast::Module::new(vec![
663                        ast::VariableDefinition::new("x".into(), "1".into()).into(),
664                        ast::VariableDefinition::new("y".into(), "$x".into()).into(),
665                        ast::VariableDefinition::new("x".into(), "2".into()).into(),
666                        ast_rule("foo", &[("command", "$y")]).into(),
667                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
668                    ])
669                )]
670                .into_iter()
671                .collect(),
672                &DEFAULT_DEPENDENCIES,
673                &ROOT_MODULE_PATH
674            )
675            .unwrap(),
676            create_simple_config(
677                [(
678                    "bar".into(),
679                    ir_explicit_build(vec!["bar".into()], Rule::new("1".into(), None), vec![])
680                        .into()
681                )]
682                .into_iter()
683                .collect(),
684                ["bar".into()].into_iter().collect()
685            )
686        );
687    }
688
689    #[test]
690    fn append_value_to_variable() {
691        assert_eq!(
692            compile(
693                &[(
694                    ROOT_MODULE_PATH.clone(),
695                    ast::Module::new(vec![
696                        ast::VariableDefinition::new("x".into(), "1".into()).into(),
697                        ast::VariableDefinition::new("x".into(), "$x 2".into()).into(),
698                        ast_rule("foo", &[("command", "$x")]).into(),
699                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
700                    ])
701                )]
702                .into_iter()
703                .collect(),
704                &DEFAULT_DEPENDENCIES,
705                &ROOT_MODULE_PATH
706            )
707            .unwrap(),
708            create_simple_config(
709                [(
710                    "bar".into(),
711                    ir_explicit_build(vec!["bar".into()], Rule::new("1 2".into(), None), vec![])
712                        .into()
713                )]
714                .into_iter()
715                .collect(),
716                ["bar".into()].into_iter().collect()
717            )
718        );
719    }
720
721    #[test]
722    fn expand_escaped_variable_on_definition() {
723        assert_eq!(
724            compile(
725                &[(
726                    ROOT_MODULE_PATH.clone(),
727                    ast::Module::new(vec![
728                        ast::VariableDefinition::new("y".into(), "42".into()).into(),
729                        ast::VariableDefinition::new("x".into(), "$$y".into()).into(),
730                        ast_rule("foo", &[("command", "$x")]).into(),
731                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
732                    ])
733                )]
734                .into_iter()
735                .collect(),
736                &DEFAULT_DEPENDENCIES,
737                &ROOT_MODULE_PATH
738            )
739            .unwrap(),
740            create_simple_config(
741                [(
742                    "bar".into(),
743                    ir_explicit_build(vec!["bar".into()], Rule::new("$y".into(), None), vec![])
744                        .into()
745                )]
746                .into_iter()
747                .collect(),
748                ["bar".into()].into_iter().collect()
749            )
750        );
751    }
752
753    #[test]
754    fn unescape_dollar_signs_once() {
755        assert_eq!(
756            compile(
757                &[(
758                    ROOT_MODULE_PATH.clone(),
759                    ast::Module::new(vec![
760                        ast::VariableDefinition::new("x".into(), "$$$$".into()).into(),
761                        ast_rule("foo", &[("command", "$x")]).into(),
762                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
763                    ])
764                )]
765                .into_iter()
766                .collect(),
767                &DEFAULT_DEPENDENCIES,
768                &ROOT_MODULE_PATH
769            )
770            .unwrap(),
771            create_simple_config(
772                [(
773                    "bar".into(),
774                    ir_explicit_build(vec!["bar".into()], Rule::new("$$".into(), None), vec![])
775                        .into()
776                )]
777                .into_iter()
778                .collect(),
779                ["bar".into()].into_iter().collect()
780            )
781        );
782    }
783
784    #[test]
785    fn interpolate_in_variable_in_command() {
786        assert_eq!(
787            compile(
788                &[(
789                    ROOT_MODULE_PATH.clone(),
790                    ast::Module::new(vec![
791                        ast_rule("foo", &[("command", "$in")]).into(),
792                        ast_explicit_build(
793                            vec!["bar".into()],
794                            "foo".into(),
795                            vec!["baz".into()],
796                            vec![]
797                        )
798                        .into(),
799                    ])
800                )]
801                .into_iter()
802                .collect(),
803                &DEFAULT_DEPENDENCIES,
804                &ROOT_MODULE_PATH
805            )
806            .unwrap(),
807            create_simple_config(
808                [(
809                    "bar".into(),
810                    ir_explicit_build(
811                        vec!["bar".into()],
812                        Rule::new("baz".into(), None),
813                        vec!["baz".into()]
814                    )
815                    .into()
816                )]
817                .into_iter()
818                .collect(),
819                ["bar".into()].into_iter().collect()
820            )
821        );
822    }
823
824    #[test]
825    fn interpolate_in_variable_with_implicit_input() {
826        assert_eq!(
827            compile(
828                &[(
829                    ROOT_MODULE_PATH.clone(),
830                    ast::Module::new(vec![
831                        ast_rule("foo", &[("command", "$in")]).into(),
832                        ast::Build::new(
833                            vec!["bar".into()],
834                            vec![],
835                            "foo".into(),
836                            vec!["baz".into()],
837                            vec!["blah".into()],
838                            vec![],
839                            vec![]
840                        )
841                        .into(),
842                    ])
843                )]
844                .into_iter()
845                .collect(),
846                &DEFAULT_DEPENDENCIES,
847                &ROOT_MODULE_PATH
848            )
849            .unwrap(),
850            create_simple_config(
851                [(
852                    "bar".into(),
853                    ir_explicit_build(
854                        vec!["bar".into()],
855                        Rule::new("baz".into(), None),
856                        vec!["baz".into(), "blah".into()]
857                    )
858                    .into()
859                )]
860                .into_iter()
861                .collect(),
862                ["bar".into()].into_iter().collect()
863            )
864        );
865    }
866
867    #[test]
868    fn interpolate_in_newline_variable_in_command() {
869        assert_eq!(
870            compile(
871                &[(
872                    ROOT_MODULE_PATH.clone(),
873                    ast::Module::new(vec![
874                        ast_rule("foo", &[("command", "$in_newline")]).into(),
875                        ast_explicit_build(
876                            vec!["bar".into()],
877                            "foo".into(),
878                            vec!["baz".into(), "qux".into()],
879                            vec![]
880                        )
881                        .into(),
882                    ])
883                )]
884                .into_iter()
885                .collect(),
886                &DEFAULT_DEPENDENCIES,
887                &ROOT_MODULE_PATH
888            )
889            .unwrap(),
890            create_simple_config(
891                [(
892                    "bar".into(),
893                    ir_explicit_build(
894                        vec!["bar".into()],
895                        Rule::new("baz\nqux".into(), None),
896                        vec!["baz".into(), "qux".into()]
897                    )
898                    .into()
899                )]
900                .into_iter()
901                .collect(),
902                ["bar".into()].into_iter().collect()
903            )
904        );
905    }
906
907    #[test]
908    fn interpolate_in_newline_variable_with_implicit_and_order_only_inputs() {
909        assert_eq!(
910            compile(
911                &[(
912                    ROOT_MODULE_PATH.clone(),
913                    ast::Module::new(vec![
914                        ast_rule("foo", &[("command", "$in_newline")]).into(),
915                        ast::Build::new(
916                            vec!["bar".into()],
917                            vec![],
918                            "foo".into(),
919                            vec!["baz".into(), "qux".into()],
920                            vec!["blah".into()],
921                            vec!["corge".into()],
922                            vec![]
923                        )
924                        .into(),
925                    ])
926                )]
927                .into_iter()
928                .collect(),
929                &DEFAULT_DEPENDENCIES,
930                &ROOT_MODULE_PATH
931            )
932            .unwrap(),
933            create_simple_config(
934                [(
935                    "bar".into(),
936                    Build::new(
937                        vec!["bar".into()],
938                        vec![],
939                        Some(Rule::new("baz\nqux".into(), None)),
940                        vec!["baz".into(), "qux".into(), "blah".into()],
941                        vec!["corge".into()],
942                        None
943                    )
944                    .into()
945                )]
946                .into_iter()
947                .collect(),
948                ["bar".into()].into_iter().collect()
949            )
950        );
951    }
952
953    #[test]
954    fn interpolate_out_variable_in_command() {
955        assert_eq!(
956            compile(
957                &[(
958                    ROOT_MODULE_PATH.clone(),
959                    ast::Module::new(vec![
960                        ast_rule("foo", &[("command", "$out")]).into(),
961                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
962                    ])
963                )]
964                .into_iter()
965                .collect(),
966                &DEFAULT_DEPENDENCIES,
967                &ROOT_MODULE_PATH
968            )
969            .unwrap(),
970            create_simple_config(
971                [(
972                    "bar".into(),
973                    ir_explicit_build(vec!["bar".into()], Rule::new("bar".into(), None), vec![])
974                        .into()
975                )]
976                .into_iter()
977                .collect(),
978                ["bar".into()].into_iter().collect()
979            )
980        );
981    }
982
983    #[test]
984    fn interpolate_out_variable_with_implicit_output() {
985        let build = Arc::new(Build::new(
986            vec!["bar".into()],
987            vec!["baz".into()],
988            Rule::new("bar".into(), None).into(),
989            vec![],
990            vec![],
991            None,
992        ));
993
994        assert_eq!(
995            compile(
996                &[(
997                    ROOT_MODULE_PATH.clone(),
998                    ast::Module::new(vec![
999                        ast_rule("foo", &[("command", "$out")]).into(),
1000                        ast::Build::new(
1001                            vec!["bar".into()],
1002                            vec!["baz".into()],
1003                            "foo".into(),
1004                            vec![],
1005                            vec![],
1006                            vec![],
1007                            vec![]
1008                        )
1009                        .into(),
1010                    ])
1011                )]
1012                .into_iter()
1013                .collect(),
1014                &DEFAULT_DEPENDENCIES,
1015                &ROOT_MODULE_PATH
1016            )
1017            .unwrap(),
1018            create_simple_config(
1019                [("baz".into(), build.clone()), ("bar".into(), build)]
1020                    .into_iter()
1021                    .collect(),
1022                ["baz".into(), "bar".into()].into_iter().collect()
1023            )
1024        );
1025    }
1026
1027    #[test]
1028    fn compile_order_only_inputs() {
1029        assert_eq!(
1030            compile(
1031                &[(
1032                    ROOT_MODULE_PATH.clone(),
1033                    ast::Module::new(vec![
1034                        ast_rule("foo", &[("command", "$in")]).into(),
1035                        ast::Build::new(
1036                            vec!["bar".into()],
1037                            vec![],
1038                            "foo".into(),
1039                            vec![],
1040                            vec![],
1041                            vec!["baz".into()],
1042                            vec![]
1043                        )
1044                        .into(),
1045                    ])
1046                )]
1047                .into_iter()
1048                .collect(),
1049                &DEFAULT_DEPENDENCIES,
1050                &ROOT_MODULE_PATH
1051            )
1052            .unwrap(),
1053            create_simple_config(
1054                [(
1055                    "bar".into(),
1056                    Build::new(
1057                        vec!["bar".into()],
1058                        vec![],
1059                        Some(Rule::new("".into(), None)),
1060                        vec![],
1061                        vec!["baz".into()],
1062                        None
1063                    )
1064                    .into()
1065                )]
1066                .into_iter()
1067                .collect(),
1068                ["bar".into()].into_iter().collect()
1069            )
1070        );
1071    }
1072
1073    #[test]
1074    fn interpolate_variables_in_paths() {
1075        let build = Arc::new(Build::new(
1076            vec!["foo/output".into()],
1077            vec!["foo/implicit_output".into()],
1078            Rule::new("foo/input foo/output".into(), None).into(),
1079            vec!["foo/input".into(), "foo/implicit_input".into()],
1080            vec!["foo/order_only_input".into()],
1081            None,
1082        ));
1083
1084        assert_eq!(
1085            compile(
1086                &[(
1087                    ROOT_MODULE_PATH.clone(),
1088                    ast::Module::new(vec![
1089                        ast::VariableDefinition::new("x".into(), "foo".into()).into(),
1090                        ast_rule("bar", &[("command", "$in $out")]).into(),
1091                        ast::Build::new(
1092                            vec!["$x/output".into()],
1093                            vec!["${x}/implicit_output".into()],
1094                            "bar".into(),
1095                            vec!["$x/input".into()],
1096                            vec!["${x}/implicit_input".into()],
1097                            vec!["$x/order_only_input".into()],
1098                            vec![]
1099                        )
1100                        .into(),
1101                    ])
1102                )]
1103                .into_iter()
1104                .collect(),
1105                &DEFAULT_DEPENDENCIES,
1106                &ROOT_MODULE_PATH
1107            )
1108            .unwrap(),
1109            create_simple_config(
1110                [
1111                    ("foo/output".into(), build.clone()),
1112                    ("foo/implicit_output".into(), build)
1113                ]
1114                .into_iter()
1115                .collect(),
1116                ["foo/output".into(), "foo/implicit_output".into()]
1117                    .into_iter()
1118                    .collect()
1119            )
1120        );
1121    }
1122
1123    #[test]
1124    fn interpolate_build_local_variable_in_path() {
1125        assert_eq!(
1126            compile(
1127                &[(
1128                    ROOT_MODULE_PATH.clone(),
1129                    ast::Module::new(vec![
1130                        ast_rule("foo", &[("command", "$out")]).into(),
1131                        ast_explicit_build(
1132                            vec!["$x/bar".into()],
1133                            "foo".into(),
1134                            vec![],
1135                            vec![ast::VariableDefinition::new("x".into(), "baz".into())]
1136                        )
1137                        .into(),
1138                    ])
1139                )]
1140                .into_iter()
1141                .collect(),
1142                &DEFAULT_DEPENDENCIES,
1143                &ROOT_MODULE_PATH
1144            )
1145            .unwrap(),
1146            create_simple_config(
1147                [(
1148                    "baz/bar".into(),
1149                    ir_explicit_build(
1150                        vec!["baz/bar".into()],
1151                        Rule::new("baz/bar".into(), None),
1152                        vec![]
1153                    )
1154                    .into()
1155                )]
1156                .into_iter()
1157                .collect(),
1158                ["baz/bar".into()].into_iter().collect()
1159            )
1160        );
1161    }
1162
1163    #[test]
1164    fn unescape_dollar_sign_in_path() {
1165        assert_eq!(
1166            compile(
1167                &[(
1168                    ROOT_MODULE_PATH.clone(),
1169                    ast::Module::new(vec![
1170                        ast_rule("foo", &[("command", "")]).into(),
1171                        ast_explicit_build(vec!["bar$$baz".into()], "foo".into(), vec![], vec![])
1172                            .into(),
1173                    ])
1174                )]
1175                .into_iter()
1176                .collect(),
1177                &DEFAULT_DEPENDENCIES,
1178                &ROOT_MODULE_PATH
1179            )
1180            .unwrap(),
1181            create_simple_config(
1182                [(
1183                    "bar$baz".into(),
1184                    ir_explicit_build(vec!["bar$baz".into()], Rule::new("".into(), None), vec![])
1185                        .into()
1186                )]
1187                .into_iter()
1188                .collect(),
1189                ["bar$baz".into()].into_iter().collect()
1190            )
1191        );
1192    }
1193
1194    #[test]
1195    fn do_not_interpolate_in_and_out_variables_in_paths() {
1196        assert_eq!(
1197            compile(
1198                &[(
1199                    ROOT_MODULE_PATH.clone(),
1200                    ast::Module::new(vec![
1201                        ast_rule("foo", &[("command", "$in $out")]).into(),
1202                        ast_explicit_build(
1203                            vec!["bar$out".into()],
1204                            "foo".into(),
1205                            vec!["baz$in".into()],
1206                            vec![]
1207                        )
1208                        .into(),
1209                    ])
1210                )]
1211                .into_iter()
1212                .collect(),
1213                &DEFAULT_DEPENDENCIES,
1214                &ROOT_MODULE_PATH
1215            )
1216            .unwrap(),
1217            create_simple_config(
1218                [(
1219                    "bar".into(),
1220                    ir_explicit_build(
1221                        vec!["bar".into()],
1222                        Rule::new("baz bar".into(), None),
1223                        vec!["baz".into()]
1224                    )
1225                    .into()
1226                )]
1227                .into_iter()
1228                .collect(),
1229                ["bar".into()].into_iter().collect()
1230            )
1231        );
1232    }
1233
1234    #[test]
1235    fn interpolate_variable_in_default_output() {
1236        assert_eq!(
1237            compile(
1238                &[(
1239                    ROOT_MODULE_PATH.clone(),
1240                    ast::Module::new(vec![
1241                        ast::VariableDefinition::new("x".into(), "foo".into()).into(),
1242                        ast_rule("bar", &[("command", "")]).into(),
1243                        ast_explicit_build(vec!["foo/baz".into()], "bar".into(), vec![], vec![])
1244                            .into(),
1245                        ast_explicit_build(vec!["qux".into()], "bar".into(), vec![], vec![]).into(),
1246                        ast::DefaultOutput::new(vec!["$x/baz".into()]).into(),
1247                    ])
1248                )]
1249                .into_iter()
1250                .collect(),
1251                &DEFAULT_DEPENDENCIES,
1252                &ROOT_MODULE_PATH
1253            )
1254            .unwrap(),
1255            create_simple_config(
1256                [
1257                    (
1258                        "foo/baz".into(),
1259                        ir_explicit_build(
1260                            vec!["foo/baz".into()],
1261                            Rule::new("".into(), None),
1262                            vec![]
1263                        )
1264                        .into()
1265                    ),
1266                    (
1267                        "qux".into(),
1268                        ir_explicit_build(vec!["qux".into()], Rule::new("".into(), None), vec![])
1269                            .into()
1270                    )
1271                ]
1272                .into_iter()
1273                .collect(),
1274                ["foo/baz".into()].into_iter().collect()
1275            )
1276        );
1277    }
1278
1279    #[test]
1280    fn generate_build_ids() {
1281        assert_eq!(
1282            compile(
1283                &[(
1284                    ROOT_MODULE_PATH.clone(),
1285                    ast::Module::new(vec![
1286                        ast_rule("foo", &[("command", "")]).into(),
1287                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
1288                        ast_explicit_build(vec!["baz".into()], "foo".into(), vec![], vec![]).into()
1289                    ])
1290                )]
1291                .into_iter()
1292                .collect(),
1293                &DEFAULT_DEPENDENCIES,
1294                &ROOT_MODULE_PATH
1295            )
1296            .unwrap(),
1297            create_simple_config(
1298                [
1299                    (
1300                        "bar".into(),
1301                        ir_explicit_build(vec!["bar".into()], Rule::new("".into(), None), vec![])
1302                            .into()
1303                    ),
1304                    (
1305                        "baz".into(),
1306                        ir_explicit_build(vec!["baz".into()], Rule::new("".into(), None), vec![])
1307                            .into()
1308                    )
1309                ]
1310                .into_iter()
1311                .collect(),
1312                ["bar".into(), "baz".into()].into_iter().collect()
1313            )
1314        );
1315    }
1316
1317    #[test]
1318    fn interpolate_build_local_variable() {
1319        assert_eq!(
1320            compile(
1321                &[(
1322                    ROOT_MODULE_PATH.clone(),
1323                    ast::Module::new(vec![
1324                        ast_rule("foo", &[("command", "$x")]).into(),
1325                        ast_explicit_build(
1326                            vec!["bar".into()],
1327                            "foo".into(),
1328                            vec![],
1329                            vec![ast::VariableDefinition::new("x".into(), "42".into())]
1330                        )
1331                        .into(),
1332                    ])
1333                )]
1334                .into_iter()
1335                .collect(),
1336                &DEFAULT_DEPENDENCIES,
1337                &ROOT_MODULE_PATH
1338            )
1339            .unwrap(),
1340            create_simple_config(
1341                [(
1342                    "bar".into(),
1343                    ir_explicit_build(vec!["bar".into()], Rule::new("42".into(), None), vec![])
1344                        .into()
1345                )]
1346                .into_iter()
1347                .collect(),
1348                ["bar".into()].into_iter().collect()
1349            )
1350        );
1351    }
1352
1353    #[test]
1354    fn interpolate_rule_variable_in_command() {
1355        assert_eq!(
1356            compile(
1357                &[(
1358                    ROOT_MODULE_PATH.clone(),
1359                    ast::Module::new(vec![
1360                        ast_rule(
1361                            "foo",
1362                            &[("command", "$description"), ("description", "bar")]
1363                        )
1364                        .into(),
1365                        ast_explicit_build(vec!["baz".into()], "foo".into(), vec![], vec![]).into(),
1366                    ])
1367                )]
1368                .into_iter()
1369                .collect(),
1370                &DEFAULT_DEPENDENCIES,
1371                &ROOT_MODULE_PATH
1372            )
1373            .unwrap(),
1374            create_simple_config(
1375                [(
1376                    "baz".into(),
1377                    ir_explicit_build(
1378                        vec!["baz".into()],
1379                        Rule::new("bar".into(), Some("bar".into())),
1380                        vec![]
1381                    )
1382                    .into()
1383                )]
1384                .into_iter()
1385                .collect(),
1386                ["baz".into()].into_iter().collect()
1387            )
1388        );
1389    }
1390
1391    #[test]
1392    fn compile_last_command_in_rule() {
1393        assert_eq!(
1394            compile(
1395                &[(
1396                    ROOT_MODULE_PATH.clone(),
1397                    ast::Module::new(vec![
1398                        ast_rule("foo", &[("command", "first"), ("command", "second")]).into(),
1399                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
1400                    ])
1401                )]
1402                .into_iter()
1403                .collect(),
1404                &DEFAULT_DEPENDENCIES,
1405                &ROOT_MODULE_PATH
1406            )
1407            .unwrap(),
1408            create_simple_config(
1409                [(
1410                    "bar".into(),
1411                    ir_explicit_build(vec!["bar".into()], Rule::new("second".into(), None), vec![])
1412                        .into()
1413                )]
1414                .into_iter()
1415                .collect(),
1416                ["bar".into()].into_iter().collect()
1417            )
1418        );
1419    }
1420
1421    #[test]
1422    fn compile_build_level_command_shadowing_rule_command() {
1423        assert_eq!(
1424            compile(
1425                &[(
1426                    ROOT_MODULE_PATH.clone(),
1427                    ast::Module::new(vec![
1428                        ast_rule("foo", &[("command", "rule")]).into(),
1429                        ast_explicit_build(
1430                            vec!["bar".into()],
1431                            "foo".into(),
1432                            vec![],
1433                            vec![ast::VariableDefinition::new(
1434                                "command".into(),
1435                                "build".into()
1436                            )]
1437                        )
1438                        .into(),
1439                    ])
1440                )]
1441                .into_iter()
1442                .collect(),
1443                &DEFAULT_DEPENDENCIES,
1444                &ROOT_MODULE_PATH
1445            )
1446            .unwrap(),
1447            create_simple_config(
1448                [(
1449                    "bar".into(),
1450                    ir_explicit_build(vec!["bar".into()], Rule::new("build".into(), None), vec![])
1451                        .into()
1452                )]
1453                .into_iter()
1454                .collect(),
1455                ["bar".into()].into_iter().collect()
1456            )
1457        );
1458    }
1459
1460    #[test]
1461    fn compile_global_description() {
1462        assert_eq!(
1463            compile(
1464                &[(
1465                    ROOT_MODULE_PATH.clone(),
1466                    ast::Module::new(vec![
1467                        ast::VariableDefinition::new("description".into(), "global".into()).into(),
1468                        ast_rule("foo", &[("command", "")]).into(),
1469                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
1470                    ])
1471                )]
1472                .into_iter()
1473                .collect(),
1474                &DEFAULT_DEPENDENCIES,
1475                &ROOT_MODULE_PATH
1476            )
1477            .unwrap(),
1478            create_simple_config(
1479                [(
1480                    "bar".into(),
1481                    ir_explicit_build(
1482                        vec!["bar".into()],
1483                        Rule::new("".into(), Some("global".into())),
1484                        vec![]
1485                    )
1486                    .into()
1487                )]
1488                .into_iter()
1489                .collect(),
1490                ["bar".into()].into_iter().collect()
1491            )
1492        );
1493    }
1494
1495    #[test]
1496    fn compile_source_map() {
1497        assert_eq!(
1498            compile(
1499                &[(
1500                    ROOT_MODULE_PATH.clone(),
1501                    ast::Module::new(vec![
1502                        ast_rule("foo", &[("command", "foo")]).into(),
1503                        ast_explicit_build(
1504                            vec!["bar".into()],
1505                            "foo".into(),
1506                            vec![],
1507                            vec![ast::VariableDefinition::new(
1508                                SOURCE_VARIABLE_NAME.into(),
1509                                "oh-my-src".into()
1510                            )]
1511                        )
1512                        .into(),
1513                    ])
1514                )]
1515                .into_iter()
1516                .collect(),
1517                &DEFAULT_DEPENDENCIES,
1518                &ROOT_MODULE_PATH
1519            )
1520            .unwrap(),
1521            Config::new(
1522                [(
1523                    "bar".into(),
1524                    ir_explicit_build(vec!["bar".into()], Rule::new("foo".into(), None), vec![])
1525                        .into()
1526                )]
1527                .into_iter()
1528                .collect(),
1529                ["bar".into()].into_iter().collect(),
1530                [("bar".into(), "oh-my-src".into())].into_iter().collect(),
1531                Default::default(),
1532                None,
1533            )
1534        );
1535    }
1536
1537    #[test]
1538    fn compile_phony_rule() {
1539        assert_eq!(
1540            compile(
1541                &[(
1542                    ROOT_MODULE_PATH.clone(),
1543                    ast::Module::new(vec![
1544                        ast_explicit_build(
1545                            vec!["foo".into()],
1546                            "phony".into(),
1547                            vec!["bar".into()],
1548                            vec![]
1549                        )
1550                        .into(),
1551                    ])
1552                )]
1553                .into_iter()
1554                .collect(),
1555                &DEFAULT_DEPENDENCIES,
1556                &ROOT_MODULE_PATH
1557            )
1558            .unwrap(),
1559            create_simple_config(
1560                [(
1561                    "foo".into(),
1562                    Build::new(
1563                        vec!["foo".into()],
1564                        vec![],
1565                        None,
1566                        vec!["bar".into()],
1567                        vec![],
1568                        None
1569                    )
1570                    .into()
1571                )]
1572                .into_iter()
1573                .collect(),
1574                ["foo".into()].into_iter().collect()
1575            )
1576        );
1577    }
1578
1579    #[test]
1580    fn compile_build_directory() {
1581        assert_eq!(
1582            compile(
1583                &[(
1584                    ROOT_MODULE_PATH.clone(),
1585                    ast::Module::new(vec![
1586                        ast::VariableDefinition::new("builddir".into(), "foo".into()).into()
1587                    ])
1588                )]
1589                .into_iter()
1590                .collect(),
1591                &DEFAULT_DEPENDENCIES,
1592                &ROOT_MODULE_PATH
1593            )
1594            .unwrap(),
1595            Config::new(
1596                Default::default(),
1597                Default::default(),
1598                Default::default(),
1599                Default::default(),
1600                Some("foo".into())
1601            )
1602        );
1603    }
1604
1605    #[test]
1606    fn compile_build_directory_with_variable() {
1607        assert_eq!(
1608            compile(
1609                &[(
1610                    ROOT_MODULE_PATH.clone(),
1611                    ast::Module::new(vec![
1612                        ast::VariableDefinition::new("x".into(), "foo".into()).into(),
1613                        ast::VariableDefinition::new("builddir".into(), "$x/bar".into()).into(),
1614                    ])
1615                )]
1616                .into_iter()
1617                .collect(),
1618                &DEFAULT_DEPENDENCIES,
1619                &ROOT_MODULE_PATH
1620            )
1621            .unwrap(),
1622            Config::new(
1623                Default::default(),
1624                Default::default(),
1625                Default::default(),
1626                Default::default(),
1627                Some("foo/bar".into())
1628            )
1629        );
1630    }
1631
1632    #[test]
1633    fn compile_dynamic_module_variable() {
1634        assert_eq!(
1635            compile(
1636                &[(
1637                    ROOT_MODULE_PATH.clone(),
1638                    ast::Module::new(vec![
1639                        ast_explicit_build(
1640                            vec!["foo".into()],
1641                            "phony".into(),
1642                            vec![],
1643                            vec![ast::VariableDefinition::new("dyndep".into(), "bar".into())]
1644                        )
1645                        .into()
1646                    ])
1647                )]
1648                .into_iter()
1649                .collect(),
1650                &DEFAULT_DEPENDENCIES,
1651                &ROOT_MODULE_PATH
1652            )
1653            .unwrap(),
1654            create_simple_config(
1655                [(
1656                    "foo".into(),
1657                    Build::new(
1658                        vec!["foo".into()],
1659                        vec![],
1660                        None,
1661                        vec![],
1662                        vec![],
1663                        Some("bar".into())
1664                    )
1665                    .into()
1666                )]
1667                .into_iter()
1668                .collect(),
1669                ["foo".into()].into_iter().collect()
1670            )
1671        );
1672    }
1673
1674    #[test]
1675    fn compile_rule_level_dynamic_module_variable() {
1676        assert_eq!(
1677            compile(
1678                &[(
1679                    ROOT_MODULE_PATH.clone(),
1680                    ast::Module::new(vec![
1681                        ast_rule("foo", &[("command", ""), ("dyndep", "$out.dd")]).into(),
1682                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
1683                    ])
1684                )]
1685                .into_iter()
1686                .collect(),
1687                &DEFAULT_DEPENDENCIES,
1688                &ROOT_MODULE_PATH
1689            )
1690            .unwrap(),
1691            create_simple_config(
1692                [(
1693                    "bar".into(),
1694                    Build::new(
1695                        vec!["bar".into()],
1696                        vec![],
1697                        Some(Rule::new("".into(), None)),
1698                        vec![],
1699                        vec![],
1700                        Some("bar.dd".into())
1701                    )
1702                    .into()
1703                )]
1704                .into_iter()
1705                .collect(),
1706                ["bar".into()].into_iter().collect()
1707            )
1708        );
1709    }
1710
1711    #[test]
1712    fn compile_build_level_dynamic_module_variable_shadowing_rule_level_one() {
1713        assert_eq!(
1714            compile(
1715                &[(
1716                    ROOT_MODULE_PATH.clone(),
1717                    ast::Module::new(vec![
1718                        ast_rule("foo", &[("command", ""), ("dyndep", "rule.dd")]).into(),
1719                        ast_explicit_build(
1720                            vec!["bar".into()],
1721                            "foo".into(),
1722                            vec![],
1723                            vec![ast::VariableDefinition::new(
1724                                "dyndep".into(),
1725                                "build.dd".into()
1726                            )]
1727                        )
1728                        .into(),
1729                    ])
1730                )]
1731                .into_iter()
1732                .collect(),
1733                &DEFAULT_DEPENDENCIES,
1734                &ROOT_MODULE_PATH
1735            )
1736            .unwrap(),
1737            create_simple_config(
1738                [(
1739                    "bar".into(),
1740                    Build::new(
1741                        vec!["bar".into()],
1742                        vec![],
1743                        Some(Rule::new("".into(), None)),
1744                        vec![],
1745                        vec![],
1746                        Some("build.dd".into())
1747                    )
1748                    .into()
1749                )]
1750                .into_iter()
1751                .collect(),
1752                ["bar".into()].into_iter().collect()
1753            )
1754        );
1755    }
1756
1757    #[test]
1758    fn compile_make_header_dependency() {
1759        assert_eq!(
1760            compile(
1761                &[(
1762                    ROOT_MODULE_PATH.clone(),
1763                    ast::Module::new(vec![
1764                        ast_rule("foo", &[("command", "bar"), ("depfile", "foo.d")]).into(),
1765                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
1766                    ])
1767                )]
1768                .into_iter()
1769                .collect(),
1770                &DEFAULT_DEPENDENCIES,
1771                &ROOT_MODULE_PATH
1772            )
1773            .unwrap(),
1774            create_simple_config(
1775                [(
1776                    "bar".into(),
1777                    ir_explicit_build(
1778                        vec!["bar".into()],
1779                        Rule::new("bar".into(), None).with_header_dependency(Some(
1780                            HeaderDependency::Make {
1781                                path: "foo.d".into()
1782                            }
1783                        )),
1784                        vec![]
1785                    )
1786                    .into()
1787                )]
1788                .into_iter()
1789                .collect(),
1790                ["bar".into()].into_iter().collect()
1791            )
1792        );
1793    }
1794
1795    #[test]
1796    fn compile_gcc_header_dependency() {
1797        assert_eq!(
1798            compile(
1799                &[(
1800                    ROOT_MODULE_PATH.clone(),
1801                    ast::Module::new(vec![
1802                        ast_rule(
1803                            "foo",
1804                            &[("command", "bar"), ("depfile", "foo.d"), ("deps", "gcc")]
1805                        )
1806                        .into(),
1807                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
1808                    ])
1809                )]
1810                .into_iter()
1811                .collect(),
1812                &DEFAULT_DEPENDENCIES,
1813                &ROOT_MODULE_PATH
1814            )
1815            .unwrap(),
1816            create_simple_config(
1817                [(
1818                    "bar".into(),
1819                    ir_explicit_build(
1820                        vec!["bar".into()],
1821                        Rule::new("bar".into(), None).with_header_dependency(Some(
1822                            HeaderDependency::Gcc {
1823                                path: "foo.d".into()
1824                            }
1825                        )),
1826                        vec![]
1827                    )
1828                    .into()
1829                )]
1830                .into_iter()
1831                .collect(),
1832                ["bar".into()].into_iter().collect()
1833            )
1834        );
1835    }
1836
1837    #[test]
1838    fn compile_make_header_dependency_with_build_level_depfile() {
1839        assert_eq!(
1840            compile(
1841                &[(
1842                    ROOT_MODULE_PATH.clone(),
1843                    ast::Module::new(vec![
1844                        ast_rule("foo", &[("command", "bar")]).into(),
1845                        ast_explicit_build(
1846                            vec!["bar".into()],
1847                            "foo".into(),
1848                            vec![],
1849                            vec![ast::VariableDefinition::new(
1850                                "depfile".into(),
1851                                "foo.d".into()
1852                            )]
1853                        )
1854                        .into(),
1855                    ])
1856                )]
1857                .into_iter()
1858                .collect(),
1859                &DEFAULT_DEPENDENCIES,
1860                &ROOT_MODULE_PATH
1861            )
1862            .unwrap(),
1863            create_simple_config(
1864                [(
1865                    "bar".into(),
1866                    ir_explicit_build(
1867                        vec!["bar".into()],
1868                        Rule::new("bar".into(), None).with_header_dependency(Some(
1869                            HeaderDependency::Make {
1870                                path: "foo.d".into()
1871                            }
1872                        )),
1873                        vec![]
1874                    )
1875                    .into()
1876                )]
1877                .into_iter()
1878                .collect(),
1879                ["bar".into()].into_iter().collect()
1880            )
1881        );
1882    }
1883
1884    #[test]
1885    fn compile_gcc_header_dependency_with_build_level_deps() {
1886        assert_eq!(
1887            compile(
1888                &[(
1889                    ROOT_MODULE_PATH.clone(),
1890                    ast::Module::new(vec![
1891                        ast_rule("foo", &[("command", "bar"), ("depfile", "$out.d")]).into(),
1892                        ast_explicit_build(
1893                            vec!["bar".into()],
1894                            "foo".into(),
1895                            vec![],
1896                            vec![ast::VariableDefinition::new("deps".into(), "gcc".into())]
1897                        )
1898                        .into(),
1899                    ])
1900                )]
1901                .into_iter()
1902                .collect(),
1903                &DEFAULT_DEPENDENCIES,
1904                &ROOT_MODULE_PATH
1905            )
1906            .unwrap(),
1907            create_simple_config(
1908                [(
1909                    "bar".into(),
1910                    ir_explicit_build(
1911                        vec!["bar".into()],
1912                        Rule::new("bar".into(), None).with_header_dependency(Some(
1913                            HeaderDependency::Gcc {
1914                                path: "bar.d".into()
1915                            }
1916                        )),
1917                        vec![]
1918                    )
1919                    .into()
1920                )]
1921                .into_iter()
1922                .collect(),
1923                ["bar".into()].into_iter().collect()
1924            )
1925        );
1926    }
1927
1928    #[test]
1929    fn compile_make_header_dependency_with_build_level_empty_deps_shadowing_rule_deps() {
1930        assert_eq!(
1931            compile(
1932                &[(
1933                    ROOT_MODULE_PATH.clone(),
1934                    ast::Module::new(vec![
1935                        ast_rule(
1936                            "foo",
1937                            &[("command", "bar"), ("depfile", "foo.d"), ("deps", "gcc")]
1938                        )
1939                        .into(),
1940                        ast_explicit_build(
1941                            vec!["bar".into()],
1942                            "foo".into(),
1943                            vec![],
1944                            vec![ast::VariableDefinition::new("deps".into(), "".into())]
1945                        )
1946                        .into(),
1947                    ])
1948                )]
1949                .into_iter()
1950                .collect(),
1951                &DEFAULT_DEPENDENCIES,
1952                &ROOT_MODULE_PATH
1953            )
1954            .unwrap(),
1955            create_simple_config(
1956                [(
1957                    "bar".into(),
1958                    ir_explicit_build(
1959                        vec!["bar".into()],
1960                        Rule::new("bar".into(), None).with_header_dependency(Some(
1961                            HeaderDependency::Make {
1962                                path: "foo.d".into()
1963                            }
1964                        )),
1965                        vec![]
1966                    )
1967                    .into()
1968                )]
1969                .into_iter()
1970                .collect(),
1971                ["bar".into()].into_iter().collect()
1972            )
1973        );
1974    }
1975
1976    #[test]
1977    fn fail_to_compile_gcc_header_dependency_without_depfile() {
1978        assert_eq!(
1979            compile(
1980                &[(
1981                    ROOT_MODULE_PATH.clone(),
1982                    ast::Module::new(vec![
1983                        ast_rule("foo", &[("command", "bar"), ("deps", "gcc")]).into(),
1984                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
1985                    ])
1986                )]
1987                .into_iter()
1988                .collect(),
1989                &DEFAULT_DEPENDENCIES,
1990                &ROOT_MODULE_PATH
1991            ),
1992            Err(CompileError::MissingDepfile("foo".into()))
1993        );
1994    }
1995
1996    #[test]
1997    fn fail_to_compile_unknown_header_dependency() {
1998        assert_eq!(
1999            compile(
2000                &[(
2001                    ROOT_MODULE_PATH.clone(),
2002                    ast::Module::new(vec![
2003                        ast_rule("foo", &[("command", "bar"), ("deps", "clang")]).into(),
2004                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
2005                    ])
2006                )]
2007                .into_iter()
2008                .collect(),
2009                &DEFAULT_DEPENDENCIES,
2010                &ROOT_MODULE_PATH
2011            ),
2012            Err(CompileError::InvalidDependencyStyle("clang".into()))
2013        );
2014    }
2015
2016    #[test]
2017    fn compile_msvc_header_dependency_with_default_prefix() {
2018        assert_eq!(
2019            compile(
2020                &[(
2021                    ROOT_MODULE_PATH.clone(),
2022                    ast::Module::new(vec![
2023                        ast_rule("foo", &[("command", "bar"), ("deps", "msvc")]).into(),
2024                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
2025                    ])
2026                )]
2027                .into_iter()
2028                .collect(),
2029                &DEFAULT_DEPENDENCIES,
2030                &ROOT_MODULE_PATH
2031            )
2032            .unwrap(),
2033            create_simple_config(
2034                [(
2035                    "bar".into(),
2036                    ir_explicit_build(
2037                        vec!["bar".into()],
2038                        Rule::new("bar".into(), None).with_header_dependency(Some(
2039                            HeaderDependency::Msvc {
2040                                prefix: "Note: including file: ".into()
2041                            }
2042                        )),
2043                        vec![]
2044                    )
2045                    .into()
2046                )]
2047                .into_iter()
2048                .collect(),
2049                ["bar".into()].into_iter().collect()
2050            )
2051        );
2052    }
2053
2054    #[test]
2055    fn compile_msvc_header_dependency_ignoring_depfile() {
2056        assert_eq!(
2057            compile(
2058                &[(
2059                    ROOT_MODULE_PATH.clone(),
2060                    ast::Module::new(vec![
2061                        ast_rule(
2062                            "foo",
2063                            &[("command", "bar"), ("depfile", "foo.d"), ("deps", "msvc")]
2064                        )
2065                        .into(),
2066                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
2067                    ])
2068                )]
2069                .into_iter()
2070                .collect(),
2071                &DEFAULT_DEPENDENCIES,
2072                &ROOT_MODULE_PATH
2073            )
2074            .unwrap(),
2075            create_simple_config(
2076                [(
2077                    "bar".into(),
2078                    ir_explicit_build(
2079                        vec!["bar".into()],
2080                        Rule::new("bar".into(), None).with_header_dependency(Some(
2081                            HeaderDependency::Msvc {
2082                                prefix: "Note: including file: ".into()
2083                            }
2084                        )),
2085                        vec![]
2086                    )
2087                    .into()
2088                )]
2089                .into_iter()
2090                .collect(),
2091                ["bar".into()].into_iter().collect()
2092            )
2093        );
2094    }
2095
2096    #[test]
2097    fn compile_msvc_header_dependency_with_empty_prefix() {
2098        assert_eq!(
2099            compile(
2100                &[(
2101                    ROOT_MODULE_PATH.clone(),
2102                    ast::Module::new(vec![
2103                        ast_rule(
2104                            "foo",
2105                            &[
2106                                ("command", "bar"),
2107                                ("deps", "msvc"),
2108                                ("msvc_deps_prefix", "")
2109                            ]
2110                        )
2111                        .into(),
2112                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
2113                    ])
2114                )]
2115                .into_iter()
2116                .collect(),
2117                &DEFAULT_DEPENDENCIES,
2118                &ROOT_MODULE_PATH
2119            )
2120            .unwrap(),
2121            create_simple_config(
2122                [(
2123                    "bar".into(),
2124                    ir_explicit_build(
2125                        vec!["bar".into()],
2126                        Rule::new("bar".into(), None).with_header_dependency(Some(
2127                            HeaderDependency::Msvc {
2128                                prefix: "Note: including file: ".into()
2129                            }
2130                        )),
2131                        vec![]
2132                    )
2133                    .into()
2134                )]
2135                .into_iter()
2136                .collect(),
2137                ["bar".into()].into_iter().collect()
2138            )
2139        );
2140    }
2141
2142    #[test]
2143    fn compile_msvc_header_dependency_with_custom_prefix() {
2144        assert_eq!(
2145            compile(
2146                &[(
2147                    ROOT_MODULE_PATH.clone(),
2148                    ast::Module::new(vec![
2149                        ast_rule(
2150                            "foo",
2151                            &[
2152                                ("command", "bar"),
2153                                ("deps", "msvc"),
2154                                ("msvc_deps_prefix", "Hinweis: Einlesen der Datei ")
2155                            ]
2156                        )
2157                        .into(),
2158                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
2159                    ])
2160                )]
2161                .into_iter()
2162                .collect(),
2163                &DEFAULT_DEPENDENCIES,
2164                &ROOT_MODULE_PATH
2165            )
2166            .unwrap(),
2167            create_simple_config(
2168                [(
2169                    "bar".into(),
2170                    ir_explicit_build(
2171                        vec!["bar".into()],
2172                        Rule::new("bar".into(), None).with_header_dependency(Some(
2173                            HeaderDependency::Msvc {
2174                                prefix: "Hinweis: Einlesen der Datei ".into()
2175                            }
2176                        )),
2177                        vec![]
2178                    )
2179                    .into()
2180                )]
2181                .into_iter()
2182                .collect(),
2183                ["bar".into()].into_iter().collect()
2184            )
2185        );
2186    }
2187
2188    #[test]
2189    fn compile_msvc_header_dependency_with_global_prefix() {
2190        assert_eq!(
2191            compile(
2192                &[(
2193                    ROOT_MODULE_PATH.clone(),
2194                    ast::Module::new(vec![
2195                        ast::VariableDefinition::new(
2196                            "msvc_deps_prefix".into(),
2197                            "Hinweis: Einlesen der Datei ".into()
2198                        )
2199                        .into(),
2200                        ast_rule("foo", &[("command", "bar"), ("deps", "msvc")]).into(),
2201                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
2202                    ])
2203                )]
2204                .into_iter()
2205                .collect(),
2206                &DEFAULT_DEPENDENCIES,
2207                &ROOT_MODULE_PATH
2208            )
2209            .unwrap(),
2210            create_simple_config(
2211                [(
2212                    "bar".into(),
2213                    ir_explicit_build(
2214                        vec!["bar".into()],
2215                        Rule::new("bar".into(), None).with_header_dependency(Some(
2216                            HeaderDependency::Msvc {
2217                                prefix: "Hinweis: Einlesen der Datei ".into()
2218                            }
2219                        )),
2220                        vec![]
2221                    )
2222                    .into()
2223                )]
2224                .into_iter()
2225                .collect(),
2226                ["bar".into()].into_iter().collect()
2227            )
2228        );
2229    }
2230
2231    #[test]
2232    fn compile_msvc_header_dependency_with_rule_prefix_shadowing_global_prefix() {
2233        assert_eq!(
2234            compile(
2235                &[(
2236                    ROOT_MODULE_PATH.clone(),
2237                    ast::Module::new(vec![
2238                        ast::VariableDefinition::new(
2239                            "msvc_deps_prefix".into(),
2240                            "global prefix: ".into()
2241                        )
2242                        .into(),
2243                        ast_rule(
2244                            "foo",
2245                            &[
2246                                ("command", "bar"),
2247                                ("deps", "msvc"),
2248                                ("msvc_deps_prefix", "rule prefix: ")
2249                            ]
2250                        )
2251                        .into(),
2252                        ast_explicit_build(vec!["bar".into()], "foo".into(), vec![], vec![]).into(),
2253                    ])
2254                )]
2255                .into_iter()
2256                .collect(),
2257                &DEFAULT_DEPENDENCIES,
2258                &ROOT_MODULE_PATH
2259            )
2260            .unwrap(),
2261            create_simple_config(
2262                [(
2263                    "bar".into(),
2264                    ir_explicit_build(
2265                        vec!["bar".into()],
2266                        Rule::new("bar".into(), None).with_header_dependency(Some(
2267                            HeaderDependency::Msvc {
2268                                prefix: "rule prefix: ".into()
2269                            }
2270                        )),
2271                        vec![]
2272                    )
2273                    .into()
2274                )]
2275                .into_iter()
2276                .collect(),
2277                ["bar".into()].into_iter().collect()
2278            )
2279        );
2280    }
2281
2282    #[test]
2283    fn compile_msvc_header_dependency_with_build_level_prefix_shadowing_rule_prefix() {
2284        assert_eq!(
2285            compile(
2286                &[(
2287                    ROOT_MODULE_PATH.clone(),
2288                    ast::Module::new(vec![
2289                        ast_rule(
2290                            "foo",
2291                            &[
2292                                ("command", "bar"),
2293                                ("deps", "msvc"),
2294                                ("msvc_deps_prefix", "rule prefix: ")
2295                            ]
2296                        )
2297                        .into(),
2298                        ast_explicit_build(
2299                            vec!["bar".into()],
2300                            "foo".into(),
2301                            vec![],
2302                            vec![ast::VariableDefinition::new(
2303                                "msvc_deps_prefix".into(),
2304                                "build prefix: ".into()
2305                            )]
2306                        )
2307                        .into(),
2308                    ])
2309                )]
2310                .into_iter()
2311                .collect(),
2312                &DEFAULT_DEPENDENCIES,
2313                &ROOT_MODULE_PATH
2314            )
2315            .unwrap(),
2316            create_simple_config(
2317                [(
2318                    "bar".into(),
2319                    ir_explicit_build(
2320                        vec!["bar".into()],
2321                        Rule::new("bar".into(), None).with_header_dependency(Some(
2322                            HeaderDependency::Msvc {
2323                                prefix: "build prefix: ".into()
2324                            }
2325                        )),
2326                        vec![]
2327                    )
2328                    .into()
2329                )]
2330                .into_iter()
2331                .collect(),
2332                ["bar".into()].into_iter().collect()
2333            )
2334        );
2335    }
2336
2337    #[test]
2338    fn compile_rule_level_pool() {
2339        assert_eq!(
2340            compile_root_module(vec![
2341                ast::Pool::new("foo".into(), "2".into()).into(),
2342                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2343                ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into(),
2344            ]),
2345            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2346        );
2347    }
2348
2349    #[test]
2350    fn compile_build_level_pool() {
2351        assert_eq!(
2352            compile_root_module(vec![
2353                ast::Pool::new("foo".into(), "2".into()).into(),
2354                ast_rule("qux", &[("command", "baz")]).into(),
2355                ast_explicit_build(
2356                    vec!["bar".into()],
2357                    "qux".into(),
2358                    vec![],
2359                    vec![ast::VariableDefinition::new("pool".into(), "foo".into())]
2360                )
2361                .into(),
2362            ]),
2363            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2364        );
2365    }
2366
2367    #[test]
2368    fn compile_build_level_pool_shadowing_rule_level_pool() {
2369        assert_eq!(
2370            compile_root_module(vec![
2371                ast::Pool::new("foo".into(), "2".into()).into(),
2372                ast_rule("qux", &[("command", "baz"), ("pool", "console")]).into(),
2373                ast_explicit_build(
2374                    vec!["bar".into()],
2375                    "qux".into(),
2376                    vec![],
2377                    vec![ast::VariableDefinition::new("pool".into(), "foo".into())]
2378                )
2379                .into(),
2380            ]),
2381            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2382        );
2383    }
2384
2385    #[test]
2386    fn compile_build_level_empty_pool_shadowing_rule_level_pool() {
2387        assert_eq!(
2388            compile_root_module(vec![
2389                ast_rule("qux", &[("command", "baz"), ("pool", "console")]).into(),
2390                ast_explicit_build(
2391                    vec!["bar".into()],
2392                    "qux".into(),
2393                    vec![],
2394                    vec![ast::VariableDefinition::new("pool".into(), "".into())]
2395                )
2396                .into(),
2397            ]),
2398            Ok(create_pool_config(None, &[]))
2399        );
2400    }
2401
2402    #[test]
2403    fn compile_console_pool() {
2404        assert_eq!(
2405            compile_root_module(vec![
2406                ast_rule("qux", &[("command", "baz"), ("pool", "console")]).into(),
2407                ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into(),
2408            ]),
2409            Ok(create_pool_config(Some(Pool::Console), &[]))
2410        );
2411    }
2412
2413    #[test]
2414    fn compile_console_pool_in_phony_build() {
2415        assert_eq!(
2416            compile_root_module(vec![
2417                ast_explicit_build(
2418                    vec!["foo".into()],
2419                    "phony".into(),
2420                    vec![],
2421                    vec![ast::VariableDefinition::new(
2422                        "pool".into(),
2423                        "console".into()
2424                    )]
2425                )
2426                .into(),
2427            ]),
2428            Ok(create_simple_config(
2429                [(
2430                    "foo".into(),
2431                    Build::new(vec!["foo".into()], vec![], None, vec![], vec![], None).into()
2432                )]
2433                .into_iter()
2434                .collect(),
2435                ["foo".into()].into_iter().collect()
2436            ))
2437        );
2438    }
2439
2440    #[test]
2441    fn compile_pool_with_zero_depth() {
2442        assert_eq!(
2443            compile_root_module(vec![
2444                ast::Pool::new("foo".into(), "0".into()).into(),
2445                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2446                ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into(),
2447            ]),
2448            Ok(create_pool_config(None, &[]))
2449        );
2450    }
2451
2452    #[test]
2453    fn compile_pool_depth_with_variable() {
2454        assert_eq!(
2455            compile_root_module(vec![
2456                ast::VariableDefinition::new("x".into(), "2".into()).into(),
2457                ast::Pool::new("foo".into(), "$x".into()).into(),
2458                ast::VariableDefinition::new("x".into(), "3".into()).into(),
2459                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2460                ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into(),
2461            ]),
2462            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2463        );
2464    }
2465
2466    #[test]
2467    fn compile_pool_depth_with_empty_variable() {
2468        assert_eq!(
2469            compile_root_module(vec![
2470                ast::VariableDefinition::new("x".into(), "".into()).into(),
2471                ast::Pool::new("foo".into(), "$x 2".into()).into(),
2472                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2473                ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into(),
2474            ]),
2475            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2476        );
2477    }
2478
2479    #[test]
2480    fn compile_pool_name_with_variable() {
2481        assert_eq!(
2482            compile_root_module(vec![
2483                ast::Pool::new("foo".into(), "2".into()).into(),
2484                ast_rule("qux", &[("command", "baz"), ("pool", "$x")]).into(),
2485                ast_explicit_build(
2486                    vec!["bar".into()],
2487                    "qux".into(),
2488                    vec![],
2489                    vec![ast::VariableDefinition::new("x".into(), "foo".into())]
2490                )
2491                .into(),
2492            ]),
2493            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2494        );
2495    }
2496
2497    #[test]
2498    fn do_not_interpolate_out_variable_in_pool() {
2499        assert_eq!(
2500            compile_root_module(vec![
2501                ast::Pool::new("bar".into(), "2".into()).into(),
2502                ast_rule("qux", &[("command", "baz"), ("pool", "$out")]).into(),
2503                ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into(),
2504            ]),
2505            Ok(create_pool_config(None, &[("bar", 2)]))
2506        );
2507    }
2508
2509    #[test]
2510    fn fail_to_compile_unknown_pool() {
2511        assert_eq!(
2512            compile_root_module(vec![
2513                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2514                ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into(),
2515            ]),
2516            Err(CompileError::PoolNotFound("foo".into()))
2517        );
2518    }
2519
2520    #[test]
2521    fn fail_to_compile_unknown_pool_in_phony_build() {
2522        assert_eq!(
2523            compile_root_module(vec![
2524                ast_explicit_build(
2525                    vec!["bar".into()],
2526                    "phony".into(),
2527                    vec![],
2528                    vec![ast::VariableDefinition::new("pool".into(), "foo".into())]
2529                )
2530                .into(),
2531            ]),
2532            Err(CompileError::PoolNotFound("foo".into()))
2533        );
2534    }
2535
2536    #[test]
2537    fn fail_to_compile_pool_declared_after_build() {
2538        assert_eq!(
2539            compile_root_module(vec![
2540                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2541                ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into(),
2542                ast::Pool::new("foo".into(), "2".into()).into(),
2543            ]),
2544            Err(CompileError::PoolNotFound("foo".into()))
2545        );
2546    }
2547
2548    #[test]
2549    fn fail_to_compile_duplicate_pool() {
2550        assert_eq!(
2551            compile_root_module(vec![
2552                ast::Pool::new("foo".into(), "1".into()).into(),
2553                ast::Pool::new("foo".into(), "2".into()).into(),
2554            ]),
2555            Err(CompileError::DuplicatePool("foo".into()))
2556        );
2557    }
2558
2559    #[test]
2560    fn fail_to_compile_duplicate_console_pool() {
2561        assert_eq!(
2562            compile_root_module(vec![ast::Pool::new("console".into(), "foo".into()).into()]),
2563            Err(CompileError::DuplicatePool("console".into()))
2564        );
2565    }
2566
2567    #[test]
2568    fn fail_to_compile_invalid_pool_depth() {
2569        for depth in ["", "foo", "-1", "1x", "99999999999999999999"] {
2570            assert_eq!(
2571                compile_root_module(vec![ast::Pool::new("foo".into(), depth.into()).into()]),
2572                Err(CompileError::InvalidPoolDepth("foo".into(), depth.into()))
2573            );
2574        }
2575    }
2576
2577    mod submodule {
2578        use super::*;
2579        use pretty_assertions::assert_eq;
2580
2581        const SUBMODULE_PATH: &str = "foo.ninja";
2582
2583        fn compile_with_submodule(
2584            statements: Vec<ast::Statement>,
2585            submodule_statements: Vec<ast::Statement>,
2586        ) -> Result<Config, CompileError> {
2587            compile(
2588                &[
2589                    (ROOT_MODULE_PATH.clone(), ast::Module::new(statements)),
2590                    (
2591                        SUBMODULE_PATH.into(),
2592                        ast::Module::new(submodule_statements),
2593                    ),
2594                ]
2595                .into_iter()
2596                .collect(),
2597                &[(
2598                    ROOT_MODULE_PATH.clone(),
2599                    [(SUBMODULE_PATH.into(), PathBuf::from(SUBMODULE_PATH))]
2600                        .into_iter()
2601                        .collect(),
2602                )]
2603                .into_iter()
2604                .collect(),
2605                &ROOT_MODULE_PATH,
2606            )
2607        }
2608
2609        #[test]
2610        fn reference_variable_in_parent_module() {
2611            assert_eq!(
2612                compile(
2613                    &[
2614                        (
2615                            ROOT_MODULE_PATH.clone(),
2616                            ast::Module::new(vec![
2617                                ast::VariableDefinition::new("x".into(), "42".into()).into(),
2618                                ast::Submodule::new(SUBMODULE_PATH.into()).into(),
2619                            ])
2620                        ),
2621                        (
2622                            SUBMODULE_PATH.into(),
2623                            ast::Module::new(vec![
2624                                ast_rule("foo", &[("command", "$x")]).into(),
2625                                ast_explicit_build(
2626                                    vec!["bar".into()],
2627                                    "foo".into(),
2628                                    vec![],
2629                                    vec![]
2630                                )
2631                                .into()
2632                            ])
2633                        )
2634                    ]
2635                    .into_iter()
2636                    .collect(),
2637                    &[(
2638                        ROOT_MODULE_PATH.clone(),
2639                        [(SUBMODULE_PATH.into(), PathBuf::from(SUBMODULE_PATH))]
2640                            .into_iter()
2641                            .collect()
2642                    )]
2643                    .into_iter()
2644                    .collect(),
2645                    &ROOT_MODULE_PATH
2646                )
2647                .unwrap(),
2648                create_simple_config(
2649                    [(
2650                        "bar".into(),
2651                        ir_explicit_build(vec!["bar".into()], Rule::new("42".into(), None), vec![])
2652                            .into()
2653                    )]
2654                    .into_iter()
2655                    .collect(),
2656                    ["bar".into()].into_iter().collect()
2657                )
2658            );
2659        }
2660
2661        #[test]
2662        fn reference_rule_in_parent_module() {
2663            assert_eq!(
2664                compile(
2665                    &[
2666                        (
2667                            ROOT_MODULE_PATH.clone(),
2668                            ast::Module::new(vec![
2669                                ast::VariableDefinition::new("x".into(), "42".into()).into(),
2670                                ast_rule("foo", &[("command", "$x")]).into(),
2671                                ast::Submodule::new(SUBMODULE_PATH.into()).into(),
2672                            ])
2673                        ),
2674                        (
2675                            SUBMODULE_PATH.into(),
2676                            ast::Module::new(vec![
2677                                ast_explicit_build(
2678                                    vec!["bar".into()],
2679                                    "foo".into(),
2680                                    vec![],
2681                                    vec![]
2682                                )
2683                                .into()
2684                            ])
2685                        )
2686                    ]
2687                    .into_iter()
2688                    .collect(),
2689                    &[(
2690                        ROOT_MODULE_PATH.clone(),
2691                        [(SUBMODULE_PATH.into(), PathBuf::from(SUBMODULE_PATH))]
2692                            .into_iter()
2693                            .collect()
2694                    )]
2695                    .into_iter()
2696                    .collect(),
2697                    &ROOT_MODULE_PATH
2698                )
2699                .unwrap(),
2700                create_simple_config(
2701                    [(
2702                        "bar".into(),
2703                        ir_explicit_build(vec!["bar".into()], Rule::new("42".into(), None), vec![])
2704                            .into()
2705                    )]
2706                    .into_iter()
2707                    .collect(),
2708                    ["bar".into()].into_iter().collect()
2709                )
2710            );
2711        }
2712
2713        #[test]
2714        fn do_not_overwrite_variable_in_parent_module() {
2715            assert_eq!(
2716                compile(
2717                    &[
2718                        (
2719                            ROOT_MODULE_PATH.clone(),
2720                            ast::Module::new(vec![
2721                                ast::VariableDefinition::new("x".into(), "42".into()).into(),
2722                                ast_rule("foo", &[("command", "$x")]).into(),
2723                                ast::Submodule::new(SUBMODULE_PATH.into()).into(),
2724                                ast_explicit_build(
2725                                    vec!["bar".into()],
2726                                    "foo".into(),
2727                                    vec![],
2728                                    vec![]
2729                                )
2730                                .into(),
2731                            ])
2732                        ),
2733                        (
2734                            SUBMODULE_PATH.into(),
2735                            ast::Module::new(vec![
2736                                ast::VariableDefinition::new("x".into(), "13".into()).into(),
2737                            ])
2738                        )
2739                    ]
2740                    .into_iter()
2741                    .collect(),
2742                    &[(
2743                        ROOT_MODULE_PATH.clone(),
2744                        [(SUBMODULE_PATH.into(), PathBuf::from(SUBMODULE_PATH))]
2745                            .into_iter()
2746                            .collect()
2747                    )]
2748                    .into_iter()
2749                    .collect(),
2750                    &ROOT_MODULE_PATH
2751                )
2752                .unwrap(),
2753                create_simple_config(
2754                    [(
2755                        "bar".into(),
2756                        ir_explicit_build(vec!["bar".into()], Rule::new("42".into(), None), vec![])
2757                            .into()
2758                    )]
2759                    .into_iter()
2760                    .collect(),
2761                    ["bar".into()].into_iter().collect()
2762                )
2763            );
2764        }
2765
2766        #[test]
2767        fn reference_pool_in_parent_module() {
2768            assert_eq!(
2769                compile_with_submodule(
2770                    vec![
2771                        ast::Pool::new("foo".into(), "2".into()).into(),
2772                        ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2773                        ast::Submodule::new(SUBMODULE_PATH.into()).into(),
2774                    ],
2775                    vec![
2776                        ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into()
2777                    ],
2778                ),
2779                Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2780            );
2781        }
2782
2783        #[test]
2784        fn reference_pool_in_child_module() {
2785            assert_eq!(
2786                compile_with_submodule(
2787                    vec![
2788                        ast::Submodule::new(SUBMODULE_PATH.into()).into(),
2789                        ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2790                        ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into(),
2791                    ],
2792                    vec![ast::Pool::new("foo".into(), "2".into()).into()],
2793                ),
2794                Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2795            );
2796        }
2797
2798        #[test]
2799        fn fail_to_reference_pool_declared_after_submodule() {
2800            assert_eq!(
2801                compile_with_submodule(
2802                    vec![
2803                        ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2804                        ast::Submodule::new(SUBMODULE_PATH.into()).into(),
2805                        ast::Pool::new("foo".into(), "2".into()).into(),
2806                    ],
2807                    vec![
2808                        ast_explicit_build(vec!["bar".into()], "qux".into(), vec![], vec![]).into()
2809                    ],
2810                ),
2811                Err(CompileError::PoolNotFound("foo".into()))
2812            );
2813        }
2814
2815        #[test]
2816        fn fail_to_declare_pool_in_submodule_referenced_twice() {
2817            assert_eq!(
2818                compile_with_submodule(
2819                    vec![
2820                        ast::Submodule::new(SUBMODULE_PATH.into()).into(),
2821                        ast::Submodule::new(SUBMODULE_PATH.into()).into(),
2822                    ],
2823                    vec![ast::Pool::new("foo".into(), "2".into()).into()],
2824                ),
2825                Err(CompileError::DuplicatePool("foo".into()))
2826            );
2827        }
2828    }
2829}