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