Skip to main content

turtle_build/
run.rs

1mod context;
2mod hash;
3mod header_dependency;
4mod log;
5mod options;
6
7use self::{
8    context::Context as RunContext,
9    hash::{calculate_content_hash, calculate_timestamp_hash},
10    header_dependency::{exclude_show_includes, read_header_dependencies},
11};
12use crate::{
13    build_graph::{BuildGraph, BuildGraphError},
14    compile::compile_dynamic,
15    context::Context,
16    debug,
17    error::BuildError,
18    hash_type::HashType,
19    ir::{Build, Config, Rule},
20    parse::parse_dynamic,
21    profile,
22};
23use alloc::sync::Arc;
24use async_recursion::async_recursion;
25use core::{future::Future, pin::Pin};
26use futures::future::{FutureExt, Shared, try_join_all};
27use itertools::Itertools;
28pub use options::RunOptions;
29use std::{path::Path, process::Output};
30use tokio::{spawn, time::Instant, try_join};
31
32type BuildFuture = Shared<Pin<Box<dyn Future<Output = Result<(), BuildError>> + Send>>>;
33
34/// Runs builds.
35pub async fn run(
36    context: &Arc<Context>,
37    config: Arc<Config>,
38    outputs: &[String],
39    options: RunOptions,
40) -> Result<(), BuildError> {
41    let mut graph = BuildGraph::new(config.outputs());
42
43    for build in config
44        .outputs()
45        .values()
46        .filter(|build| build.rule().is_some())
47        .unique_by(|build| build.id())
48    {
49        graph.add_header_dependencies(
50            &build.outputs()[0],
51            &context.database().get_header_dependencies(build.id())?,
52        );
53    }
54
55    let context = Arc::new(RunContext::new(context.clone(), config, graph, options));
56
57    context
58        .build_graph()
59        .lock()
60        .await
61        .validate()
62        .map_err(|error| map_build_graph_error(&context, &error))?;
63
64    if outputs.is_empty() {
65        for output in context.config().default_outputs() {
66            trigger_build(
67                context.clone(),
68                context
69                    .config()
70                    .outputs()
71                    .get(output.as_ref())
72                    .ok_or_else(|| BuildError::DefaultOutputNotFound(output.clone()))?,
73            )
74            .await?;
75        }
76    } else {
77        for output in outputs {
78            trigger_build(
79                context.clone(),
80                context
81                    .config()
82                    .outputs()
83                    .get(output.as_str())
84                    .ok_or_else(|| BuildError::OutputNotFound(output.clone()))?,
85            )
86            .await?;
87        }
88    }
89
90    // Do not inline this to avoid borrowing a lock of builds.
91    let futures = context
92        .build_futures()
93        .iter()
94        .map(|r#ref| r#ref.value().clone())
95        .collect::<Vec<_>>();
96
97    let result = try_join_all(futures).await;
98
99    context.application().database().flush().await?;
100
101    result.map(|_| ())
102}
103
104#[async_recursion]
105async fn trigger_build(context: Arc<RunContext>, build: &Arc<Build>) -> Result<(), BuildError> {
106    context
107        .build_futures()
108        .entry(build.id())
109        .or_insert_with(|| spawn_build(context.clone(), build.clone()).boxed().shared());
110
111    Ok(())
112}
113
114async fn spawn_build(context: Arc<RunContext>, build: Arc<Build>) -> Result<(), BuildError> {
115    spawn(async move {
116        try_join_all(
117            build
118                .inputs()
119                .iter()
120                .chain(build.order_only_inputs())
121                .map(|input| build_input(context.clone(), input)),
122        )
123        .await?;
124
125        // TODO Consider caching dynamic modules.
126        let dynamic_config = if let Some(dynamic_module) = build.dynamic_module() {
127            let mut source = String::new();
128            context
129                .application()
130                .file_system()
131                .read_file_to_string(dynamic_module.as_ref().as_ref(), &mut source)
132                .await?;
133            let config = compile_dynamic(&parse_dynamic(&source)?)?;
134
135            context
136                .build_graph()
137                .lock()
138                .await
139                .validate_dynamic(&config)
140                .map_err(|error| map_build_graph_error(&context, &error))?;
141
142            Some(config)
143        } else {
144            None
145        };
146
147        let dynamic_inputs = if let Some(config) = &dynamic_config {
148            build
149                .outputs()
150                .iter()
151                .chain(build.implicit_outputs())
152                .find_map(|output| config.outputs().get(output.as_ref()))
153                .map(|build| build.inputs())
154                .ok_or_else(|| BuildError::DynamicDependencyNotFound(build.clone()))?
155        } else {
156            &[]
157        };
158
159        try_join_all(
160            dynamic_inputs
161                .iter()
162                .map(|input| build_input(context.clone(), input)),
163        )
164        .await?;
165
166        let header_dependencies = build_header_dependencies(
167            &context,
168            &if build.rule().is_some() {
169                context
170                    .application()
171                    .database()
172                    .get_header_dependencies(build.id())?
173            } else {
174                vec![]
175            },
176        )
177        .await?;
178
179        let outputs_exist = try_join_all(
180            build
181                .outputs()
182                .iter()
183                .chain(build.implicit_outputs())
184                .map(|path| check_file_existence(&context, path)),
185        )
186        .await
187        .is_ok();
188        let (phony_inputs, file_inputs) =
189            classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
190        let mut timestamp_hash =
191            calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
192
193        if outputs_exist
194            && Some(timestamp_hash)
195                == context
196                    .application()
197                    .database()
198                    .get_hash(HashType::Timestamp, build.id())?
199        {
200            return Ok(());
201        }
202
203        let mut content_hash =
204            calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
205
206        if outputs_exist
207            && Some(content_hash)
208                == context
209                    .application()
210                    .database()
211                    .get_hash(HashType::Content, build.id())?
212        {
213            return Ok(());
214        } else if let Some(rule) = build.rule() {
215            try_join_all(
216                build
217                    .outputs()
218                    .iter()
219                    .chain(build.implicit_outputs())
220                    .map(|path| prepare_directory(&context, path.as_ref())),
221            )
222            .await?;
223
224            let output = run_rule(&context, rule).await?;
225            let new_header_dependencies = read_header_dependencies(&context, rule, &output).await?;
226
227            context
228                .application()
229                .database()
230                .set_header_dependencies(build.id(), &new_header_dependencies)?;
231
232            for output in build.outputs() {
233                context.application().database().set_output(output)?;
234
235                if let Some(source) = context.config().source_map().get(output) {
236                    context
237                        .application()
238                        .database()
239                        .set_source(output, source)?;
240                }
241            }
242
243            if header_dependencies != new_header_dependencies {
244                let header_dependencies =
245                    filter_existing_header_dependencies(&context, &new_header_dependencies).await?;
246                let (phony_inputs, file_inputs) =
247                    classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
248
249                timestamp_hash =
250                    calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
251                content_hash =
252                    calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
253            }
254        }
255
256        context.application().database().set_hash(
257            HashType::Timestamp,
258            build.id(),
259            timestamp_hash,
260        )?;
261        context
262            .application()
263            .database()
264            .set_hash(HashType::Content, build.id(), content_hash)?;
265
266        Ok(())
267    })
268    .await?
269}
270
271async fn build_input(context: Arc<RunContext>, input: &str) -> Result<(), BuildError> {
272    if let Some(build) = context.config().outputs().get(input) {
273        trigger_build(context.clone(), build).await?;
274
275        // Do not inline this to avoid holding a lock of build futures across an await point.
276        let future = context.build_futures().get(&build.id()).unwrap().clone();
277
278        future.await
279    } else {
280        check_file_existence(&context, input).await
281    }
282}
283
284async fn build_header_dependencies(
285    context: &Arc<RunContext>,
286    inputs: &[String],
287) -> Result<Vec<String>, BuildError> {
288    let mut futures = vec![];
289
290    for input in inputs {
291        if let Some(build) = context.config().outputs().get(input.as_str()) {
292            trigger_build(context.clone(), build).await?;
293
294            futures.push(context.build_futures().get(&build.id()).unwrap().clone());
295        }
296    }
297
298    try_join_all(futures).await?;
299
300    filter_existing_header_dependencies(context, inputs).await
301}
302
303async fn filter_existing_header_dependencies(
304    context: &RunContext,
305    dependencies: &[String],
306) -> Result<Vec<String>, BuildError> {
307    let mut existing_dependencies = vec![];
308
309    for dependency in dependencies {
310        if context
311            .application()
312            .file_system()
313            .exists(dependency.as_ref())
314            .await?
315        {
316            existing_dependencies.push(dependency.clone());
317        }
318    }
319
320    Ok(existing_dependencies)
321}
322
323async fn check_file_existence(context: &RunContext, path: &str) -> Result<(), BuildError> {
324    if !context
325        .application()
326        .file_system()
327        .exists(path.as_ref())
328        .await?
329    {
330        return Err(BuildError::FileNotFound(
331            context
332                .application()
333                .database()
334                .get_source(path)?
335                .unwrap_or_else(|| path.into()),
336        ));
337    }
338
339    Ok(())
340}
341
342async fn prepare_directory(context: &RunContext, path: impl AsRef<Path>) -> Result<(), BuildError> {
343    if let Some(directory) = path.as_ref().parent() {
344        context
345            .application()
346            .file_system()
347            .create_directory(directory)
348            .await?;
349    }
350
351    Ok(())
352}
353
354fn classify_inputs<'a>(
355    context: &'a RunContext,
356    build: &'a Build,
357    dynamic_inputs: &'a [Arc<str>],
358    header_dependencies: &'a [String],
359) -> (Vec<&'a str>, Vec<&'a str>) {
360    let (phony_inputs, file_inputs) = build
361        .inputs()
362        .iter()
363        .chain(dynamic_inputs)
364        .map(AsRef::as_ref)
365        .unique()
366        .partition::<Vec<_>, _>(|&input| {
367            context
368                .config()
369                .outputs()
370                .get(input)
371                .map_or_default(|build| build.rule().is_none())
372        });
373
374    (
375        phony_inputs,
376        file_inputs
377            .into_iter()
378            .chain(header_dependencies.iter().map(String::as_str))
379            .unique()
380            .collect(),
381    )
382}
383
384async fn run_rule(context: &RunContext, rule: &Rule) -> Result<Output, BuildError> {
385    let ((output, duration), mut console) = try_join!(
386        async {
387            let start_time = Instant::now();
388            let output = context
389                .application()
390                .command_runner()
391                .run(rule.command())
392                .await?;
393
394            Ok::<_, BuildError>((output, Instant::now() - start_time))
395        },
396        async {
397            let mut console = context.application().console().lock().await;
398
399            if let Some(description) = rule.description() {
400                console.write_stderr(description.as_bytes()).await?;
401                console.write_stderr(b"\n").await?;
402            }
403
404            debug!(context, console, "command: {}", rule.command());
405
406            Ok(console)
407        }
408    )?;
409
410    profile!(context, console, "duration: {} ms", duration.as_millis());
411
412    console
413        .write_stdout(&exclude_show_includes(rule, &output.stdout))
414        .await?;
415    console.write_stderr(&output.stderr).await?;
416
417    if !output.status.success() {
418        debug!(
419            context,
420            console,
421            "exit status: {}",
422            output
423                .status
424                .code()
425                .map(|code| code.to_string())
426                .unwrap_or_else(|| "-".into())
427        );
428
429        return Err(BuildError::Build);
430    }
431
432    Ok(output)
433}
434
435fn map_build_graph_error(context: &RunContext, error: &BuildGraphError) -> BuildError {
436    match error {
437        BuildGraphError::CircularDependency(outputs) => {
438            match outputs
439                .iter()
440                .map(|output| {
441                    Ok(context
442                        .application()
443                        .database()
444                        .get_source(output)?
445                        .map(|string| string.into())
446                        .unwrap_or_else(|| output.clone()))
447                })
448                .collect::<Result<Vec<_>, BuildError>>()
449            {
450                Ok(outputs) => {
451                    BuildGraphError::CircularDependency(outputs.into_iter().dedup().collect())
452                        .into()
453                }
454                Err(error) => error,
455            }
456        }
457        BuildGraphError::OutputNotFound(_) => error.clone().into(),
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464    use crate::{
465        infrastructure::{FakeCommandRunner, FakeConsole, FakeDatabase, FakeFileSystem},
466        ir::HeaderDependency,
467    };
468    use pretty_assertions::assert_eq;
469    use regex::Regex;
470    #[cfg(unix)]
471    use std::os::unix::process::ExitStatusExt;
472    #[cfg(windows)]
473    use std::os::windows::process::ExitStatusExt;
474    use std::{collections::HashMap, process::ExitStatus};
475
476    const DEFAULT_OPTIONS: RunOptions = RunOptions {
477        debug: false,
478        profile: false,
479    };
480
481    fn create_context(
482        command_runner: &FakeCommandRunner,
483        console: &FakeConsole,
484        file_system: &FakeFileSystem,
485    ) -> Arc<Context> {
486        Context::new(
487            command_runner.clone(),
488            console.clone(),
489            FakeDatabase::default(),
490            file_system.clone(),
491        )
492        .into()
493    }
494
495    fn create_outputs(builds: Vec<Build>) -> HashMap<Arc<str>, Arc<Build>> {
496        builds
497            .into_iter()
498            .map(Arc::new)
499            .flat_map(|build| {
500                build
501                    .outputs()
502                    .iter()
503                    .chain(build.implicit_outputs())
504                    .map(|output| (output.clone(), build.clone()))
505                    .collect::<Vec<_>>()
506            })
507            .collect()
508    }
509
510    fn create_simple_config(builds: Vec<Build>, default_outputs: &[&str]) -> Arc<Config> {
511        Config::new(
512            create_outputs(builds),
513            default_outputs
514                .iter()
515                .map(|&output| output.into())
516                .collect(),
517            Default::default(),
518            None,
519        )
520        .into()
521    }
522
523    fn explicit_build(outputs: Vec<Arc<str>>, rule: Rule, inputs: Vec<Arc<str>>) -> Build {
524        Build::new(outputs, vec![], rule.into(), inputs, vec![], None)
525    }
526
527    fn failed_output() -> Output {
528        Output {
529            status: ExitStatus::from_raw(cfg_select! {
530                unix => 1 << 8,
531                windows => 1,
532            }),
533            stdout: vec![],
534            stderr: vec![],
535        }
536    }
537
538    #[tokio::test]
539    async fn build_nothing() {
540        let command_runner = FakeCommandRunner::default();
541
542        run(
543            &create_context(&command_runner, &Default::default(), &Default::default()),
544            create_simple_config(vec![], &[]),
545            &[],
546            DEFAULT_OPTIONS,
547        )
548        .await
549        .unwrap();
550
551        assert_eq!(command_runner.commands(), Vec::<String>::new());
552    }
553
554    #[tokio::test]
555    async fn build_default_output() {
556        let command_runner = FakeCommandRunner::default();
557
558        run(
559            &create_context(&command_runner, &Default::default(), &Default::default()),
560            create_simple_config(
561                vec![
562                    explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]),
563                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
564                ],
565                &["foo"],
566            ),
567            &[],
568            DEFAULT_OPTIONS,
569        )
570        .await
571        .unwrap();
572
573        assert_eq!(command_runner.commands(), ["touch foo"]);
574    }
575
576    #[tokio::test]
577    async fn build_specified_output() {
578        let command_runner = FakeCommandRunner::default();
579
580        run(
581            &create_context(&command_runner, &Default::default(), &Default::default()),
582            create_simple_config(
583                vec![
584                    explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]),
585                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
586                ],
587                &["foo"],
588            ),
589            &["bar".into()],
590            DEFAULT_OPTIONS,
591        )
592        .await
593        .unwrap();
594
595        assert_eq!(command_runner.commands(), ["touch bar"]);
596    }
597
598    #[tokio::test]
599    async fn fail_with_unknown_default_output() {
600        assert_eq!(
601            run(
602                &create_context(
603                    &Default::default(),
604                    &Default::default(),
605                    &Default::default()
606                ),
607                create_simple_config(vec![], &["foo"]),
608                &[],
609                DEFAULT_OPTIONS,
610            )
611            .await,
612            Err(BuildError::DefaultOutputNotFound("foo".into()))
613        );
614    }
615
616    #[tokio::test]
617    async fn fail_with_unknown_output() {
618        assert_eq!(
619            run(
620                &create_context(
621                    &Default::default(),
622                    &Default::default(),
623                    &Default::default()
624                ),
625                create_simple_config(vec![], &[]),
626                &["foo".into()],
627                DEFAULT_OPTIONS,
628            )
629            .await,
630            Err(BuildError::OutputNotFound("foo".into()))
631        );
632    }
633
634    #[tokio::test]
635    async fn build_shared_input_once() {
636        let command_runner = FakeCommandRunner::default();
637        let file_system = FakeFileSystem::default();
638
639        file_system.write_file("bar", "");
640        file_system.write_file("baz", "");
641
642        run(
643            &create_context(&command_runner, &Default::default(), &file_system),
644            create_simple_config(
645                vec![
646                    explicit_build(
647                        vec!["foo".into()],
648                        Rule::new("touch foo", None),
649                        vec!["bar".into(), "baz".into()],
650                    ),
651                    explicit_build(
652                        vec!["bar".into()],
653                        Rule::new("touch bar", None),
654                        vec!["baz".into()],
655                    ),
656                    explicit_build(vec!["baz".into()], Rule::new("touch baz", None), vec![]),
657                ],
658                &["foo"],
659            ),
660            &[],
661            DEFAULT_OPTIONS,
662        )
663        .await
664        .unwrap();
665
666        assert_eq!(
667            command_runner.commands(),
668            ["touch baz", "touch bar", "touch foo"]
669        );
670    }
671
672    #[tokio::test]
673    async fn build_order_only_input() {
674        let command_runner = FakeCommandRunner::default();
675
676        run(
677            &create_context(&command_runner, &Default::default(), &Default::default()),
678            create_simple_config(
679                vec![
680                    Build::new(
681                        vec!["foo".into()],
682                        vec![],
683                        Rule::new("touch foo", None).into(),
684                        vec![],
685                        vec!["bar".into()],
686                        None,
687                    ),
688                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
689                ],
690                &["foo"],
691            ),
692            &[],
693            DEFAULT_OPTIONS,
694        )
695        .await
696        .unwrap();
697
698        assert_eq!(command_runner.commands(), ["touch bar", "touch foo"]);
699    }
700
701    #[tokio::test]
702    async fn build_multiple_outputs_of_build() {
703        let command_runner = FakeCommandRunner::default();
704        let file_system = FakeFileSystem::default();
705
706        file_system.write_file("foo", "");
707        file_system.write_file("bar", "");
708
709        run(
710            &create_context(&command_runner, &Default::default(), &file_system),
711            create_simple_config(
712                vec![
713                    explicit_build(
714                        vec!["foo".into(), "bar".into()],
715                        Rule::new("touch foo bar", None),
716                        vec![],
717                    ),
718                    explicit_build(
719                        vec!["baz".into()],
720                        Rule::new("cat foo bar > baz", None),
721                        vec!["foo".into(), "bar".into()],
722                    ),
723                ],
724                &["baz"],
725            ),
726            &[],
727            DEFAULT_OPTIONS,
728        )
729        .await
730        .unwrap();
731
732        assert_eq!(
733            command_runner.commands(),
734            ["touch foo bar", "cat foo bar > baz"]
735        );
736    }
737
738    #[tokio::test]
739    async fn fail_with_missing_input() {
740        let command_runner = FakeCommandRunner::default();
741
742        assert_eq!(
743            run(
744                &create_context(&command_runner, &Default::default(), &Default::default()),
745                create_simple_config(
746                    vec![explicit_build(
747                        vec!["foo".into()],
748                        Rule::new("cp bar foo", None),
749                        vec!["bar".into()],
750                    )],
751                    &["foo"],
752                ),
753                &[],
754                DEFAULT_OPTIONS,
755            )
756            .await,
757            Err(BuildError::FileNotFound("bar".into()))
758        );
759        assert_eq!(command_runner.commands(), Vec::<String>::new());
760    }
761
762    #[tokio::test]
763    async fn report_source_of_missing_input() {
764        let context = create_context(
765            &Default::default(),
766            &Default::default(),
767            &Default::default(),
768        );
769
770        context.database().set_source("foo.o", "foo.c").unwrap();
771
772        assert_eq!(
773            run(
774                &context,
775                create_simple_config(
776                    vec![explicit_build(
777                        vec!["foo".into()],
778                        Rule::new("cc foo.o", None),
779                        vec!["foo.o".into()],
780                    )],
781                    &["foo"],
782                ),
783                &[],
784                DEFAULT_OPTIONS,
785            )
786            .await,
787            Err(BuildError::FileNotFound("foo.c".into()))
788        );
789    }
790
791    #[tokio::test]
792    async fn detect_circular_dependency() {
793        assert_eq!(
794            run(
795                &create_context(
796                    &Default::default(),
797                    &Default::default(),
798                    &Default::default()
799                ),
800                create_simple_config(
801                    vec![explicit_build(
802                        vec!["foo".into()],
803                        Rule::new("cp foo foo", None),
804                        vec!["foo".into()],
805                    )],
806                    &["foo"],
807                ),
808                &[],
809                DEFAULT_OPTIONS,
810            )
811            .await,
812            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
813                vec!["foo".into()]
814            )))
815        );
816    }
817
818    #[tokio::test]
819    async fn report_sources_in_circular_dependency() {
820        let context = create_context(
821            &Default::default(),
822            &Default::default(),
823            &Default::default(),
824        );
825
826        context.database().set_source("foo", "baz").unwrap();
827        context.database().set_source("bar", "baz").unwrap();
828
829        assert_eq!(
830            run(
831                &context,
832                create_simple_config(
833                    vec![
834                        explicit_build(
835                            vec!["foo".into()],
836                            Rule::new("cp bar foo", None),
837                            vec!["bar".into()],
838                        ),
839                        explicit_build(
840                            vec!["bar".into()],
841                            Rule::new("cp foo bar", None),
842                            vec!["foo".into()],
843                        ),
844                    ],
845                    &["foo"],
846                ),
847                &[],
848                DEFAULT_OPTIONS,
849            )
850            .await,
851            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
852                vec!["baz".into()]
853            )))
854        );
855    }
856
857    #[tokio::test]
858    async fn prepare_output_directory() {
859        let context = create_context(
860            &Default::default(),
861            &Default::default(),
862            &Default::default(),
863        );
864
865        run(
866            &context,
867            create_simple_config(
868                vec![explicit_build(
869                    vec!["foo/bar".into()],
870                    Rule::new("touch foo/bar", None),
871                    vec![],
872                )],
873                &["foo/bar"],
874            ),
875            &[],
876            DEFAULT_OPTIONS,
877        )
878        .await
879        .unwrap();
880
881        assert!(context.file_system().exists("foo".as_ref()).await.unwrap());
882    }
883
884    #[tokio::test]
885    async fn record_output_with_source() {
886        let file_system = FakeFileSystem::default();
887        let context = create_context(&Default::default(), &Default::default(), &file_system);
888
889        file_system.write_file("foo.c", "");
890
891        run(
892            &context,
893            Config::new(
894                create_outputs(vec![explicit_build(
895                    vec!["foo.o".into()],
896                    Rule::new("cc foo.c", None),
897                    vec!["foo.c".into()],
898                )]),
899                ["foo.o".into()].into_iter().collect(),
900                [("foo.o".into(), "foo.c".into())].into_iter().collect(),
901                None,
902            )
903            .into(),
904            &[],
905            DEFAULT_OPTIONS,
906        )
907        .await
908        .unwrap();
909
910        assert_eq!(context.database().get_outputs().unwrap(), ["foo.o"]);
911        assert_eq!(
912            context.database().get_source("foo.o").unwrap(),
913            Some("foo.c".into())
914        );
915    }
916
917    #[tokio::test]
918    async fn skip_up_to_date_build() {
919        let command_runner = FakeCommandRunner::default();
920        let file_system = FakeFileSystem::default();
921        let context = create_context(&command_runner, &Default::default(), &file_system);
922        let config = create_simple_config(
923            vec![explicit_build(
924                vec!["foo".into()],
925                Rule::new("cp bar foo", None),
926                vec!["bar".into()],
927            )],
928            &["foo"],
929        );
930
931        file_system.write_file("foo", "");
932        file_system.write_file("bar", "");
933
934        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
935            .await
936            .unwrap();
937        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
938
939        assert_eq!(command_runner.commands(), ["cp bar foo"]);
940    }
941
942    #[tokio::test]
943    async fn skip_build_on_timestamp_update_of_input() {
944        let command_runner = FakeCommandRunner::default();
945        let file_system = FakeFileSystem::default();
946        let context = create_context(&command_runner, &Default::default(), &file_system);
947        let config = create_simple_config(
948            vec![explicit_build(
949                vec!["foo".into()],
950                Rule::new("cp bar foo", None),
951                vec!["bar".into()],
952            )],
953            &["foo"],
954        );
955
956        file_system.write_file("foo", "");
957        file_system.write_file("bar", "");
958
959        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
960            .await
961            .unwrap();
962
963        file_system.write_file("bar", "");
964
965        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
966
967        assert_eq!(command_runner.commands(), ["cp bar foo"]);
968    }
969
970    #[tokio::test]
971    async fn rebuild_on_content_update_of_input() {
972        let command_runner = FakeCommandRunner::default();
973        let file_system = FakeFileSystem::default();
974        let context = create_context(&command_runner, &Default::default(), &file_system);
975        let config = create_simple_config(
976            vec![explicit_build(
977                vec!["foo".into()],
978                Rule::new("cp bar foo", None),
979                vec!["bar".into()],
980            )],
981            &["foo"],
982        );
983
984        file_system.write_file("foo", "");
985        file_system.write_file("bar", "");
986
987        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
988            .await
989            .unwrap();
990
991        file_system.write_file("bar", "bar");
992
993        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
994
995        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
996    }
997
998    #[tokio::test]
999    async fn rebuild_on_update_of_phony_input() {
1000        let command_runner = FakeCommandRunner::default();
1001        let file_system = FakeFileSystem::default();
1002        let context = create_context(&command_runner, &Default::default(), &file_system);
1003        let config = create_simple_config(
1004            vec![
1005                explicit_build(
1006                    vec!["foo".into()],
1007                    Rule::new("cp bar foo", None),
1008                    vec!["bar".into()],
1009                ),
1010                Build::new(
1011                    vec!["bar".into()],
1012                    vec![],
1013                    None,
1014                    vec!["baz".into()],
1015                    vec![],
1016                    None,
1017                ),
1018            ],
1019            &["foo"],
1020        );
1021
1022        file_system.write_file("foo", "");
1023        file_system.write_file("baz", "");
1024
1025        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1026            .await
1027            .unwrap();
1028
1029        file_system.write_file("baz", "baz");
1030
1031        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1032
1033        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1034    }
1035
1036    #[tokio::test]
1037    async fn rebuild_missing_output() {
1038        let command_runner = FakeCommandRunner::default();
1039        let file_system = FakeFileSystem::default();
1040        let context = create_context(&command_runner, &Default::default(), &file_system);
1041        let config = create_simple_config(
1042            vec![explicit_build(
1043                vec!["foo".into()],
1044                Rule::new("cp bar foo", None),
1045                vec!["bar".into()],
1046            )],
1047            &["foo"],
1048        );
1049
1050        file_system.write_file("bar", "");
1051
1052        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1053            .await
1054            .unwrap();
1055        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1056
1057        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1058    }
1059
1060    #[tokio::test]
1061    async fn rebuild_missing_implicit_output() {
1062        let command_runner = FakeCommandRunner::default();
1063        let file_system = FakeFileSystem::default();
1064        let context = create_context(&command_runner, &Default::default(), &file_system);
1065        let config = create_simple_config(
1066            vec![Build::new(
1067                vec!["foo".into()],
1068                vec!["baz".into()],
1069                Rule::new("cp bar foo", None).into(),
1070                vec!["bar".into()],
1071                vec![],
1072                None,
1073            )],
1074            &["foo"],
1075        );
1076
1077        file_system.write_file("foo", "");
1078        file_system.write_file("bar", "");
1079
1080        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1081            .await
1082            .unwrap();
1083        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1084
1085        assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1086    }
1087
1088    #[tokio::test]
1089    async fn rerun_failed_build() {
1090        let command_runner =
1091            FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1092        let file_system = FakeFileSystem::default();
1093        let context = create_context(&command_runner, &Default::default(), &file_system);
1094        let config = create_simple_config(
1095            vec![explicit_build(
1096                vec!["foo".into()],
1097                Rule::new("exit 1", None),
1098                vec![],
1099            )],
1100            &["foo"],
1101        );
1102
1103        file_system.write_file("foo", "");
1104
1105        assert_eq!(
1106            run(&context, config.clone(), &[], DEFAULT_OPTIONS).await,
1107            Err(BuildError::Build)
1108        );
1109        assert_eq!(
1110            run(&context, config, &[], DEFAULT_OPTIONS).await,
1111            Err(BuildError::Build)
1112        );
1113        assert_eq!(command_runner.commands(), ["exit 1", "exit 1"]);
1114    }
1115
1116    #[tokio::test]
1117    async fn write_command_output() {
1118        let console = FakeConsole::default();
1119
1120        run(
1121            &create_context(
1122                &FakeCommandRunner::new(
1123                    [(
1124                        "touch foo".into(),
1125                        Output {
1126                            status: ExitStatus::default(),
1127                            stdout: b"bar\n".into(),
1128                            stderr: b"baz\n".into(),
1129                        },
1130                    )]
1131                    .into_iter()
1132                    .collect(),
1133                ),
1134                &console,
1135                &Default::default(),
1136            ),
1137            create_simple_config(
1138                vec![explicit_build(
1139                    vec!["foo".into()],
1140                    Rule::new("touch foo", Some("build foo".into())),
1141                    vec![],
1142                )],
1143                &["foo"],
1144            ),
1145            &[],
1146            DEFAULT_OPTIONS,
1147        )
1148        .await
1149        .unwrap();
1150
1151        assert_eq!(console.stdout(), "bar\n");
1152        assert_eq!(console.stderr(), "build foo\nbaz\n");
1153    }
1154
1155    #[tokio::test]
1156    async fn write_debug_log_of_failed_build() {
1157        let console = FakeConsole::default();
1158
1159        assert_eq!(
1160            run(
1161                &create_context(
1162                    &FakeCommandRunner::new(
1163                        [("exit 1".into(), failed_output())].into_iter().collect(),
1164                    ),
1165                    &console,
1166                    &Default::default(),
1167                ),
1168                create_simple_config(
1169                    vec![explicit_build(
1170                        vec!["foo".into()],
1171                        Rule::new("exit 1", None),
1172                        vec![],
1173                    )],
1174                    &["foo"],
1175                ),
1176                &[],
1177                RunOptions {
1178                    debug: true,
1179                    profile: false,
1180                },
1181            )
1182            .await,
1183            Err(BuildError::Build)
1184        );
1185        assert_eq!(
1186            console.stderr(),
1187            "turtle: command: exit 1\nturtle: exit status: 1\n"
1188        );
1189    }
1190
1191    #[tokio::test]
1192    async fn write_profile_log() {
1193        let console = FakeConsole::default();
1194
1195        run(
1196            &create_context(&Default::default(), &console, &Default::default()),
1197            create_simple_config(
1198                vec![explicit_build(
1199                    vec!["foo".into()],
1200                    Rule::new("touch foo", None),
1201                    vec![],
1202                )],
1203                &["foo"],
1204            ),
1205            &[],
1206            RunOptions {
1207                debug: false,
1208                profile: true,
1209            },
1210        )
1211        .await
1212        .unwrap();
1213
1214        assert!(
1215            Regex::new(r"^turtle: duration: \d+ ms\n$")
1216                .unwrap()
1217                .is_match(&console.stderr())
1218        );
1219    }
1220
1221    #[tokio::test]
1222    async fn rebuild_on_update_of_header_dependency() {
1223        let command_runner = FakeCommandRunner::default();
1224        let file_system = FakeFileSystem::default();
1225        let context = create_context(&command_runner, &Default::default(), &file_system);
1226        let config = create_simple_config(
1227            vec![explicit_build(
1228                vec!["foo.o".into()],
1229                Rule::new("cc foo.c", None).with_header_dependency(Some(HeaderDependency::Make {
1230                    path: "foo.d".into(),
1231                })),
1232                vec!["foo.c".into()],
1233            )],
1234            &["foo.o"],
1235        );
1236
1237        file_system.write_file("foo.o", "");
1238        file_system.write_file("foo.c", "");
1239        file_system.write_file("foo.h", "");
1240        file_system.write_file("foo.d", "foo.o: foo.h\n");
1241
1242        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1243            .await
1244            .unwrap();
1245        run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1246            .await
1247            .unwrap();
1248
1249        file_system.write_file("foo.h", "foo");
1250
1251        run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1252
1253        assert_eq!(command_runner.commands(), ["cc foo.c", "cc foo.c"]);
1254    }
1255
1256    #[tokio::test]
1257    async fn read_and_remove_gcc_depfile() {
1258        let file_system = FakeFileSystem::default();
1259        let context = create_context(&Default::default(), &Default::default(), &file_system);
1260        let build = explicit_build(
1261            vec!["foo.o".into()],
1262            Rule::new("cc foo.c", None).with_header_dependency(Some(HeaderDependency::Gcc {
1263                path: "foo.d".into(),
1264            })),
1265            vec!["foo.c".into()],
1266        );
1267
1268        file_system.write_file("foo.c", "");
1269        file_system.write_file("foo.h", "");
1270        file_system.write_file("foo.d", "foo.o: foo.h\n");
1271
1272        run(
1273            &context,
1274            create_simple_config(vec![build.clone()], &["foo.o"]),
1275            &[],
1276            DEFAULT_OPTIONS,
1277        )
1278        .await
1279        .unwrap();
1280
1281        assert_eq!(
1282            context
1283                .database()
1284                .get_header_dependencies(build.id())
1285                .unwrap(),
1286            ["foo.h"]
1287        );
1288        assert!(
1289            !context
1290                .file_system()
1291                .exists("foo.d".as_ref())
1292                .await
1293                .unwrap()
1294        );
1295    }
1296
1297    #[tokio::test]
1298    async fn read_msvc_header_dependencies() {
1299        let console = FakeConsole::default();
1300        let file_system = FakeFileSystem::default();
1301        let context = create_context(
1302            &FakeCommandRunner::new(
1303                [(
1304                    "cl foo.c".into(),
1305                    Output {
1306                        status: ExitStatus::default(),
1307                        stdout: b"Note: including file: foo.h\nfoo.c\n".into(),
1308                        stderr: vec![],
1309                    },
1310                )]
1311                .into_iter()
1312                .collect(),
1313            ),
1314            &console,
1315            &file_system,
1316        );
1317        let build = explicit_build(
1318            vec!["foo.obj".into()],
1319            Rule::new("cl foo.c", None).with_header_dependency(Some(HeaderDependency::Msvc {
1320                prefix: "Note: including file: ".into(),
1321            })),
1322            vec!["foo.c".into()],
1323        );
1324
1325        file_system.write_file("foo.c", "");
1326        file_system.write_file("foo.h", "");
1327
1328        run(
1329            &context,
1330            create_simple_config(vec![build.clone()], &["foo.obj"]),
1331            &[],
1332            DEFAULT_OPTIONS,
1333        )
1334        .await
1335        .unwrap();
1336
1337        assert_eq!(
1338            context
1339                .database()
1340                .get_header_dependencies(build.id())
1341                .unwrap(),
1342            ["foo.h"]
1343        );
1344        assert_eq!(console.stdout(), "foo.c\n");
1345    }
1346
1347    #[tokio::test]
1348    async fn build_generated_header_dependency() {
1349        let command_runner = FakeCommandRunner::default();
1350        let file_system = FakeFileSystem::default();
1351        let context = create_context(&command_runner, &Default::default(), &file_system);
1352        let build = explicit_build(
1353            vec!["foo.o".into()],
1354            Rule::new("cc foo.c", None),
1355            vec!["foo.c".into()],
1356        );
1357
1358        context
1359            .database()
1360            .set_header_dependencies(build.id(), &["foo.h".into()])
1361            .unwrap();
1362        file_system.write_file("foo.c", "");
1363        file_system.write_file("foo.h", "");
1364
1365        run(
1366            &context,
1367            create_simple_config(
1368                vec![
1369                    build,
1370                    explicit_build(vec!["foo.h".into()], Rule::new("touch foo.h", None), vec![]),
1371                ],
1372                &["foo.o"],
1373            ),
1374            &[],
1375            DEFAULT_OPTIONS,
1376        )
1377        .await
1378        .unwrap();
1379
1380        assert_eq!(command_runner.commands(), ["touch foo.h", "cc foo.c"]);
1381    }
1382
1383    #[tokio::test]
1384    async fn ignore_missing_header_dependency() {
1385        let file_system = FakeFileSystem::default();
1386        let context = create_context(&Default::default(), &Default::default(), &file_system);
1387        let build = explicit_build(
1388            vec!["foo.o".into()],
1389            Rule::new("cc foo.c", None),
1390            vec!["foo.c".into()],
1391        );
1392
1393        context
1394            .database()
1395            .set_header_dependencies(build.id(), &["foo.h".into()])
1396            .unwrap();
1397        file_system.write_file("foo.c", "");
1398
1399        run(
1400            &context,
1401            create_simple_config(vec![build], &["foo.o"]),
1402            &[],
1403            DEFAULT_OPTIONS,
1404        )
1405        .await
1406        .unwrap();
1407    }
1408
1409    #[tokio::test]
1410    async fn detect_circular_header_dependency() {
1411        let context = create_context(
1412            &Default::default(),
1413            &Default::default(),
1414            &Default::default(),
1415        );
1416        let build = explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]);
1417
1418        context
1419            .database()
1420            .set_header_dependencies(build.id(), &["foo".into()])
1421            .unwrap();
1422
1423        assert_eq!(
1424            run(
1425                &context,
1426                create_simple_config(vec![build], &["foo"]),
1427                &[],
1428                DEFAULT_OPTIONS,
1429            )
1430            .await,
1431            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1432                vec!["foo".into()]
1433            )))
1434        );
1435    }
1436
1437    #[tokio::test]
1438    async fn build_dynamic_input() {
1439        let command_runner = FakeCommandRunner::default();
1440        let file_system = FakeFileSystem::default();
1441
1442        file_system.write_file(
1443            "foo.dd",
1444            "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
1445        );
1446        file_system.write_file("bar", "");
1447
1448        run(
1449            &create_context(&command_runner, &Default::default(), &file_system),
1450            create_simple_config(
1451                vec![
1452                    Build::new(
1453                        vec!["foo".into()],
1454                        vec![],
1455                        Rule::new("touch foo", None).into(),
1456                        vec![],
1457                        vec!["foo.dd".into()],
1458                        Some("foo.dd".into()),
1459                    ),
1460                    explicit_build(
1461                        vec!["foo.dd".into()],
1462                        Rule::new("touch foo.dd", None),
1463                        vec![],
1464                    ),
1465                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
1466                ],
1467                &["foo"],
1468            ),
1469            &[],
1470            DEFAULT_OPTIONS,
1471        )
1472        .await
1473        .unwrap();
1474
1475        assert_eq!(
1476            command_runner.commands(),
1477            ["touch foo.dd", "touch bar", "touch foo"]
1478        );
1479    }
1480
1481    #[tokio::test]
1482    async fn build_dynamic_input_declared_for_implicit_output() {
1483        let command_runner = FakeCommandRunner::default();
1484        let file_system = FakeFileSystem::default();
1485
1486        file_system.write_file(
1487            "foo.dd",
1488            "ninja_dyndep_version = 1\nbuild baz: dyndep | bar\n",
1489        );
1490        file_system.write_file("bar", "");
1491
1492        run(
1493            &create_context(&command_runner, &Default::default(), &file_system),
1494            create_simple_config(
1495                vec![
1496                    Build::new(
1497                        vec!["foo".into()],
1498                        vec!["baz".into()],
1499                        Rule::new("touch foo", None).into(),
1500                        vec![],
1501                        vec!["foo.dd".into()],
1502                        Some("foo.dd".into()),
1503                    ),
1504                    explicit_build(
1505                        vec!["foo.dd".into()],
1506                        Rule::new("touch foo.dd", None),
1507                        vec![],
1508                    ),
1509                    explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
1510                ],
1511                &["foo"],
1512            ),
1513            &[],
1514            DEFAULT_OPTIONS,
1515        )
1516        .await
1517        .unwrap();
1518
1519        assert_eq!(
1520            command_runner.commands(),
1521            ["touch foo.dd", "touch bar", "touch foo"]
1522        );
1523    }
1524
1525    #[tokio::test]
1526    async fn fail_with_missing_dynamic_dependency() {
1527        let file_system = FakeFileSystem::default();
1528        let build = Build::new(
1529            vec!["foo".into()],
1530            vec![],
1531            Rule::new("touch foo", None).into(),
1532            vec![],
1533            vec![],
1534            Some("foo.dd".into()),
1535        );
1536
1537        file_system.write_file("foo.dd", "ninja_dyndep_version = 1\n");
1538
1539        assert_eq!(
1540            run(
1541                &create_context(&Default::default(), &Default::default(), &file_system),
1542                create_simple_config(vec![build.clone()], &["foo"]),
1543                &[],
1544                DEFAULT_OPTIONS,
1545            )
1546            .await,
1547            Err(BuildError::DynamicDependencyNotFound(build.into()))
1548        );
1549    }
1550
1551    #[tokio::test]
1552    async fn fail_with_unknown_output_in_dynamic_dependency() {
1553        let file_system = FakeFileSystem::default();
1554
1555        file_system.write_file(
1556            "foo.dd",
1557            "ninja_dyndep_version = 1\nbuild bar: dyndep | baz\n",
1558        );
1559
1560        assert_eq!(
1561            run(
1562                &create_context(&Default::default(), &Default::default(), &file_system),
1563                create_simple_config(
1564                    vec![Build::new(
1565                        vec!["foo".into()],
1566                        vec![],
1567                        Rule::new("touch foo", None).into(),
1568                        vec![],
1569                        vec![],
1570                        Some("foo.dd".into()),
1571                    )],
1572                    &["foo"],
1573                ),
1574                &[],
1575                DEFAULT_OPTIONS,
1576            )
1577            .await,
1578            Err(BuildError::BuildGraph(BuildGraphError::OutputNotFound(
1579                "bar".into()
1580            )))
1581        );
1582    }
1583
1584    #[tokio::test]
1585    async fn detect_circular_dynamic_dependency() {
1586        let file_system = FakeFileSystem::default();
1587
1588        file_system.write_file(
1589            "foo.dd",
1590            "ninja_dyndep_version = 1\nbuild foo: dyndep | foo\n",
1591        );
1592
1593        assert_eq!(
1594            run(
1595                &create_context(&Default::default(), &Default::default(), &file_system),
1596                create_simple_config(
1597                    vec![Build::new(
1598                        vec!["foo".into()],
1599                        vec![],
1600                        Rule::new("touch foo", None).into(),
1601                        vec![],
1602                        vec![],
1603                        Some("foo.dd".into()),
1604                    )],
1605                    &["foo"],
1606                ),
1607                &[],
1608                DEFAULT_OPTIONS,
1609            )
1610            .await,
1611            Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1612                vec!["foo".into()]
1613            )))
1614        );
1615    }
1616
1617    #[test]
1618    fn classify_phony_and_file_inputs() {
1619        let build = explicit_build(
1620            vec!["foo".into()],
1621            Rule::new("", None),
1622            vec!["bar".into(), "baz".into(), "qux".into(), "bar".into()],
1623        );
1624        let context = RunContext::new(
1625            create_context(
1626                &Default::default(),
1627                &Default::default(),
1628                &Default::default(),
1629            ),
1630            create_simple_config(
1631                vec![
1632                    build.clone(),
1633                    Build::new(vec!["bar".into()], vec![], None, vec![], vec![], None),
1634                    explicit_build(vec!["baz".into()], Rule::new("", None), vec![]),
1635                ],
1636                &[],
1637            ),
1638            BuildGraph::new(&Default::default()),
1639            DEFAULT_OPTIONS,
1640        );
1641
1642        assert_eq!(
1643            classify_inputs(
1644                &context,
1645                &build,
1646                &["quux".into(), "baz".into()],
1647                &["qux".into(), "corge".into()],
1648            ),
1649            (vec!["bar"], vec!["baz", "qux", "quux", "corge"])
1650        );
1651    }
1652}