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