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