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