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: impl Into<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,
362            variable_definitions
363                .iter()
364                .map(|(name, value)| ast::VariableDefinition::new(*name, *value))
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", 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", "42").into(),
446                        ast_rule("foo", &[("command", "$x")]).into(),
447                        ast_explicit_build(vec!["bar".into()], "foo", 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", None), vec![]).into()
460                )]
461                .into_iter()
462                .collect(),
463                ["bar".into()].into_iter().collect()
464            )
465        );
466    }
467
468    #[test]
469    fn interpolate_two_variables_in_command() {
470        assert_eq!(
471            compile(
472                &[(
473                    ROOT_MODULE_PATH.clone(),
474                    ast::Module::new(vec![
475                        ast::VariableDefinition::new("x", "1").into(),
476                        ast::VariableDefinition::new("y", "2").into(),
477                        ast_rule("foo", &[("command", "$x $y")]).into(),
478                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
479                    ])
480                )]
481                .into_iter()
482                .collect(),
483                &DEFAULT_DEPENDENCIES,
484                &ROOT_MODULE_PATH
485            )
486            .unwrap(),
487            create_simple_config(
488                [(
489                    "bar".into(),
490                    ir_explicit_build(vec!["bar".into()], Rule::new("1 2", None), vec![]).into()
491                )]
492                .into_iter()
493                .collect(),
494                ["bar".into()].into_iter().collect()
495            )
496        );
497    }
498
499    #[test]
500    fn interpolate_variable_with_underscore_in_command() {
501        assert_eq!(
502            compile(
503                &[(
504                    ROOT_MODULE_PATH.clone(),
505                    ast::Module::new(vec![
506                        ast::VariableDefinition::new("x_y", "42").into(),
507                        ast_rule("foo", &[("command", "$x_y")]).into(),
508                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
509                    ])
510                )]
511                .into_iter()
512                .collect(),
513                &DEFAULT_DEPENDENCIES,
514                &ROOT_MODULE_PATH
515            )
516            .unwrap(),
517            create_simple_config(
518                [(
519                    "bar".into(),
520                    ir_explicit_build(vec!["bar".into()], Rule::new("42", None), vec![]).into()
521                )]
522                .into_iter()
523                .collect(),
524                ["bar".into()].into_iter().collect()
525            )
526        );
527    }
528
529    #[test]
530    fn interpolate_variable_with_braces_in_command() {
531        assert_eq!(
532            compile(
533                &[(
534                    ROOT_MODULE_PATH.clone(),
535                    ast::Module::new(vec![
536                        ast::VariableDefinition::new("x.y", "42").into(),
537                        ast_rule("foo", &[("command", "${x.y}")]).into(),
538                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
539                    ])
540                )]
541                .into_iter()
542                .collect(),
543                &DEFAULT_DEPENDENCIES,
544                &ROOT_MODULE_PATH
545            )
546            .unwrap(),
547            create_simple_config(
548                [(
549                    "bar".into(),
550                    ir_explicit_build(vec!["bar".into()], Rule::new("42", None), vec![]).into()
551                )]
552                .into_iter()
553                .collect(),
554                ["bar".into()].into_iter().collect()
555            )
556        );
557    }
558
559    #[test]
560    fn interpolate_dollar_sign_in_command() {
561        assert_eq!(
562            compile(
563                &[(
564                    ROOT_MODULE_PATH.clone(),
565                    ast::Module::new(vec![
566                        ast_rule("foo", &[("command", "$$")]).into(),
567                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into()
568                    ])
569                )]
570                .into_iter()
571                .collect(),
572                &DEFAULT_DEPENDENCIES,
573                &ROOT_MODULE_PATH
574            )
575            .unwrap(),
576            create_simple_config(
577                [(
578                    "bar".into(),
579                    ir_explicit_build(vec!["bar".into()], Rule::new("$", None), vec![]).into()
580                )]
581                .into_iter()
582                .collect(),
583                ["bar".into()].into_iter().collect()
584            )
585        );
586    }
587
588    #[test]
589    fn interpolate_escaped_variable_in_command() {
590        assert_eq!(
591            compile(
592                &[(
593                    ROOT_MODULE_PATH.clone(),
594                    ast::Module::new(vec![
595                        ast::VariableDefinition::new("x", "42").into(),
596                        ast_rule("foo", &[("command", "$$x")]).into(),
597                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
598                    ])
599                )]
600                .into_iter()
601                .collect(),
602                &DEFAULT_DEPENDENCIES,
603                &ROOT_MODULE_PATH
604            )
605            .unwrap(),
606            create_simple_config(
607                [(
608                    "bar".into(),
609                    ir_explicit_build(vec!["bar".into()], Rule::new("$x", None), vec![]).into()
610                )]
611                .into_iter()
612                .collect(),
613                ["bar".into()].into_iter().collect()
614            )
615        );
616    }
617
618    #[test]
619    fn interpolate_nested_variable_in_command() {
620        assert_eq!(
621            compile(
622                &[(
623                    ROOT_MODULE_PATH.clone(),
624                    ast::Module::new(vec![
625                        ast::VariableDefinition::new("x", "42").into(),
626                        ast::VariableDefinition::new("y", "$x").into(),
627                        ast_rule("foo", &[("command", "$y")]).into(),
628                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
629                    ])
630                )]
631                .into_iter()
632                .collect(),
633                &DEFAULT_DEPENDENCIES,
634                &ROOT_MODULE_PATH
635            )
636            .unwrap(),
637            create_simple_config(
638                [(
639                    "bar".into(),
640                    ir_explicit_build(vec!["bar".into()], Rule::new("42", None), vec![]).into()
641                )]
642                .into_iter()
643                .collect(),
644                ["bar".into()].into_iter().collect()
645            )
646        );
647    }
648
649    #[test]
650    fn expand_variable_on_definition() {
651        assert_eq!(
652            compile(
653                &[(
654                    ROOT_MODULE_PATH.clone(),
655                    ast::Module::new(vec![
656                        ast::VariableDefinition::new("x", "1").into(),
657                        ast::VariableDefinition::new("y", "$x").into(),
658                        ast::VariableDefinition::new("x", "2").into(),
659                        ast_rule("foo", &[("command", "$y")]).into(),
660                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
661                    ])
662                )]
663                .into_iter()
664                .collect(),
665                &DEFAULT_DEPENDENCIES,
666                &ROOT_MODULE_PATH
667            )
668            .unwrap(),
669            create_simple_config(
670                [(
671                    "bar".into(),
672                    ir_explicit_build(vec!["bar".into()], Rule::new("1", None), vec![]).into()
673                )]
674                .into_iter()
675                .collect(),
676                ["bar".into()].into_iter().collect()
677            )
678        );
679    }
680
681    #[test]
682    fn append_value_to_variable() {
683        assert_eq!(
684            compile(
685                &[(
686                    ROOT_MODULE_PATH.clone(),
687                    ast::Module::new(vec![
688                        ast::VariableDefinition::new("x", "1").into(),
689                        ast::VariableDefinition::new("x", "$x 2").into(),
690                        ast_rule("foo", &[("command", "$x")]).into(),
691                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
692                    ])
693                )]
694                .into_iter()
695                .collect(),
696                &DEFAULT_DEPENDENCIES,
697                &ROOT_MODULE_PATH
698            )
699            .unwrap(),
700            create_simple_config(
701                [(
702                    "bar".into(),
703                    ir_explicit_build(vec!["bar".into()], Rule::new("1 2", None), vec![]).into()
704                )]
705                .into_iter()
706                .collect(),
707                ["bar".into()].into_iter().collect()
708            )
709        );
710    }
711
712    #[test]
713    fn expand_escaped_variable_on_definition() {
714        assert_eq!(
715            compile(
716                &[(
717                    ROOT_MODULE_PATH.clone(),
718                    ast::Module::new(vec![
719                        ast::VariableDefinition::new("y", "42").into(),
720                        ast::VariableDefinition::new("x", "$$y").into(),
721                        ast_rule("foo", &[("command", "$x")]).into(),
722                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
723                    ])
724                )]
725                .into_iter()
726                .collect(),
727                &DEFAULT_DEPENDENCIES,
728                &ROOT_MODULE_PATH
729            )
730            .unwrap(),
731            create_simple_config(
732                [(
733                    "bar".into(),
734                    ir_explicit_build(vec!["bar".into()], Rule::new("$y", None), vec![]).into()
735                )]
736                .into_iter()
737                .collect(),
738                ["bar".into()].into_iter().collect()
739            )
740        );
741    }
742
743    #[test]
744    fn unescape_dollar_signs_once() {
745        assert_eq!(
746            compile(
747                &[(
748                    ROOT_MODULE_PATH.clone(),
749                    ast::Module::new(vec![
750                        ast::VariableDefinition::new("x", "$$$$").into(),
751                        ast_rule("foo", &[("command", "$x")]).into(),
752                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
753                    ])
754                )]
755                .into_iter()
756                .collect(),
757                &DEFAULT_DEPENDENCIES,
758                &ROOT_MODULE_PATH
759            )
760            .unwrap(),
761            create_simple_config(
762                [(
763                    "bar".into(),
764                    ir_explicit_build(vec!["bar".into()], Rule::new("$$", None), vec![]).into()
765                )]
766                .into_iter()
767                .collect(),
768                ["bar".into()].into_iter().collect()
769            )
770        );
771    }
772
773    #[test]
774    fn interpolate_in_variable_in_command() {
775        assert_eq!(
776            compile(
777                &[(
778                    ROOT_MODULE_PATH.clone(),
779                    ast::Module::new(vec![
780                        ast_rule("foo", &[("command", "$in")]).into(),
781                        ast_explicit_build(vec!["bar".into()], "foo", vec!["baz".into()], vec![])
782                            .into(),
783                    ])
784                )]
785                .into_iter()
786                .collect(),
787                &DEFAULT_DEPENDENCIES,
788                &ROOT_MODULE_PATH
789            )
790            .unwrap(),
791            create_simple_config(
792                [(
793                    "bar".into(),
794                    ir_explicit_build(
795                        vec!["bar".into()],
796                        Rule::new("baz", None),
797                        vec!["baz".into()]
798                    )
799                    .into()
800                )]
801                .into_iter()
802                .collect(),
803                ["bar".into()].into_iter().collect()
804            )
805        );
806    }
807
808    #[test]
809    fn interpolate_in_variable_with_implicit_input() {
810        assert_eq!(
811            compile(
812                &[(
813                    ROOT_MODULE_PATH.clone(),
814                    ast::Module::new(vec![
815                        ast_rule("foo", &[("command", "$in")]).into(),
816                        ast::Build::new(
817                            vec!["bar".into()],
818                            vec![],
819                            "foo",
820                            vec!["baz".into()],
821                            vec!["blah".into()],
822                            vec![],
823                            vec![]
824                        )
825                        .into(),
826                    ])
827                )]
828                .into_iter()
829                .collect(),
830                &DEFAULT_DEPENDENCIES,
831                &ROOT_MODULE_PATH
832            )
833            .unwrap(),
834            create_simple_config(
835                [(
836                    "bar".into(),
837                    ir_explicit_build(
838                        vec!["bar".into()],
839                        Rule::new("baz", None),
840                        vec!["baz".into(), "blah".into()]
841                    )
842                    .into()
843                )]
844                .into_iter()
845                .collect(),
846                ["bar".into()].into_iter().collect()
847            )
848        );
849    }
850
851    #[test]
852    fn interpolate_in_newline_variable_in_command() {
853        assert_eq!(
854            compile(
855                &[(
856                    ROOT_MODULE_PATH.clone(),
857                    ast::Module::new(vec![
858                        ast_rule("foo", &[("command", "$in_newline")]).into(),
859                        ast_explicit_build(
860                            vec!["bar".into()],
861                            "foo",
862                            vec!["baz".into(), "qux".into()],
863                            vec![]
864                        )
865                        .into(),
866                    ])
867                )]
868                .into_iter()
869                .collect(),
870                &DEFAULT_DEPENDENCIES,
871                &ROOT_MODULE_PATH
872            )
873            .unwrap(),
874            create_simple_config(
875                [(
876                    "bar".into(),
877                    ir_explicit_build(
878                        vec!["bar".into()],
879                        Rule::new("baz\nqux", None),
880                        vec!["baz".into(), "qux".into()]
881                    )
882                    .into()
883                )]
884                .into_iter()
885                .collect(),
886                ["bar".into()].into_iter().collect()
887            )
888        );
889    }
890
891    #[test]
892    fn interpolate_in_newline_variable_with_implicit_and_order_only_inputs() {
893        assert_eq!(
894            compile(
895                &[(
896                    ROOT_MODULE_PATH.clone(),
897                    ast::Module::new(vec![
898                        ast_rule("foo", &[("command", "$in_newline")]).into(),
899                        ast::Build::new(
900                            vec!["bar".into()],
901                            vec![],
902                            "foo",
903                            vec!["baz".into(), "qux".into()],
904                            vec!["blah".into()],
905                            vec!["corge".into()],
906                            vec![]
907                        )
908                        .into(),
909                    ])
910                )]
911                .into_iter()
912                .collect(),
913                &DEFAULT_DEPENDENCIES,
914                &ROOT_MODULE_PATH
915            )
916            .unwrap(),
917            create_simple_config(
918                [(
919                    "bar".into(),
920                    Build::new(
921                        vec!["bar".into()],
922                        vec![],
923                        Some(Rule::new("baz\nqux", None)),
924                        vec!["baz".into(), "qux".into(), "blah".into()],
925                        vec!["corge".into()],
926                        None
927                    )
928                    .into()
929                )]
930                .into_iter()
931                .collect(),
932                ["bar".into()].into_iter().collect()
933            )
934        );
935    }
936
937    #[test]
938    fn interpolate_out_variable_in_command() {
939        assert_eq!(
940            compile(
941                &[(
942                    ROOT_MODULE_PATH.clone(),
943                    ast::Module::new(vec![
944                        ast_rule("foo", &[("command", "$out")]).into(),
945                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
946                    ])
947                )]
948                .into_iter()
949                .collect(),
950                &DEFAULT_DEPENDENCIES,
951                &ROOT_MODULE_PATH
952            )
953            .unwrap(),
954            create_simple_config(
955                [(
956                    "bar".into(),
957                    ir_explicit_build(vec!["bar".into()], Rule::new("bar", None), vec![]).into()
958                )]
959                .into_iter()
960                .collect(),
961                ["bar".into()].into_iter().collect()
962            )
963        );
964    }
965
966    #[test]
967    fn interpolate_out_variable_with_implicit_output() {
968        let build = Arc::new(Build::new(
969            vec!["bar".into()],
970            vec!["baz".into()],
971            Rule::new("bar", None).into(),
972            vec![],
973            vec![],
974            None,
975        ));
976
977        assert_eq!(
978            compile(
979                &[(
980                    ROOT_MODULE_PATH.clone(),
981                    ast::Module::new(vec![
982                        ast_rule("foo", &[("command", "$out")]).into(),
983                        ast::Build::new(
984                            vec!["bar".into()],
985                            vec!["baz".into()],
986                            "foo",
987                            vec![],
988                            vec![],
989                            vec![],
990                            vec![]
991                        )
992                        .into(),
993                    ])
994                )]
995                .into_iter()
996                .collect(),
997                &DEFAULT_DEPENDENCIES,
998                &ROOT_MODULE_PATH
999            )
1000            .unwrap(),
1001            create_simple_config(
1002                [("baz".into(), build.clone()), ("bar".into(), build)]
1003                    .into_iter()
1004                    .collect(),
1005                ["baz".into(), "bar".into()].into_iter().collect()
1006            )
1007        );
1008    }
1009
1010    #[test]
1011    fn compile_order_only_inputs() {
1012        assert_eq!(
1013            compile(
1014                &[(
1015                    ROOT_MODULE_PATH.clone(),
1016                    ast::Module::new(vec![
1017                        ast_rule("foo", &[("command", "$in")]).into(),
1018                        ast::Build::new(
1019                            vec!["bar".into()],
1020                            vec![],
1021                            "foo",
1022                            vec![],
1023                            vec![],
1024                            vec!["baz".into()],
1025                            vec![]
1026                        )
1027                        .into(),
1028                    ])
1029                )]
1030                .into_iter()
1031                .collect(),
1032                &DEFAULT_DEPENDENCIES,
1033                &ROOT_MODULE_PATH
1034            )
1035            .unwrap(),
1036            create_simple_config(
1037                [(
1038                    "bar".into(),
1039                    Build::new(
1040                        vec!["bar".into()],
1041                        vec![],
1042                        Some(Rule::new("", None)),
1043                        vec![],
1044                        vec!["baz".into()],
1045                        None
1046                    )
1047                    .into()
1048                )]
1049                .into_iter()
1050                .collect(),
1051                ["bar".into()].into_iter().collect()
1052            )
1053        );
1054    }
1055
1056    #[test]
1057    fn interpolate_variables_in_paths() {
1058        let build = Arc::new(Build::new(
1059            vec!["foo/output".into()],
1060            vec!["foo/implicit_output".into()],
1061            Rule::new("foo/input foo/output", None).into(),
1062            vec!["foo/input".into(), "foo/implicit_input".into()],
1063            vec!["foo/order_only_input".into()],
1064            None,
1065        ));
1066
1067        assert_eq!(
1068            compile(
1069                &[(
1070                    ROOT_MODULE_PATH.clone(),
1071                    ast::Module::new(vec![
1072                        ast::VariableDefinition::new("x", "foo").into(),
1073                        ast_rule("bar", &[("command", "$in $out")]).into(),
1074                        ast::Build::new(
1075                            vec!["$x/output".into()],
1076                            vec!["${x}/implicit_output".into()],
1077                            "bar",
1078                            vec!["$x/input".into()],
1079                            vec!["${x}/implicit_input".into()],
1080                            vec!["$x/order_only_input".into()],
1081                            vec![]
1082                        )
1083                        .into(),
1084                    ])
1085                )]
1086                .into_iter()
1087                .collect(),
1088                &DEFAULT_DEPENDENCIES,
1089                &ROOT_MODULE_PATH
1090            )
1091            .unwrap(),
1092            create_simple_config(
1093                [
1094                    ("foo/output".into(), build.clone()),
1095                    ("foo/implicit_output".into(), build)
1096                ]
1097                .into_iter()
1098                .collect(),
1099                ["foo/output".into(), "foo/implicit_output".into()]
1100                    .into_iter()
1101                    .collect()
1102            )
1103        );
1104    }
1105
1106    #[test]
1107    fn interpolate_build_local_variable_in_path() {
1108        assert_eq!(
1109            compile(
1110                &[(
1111                    ROOT_MODULE_PATH.clone(),
1112                    ast::Module::new(vec![
1113                        ast_rule("foo", &[("command", "$out")]).into(),
1114                        ast_explicit_build(
1115                            vec!["$x/bar".into()],
1116                            "foo",
1117                            vec![],
1118                            vec![ast::VariableDefinition::new("x", "baz")]
1119                        )
1120                        .into(),
1121                    ])
1122                )]
1123                .into_iter()
1124                .collect(),
1125                &DEFAULT_DEPENDENCIES,
1126                &ROOT_MODULE_PATH
1127            )
1128            .unwrap(),
1129            create_simple_config(
1130                [(
1131                    "baz/bar".into(),
1132                    ir_explicit_build(vec!["baz/bar".into()], Rule::new("baz/bar", None), vec![])
1133                        .into()
1134                )]
1135                .into_iter()
1136                .collect(),
1137                ["baz/bar".into()].into_iter().collect()
1138            )
1139        );
1140    }
1141
1142    #[test]
1143    fn unescape_dollar_sign_in_path() {
1144        assert_eq!(
1145            compile(
1146                &[(
1147                    ROOT_MODULE_PATH.clone(),
1148                    ast::Module::new(vec![
1149                        ast_rule("foo", &[("command", "")]).into(),
1150                        ast_explicit_build(vec!["bar$$baz".into()], "foo", vec![], vec![]).into(),
1151                    ])
1152                )]
1153                .into_iter()
1154                .collect(),
1155                &DEFAULT_DEPENDENCIES,
1156                &ROOT_MODULE_PATH
1157            )
1158            .unwrap(),
1159            create_simple_config(
1160                [(
1161                    "bar$baz".into(),
1162                    ir_explicit_build(vec!["bar$baz".into()], Rule::new("", None), vec![]).into()
1163                )]
1164                .into_iter()
1165                .collect(),
1166                ["bar$baz".into()].into_iter().collect()
1167            )
1168        );
1169    }
1170
1171    #[test]
1172    fn do_not_interpolate_in_and_out_variables_in_paths() {
1173        assert_eq!(
1174            compile(
1175                &[(
1176                    ROOT_MODULE_PATH.clone(),
1177                    ast::Module::new(vec![
1178                        ast_rule("foo", &[("command", "$in $out")]).into(),
1179                        ast_explicit_build(
1180                            vec!["bar$out".into()],
1181                            "foo",
1182                            vec!["baz$in".into()],
1183                            vec![]
1184                        )
1185                        .into(),
1186                    ])
1187                )]
1188                .into_iter()
1189                .collect(),
1190                &DEFAULT_DEPENDENCIES,
1191                &ROOT_MODULE_PATH
1192            )
1193            .unwrap(),
1194            create_simple_config(
1195                [(
1196                    "bar".into(),
1197                    ir_explicit_build(
1198                        vec!["bar".into()],
1199                        Rule::new("baz bar", None),
1200                        vec!["baz".into()]
1201                    )
1202                    .into()
1203                )]
1204                .into_iter()
1205                .collect(),
1206                ["bar".into()].into_iter().collect()
1207            )
1208        );
1209    }
1210
1211    #[test]
1212    fn interpolate_variable_in_default_output() {
1213        assert_eq!(
1214            compile(
1215                &[(
1216                    ROOT_MODULE_PATH.clone(),
1217                    ast::Module::new(vec![
1218                        ast::VariableDefinition::new("x", "foo").into(),
1219                        ast_rule("bar", &[("command", "")]).into(),
1220                        ast_explicit_build(vec!["foo/baz".into()], "bar", vec![], vec![]).into(),
1221                        ast_explicit_build(vec!["qux".into()], "bar", vec![], vec![]).into(),
1222                        ast::DefaultOutput::new(vec!["$x/baz".into()]).into(),
1223                    ])
1224                )]
1225                .into_iter()
1226                .collect(),
1227                &DEFAULT_DEPENDENCIES,
1228                &ROOT_MODULE_PATH
1229            )
1230            .unwrap(),
1231            create_simple_config(
1232                [
1233                    (
1234                        "foo/baz".into(),
1235                        ir_explicit_build(vec!["foo/baz".into()], Rule::new("", None), vec![])
1236                            .into()
1237                    ),
1238                    (
1239                        "qux".into(),
1240                        ir_explicit_build(vec!["qux".into()], Rule::new("", None), vec![]).into()
1241                    )
1242                ]
1243                .into_iter()
1244                .collect(),
1245                ["foo/baz".into()].into_iter().collect()
1246            )
1247        );
1248    }
1249
1250    #[test]
1251    fn generate_build_ids() {
1252        assert_eq!(
1253            compile(
1254                &[(
1255                    ROOT_MODULE_PATH.clone(),
1256                    ast::Module::new(vec![
1257                        ast_rule("foo", &[("command", "")]).into(),
1258                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
1259                        ast_explicit_build(vec!["baz".into()], "foo", vec![], vec![]).into()
1260                    ])
1261                )]
1262                .into_iter()
1263                .collect(),
1264                &DEFAULT_DEPENDENCIES,
1265                &ROOT_MODULE_PATH
1266            )
1267            .unwrap(),
1268            create_simple_config(
1269                [
1270                    (
1271                        "bar".into(),
1272                        ir_explicit_build(vec!["bar".into()], Rule::new("", None), vec![]).into()
1273                    ),
1274                    (
1275                        "baz".into(),
1276                        ir_explicit_build(vec!["baz".into()], Rule::new("", None), vec![]).into()
1277                    )
1278                ]
1279                .into_iter()
1280                .collect(),
1281                ["bar".into(), "baz".into()].into_iter().collect()
1282            )
1283        );
1284    }
1285
1286    #[test]
1287    fn interpolate_build_local_variable() {
1288        assert_eq!(
1289            compile(
1290                &[(
1291                    ROOT_MODULE_PATH.clone(),
1292                    ast::Module::new(vec![
1293                        ast_rule("foo", &[("command", "$x")]).into(),
1294                        ast_explicit_build(
1295                            vec!["bar".into()],
1296                            "foo",
1297                            vec![],
1298                            vec![ast::VariableDefinition::new("x", "42")]
1299                        )
1300                        .into(),
1301                    ])
1302                )]
1303                .into_iter()
1304                .collect(),
1305                &DEFAULT_DEPENDENCIES,
1306                &ROOT_MODULE_PATH
1307            )
1308            .unwrap(),
1309            create_simple_config(
1310                [(
1311                    "bar".into(),
1312                    ir_explicit_build(vec!["bar".into()], Rule::new("42", None), vec![]).into()
1313                )]
1314                .into_iter()
1315                .collect(),
1316                ["bar".into()].into_iter().collect()
1317            )
1318        );
1319    }
1320
1321    #[test]
1322    fn interpolate_rule_variable_in_command() {
1323        assert_eq!(
1324            compile(
1325                &[(
1326                    ROOT_MODULE_PATH.clone(),
1327                    ast::Module::new(vec![
1328                        ast_rule(
1329                            "foo",
1330                            &[("command", "$description"), ("description", "bar")]
1331                        )
1332                        .into(),
1333                        ast_explicit_build(vec!["baz".into()], "foo", vec![], vec![]).into(),
1334                    ])
1335                )]
1336                .into_iter()
1337                .collect(),
1338                &DEFAULT_DEPENDENCIES,
1339                &ROOT_MODULE_PATH
1340            )
1341            .unwrap(),
1342            create_simple_config(
1343                [(
1344                    "baz".into(),
1345                    ir_explicit_build(
1346                        vec!["baz".into()],
1347                        Rule::new("bar", Some("bar".into())),
1348                        vec![]
1349                    )
1350                    .into()
1351                )]
1352                .into_iter()
1353                .collect(),
1354                ["baz".into()].into_iter().collect()
1355            )
1356        );
1357    }
1358
1359    #[test]
1360    fn compile_last_command_in_rule() {
1361        assert_eq!(
1362            compile(
1363                &[(
1364                    ROOT_MODULE_PATH.clone(),
1365                    ast::Module::new(vec![
1366                        ast_rule("foo", &[("command", "first"), ("command", "second")]).into(),
1367                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
1368                    ])
1369                )]
1370                .into_iter()
1371                .collect(),
1372                &DEFAULT_DEPENDENCIES,
1373                &ROOT_MODULE_PATH
1374            )
1375            .unwrap(),
1376            create_simple_config(
1377                [(
1378                    "bar".into(),
1379                    ir_explicit_build(vec!["bar".into()], Rule::new("second", None), vec![]).into()
1380                )]
1381                .into_iter()
1382                .collect(),
1383                ["bar".into()].into_iter().collect()
1384            )
1385        );
1386    }
1387
1388    #[test]
1389    fn compile_build_level_command_shadowing_rule_command() {
1390        assert_eq!(
1391            compile(
1392                &[(
1393                    ROOT_MODULE_PATH.clone(),
1394                    ast::Module::new(vec![
1395                        ast_rule("foo", &[("command", "rule")]).into(),
1396                        ast_explicit_build(
1397                            vec!["bar".into()],
1398                            "foo",
1399                            vec![],
1400                            vec![ast::VariableDefinition::new("command", "build")]
1401                        )
1402                        .into(),
1403                    ])
1404                )]
1405                .into_iter()
1406                .collect(),
1407                &DEFAULT_DEPENDENCIES,
1408                &ROOT_MODULE_PATH
1409            )
1410            .unwrap(),
1411            create_simple_config(
1412                [(
1413                    "bar".into(),
1414                    ir_explicit_build(vec!["bar".into()], Rule::new("build", None), vec![]).into()
1415                )]
1416                .into_iter()
1417                .collect(),
1418                ["bar".into()].into_iter().collect()
1419            )
1420        );
1421    }
1422
1423    #[test]
1424    fn compile_global_description() {
1425        assert_eq!(
1426            compile(
1427                &[(
1428                    ROOT_MODULE_PATH.clone(),
1429                    ast::Module::new(vec![
1430                        ast::VariableDefinition::new("description", "global").into(),
1431                        ast_rule("foo", &[("command", "")]).into(),
1432                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
1433                    ])
1434                )]
1435                .into_iter()
1436                .collect(),
1437                &DEFAULT_DEPENDENCIES,
1438                &ROOT_MODULE_PATH
1439            )
1440            .unwrap(),
1441            create_simple_config(
1442                [(
1443                    "bar".into(),
1444                    ir_explicit_build(
1445                        vec!["bar".into()],
1446                        Rule::new("", Some("global".into())),
1447                        vec![]
1448                    )
1449                    .into()
1450                )]
1451                .into_iter()
1452                .collect(),
1453                ["bar".into()].into_iter().collect()
1454            )
1455        );
1456    }
1457
1458    #[test]
1459    fn compile_source_map() {
1460        assert_eq!(
1461            compile(
1462                &[(
1463                    ROOT_MODULE_PATH.clone(),
1464                    ast::Module::new(vec![
1465                        ast_rule("foo", &[("command", "foo")]).into(),
1466                        ast_explicit_build(
1467                            vec!["bar".into()],
1468                            "foo",
1469                            vec![],
1470                            vec![ast::VariableDefinition::new(
1471                                SOURCE_VARIABLE_NAME,
1472                                "oh-my-src"
1473                            )]
1474                        )
1475                        .into(),
1476                    ])
1477                )]
1478                .into_iter()
1479                .collect(),
1480                &DEFAULT_DEPENDENCIES,
1481                &ROOT_MODULE_PATH
1482            )
1483            .unwrap(),
1484            Config::new(
1485                [(
1486                    "bar".into(),
1487                    ir_explicit_build(vec!["bar".into()], Rule::new("foo", None), vec![]).into()
1488                )]
1489                .into_iter()
1490                .collect(),
1491                ["bar".into()].into_iter().collect(),
1492                [("bar".into(), "oh-my-src".into())].into_iter().collect(),
1493                Default::default(),
1494                None,
1495            )
1496        );
1497    }
1498
1499    #[test]
1500    fn compile_phony_rule() {
1501        assert_eq!(
1502            compile(
1503                &[(
1504                    ROOT_MODULE_PATH.clone(),
1505                    ast::Module::new(vec![
1506                        ast_explicit_build(vec!["foo".into()], "phony", vec!["bar".into()], vec![])
1507                            .into(),
1508                    ])
1509                )]
1510                .into_iter()
1511                .collect(),
1512                &DEFAULT_DEPENDENCIES,
1513                &ROOT_MODULE_PATH
1514            )
1515            .unwrap(),
1516            create_simple_config(
1517                [(
1518                    "foo".into(),
1519                    Build::new(
1520                        vec!["foo".into()],
1521                        vec![],
1522                        None,
1523                        vec!["bar".into()],
1524                        vec![],
1525                        None
1526                    )
1527                    .into()
1528                )]
1529                .into_iter()
1530                .collect(),
1531                ["foo".into()].into_iter().collect()
1532            )
1533        );
1534    }
1535
1536    #[test]
1537    fn compile_build_directory() {
1538        assert_eq!(
1539            compile(
1540                &[(
1541                    ROOT_MODULE_PATH.clone(),
1542                    ast::Module::new(vec![ast::VariableDefinition::new("builddir", "foo").into()])
1543                )]
1544                .into_iter()
1545                .collect(),
1546                &DEFAULT_DEPENDENCIES,
1547                &ROOT_MODULE_PATH
1548            )
1549            .unwrap(),
1550            Config::new(
1551                Default::default(),
1552                Default::default(),
1553                Default::default(),
1554                Default::default(),
1555                Some("foo".into())
1556            )
1557        );
1558    }
1559
1560    #[test]
1561    fn compile_build_directory_with_variable() {
1562        assert_eq!(
1563            compile(
1564                &[(
1565                    ROOT_MODULE_PATH.clone(),
1566                    ast::Module::new(vec![
1567                        ast::VariableDefinition::new("x", "foo").into(),
1568                        ast::VariableDefinition::new("builddir", "$x/bar").into(),
1569                    ])
1570                )]
1571                .into_iter()
1572                .collect(),
1573                &DEFAULT_DEPENDENCIES,
1574                &ROOT_MODULE_PATH
1575            )
1576            .unwrap(),
1577            Config::new(
1578                Default::default(),
1579                Default::default(),
1580                Default::default(),
1581                Default::default(),
1582                Some("foo/bar".into())
1583            )
1584        );
1585    }
1586
1587    #[test]
1588    fn compile_dynamic_module_variable() {
1589        assert_eq!(
1590            compile(
1591                &[(
1592                    ROOT_MODULE_PATH.clone(),
1593                    ast::Module::new(vec![
1594                        ast_explicit_build(
1595                            vec!["foo".into()],
1596                            "phony",
1597                            vec![],
1598                            vec![ast::VariableDefinition::new("dyndep", "bar")]
1599                        )
1600                        .into()
1601                    ])
1602                )]
1603                .into_iter()
1604                .collect(),
1605                &DEFAULT_DEPENDENCIES,
1606                &ROOT_MODULE_PATH
1607            )
1608            .unwrap(),
1609            create_simple_config(
1610                [(
1611                    "foo".into(),
1612                    Build::new(
1613                        vec!["foo".into()],
1614                        vec![],
1615                        None,
1616                        vec![],
1617                        vec![],
1618                        Some("bar".into())
1619                    )
1620                    .into()
1621                )]
1622                .into_iter()
1623                .collect(),
1624                ["foo".into()].into_iter().collect()
1625            )
1626        );
1627    }
1628
1629    #[test]
1630    fn compile_rule_level_dynamic_module_variable() {
1631        assert_eq!(
1632            compile(
1633                &[(
1634                    ROOT_MODULE_PATH.clone(),
1635                    ast::Module::new(vec![
1636                        ast_rule("foo", &[("command", ""), ("dyndep", "$out.dd")]).into(),
1637                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
1638                    ])
1639                )]
1640                .into_iter()
1641                .collect(),
1642                &DEFAULT_DEPENDENCIES,
1643                &ROOT_MODULE_PATH
1644            )
1645            .unwrap(),
1646            create_simple_config(
1647                [(
1648                    "bar".into(),
1649                    Build::new(
1650                        vec!["bar".into()],
1651                        vec![],
1652                        Some(Rule::new("", None)),
1653                        vec![],
1654                        vec![],
1655                        Some("bar.dd".into())
1656                    )
1657                    .into()
1658                )]
1659                .into_iter()
1660                .collect(),
1661                ["bar".into()].into_iter().collect()
1662            )
1663        );
1664    }
1665
1666    #[test]
1667    fn compile_build_level_dynamic_module_variable_shadowing_rule_level_one() {
1668        assert_eq!(
1669            compile(
1670                &[(
1671                    ROOT_MODULE_PATH.clone(),
1672                    ast::Module::new(vec![
1673                        ast_rule("foo", &[("command", ""), ("dyndep", "rule.dd")]).into(),
1674                        ast_explicit_build(
1675                            vec!["bar".into()],
1676                            "foo",
1677                            vec![],
1678                            vec![ast::VariableDefinition::new("dyndep", "build.dd")]
1679                        )
1680                        .into(),
1681                    ])
1682                )]
1683                .into_iter()
1684                .collect(),
1685                &DEFAULT_DEPENDENCIES,
1686                &ROOT_MODULE_PATH
1687            )
1688            .unwrap(),
1689            create_simple_config(
1690                [(
1691                    "bar".into(),
1692                    Build::new(
1693                        vec!["bar".into()],
1694                        vec![],
1695                        Some(Rule::new("", None)),
1696                        vec![],
1697                        vec![],
1698                        Some("build.dd".into())
1699                    )
1700                    .into()
1701                )]
1702                .into_iter()
1703                .collect(),
1704                ["bar".into()].into_iter().collect()
1705            )
1706        );
1707    }
1708
1709    #[test]
1710    fn compile_make_header_dependency() {
1711        assert_eq!(
1712            compile(
1713                &[(
1714                    ROOT_MODULE_PATH.clone(),
1715                    ast::Module::new(vec![
1716                        ast_rule("foo", &[("command", "bar"), ("depfile", "foo.d")]).into(),
1717                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
1718                    ])
1719                )]
1720                .into_iter()
1721                .collect(),
1722                &DEFAULT_DEPENDENCIES,
1723                &ROOT_MODULE_PATH
1724            )
1725            .unwrap(),
1726            create_simple_config(
1727                [(
1728                    "bar".into(),
1729                    ir_explicit_build(
1730                        vec!["bar".into()],
1731                        Rule::new("bar", None).with_header_dependency(Some(
1732                            HeaderDependency::Make {
1733                                path: "foo.d".into()
1734                            }
1735                        )),
1736                        vec![]
1737                    )
1738                    .into()
1739                )]
1740                .into_iter()
1741                .collect(),
1742                ["bar".into()].into_iter().collect()
1743            )
1744        );
1745    }
1746
1747    #[test]
1748    fn compile_gcc_header_dependency() {
1749        assert_eq!(
1750            compile(
1751                &[(
1752                    ROOT_MODULE_PATH.clone(),
1753                    ast::Module::new(vec![
1754                        ast_rule(
1755                            "foo",
1756                            &[("command", "bar"), ("depfile", "foo.d"), ("deps", "gcc")]
1757                        )
1758                        .into(),
1759                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
1760                    ])
1761                )]
1762                .into_iter()
1763                .collect(),
1764                &DEFAULT_DEPENDENCIES,
1765                &ROOT_MODULE_PATH
1766            )
1767            .unwrap(),
1768            create_simple_config(
1769                [(
1770                    "bar".into(),
1771                    ir_explicit_build(
1772                        vec!["bar".into()],
1773                        Rule::new("bar", None).with_header_dependency(Some(
1774                            HeaderDependency::Gcc {
1775                                path: "foo.d".into()
1776                            }
1777                        )),
1778                        vec![]
1779                    )
1780                    .into()
1781                )]
1782                .into_iter()
1783                .collect(),
1784                ["bar".into()].into_iter().collect()
1785            )
1786        );
1787    }
1788
1789    #[test]
1790    fn compile_make_header_dependency_with_build_level_depfile() {
1791        assert_eq!(
1792            compile(
1793                &[(
1794                    ROOT_MODULE_PATH.clone(),
1795                    ast::Module::new(vec![
1796                        ast_rule("foo", &[("command", "bar")]).into(),
1797                        ast_explicit_build(
1798                            vec!["bar".into()],
1799                            "foo",
1800                            vec![],
1801                            vec![ast::VariableDefinition::new("depfile", "foo.d")]
1802                        )
1803                        .into(),
1804                    ])
1805                )]
1806                .into_iter()
1807                .collect(),
1808                &DEFAULT_DEPENDENCIES,
1809                &ROOT_MODULE_PATH
1810            )
1811            .unwrap(),
1812            create_simple_config(
1813                [(
1814                    "bar".into(),
1815                    ir_explicit_build(
1816                        vec!["bar".into()],
1817                        Rule::new("bar", None).with_header_dependency(Some(
1818                            HeaderDependency::Make {
1819                                path: "foo.d".into()
1820                            }
1821                        )),
1822                        vec![]
1823                    )
1824                    .into()
1825                )]
1826                .into_iter()
1827                .collect(),
1828                ["bar".into()].into_iter().collect()
1829            )
1830        );
1831    }
1832
1833    #[test]
1834    fn compile_gcc_header_dependency_with_build_level_deps() {
1835        assert_eq!(
1836            compile(
1837                &[(
1838                    ROOT_MODULE_PATH.clone(),
1839                    ast::Module::new(vec![
1840                        ast_rule("foo", &[("command", "bar"), ("depfile", "$out.d")]).into(),
1841                        ast_explicit_build(
1842                            vec!["bar".into()],
1843                            "foo",
1844                            vec![],
1845                            vec![ast::VariableDefinition::new("deps", "gcc")]
1846                        )
1847                        .into(),
1848                    ])
1849                )]
1850                .into_iter()
1851                .collect(),
1852                &DEFAULT_DEPENDENCIES,
1853                &ROOT_MODULE_PATH
1854            )
1855            .unwrap(),
1856            create_simple_config(
1857                [(
1858                    "bar".into(),
1859                    ir_explicit_build(
1860                        vec!["bar".into()],
1861                        Rule::new("bar", None).with_header_dependency(Some(
1862                            HeaderDependency::Gcc {
1863                                path: "bar.d".into()
1864                            }
1865                        )),
1866                        vec![]
1867                    )
1868                    .into()
1869                )]
1870                .into_iter()
1871                .collect(),
1872                ["bar".into()].into_iter().collect()
1873            )
1874        );
1875    }
1876
1877    #[test]
1878    fn compile_make_header_dependency_with_build_level_empty_deps_shadowing_rule_deps() {
1879        assert_eq!(
1880            compile(
1881                &[(
1882                    ROOT_MODULE_PATH.clone(),
1883                    ast::Module::new(vec![
1884                        ast_rule(
1885                            "foo",
1886                            &[("command", "bar"), ("depfile", "foo.d"), ("deps", "gcc")]
1887                        )
1888                        .into(),
1889                        ast_explicit_build(
1890                            vec!["bar".into()],
1891                            "foo",
1892                            vec![],
1893                            vec![ast::VariableDefinition::new("deps", "")]
1894                        )
1895                        .into(),
1896                    ])
1897                )]
1898                .into_iter()
1899                .collect(),
1900                &DEFAULT_DEPENDENCIES,
1901                &ROOT_MODULE_PATH
1902            )
1903            .unwrap(),
1904            create_simple_config(
1905                [(
1906                    "bar".into(),
1907                    ir_explicit_build(
1908                        vec!["bar".into()],
1909                        Rule::new("bar", None).with_header_dependency(Some(
1910                            HeaderDependency::Make {
1911                                path: "foo.d".into()
1912                            }
1913                        )),
1914                        vec![]
1915                    )
1916                    .into()
1917                )]
1918                .into_iter()
1919                .collect(),
1920                ["bar".into()].into_iter().collect()
1921            )
1922        );
1923    }
1924
1925    #[test]
1926    fn fail_to_compile_gcc_header_dependency_without_depfile() {
1927        assert_eq!(
1928            compile(
1929                &[(
1930                    ROOT_MODULE_PATH.clone(),
1931                    ast::Module::new(vec![
1932                        ast_rule("foo", &[("command", "bar"), ("deps", "gcc")]).into(),
1933                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
1934                    ])
1935                )]
1936                .into_iter()
1937                .collect(),
1938                &DEFAULT_DEPENDENCIES,
1939                &ROOT_MODULE_PATH
1940            ),
1941            Err(CompileError::MissingDepfile("foo".into()))
1942        );
1943    }
1944
1945    #[test]
1946    fn fail_to_compile_unknown_header_dependency() {
1947        assert_eq!(
1948            compile(
1949                &[(
1950                    ROOT_MODULE_PATH.clone(),
1951                    ast::Module::new(vec![
1952                        ast_rule("foo", &[("command", "bar"), ("deps", "clang")]).into(),
1953                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
1954                    ])
1955                )]
1956                .into_iter()
1957                .collect(),
1958                &DEFAULT_DEPENDENCIES,
1959                &ROOT_MODULE_PATH
1960            ),
1961            Err(CompileError::InvalidDependencyStyle("clang".into()))
1962        );
1963    }
1964
1965    #[test]
1966    fn compile_msvc_header_dependency_with_default_prefix() {
1967        assert_eq!(
1968            compile(
1969                &[(
1970                    ROOT_MODULE_PATH.clone(),
1971                    ast::Module::new(vec![
1972                        ast_rule("foo", &[("command", "bar"), ("deps", "msvc")]).into(),
1973                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
1974                    ])
1975                )]
1976                .into_iter()
1977                .collect(),
1978                &DEFAULT_DEPENDENCIES,
1979                &ROOT_MODULE_PATH
1980            )
1981            .unwrap(),
1982            create_simple_config(
1983                [(
1984                    "bar".into(),
1985                    ir_explicit_build(
1986                        vec!["bar".into()],
1987                        Rule::new("bar", None).with_header_dependency(Some(
1988                            HeaderDependency::Msvc {
1989                                prefix: "Note: including file: ".into()
1990                            }
1991                        )),
1992                        vec![]
1993                    )
1994                    .into()
1995                )]
1996                .into_iter()
1997                .collect(),
1998                ["bar".into()].into_iter().collect()
1999            )
2000        );
2001    }
2002
2003    #[test]
2004    fn compile_msvc_header_dependency_ignoring_depfile() {
2005        assert_eq!(
2006            compile(
2007                &[(
2008                    ROOT_MODULE_PATH.clone(),
2009                    ast::Module::new(vec![
2010                        ast_rule(
2011                            "foo",
2012                            &[("command", "bar"), ("depfile", "foo.d"), ("deps", "msvc")]
2013                        )
2014                        .into(),
2015                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
2016                    ])
2017                )]
2018                .into_iter()
2019                .collect(),
2020                &DEFAULT_DEPENDENCIES,
2021                &ROOT_MODULE_PATH
2022            )
2023            .unwrap(),
2024            create_simple_config(
2025                [(
2026                    "bar".into(),
2027                    ir_explicit_build(
2028                        vec!["bar".into()],
2029                        Rule::new("bar", None).with_header_dependency(Some(
2030                            HeaderDependency::Msvc {
2031                                prefix: "Note: including file: ".into()
2032                            }
2033                        )),
2034                        vec![]
2035                    )
2036                    .into()
2037                )]
2038                .into_iter()
2039                .collect(),
2040                ["bar".into()].into_iter().collect()
2041            )
2042        );
2043    }
2044
2045    #[test]
2046    fn compile_msvc_header_dependency_with_empty_prefix() {
2047        assert_eq!(
2048            compile(
2049                &[(
2050                    ROOT_MODULE_PATH.clone(),
2051                    ast::Module::new(vec![
2052                        ast_rule(
2053                            "foo",
2054                            &[
2055                                ("command", "bar"),
2056                                ("deps", "msvc"),
2057                                ("msvc_deps_prefix", "")
2058                            ]
2059                        )
2060                        .into(),
2061                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
2062                    ])
2063                )]
2064                .into_iter()
2065                .collect(),
2066                &DEFAULT_DEPENDENCIES,
2067                &ROOT_MODULE_PATH
2068            )
2069            .unwrap(),
2070            create_simple_config(
2071                [(
2072                    "bar".into(),
2073                    ir_explicit_build(
2074                        vec!["bar".into()],
2075                        Rule::new("bar", None).with_header_dependency(Some(
2076                            HeaderDependency::Msvc {
2077                                prefix: "Note: including file: ".into()
2078                            }
2079                        )),
2080                        vec![]
2081                    )
2082                    .into()
2083                )]
2084                .into_iter()
2085                .collect(),
2086                ["bar".into()].into_iter().collect()
2087            )
2088        );
2089    }
2090
2091    #[test]
2092    fn compile_msvc_header_dependency_with_custom_prefix() {
2093        assert_eq!(
2094            compile(
2095                &[(
2096                    ROOT_MODULE_PATH.clone(),
2097                    ast::Module::new(vec![
2098                        ast_rule(
2099                            "foo",
2100                            &[
2101                                ("command", "bar"),
2102                                ("deps", "msvc"),
2103                                ("msvc_deps_prefix", "Hinweis: Einlesen der Datei ")
2104                            ]
2105                        )
2106                        .into(),
2107                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
2108                    ])
2109                )]
2110                .into_iter()
2111                .collect(),
2112                &DEFAULT_DEPENDENCIES,
2113                &ROOT_MODULE_PATH
2114            )
2115            .unwrap(),
2116            create_simple_config(
2117                [(
2118                    "bar".into(),
2119                    ir_explicit_build(
2120                        vec!["bar".into()],
2121                        Rule::new("bar", None).with_header_dependency(Some(
2122                            HeaderDependency::Msvc {
2123                                prefix: "Hinweis: Einlesen der Datei ".into()
2124                            }
2125                        )),
2126                        vec![]
2127                    )
2128                    .into()
2129                )]
2130                .into_iter()
2131                .collect(),
2132                ["bar".into()].into_iter().collect()
2133            )
2134        );
2135    }
2136
2137    #[test]
2138    fn compile_msvc_header_dependency_with_global_prefix() {
2139        assert_eq!(
2140            compile(
2141                &[(
2142                    ROOT_MODULE_PATH.clone(),
2143                    ast::Module::new(vec![
2144                        ast::VariableDefinition::new(
2145                            "msvc_deps_prefix",
2146                            "Hinweis: Einlesen der Datei "
2147                        )
2148                        .into(),
2149                        ast_rule("foo", &[("command", "bar"), ("deps", "msvc")]).into(),
2150                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
2151                    ])
2152                )]
2153                .into_iter()
2154                .collect(),
2155                &DEFAULT_DEPENDENCIES,
2156                &ROOT_MODULE_PATH
2157            )
2158            .unwrap(),
2159            create_simple_config(
2160                [(
2161                    "bar".into(),
2162                    ir_explicit_build(
2163                        vec!["bar".into()],
2164                        Rule::new("bar", None).with_header_dependency(Some(
2165                            HeaderDependency::Msvc {
2166                                prefix: "Hinweis: Einlesen der Datei ".into()
2167                            }
2168                        )),
2169                        vec![]
2170                    )
2171                    .into()
2172                )]
2173                .into_iter()
2174                .collect(),
2175                ["bar".into()].into_iter().collect()
2176            )
2177        );
2178    }
2179
2180    #[test]
2181    fn compile_msvc_header_dependency_with_rule_prefix_shadowing_global_prefix() {
2182        assert_eq!(
2183            compile(
2184                &[(
2185                    ROOT_MODULE_PATH.clone(),
2186                    ast::Module::new(vec![
2187                        ast::VariableDefinition::new("msvc_deps_prefix", "global prefix: ").into(),
2188                        ast_rule(
2189                            "foo",
2190                            &[
2191                                ("command", "bar"),
2192                                ("deps", "msvc"),
2193                                ("msvc_deps_prefix", "rule prefix: ")
2194                            ]
2195                        )
2196                        .into(),
2197                        ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![]).into(),
2198                    ])
2199                )]
2200                .into_iter()
2201                .collect(),
2202                &DEFAULT_DEPENDENCIES,
2203                &ROOT_MODULE_PATH
2204            )
2205            .unwrap(),
2206            create_simple_config(
2207                [(
2208                    "bar".into(),
2209                    ir_explicit_build(
2210                        vec!["bar".into()],
2211                        Rule::new("bar", None).with_header_dependency(Some(
2212                            HeaderDependency::Msvc {
2213                                prefix: "rule prefix: ".into()
2214                            }
2215                        )),
2216                        vec![]
2217                    )
2218                    .into()
2219                )]
2220                .into_iter()
2221                .collect(),
2222                ["bar".into()].into_iter().collect()
2223            )
2224        );
2225    }
2226
2227    #[test]
2228    fn compile_msvc_header_dependency_with_build_level_prefix_shadowing_rule_prefix() {
2229        assert_eq!(
2230            compile(
2231                &[(
2232                    ROOT_MODULE_PATH.clone(),
2233                    ast::Module::new(vec![
2234                        ast_rule(
2235                            "foo",
2236                            &[
2237                                ("command", "bar"),
2238                                ("deps", "msvc"),
2239                                ("msvc_deps_prefix", "rule prefix: ")
2240                            ]
2241                        )
2242                        .into(),
2243                        ast_explicit_build(
2244                            vec!["bar".into()],
2245                            "foo",
2246                            vec![],
2247                            vec![ast::VariableDefinition::new(
2248                                "msvc_deps_prefix",
2249                                "build prefix: "
2250                            )]
2251                        )
2252                        .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", None).with_header_dependency(Some(
2267                            HeaderDependency::Msvc {
2268                                prefix: "build 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_rule_level_pool() {
2284        assert_eq!(
2285            compile_root_module(vec![
2286                ast::Pool::new("foo", "2").into(),
2287                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2288                ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into(),
2289            ]),
2290            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2291        );
2292    }
2293
2294    #[test]
2295    fn compile_build_level_pool() {
2296        assert_eq!(
2297            compile_root_module(vec![
2298                ast::Pool::new("foo", "2").into(),
2299                ast_rule("qux", &[("command", "baz")]).into(),
2300                ast_explicit_build(
2301                    vec!["bar".into()],
2302                    "qux",
2303                    vec![],
2304                    vec![ast::VariableDefinition::new("pool", "foo")]
2305                )
2306                .into(),
2307            ]),
2308            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2309        );
2310    }
2311
2312    #[test]
2313    fn compile_build_level_pool_shadowing_rule_level_pool() {
2314        assert_eq!(
2315            compile_root_module(vec![
2316                ast::Pool::new("foo", "2").into(),
2317                ast_rule("qux", &[("command", "baz"), ("pool", "console")]).into(),
2318                ast_explicit_build(
2319                    vec!["bar".into()],
2320                    "qux",
2321                    vec![],
2322                    vec![ast::VariableDefinition::new("pool", "foo")]
2323                )
2324                .into(),
2325            ]),
2326            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2327        );
2328    }
2329
2330    #[test]
2331    fn compile_build_level_empty_pool_shadowing_rule_level_pool() {
2332        assert_eq!(
2333            compile_root_module(vec![
2334                ast_rule("qux", &[("command", "baz"), ("pool", "console")]).into(),
2335                ast_explicit_build(
2336                    vec!["bar".into()],
2337                    "qux",
2338                    vec![],
2339                    vec![ast::VariableDefinition::new("pool", "")]
2340                )
2341                .into(),
2342            ]),
2343            Ok(create_pool_config(None, &[]))
2344        );
2345    }
2346
2347    #[test]
2348    fn compile_console_pool() {
2349        assert_eq!(
2350            compile_root_module(vec![
2351                ast_rule("qux", &[("command", "baz"), ("pool", "console")]).into(),
2352                ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into(),
2353            ]),
2354            Ok(create_pool_config(Some(Pool::Console), &[]))
2355        );
2356    }
2357
2358    #[test]
2359    fn compile_console_pool_in_phony_build() {
2360        assert_eq!(
2361            compile_root_module(vec![
2362                ast_explicit_build(
2363                    vec!["foo".into()],
2364                    "phony",
2365                    vec![],
2366                    vec![ast::VariableDefinition::new("pool", "console")]
2367                )
2368                .into(),
2369            ]),
2370            Ok(create_simple_config(
2371                [(
2372                    "foo".into(),
2373                    Build::new(vec!["foo".into()], vec![], None, vec![], vec![], None).into()
2374                )]
2375                .into_iter()
2376                .collect(),
2377                ["foo".into()].into_iter().collect()
2378            ))
2379        );
2380    }
2381
2382    #[test]
2383    fn compile_pool_with_zero_depth() {
2384        assert_eq!(
2385            compile_root_module(vec![
2386                ast::Pool::new("foo", "0").into(),
2387                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2388                ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into(),
2389            ]),
2390            Ok(create_pool_config(None, &[]))
2391        );
2392    }
2393
2394    #[test]
2395    fn compile_pool_depth_with_variable() {
2396        assert_eq!(
2397            compile_root_module(vec![
2398                ast::VariableDefinition::new("x", "2").into(),
2399                ast::Pool::new("foo", "$x").into(),
2400                ast::VariableDefinition::new("x", "3").into(),
2401                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2402                ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into(),
2403            ]),
2404            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2405        );
2406    }
2407
2408    #[test]
2409    fn compile_pool_depth_with_empty_variable() {
2410        assert_eq!(
2411            compile_root_module(vec![
2412                ast::VariableDefinition::new("x", "").into(),
2413                ast::Pool::new("foo", "$x 2").into(),
2414                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2415                ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into(),
2416            ]),
2417            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2418        );
2419    }
2420
2421    #[test]
2422    fn compile_pool_name_with_variable() {
2423        assert_eq!(
2424            compile_root_module(vec![
2425                ast::Pool::new("foo", "2").into(),
2426                ast_rule("qux", &[("command", "baz"), ("pool", "$x")]).into(),
2427                ast_explicit_build(
2428                    vec!["bar".into()],
2429                    "qux",
2430                    vec![],
2431                    vec![ast::VariableDefinition::new("x", "foo")]
2432                )
2433                .into(),
2434            ]),
2435            Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2436        );
2437    }
2438
2439    #[test]
2440    fn do_not_interpolate_out_variable_in_pool() {
2441        assert_eq!(
2442            compile_root_module(vec![
2443                ast::Pool::new("bar", "2").into(),
2444                ast_rule("qux", &[("command", "baz"), ("pool", "$out")]).into(),
2445                ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into(),
2446            ]),
2447            Ok(create_pool_config(None, &[("bar", 2)]))
2448        );
2449    }
2450
2451    #[test]
2452    fn fail_to_compile_unknown_pool() {
2453        assert_eq!(
2454            compile_root_module(vec![
2455                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2456                ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into(),
2457            ]),
2458            Err(CompileError::PoolNotFound("foo".into()))
2459        );
2460    }
2461
2462    #[test]
2463    fn fail_to_compile_unknown_pool_in_phony_build() {
2464        assert_eq!(
2465            compile_root_module(vec![
2466                ast_explicit_build(
2467                    vec!["bar".into()],
2468                    "phony",
2469                    vec![],
2470                    vec![ast::VariableDefinition::new("pool", "foo")]
2471                )
2472                .into(),
2473            ]),
2474            Err(CompileError::PoolNotFound("foo".into()))
2475        );
2476    }
2477
2478    #[test]
2479    fn fail_to_compile_pool_declared_after_build() {
2480        assert_eq!(
2481            compile_root_module(vec![
2482                ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2483                ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into(),
2484                ast::Pool::new("foo", "2").into(),
2485            ]),
2486            Err(CompileError::PoolNotFound("foo".into()))
2487        );
2488    }
2489
2490    #[test]
2491    fn fail_to_compile_duplicate_pool() {
2492        assert_eq!(
2493            compile_root_module(vec![
2494                ast::Pool::new("foo", "1").into(),
2495                ast::Pool::new("foo", "2").into(),
2496            ]),
2497            Err(CompileError::DuplicatePool("foo".into()))
2498        );
2499    }
2500
2501    #[test]
2502    fn fail_to_compile_duplicate_console_pool() {
2503        assert_eq!(
2504            compile_root_module(vec![ast::Pool::new("console", "foo").into()]),
2505            Err(CompileError::DuplicatePool("console".into()))
2506        );
2507    }
2508
2509    #[test]
2510    fn fail_to_compile_invalid_pool_depth() {
2511        for depth in ["", "foo", "-1", "1x", "99999999999999999999"] {
2512            assert_eq!(
2513                compile_root_module(vec![ast::Pool::new("foo", depth).into()]),
2514                Err(CompileError::InvalidPoolDepth("foo".into(), depth.into()))
2515            );
2516        }
2517    }
2518
2519    mod submodule {
2520        use super::*;
2521        use pretty_assertions::assert_eq;
2522
2523        const SUBMODULE_PATH: &str = "foo.ninja";
2524
2525        fn compile_with_submodule(
2526            statements: Vec<ast::Statement>,
2527            submodule_statements: Vec<ast::Statement>,
2528        ) -> Result<Config, CompileError> {
2529            compile(
2530                &[
2531                    (ROOT_MODULE_PATH.clone(), ast::Module::new(statements)),
2532                    (
2533                        SUBMODULE_PATH.into(),
2534                        ast::Module::new(submodule_statements),
2535                    ),
2536                ]
2537                .into_iter()
2538                .collect(),
2539                &[(
2540                    ROOT_MODULE_PATH.clone(),
2541                    [(SUBMODULE_PATH.into(), PathBuf::from(SUBMODULE_PATH))]
2542                        .into_iter()
2543                        .collect(),
2544                )]
2545                .into_iter()
2546                .collect(),
2547                &ROOT_MODULE_PATH,
2548            )
2549        }
2550
2551        #[test]
2552        fn reference_variable_in_parent_module() {
2553            assert_eq!(
2554                compile(
2555                    &[
2556                        (
2557                            ROOT_MODULE_PATH.clone(),
2558                            ast::Module::new(vec![
2559                                ast::VariableDefinition::new("x", "42").into(),
2560                                ast::Submodule::new(SUBMODULE_PATH).into(),
2561                            ])
2562                        ),
2563                        (
2564                            SUBMODULE_PATH.into(),
2565                            ast::Module::new(vec![
2566                                ast_rule("foo", &[("command", "$x")]).into(),
2567                                ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![])
2568                                    .into()
2569                            ])
2570                        )
2571                    ]
2572                    .into_iter()
2573                    .collect(),
2574                    &[(
2575                        ROOT_MODULE_PATH.clone(),
2576                        [(SUBMODULE_PATH.into(), PathBuf::from(SUBMODULE_PATH))]
2577                            .into_iter()
2578                            .collect()
2579                    )]
2580                    .into_iter()
2581                    .collect(),
2582                    &ROOT_MODULE_PATH
2583                )
2584                .unwrap(),
2585                create_simple_config(
2586                    [(
2587                        "bar".into(),
2588                        ir_explicit_build(vec!["bar".into()], Rule::new("42", None), vec![]).into()
2589                    )]
2590                    .into_iter()
2591                    .collect(),
2592                    ["bar".into()].into_iter().collect()
2593                )
2594            );
2595        }
2596
2597        #[test]
2598        fn reference_rule_in_parent_module() {
2599            assert_eq!(
2600                compile(
2601                    &[
2602                        (
2603                            ROOT_MODULE_PATH.clone(),
2604                            ast::Module::new(vec![
2605                                ast::VariableDefinition::new("x", "42").into(),
2606                                ast_rule("foo", &[("command", "$x")]).into(),
2607                                ast::Submodule::new(SUBMODULE_PATH).into(),
2608                            ])
2609                        ),
2610                        (
2611                            SUBMODULE_PATH.into(),
2612                            ast::Module::new(vec![
2613                                ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![])
2614                                    .into()
2615                            ])
2616                        )
2617                    ]
2618                    .into_iter()
2619                    .collect(),
2620                    &[(
2621                        ROOT_MODULE_PATH.clone(),
2622                        [(SUBMODULE_PATH.into(), PathBuf::from(SUBMODULE_PATH))]
2623                            .into_iter()
2624                            .collect()
2625                    )]
2626                    .into_iter()
2627                    .collect(),
2628                    &ROOT_MODULE_PATH
2629                )
2630                .unwrap(),
2631                create_simple_config(
2632                    [(
2633                        "bar".into(),
2634                        ir_explicit_build(vec!["bar".into()], Rule::new("42", None), vec![]).into()
2635                    )]
2636                    .into_iter()
2637                    .collect(),
2638                    ["bar".into()].into_iter().collect()
2639                )
2640            );
2641        }
2642
2643        #[test]
2644        fn do_not_overwrite_variable_in_parent_module() {
2645            assert_eq!(
2646                compile(
2647                    &[
2648                        (
2649                            ROOT_MODULE_PATH.clone(),
2650                            ast::Module::new(vec![
2651                                ast::VariableDefinition::new("x", "42").into(),
2652                                ast_rule("foo", &[("command", "$x")]).into(),
2653                                ast::Submodule::new(SUBMODULE_PATH).into(),
2654                                ast_explicit_build(vec!["bar".into()], "foo", vec![], vec![])
2655                                    .into(),
2656                            ])
2657                        ),
2658                        (
2659                            SUBMODULE_PATH.into(),
2660                            ast::Module::new(vec![ast::VariableDefinition::new("x", "13").into(),])
2661                        )
2662                    ]
2663                    .into_iter()
2664                    .collect(),
2665                    &[(
2666                        ROOT_MODULE_PATH.clone(),
2667                        [(SUBMODULE_PATH.into(), PathBuf::from(SUBMODULE_PATH))]
2668                            .into_iter()
2669                            .collect()
2670                    )]
2671                    .into_iter()
2672                    .collect(),
2673                    &ROOT_MODULE_PATH
2674                )
2675                .unwrap(),
2676                create_simple_config(
2677                    [(
2678                        "bar".into(),
2679                        ir_explicit_build(vec!["bar".into()], Rule::new("42", None), vec![]).into()
2680                    )]
2681                    .into_iter()
2682                    .collect(),
2683                    ["bar".into()].into_iter().collect()
2684                )
2685            );
2686        }
2687
2688        #[test]
2689        fn reference_pool_in_parent_module() {
2690            assert_eq!(
2691                compile_with_submodule(
2692                    vec![
2693                        ast::Pool::new("foo", "2").into(),
2694                        ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2695                        ast::Submodule::new(SUBMODULE_PATH).into(),
2696                    ],
2697                    vec![ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into()],
2698                ),
2699                Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2700            );
2701        }
2702
2703        #[test]
2704        fn reference_pool_in_child_module() {
2705            assert_eq!(
2706                compile_with_submodule(
2707                    vec![
2708                        ast::Submodule::new(SUBMODULE_PATH).into(),
2709                        ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2710                        ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into(),
2711                    ],
2712                    vec![ast::Pool::new("foo", "2").into()],
2713                ),
2714                Ok(create_pool_config(limited_pool("foo"), &[("foo", 2)]))
2715            );
2716        }
2717
2718        #[test]
2719        fn fail_to_reference_pool_declared_after_submodule() {
2720            assert_eq!(
2721                compile_with_submodule(
2722                    vec![
2723                        ast_rule("qux", &[("command", "baz"), ("pool", "foo")]).into(),
2724                        ast::Submodule::new(SUBMODULE_PATH).into(),
2725                        ast::Pool::new("foo", "2").into(),
2726                    ],
2727                    vec![ast_explicit_build(vec!["bar".into()], "qux", vec![], vec![]).into()],
2728                ),
2729                Err(CompileError::PoolNotFound("foo".into()))
2730            );
2731        }
2732
2733        #[test]
2734        fn fail_to_declare_pool_in_submodule_referenced_twice() {
2735            assert_eq!(
2736                compile_with_submodule(
2737                    vec![
2738                        ast::Submodule::new(SUBMODULE_PATH).into(),
2739                        ast::Submodule::new(SUBMODULE_PATH).into(),
2740                    ],
2741                    vec![ast::Pool::new("foo", "2").into()],
2742                ),
2743                Err(CompileError::DuplicatePool("foo".into()))
2744            );
2745        }
2746    }
2747}