Skip to main content

turtle_build/
compile.rs

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