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
34pub 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 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 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 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}