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