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