1mod context;
2mod hash;
3mod header_dependency;
4mod log;
5mod options;
6
7use self::{
8 context::RunContext,
9 hash::{calculate_content_hash, calculate_timestamp_hash},
10 header_dependency::{exclude_show_includes, read_header_dependencies},
11 log::{debug, profile},
12};
13use crate::{
14 build_graph::{BuildGraph, BuildGraphError},
15 compile::compile_dynamic,
16 context::Context,
17 error::BuildError,
18 hash_type::HashType,
19 infrastructure::Console,
20 ir::{Build, Config, Pool, Rule},
21 parse::parse_dynamic,
22};
23use alloc::sync::Arc;
24use async_recursion::async_recursion;
25use futures::future::{FutureExt, try_join_all};
26use itertools::Itertools;
27pub use options::RunOptions;
28use std::{path::Path, process::Output};
29use tokio::{join, spawn, sync::MutexGuard, time::Instant};
30
31pub async fn run(
33 context: &Arc<Context>,
34 config: Arc<Config>,
35 outputs: &[String],
36 options: RunOptions,
37) -> Result<(), BuildError> {
38 let mut graph = BuildGraph::new(config.outputs());
39
40 for build in config
41 .outputs()
42 .values()
43 .filter(|build| build.rule().is_some())
44 .unique_by(|build| build.id())
45 {
46 graph.add_header_dependencies(
47 &build.outputs()[0],
48 &context.database().get_header_dependencies(build.id())?,
49 );
50 }
51
52 let context = Arc::new(RunContext::new(context.clone(), config, graph, options));
53
54 context
55 .build_graph()
56 .lock()
57 .await
58 .validate()
59 .map_err(|error| map_build_graph_error(&context, &error))?;
60
61 let result = try_join_all(
62 if outputs.is_empty() {
63 context
64 .config()
65 .default_outputs()
66 .iter()
67 .map(|output| {
68 context
69 .config()
70 .outputs()
71 .get(output.as_ref())
72 .ok_or_else(|| BuildError::DefaultOutputNotFound(output.clone()))
73 })
74 .collect::<Result<Vec<_>, _>>()?
75 } else {
76 outputs
77 .iter()
78 .map(|output| {
79 context
80 .config()
81 .outputs()
82 .get(output.as_str())
83 .ok_or_else(|| BuildError::OutputNotFound(output.clone()))
84 })
85 .collect::<Result<Vec<_>, _>>()?
86 }
87 .into_iter()
88 .map(|build| run_build(context.clone(), build)),
89 )
90 .await;
91
92 context.application().database().flush().await?;
93
94 result.map(|_| ())
95}
96
97#[async_recursion]
98async fn run_build(context: Arc<RunContext>, build: &Arc<Build>) -> Result<(), BuildError> {
99 let future = context
101 .build_futures()
102 .entry_async(build.id())
103 .await
104 .or_insert_with(|| spawn_build(context.clone(), build.clone()).boxed().shared())
105 .get()
106 .clone();
107
108 future.await
109}
110
111async fn spawn_build(context: Arc<RunContext>, build: Arc<Build>) -> Result<(), BuildError> {
112 spawn(async move {
113 try_join_all(
114 build
115 .inputs()
116 .iter()
117 .chain(build.order_only_inputs())
118 .map(|input| build_input(context.clone(), input)),
119 )
120 .await?;
121
122 let dynamic_config = if let Some(dynamic_module) = build.dynamic_module() {
124 let config = compile_dynamic(&parse_dynamic(
125 &context
126 .application()
127 .file_system()
128 .read_file_to_string(dynamic_module.as_ref().as_ref())
129 .await?,
130 )?)?;
131
132 context
133 .build_graph()
134 .lock()
135 .await
136 .validate_dynamic(&config)
137 .map_err(|error| map_build_graph_error(&context, &error))?;
138
139 Some(config)
140 } else {
141 None
142 };
143
144 let dynamic_inputs = if let Some(config) = &dynamic_config {
145 build
146 .outputs()
147 .iter()
148 .chain(build.implicit_outputs())
149 .find_map(|output| config.outputs().get(output.as_ref()))
150 .map(|build| build.inputs())
151 .ok_or_else(|| BuildError::DynamicDependencyNotFound(build.clone()))?
152 } else {
153 &[]
154 };
155
156 try_join_all(
157 dynamic_inputs
158 .iter()
159 .map(|input| build_input(context.clone(), input)),
160 )
161 .await?;
162
163 let header_dependencies = if build.rule().is_some() {
164 let dependencies = context
165 .application()
166 .database()
167 .get_header_dependencies(build.id())?;
168
169 try_join_all(
170 dependencies
171 .iter()
172 .filter_map(|dependency| context.config().outputs().get(dependency.as_str()))
173 .map(|build| run_build(context.clone(), build)),
174 )
175 .await?;
176
177 filter_existing_header_dependencies(&context, &dependencies).await?
178 } else {
179 vec![]
180 };
181
182 let outputs_exist = try_join_all(
183 build
184 .outputs()
185 .iter()
186 .chain(build.implicit_outputs())
187 .map(|path| check_file_existence(&context, path)),
188 )
189 .await
190 .is_ok();
191 let (phony_inputs, file_inputs) =
192 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
193 let mut timestamp_hash =
194 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
195
196 if outputs_exist
197 && Some(timestamp_hash)
198 == context
199 .application()
200 .database()
201 .get_hash(HashType::Timestamp, build.id())?
202 {
203 return Ok(());
204 }
205
206 let mut content_hash =
207 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
208
209 if outputs_exist
210 && Some(content_hash)
211 == context
212 .application()
213 .database()
214 .get_hash(HashType::Content, build.id())?
215 {
216 return Ok(());
217 } else if let Some(rule) = build.rule() {
218 try_join_all(
219 build
220 .outputs()
221 .iter()
222 .chain(build.implicit_outputs())
223 .map(|path| prepare_directory(&context, path.as_ref())),
224 )
225 .await?;
226
227 let output = run_rule(&context, rule).await?;
228 let new_header_dependencies = read_header_dependencies(&context, rule, &output).await?;
229
230 context
231 .application()
232 .database()
233 .set_header_dependencies(build.id(), &new_header_dependencies)?;
234
235 for output in build.outputs() {
236 context.application().database().set_output(output)?;
237
238 if let Some(source) = context.config().source_map().get(output) {
239 context
240 .application()
241 .database()
242 .set_source(output, source)?;
243 }
244 }
245
246 if header_dependencies != new_header_dependencies {
247 let header_dependencies =
248 filter_existing_header_dependencies(&context, &new_header_dependencies).await?;
249 let (phony_inputs, file_inputs) =
250 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
251
252 timestamp_hash =
253 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
254 content_hash =
255 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
256 }
257 }
258
259 context.application().database().set_hash(
260 HashType::Timestamp,
261 build.id(),
262 timestamp_hash,
263 )?;
264 context
265 .application()
266 .database()
267 .set_hash(HashType::Content, build.id(), content_hash)?;
268
269 Ok(())
270 })
271 .await?
272}
273
274async fn build_input(context: Arc<RunContext>, input: &str) -> Result<(), BuildError> {
275 if let Some(build) = context.config().outputs().get(input) {
276 run_build(context.clone(), build).await
277 } else {
278 check_file_existence(&context, input).await
279 }
280}
281
282async fn filter_existing_header_dependencies(
283 context: &RunContext,
284 dependencies: &[String],
285) -> Result<Vec<String>, BuildError> {
286 let mut existing_dependencies = vec![];
287
288 for dependency in dependencies {
289 if context
290 .application()
291 .file_system()
292 .exists(dependency.as_ref())
293 .await?
294 {
295 existing_dependencies.push(dependency.clone());
296 }
297 }
298
299 Ok(existing_dependencies)
300}
301
302async fn check_file_existence(context: &RunContext, path: &str) -> Result<(), BuildError> {
303 if !context
304 .application()
305 .file_system()
306 .exists(path.as_ref())
307 .await?
308 {
309 return Err(BuildError::FileNotFound(
310 context
311 .application()
312 .database()
313 .get_source(path)?
314 .unwrap_or_else(|| path.into()),
315 ));
316 }
317
318 Ok(())
319}
320
321async fn prepare_directory(context: &RunContext, path: impl AsRef<Path>) -> Result<(), BuildError> {
322 if let Some(directory) = path.as_ref().parent() {
323 context
324 .application()
325 .file_system()
326 .create_directory(directory)
327 .await?;
328 }
329
330 Ok(())
331}
332
333fn classify_inputs<'a>(
334 context: &'a RunContext,
335 build: &'a Build,
336 dynamic_inputs: &'a [Arc<str>],
337 header_dependencies: &'a [String],
338) -> (Vec<&'a str>, Vec<&'a str>) {
339 let (phony_inputs, file_inputs) = build
340 .inputs()
341 .iter()
342 .chain(dynamic_inputs)
343 .map(AsRef::as_ref)
344 .unique()
345 .partition::<Vec<_>, _>(|&input| {
346 context
347 .config()
348 .outputs()
349 .get(input)
350 .map_or_default(|build| build.rule().is_none())
351 });
352
353 (
354 phony_inputs,
355 file_inputs
356 .into_iter()
357 .chain(header_dependencies.iter().map(String::as_str))
358 .unique()
359 .collect(),
360 )
361}
362
363async fn run_rule(context: &RunContext, rule: &Rule) -> Result<Output, BuildError> {
364 let ((output, duration), mut console) = if rule.pool() == Some(&Pool::Console) {
365 let mut console = write_description(context, rule).await?;
366
367 console.flush().await?;
368
369 let time = Instant::now();
370 let status = context
371 .application()
372 .command_runner()
373 .run_with_console(rule.command())
374 .await?;
375
376 (
377 (
378 Output {
379 status,
380 stdout: vec![],
381 stderr: vec![],
382 },
383 Instant::now() - time,
384 ),
385 console,
386 )
387 } else {
388 let permit = context.pool(rule.pool()).await?;
389 let (output, console) = join!(
390 async {
391 let time = Instant::now();
392 let output = context
393 .application()
394 .command_runner()
395 .run(rule.command())
396 .await?;
397
398 drop(permit);
399
400 Ok::<_, BuildError>((output, Instant::now() - time))
401 },
402 write_description(context, rule)
403 );
404
405 (output?, console?)
406 };
407
408 profile!(context, console, "duration: {} ms", duration.as_millis());
409
410 console
411 .write_stdout(&exclude_show_includes(rule, &output.stdout))
412 .await?;
413 console.write_stderr(&output.stderr).await?;
414
415 if !output.status.success() {
416 debug!(
417 context,
418 console,
419 "exit status: {}",
420 output
421 .status
422 .code()
423 .map(|code| code.to_string())
424 .unwrap_or_else(|| "-".into())
425 );
426
427 return Err(BuildError::Build);
428 }
429
430 Ok(output)
431}
432
433async fn write_description<'a>(
434 context: &'a RunContext,
435 rule: &Rule,
436) -> Result<MutexGuard<'a, Box<dyn Console + Send + Sync>>, BuildError> {
437 let mut console = context.application().console().lock().await;
438
439 if let Some(description) = rule.description() {
440 console.write_stderr(description.as_bytes()).await?;
441 console.write_stderr(b"\n").await?;
442 }
443
444 debug!(context, console, "command: {}", rule.command());
445
446 Ok(console)
447}
448
449fn map_build_graph_error(context: &RunContext, error: &BuildGraphError) -> BuildError {
450 match error {
451 BuildGraphError::CircularDependency(outputs) => {
452 match outputs
453 .iter()
454 .map(|output| {
455 Ok(context
456 .application()
457 .database()
458 .get_source(output)?
459 .map(|string| string.into())
460 .unwrap_or_else(|| output.clone()))
461 })
462 .collect::<Result<Vec<_>, BuildError>>()
463 {
464 Ok(outputs) => {
465 BuildGraphError::CircularDependency(outputs.into_iter().dedup().collect())
466 .into()
467 }
468 Err(error) => error,
469 }
470 }
471 BuildGraphError::OutputNotFound(_) => error.clone().into(),
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use crate::{
479 infrastructure::{FakeCommandRunner, FakeConsole, FakeDatabase, FakeFileSystem},
480 ir::HeaderDependency,
481 };
482 use core::{num::NonZeroUsize, pin::pin};
483 use futures::poll;
484 use pretty_assertions::assert_eq;
485 use regex::Regex;
486 #[cfg(unix)]
487 use std::os::unix::process::ExitStatusExt;
488 #[cfg(windows)]
489 use std::os::windows::process::ExitStatusExt;
490 use std::{collections::HashMap, process::ExitStatus};
491 use tokio::task::yield_now;
492
493 const DEFAULT_OPTIONS: RunOptions = RunOptions {
494 debug: false,
495 profile: false,
496 };
497 const POLL_COUNT: usize = 8;
498
499 fn create_context(
500 command_runner: &FakeCommandRunner,
501 console: &FakeConsole,
502 file_system: &FakeFileSystem,
503 ) -> Arc<Context> {
504 Context::new(
505 command_runner.clone(),
506 console.clone(),
507 FakeDatabase::default(),
508 file_system.clone(),
509 )
510 .into()
511 }
512
513 fn create_outputs(builds: Vec<Build>) -> HashMap<Arc<str>, Arc<Build>> {
514 builds
515 .into_iter()
516 .map(Arc::new)
517 .flat_map(|build| {
518 build
519 .outputs()
520 .iter()
521 .chain(build.implicit_outputs())
522 .map(|output| (output.clone(), build.clone()))
523 .collect::<Vec<_>>()
524 })
525 .collect()
526 }
527
528 fn create_simple_config(builds: Vec<Build>, default_outputs: &[&str]) -> Arc<Config> {
529 create_pool_config(builds, default_outputs, &[])
530 }
531
532 fn create_pool_config(
533 builds: Vec<Build>,
534 default_outputs: &[&str],
535 pools: &[(&str, usize)],
536 ) -> Arc<Config> {
537 Config::new(
538 create_outputs(builds),
539 default_outputs
540 .iter()
541 .map(|&output| output.into())
542 .collect(),
543 Default::default(),
544 pools
545 .iter()
546 .map(|&(name, depth)| (name.into(), NonZeroUsize::new(depth).unwrap()))
547 .collect(),
548 None,
549 )
550 .into()
551 }
552
553 fn explicit_build(outputs: Vec<Arc<str>>, rule: Rule, inputs: Vec<Arc<str>>) -> Build {
554 Build::new(outputs, vec![], rule.into(), inputs, vec![], None)
555 }
556
557 fn failed_output() -> Output {
558 Output {
559 status: ExitStatus::from_raw(cfg_select! {
560 unix => 1 << 8,
561 windows => 1,
562 }),
563 stdout: vec![],
564 stderr: vec![],
565 }
566 }
567
568 fn limited_pool(name: &str) -> Option<Pool> {
569 Some(Pool::Limited(name.into()))
570 }
571
572 fn pool_build(output: &str, pool: Option<Pool>) -> Build {
573 explicit_build(
574 vec![output.into()],
575 Rule::new(format!("touch {output}"), None).with_pool(pool),
576 vec![],
577 )
578 }
579
580 fn create_run_context(
581 command_runner: &FakeCommandRunner,
582 console: &FakeConsole,
583 pools: &[(&str, usize)],
584 ) -> RunContext {
585 RunContext::new(
586 create_context(command_runner, console, &Default::default()),
587 create_pool_config(vec![], &[], pools),
588 BuildGraph::new(&Default::default()),
589 DEFAULT_OPTIONS,
590 )
591 }
592
593 #[tokio::test]
594 async fn build_nothing() {
595 let command_runner = FakeCommandRunner::default();
596
597 run(
598 &create_context(&command_runner, &Default::default(), &Default::default()),
599 create_simple_config(vec![], &[]),
600 &[],
601 DEFAULT_OPTIONS,
602 )
603 .await
604 .unwrap();
605
606 assert_eq!(command_runner.commands(), Vec::<String>::new());
607 }
608
609 #[tokio::test]
610 async fn build_default_output() {
611 let command_runner = FakeCommandRunner::default();
612
613 run(
614 &create_context(&command_runner, &Default::default(), &Default::default()),
615 create_simple_config(
616 vec![
617 explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]),
618 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
619 ],
620 &["foo"],
621 ),
622 &[],
623 DEFAULT_OPTIONS,
624 )
625 .await
626 .unwrap();
627
628 assert_eq!(command_runner.commands(), ["touch foo"]);
629 }
630
631 #[tokio::test]
632 async fn build_specified_output() {
633 let command_runner = FakeCommandRunner::default();
634
635 run(
636 &create_context(&command_runner, &Default::default(), &Default::default()),
637 create_simple_config(
638 vec![
639 explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]),
640 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
641 ],
642 &["foo"],
643 ),
644 &["bar".into()],
645 DEFAULT_OPTIONS,
646 )
647 .await
648 .unwrap();
649
650 assert_eq!(command_runner.commands(), ["touch bar"]);
651 }
652
653 #[tokio::test]
654 async fn build_duplicate_outputs() {
655 let command_runner = FakeCommandRunner::default();
656
657 run(
658 &create_context(&command_runner, &Default::default(), &Default::default()),
659 create_simple_config(
660 vec![explicit_build(
661 vec!["foo".into()],
662 Rule::new("touch foo", None),
663 vec![],
664 )],
665 &[],
666 ),
667 &["foo".into(), "foo".into()],
668 DEFAULT_OPTIONS,
669 )
670 .await
671 .unwrap();
672
673 assert_eq!(command_runner.commands(), ["touch foo"]);
674 }
675
676 #[tokio::test]
677 async fn fail_with_unknown_default_output() {
678 assert_eq!(
679 run(
680 &create_context(
681 &Default::default(),
682 &Default::default(),
683 &Default::default()
684 ),
685 create_simple_config(vec![], &["foo"]),
686 &[],
687 DEFAULT_OPTIONS,
688 )
689 .await,
690 Err(BuildError::DefaultOutputNotFound("foo".into()))
691 );
692 }
693
694 #[tokio::test]
695 async fn fail_with_unknown_output() {
696 assert_eq!(
697 run(
698 &create_context(
699 &Default::default(),
700 &Default::default(),
701 &Default::default()
702 ),
703 create_simple_config(vec![], &[]),
704 &["foo".into()],
705 DEFAULT_OPTIONS,
706 )
707 .await,
708 Err(BuildError::OutputNotFound("foo".into()))
709 );
710 }
711
712 #[tokio::test]
713 async fn fail_with_unknown_output_after_known_output() {
714 let command_runner = FakeCommandRunner::default();
715
716 assert_eq!(
717 run(
718 &create_context(&command_runner, &Default::default(), &Default::default()),
719 create_simple_config(
720 vec![explicit_build(
721 vec!["foo".into()],
722 Rule::new("touch foo", None),
723 vec![],
724 )],
725 &[],
726 ),
727 &["foo".into(), "bar".into()],
728 DEFAULT_OPTIONS,
729 )
730 .await,
731 Err(BuildError::OutputNotFound("bar".into()))
732 );
733
734 yield_now().await;
736
737 assert_eq!(command_runner.commands(), Vec::<String>::new());
738 }
739
740 #[tokio::test]
741 async fn build_shared_input_once() {
742 let command_runner = FakeCommandRunner::default();
743 let file_system = FakeFileSystem::default();
744
745 file_system.write_file("bar", "");
746 file_system.write_file("baz", "");
747
748 run(
749 &create_context(&command_runner, &Default::default(), &file_system),
750 create_simple_config(
751 vec![
752 explicit_build(
753 vec!["foo".into()],
754 Rule::new("touch foo", None),
755 vec!["bar".into(), "baz".into()],
756 ),
757 explicit_build(
758 vec!["bar".into()],
759 Rule::new("touch bar", None),
760 vec!["baz".into()],
761 ),
762 explicit_build(vec!["baz".into()], Rule::new("touch baz", None), vec![]),
763 ],
764 &["foo"],
765 ),
766 &[],
767 DEFAULT_OPTIONS,
768 )
769 .await
770 .unwrap();
771
772 assert_eq!(
773 command_runner.commands(),
774 ["touch baz", "touch bar", "touch foo"]
775 );
776 }
777
778 #[tokio::test]
779 async fn build_order_only_input() {
780 let command_runner = FakeCommandRunner::default();
781
782 run(
783 &create_context(&command_runner, &Default::default(), &Default::default()),
784 create_simple_config(
785 vec![
786 Build::new(
787 vec!["foo".into()],
788 vec![],
789 Rule::new("touch foo", None).into(),
790 vec![],
791 vec!["bar".into()],
792 None,
793 ),
794 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
795 ],
796 &["foo"],
797 ),
798 &[],
799 DEFAULT_OPTIONS,
800 )
801 .await
802 .unwrap();
803
804 assert_eq!(command_runner.commands(), ["touch bar", "touch foo"]);
805 }
806
807 #[tokio::test]
808 async fn build_multiple_outputs_of_build() {
809 let command_runner = FakeCommandRunner::default();
810 let file_system = FakeFileSystem::default();
811
812 file_system.write_file("foo", "");
813 file_system.write_file("bar", "");
814
815 run(
816 &create_context(&command_runner, &Default::default(), &file_system),
817 create_simple_config(
818 vec![
819 explicit_build(
820 vec!["foo".into(), "bar".into()],
821 Rule::new("touch foo bar", None),
822 vec![],
823 ),
824 explicit_build(
825 vec!["baz".into()],
826 Rule::new("cat foo bar > baz", None),
827 vec!["foo".into(), "bar".into()],
828 ),
829 ],
830 &["baz"],
831 ),
832 &[],
833 DEFAULT_OPTIONS,
834 )
835 .await
836 .unwrap();
837
838 assert_eq!(
839 command_runner.commands(),
840 ["touch foo bar", "cat foo bar > baz"]
841 );
842 }
843
844 #[tokio::test]
845 async fn fail_with_missing_input() {
846 let command_runner = FakeCommandRunner::default();
847
848 assert_eq!(
849 run(
850 &create_context(&command_runner, &Default::default(), &Default::default()),
851 create_simple_config(
852 vec![explicit_build(
853 vec!["foo".into()],
854 Rule::new("cp bar foo", None),
855 vec!["bar".into()],
856 )],
857 &["foo"],
858 ),
859 &[],
860 DEFAULT_OPTIONS,
861 )
862 .await,
863 Err(BuildError::FileNotFound("bar".into()))
864 );
865 assert_eq!(command_runner.commands(), Vec::<String>::new());
866 }
867
868 #[tokio::test]
869 async fn fail_with_missing_order_only_input() {
870 assert_eq!(
871 run(
872 &create_context(
873 &Default::default(),
874 &Default::default(),
875 &Default::default()
876 ),
877 create_simple_config(
878 vec![Build::new(
879 vec!["foo".into()],
880 vec![],
881 Rule::new("touch foo", None).into(),
882 vec![],
883 vec!["bar".into()],
884 None,
885 )],
886 &["foo"],
887 ),
888 &[],
889 DEFAULT_OPTIONS,
890 )
891 .await,
892 Err(BuildError::FileNotFound("bar".into()))
893 );
894 }
895
896 #[tokio::test]
897 async fn report_source_of_missing_input() {
898 let context = create_context(
899 &Default::default(),
900 &Default::default(),
901 &Default::default(),
902 );
903
904 context.database().set_source("foo.o", "foo.c").unwrap();
905
906 assert_eq!(
907 run(
908 &context,
909 create_simple_config(
910 vec![explicit_build(
911 vec!["foo".into()],
912 Rule::new("cc foo.o", None),
913 vec!["foo.o".into()],
914 )],
915 &["foo"],
916 ),
917 &[],
918 DEFAULT_OPTIONS,
919 )
920 .await,
921 Err(BuildError::FileNotFound("foo.c".into()))
922 );
923 }
924
925 #[tokio::test]
926 async fn detect_circular_dependency() {
927 assert_eq!(
928 run(
929 &create_context(
930 &Default::default(),
931 &Default::default(),
932 &Default::default()
933 ),
934 create_simple_config(
935 vec![explicit_build(
936 vec!["foo".into()],
937 Rule::new("cp foo foo", None),
938 vec!["foo".into()],
939 )],
940 &["foo"],
941 ),
942 &[],
943 DEFAULT_OPTIONS,
944 )
945 .await,
946 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
947 vec!["foo".into()]
948 )))
949 );
950 }
951
952 #[tokio::test]
953 async fn report_sources_in_circular_dependency() {
954 let context = create_context(
955 &Default::default(),
956 &Default::default(),
957 &Default::default(),
958 );
959
960 context.database().set_source("foo", "baz").unwrap();
961 context.database().set_source("bar", "baz").unwrap();
962
963 assert_eq!(
964 run(
965 &context,
966 create_simple_config(
967 vec![
968 explicit_build(
969 vec!["foo".into()],
970 Rule::new("cp bar foo", None),
971 vec!["bar".into()],
972 ),
973 explicit_build(
974 vec!["bar".into()],
975 Rule::new("cp foo bar", None),
976 vec!["foo".into()],
977 ),
978 ],
979 &["foo"],
980 ),
981 &[],
982 DEFAULT_OPTIONS,
983 )
984 .await,
985 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
986 vec!["baz".into()]
987 )))
988 );
989 }
990
991 #[tokio::test]
992 async fn prepare_output_directory() {
993 let context = create_context(
994 &Default::default(),
995 &Default::default(),
996 &Default::default(),
997 );
998
999 run(
1000 &context,
1001 create_simple_config(
1002 vec![explicit_build(
1003 vec!["foo/bar".into()],
1004 Rule::new("touch foo/bar", None),
1005 vec![],
1006 )],
1007 &["foo/bar"],
1008 ),
1009 &[],
1010 DEFAULT_OPTIONS,
1011 )
1012 .await
1013 .unwrap();
1014
1015 assert!(context.file_system().exists("foo".as_ref()).await.unwrap());
1016 }
1017
1018 #[tokio::test]
1019 async fn record_output_with_source() {
1020 let file_system = FakeFileSystem::default();
1021 let context = create_context(&Default::default(), &Default::default(), &file_system);
1022
1023 file_system.write_file("foo.c", "");
1024
1025 run(
1026 &context,
1027 Config::new(
1028 create_outputs(vec![explicit_build(
1029 vec!["foo.o".into()],
1030 Rule::new("cc foo.c", None),
1031 vec!["foo.c".into()],
1032 )]),
1033 ["foo.o".into()].into_iter().collect(),
1034 [("foo.o".into(), "foo.c".into())].into_iter().collect(),
1035 Default::default(),
1036 None,
1037 )
1038 .into(),
1039 &[],
1040 DEFAULT_OPTIONS,
1041 )
1042 .await
1043 .unwrap();
1044
1045 assert_eq!(context.database().get_outputs().unwrap(), ["foo.o"]);
1046 assert_eq!(
1047 context.database().get_source("foo.o").unwrap(),
1048 Some("foo.c".into())
1049 );
1050 }
1051
1052 #[tokio::test]
1053 async fn skip_up_to_date_build() {
1054 let command_runner = FakeCommandRunner::default();
1055 let file_system = FakeFileSystem::default();
1056 let context = create_context(&command_runner, &Default::default(), &file_system);
1057 let config = create_simple_config(
1058 vec![explicit_build(
1059 vec!["foo".into()],
1060 Rule::new("cp bar foo", None),
1061 vec!["bar".into()],
1062 )],
1063 &["foo"],
1064 );
1065
1066 file_system.write_file("foo", "");
1067 file_system.write_file("bar", "");
1068
1069 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1070 .await
1071 .unwrap();
1072 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1073
1074 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1075 }
1076
1077 #[tokio::test]
1078 async fn skip_build_on_timestamp_update_of_input() {
1079 let command_runner = FakeCommandRunner::default();
1080 let file_system = FakeFileSystem::default();
1081 let context = create_context(&command_runner, &Default::default(), &file_system);
1082 let config = create_simple_config(
1083 vec![explicit_build(
1084 vec!["foo".into()],
1085 Rule::new("cp bar foo", None),
1086 vec!["bar".into()],
1087 )],
1088 &["foo"],
1089 );
1090
1091 file_system.write_file("foo", "");
1092 file_system.write_file("bar", "");
1093
1094 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1095 .await
1096 .unwrap();
1097
1098 file_system.write_file("bar", "");
1099
1100 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1101
1102 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1103 }
1104
1105 #[tokio::test]
1106 async fn rebuild_on_content_update_of_input() {
1107 let command_runner = FakeCommandRunner::default();
1108 let file_system = FakeFileSystem::default();
1109 let context = create_context(&command_runner, &Default::default(), &file_system);
1110 let config = create_simple_config(
1111 vec![explicit_build(
1112 vec!["foo".into()],
1113 Rule::new("cp bar foo", None),
1114 vec!["bar".into()],
1115 )],
1116 &["foo"],
1117 );
1118
1119 file_system.write_file("foo", "");
1120 file_system.write_file("bar", "");
1121
1122 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1123 .await
1124 .unwrap();
1125
1126 file_system.write_file("bar", "bar");
1127
1128 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1129
1130 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1131 }
1132
1133 #[tokio::test]
1134 async fn rebuild_on_update_of_phony_input() {
1135 let command_runner = FakeCommandRunner::default();
1136 let file_system = FakeFileSystem::default();
1137 let context = create_context(&command_runner, &Default::default(), &file_system);
1138 let config = create_simple_config(
1139 vec![
1140 explicit_build(
1141 vec!["foo".into()],
1142 Rule::new("cp bar foo", None),
1143 vec!["bar".into()],
1144 ),
1145 Build::new(
1146 vec!["bar".into()],
1147 vec![],
1148 None,
1149 vec!["baz".into()],
1150 vec![],
1151 None,
1152 ),
1153 ],
1154 &["foo"],
1155 );
1156
1157 file_system.write_file("foo", "");
1158 file_system.write_file("baz", "");
1159
1160 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1161 .await
1162 .unwrap();
1163
1164 file_system.write_file("baz", "baz");
1165
1166 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1167
1168 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1169 }
1170
1171 #[tokio::test]
1172 async fn rebuild_missing_output() {
1173 let command_runner = FakeCommandRunner::default();
1174 let file_system = FakeFileSystem::default();
1175 let context = create_context(&command_runner, &Default::default(), &file_system);
1176 let config = create_simple_config(
1177 vec![explicit_build(
1178 vec!["foo".into()],
1179 Rule::new("cp bar foo", None),
1180 vec!["bar".into()],
1181 )],
1182 &["foo"],
1183 );
1184
1185 file_system.write_file("bar", "");
1186
1187 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1188 .await
1189 .unwrap();
1190 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1191
1192 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1193 }
1194
1195 #[tokio::test]
1196 async fn rebuild_missing_implicit_output() {
1197 let command_runner = FakeCommandRunner::default();
1198 let file_system = FakeFileSystem::default();
1199 let context = create_context(&command_runner, &Default::default(), &file_system);
1200 let config = create_simple_config(
1201 vec![Build::new(
1202 vec!["foo".into()],
1203 vec!["baz".into()],
1204 Rule::new("cp bar foo", None).into(),
1205 vec!["bar".into()],
1206 vec![],
1207 None,
1208 )],
1209 &["foo"],
1210 );
1211
1212 file_system.write_file("foo", "");
1213 file_system.write_file("bar", "");
1214
1215 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1216 .await
1217 .unwrap();
1218 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1219
1220 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1221 }
1222
1223 #[tokio::test]
1224 async fn rerun_failed_build() {
1225 let command_runner =
1226 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1227 let file_system = FakeFileSystem::default();
1228 let context = create_context(&command_runner, &Default::default(), &file_system);
1229 let config = create_simple_config(
1230 vec![explicit_build(
1231 vec!["foo".into()],
1232 Rule::new("exit 1", None),
1233 vec![],
1234 )],
1235 &["foo"],
1236 );
1237
1238 file_system.write_file("foo", "");
1239
1240 assert_eq!(
1241 run(&context, config.clone(), &[], DEFAULT_OPTIONS).await,
1242 Err(BuildError::Build)
1243 );
1244 assert_eq!(
1245 run(&context, config, &[], DEFAULT_OPTIONS).await,
1246 Err(BuildError::Build)
1247 );
1248 assert_eq!(command_runner.commands(), ["exit 1", "exit 1"]);
1249 }
1250
1251 #[tokio::test]
1252 async fn write_command_output() {
1253 let console = FakeConsole::default();
1254
1255 run(
1256 &create_context(
1257 &FakeCommandRunner::new(
1258 [(
1259 "touch foo".into(),
1260 Output {
1261 status: ExitStatus::default(),
1262 stdout: b"bar\n".into(),
1263 stderr: b"baz\n".into(),
1264 },
1265 )]
1266 .into_iter()
1267 .collect(),
1268 ),
1269 &console,
1270 &Default::default(),
1271 ),
1272 create_simple_config(
1273 vec![explicit_build(
1274 vec!["foo".into()],
1275 Rule::new("touch foo", Some("build foo".into())),
1276 vec![],
1277 )],
1278 &["foo"],
1279 ),
1280 &[],
1281 DEFAULT_OPTIONS,
1282 )
1283 .await
1284 .unwrap();
1285
1286 assert_eq!(console.stdout(), "bar\n");
1287 assert_eq!(console.stderr(), "build foo\nbaz\n");
1288 }
1289
1290 #[tokio::test]
1291 async fn write_debug_log_of_failed_build() {
1292 let console = FakeConsole::default();
1293
1294 assert_eq!(
1295 run(
1296 &create_context(
1297 &FakeCommandRunner::new(
1298 [("exit 1".into(), failed_output())].into_iter().collect(),
1299 ),
1300 &console,
1301 &Default::default(),
1302 ),
1303 create_simple_config(
1304 vec![explicit_build(
1305 vec!["foo".into()],
1306 Rule::new("exit 1", None),
1307 vec![],
1308 )],
1309 &["foo"],
1310 ),
1311 &[],
1312 RunOptions {
1313 debug: true,
1314 profile: false,
1315 },
1316 )
1317 .await,
1318 Err(BuildError::Build)
1319 );
1320 assert_eq!(
1321 console.stderr(),
1322 "turtle: command: exit 1\nturtle: exit status: 1\n"
1323 );
1324 }
1325
1326 #[tokio::test]
1327 async fn write_profile_log() {
1328 let console = FakeConsole::default();
1329
1330 run(
1331 &create_context(&Default::default(), &console, &Default::default()),
1332 create_simple_config(
1333 vec![explicit_build(
1334 vec!["foo".into()],
1335 Rule::new("touch foo", None),
1336 vec![],
1337 )],
1338 &["foo"],
1339 ),
1340 &[],
1341 RunOptions {
1342 debug: false,
1343 profile: true,
1344 },
1345 )
1346 .await
1347 .unwrap();
1348
1349 assert!(
1350 Regex::new(r"^turtle: duration: \d+ ms\n$")
1351 .unwrap()
1352 .is_match(&console.stderr())
1353 );
1354 }
1355
1356 #[tokio::test]
1357 async fn rebuild_on_update_of_header_dependency() {
1358 let command_runner = FakeCommandRunner::default();
1359 let file_system = FakeFileSystem::default();
1360 let context = create_context(&command_runner, &Default::default(), &file_system);
1361 let config = create_simple_config(
1362 vec![explicit_build(
1363 vec!["foo.o".into()],
1364 Rule::new("cc foo.c", None).with_header_dependency(Some(HeaderDependency::Make {
1365 path: "foo.d".into(),
1366 })),
1367 vec!["foo.c".into()],
1368 )],
1369 &["foo.o"],
1370 );
1371
1372 file_system.write_file("foo.o", "");
1373 file_system.write_file("foo.c", "");
1374 file_system.write_file("foo.h", "");
1375 file_system.write_file("foo.d", "foo.o: foo.h\n");
1376
1377 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1378 .await
1379 .unwrap();
1380 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1381 .await
1382 .unwrap();
1383
1384 file_system.write_file("foo.h", "foo");
1385
1386 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1387
1388 assert_eq!(command_runner.commands(), ["cc foo.c", "cc foo.c"]);
1389 }
1390
1391 #[tokio::test]
1392 async fn read_and_remove_gcc_depfile() {
1393 let file_system = FakeFileSystem::default();
1394 let context = create_context(&Default::default(), &Default::default(), &file_system);
1395 let build = explicit_build(
1396 vec!["foo.o".into()],
1397 Rule::new("cc foo.c", None).with_header_dependency(Some(HeaderDependency::Gcc {
1398 path: "foo.d".into(),
1399 })),
1400 vec!["foo.c".into()],
1401 );
1402
1403 file_system.write_file("foo.c", "");
1404 file_system.write_file("foo.h", "");
1405 file_system.write_file("foo.d", "foo.o: foo.h\n");
1406
1407 run(
1408 &context,
1409 create_simple_config(vec![build.clone()], &["foo.o"]),
1410 &[],
1411 DEFAULT_OPTIONS,
1412 )
1413 .await
1414 .unwrap();
1415
1416 assert_eq!(
1417 context
1418 .database()
1419 .get_header_dependencies(build.id())
1420 .unwrap(),
1421 ["foo.h"]
1422 );
1423 assert!(
1424 !context
1425 .file_system()
1426 .exists("foo.d".as_ref())
1427 .await
1428 .unwrap()
1429 );
1430 }
1431
1432 #[tokio::test]
1433 async fn read_msvc_header_dependencies() {
1434 let console = FakeConsole::default();
1435 let file_system = FakeFileSystem::default();
1436 let context = create_context(
1437 &FakeCommandRunner::new(
1438 [(
1439 "cl foo.c".into(),
1440 Output {
1441 status: ExitStatus::default(),
1442 stdout: b"Note: including file: foo.h\nfoo.c\n".into(),
1443 stderr: vec![],
1444 },
1445 )]
1446 .into_iter()
1447 .collect(),
1448 ),
1449 &console,
1450 &file_system,
1451 );
1452 let build = explicit_build(
1453 vec!["foo.obj".into()],
1454 Rule::new("cl foo.c", None).with_header_dependency(Some(HeaderDependency::Msvc {
1455 prefix: "Note: including file: ".into(),
1456 })),
1457 vec!["foo.c".into()],
1458 );
1459
1460 file_system.write_file("foo.c", "");
1461 file_system.write_file("foo.h", "");
1462
1463 run(
1464 &context,
1465 create_simple_config(vec![build.clone()], &["foo.obj"]),
1466 &[],
1467 DEFAULT_OPTIONS,
1468 )
1469 .await
1470 .unwrap();
1471
1472 assert_eq!(
1473 context
1474 .database()
1475 .get_header_dependencies(build.id())
1476 .unwrap(),
1477 ["foo.h"]
1478 );
1479 assert_eq!(console.stdout(), "foo.c\n");
1480 }
1481
1482 #[tokio::test]
1483 async fn build_generated_header_dependency() {
1484 let command_runner = FakeCommandRunner::default();
1485 let file_system = FakeFileSystem::default();
1486 let context = create_context(&command_runner, &Default::default(), &file_system);
1487 let build = explicit_build(
1488 vec!["foo.o".into()],
1489 Rule::new("cc foo.c", None),
1490 vec!["foo.c".into()],
1491 );
1492
1493 context
1494 .database()
1495 .set_header_dependencies(build.id(), &["foo.h".into()])
1496 .unwrap();
1497 file_system.write_file("foo.c", "");
1498 file_system.write_file("foo.h", "");
1499
1500 run(
1501 &context,
1502 create_simple_config(
1503 vec![
1504 build,
1505 explicit_build(vec!["foo.h".into()], Rule::new("touch foo.h", None), vec![]),
1506 ],
1507 &["foo.o"],
1508 ),
1509 &[],
1510 DEFAULT_OPTIONS,
1511 )
1512 .await
1513 .unwrap();
1514
1515 assert_eq!(command_runner.commands(), ["touch foo.h", "cc foo.c"]);
1516 }
1517
1518 #[tokio::test]
1519 async fn ignore_missing_header_dependency() {
1520 let file_system = FakeFileSystem::default();
1521 let context = create_context(&Default::default(), &Default::default(), &file_system);
1522 let build = explicit_build(
1523 vec!["foo.o".into()],
1524 Rule::new("cc foo.c", None),
1525 vec!["foo.c".into()],
1526 );
1527
1528 context
1529 .database()
1530 .set_header_dependencies(build.id(), &["foo.h".into()])
1531 .unwrap();
1532 file_system.write_file("foo.c", "");
1533
1534 run(
1535 &context,
1536 create_simple_config(vec![build], &["foo.o"]),
1537 &[],
1538 DEFAULT_OPTIONS,
1539 )
1540 .await
1541 .unwrap();
1542 }
1543
1544 #[tokio::test]
1545 async fn detect_circular_header_dependency() {
1546 let context = create_context(
1547 &Default::default(),
1548 &Default::default(),
1549 &Default::default(),
1550 );
1551 let build = explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]);
1552
1553 context
1554 .database()
1555 .set_header_dependencies(build.id(), &["foo".into()])
1556 .unwrap();
1557
1558 assert_eq!(
1559 run(
1560 &context,
1561 create_simple_config(vec![build], &["foo"]),
1562 &[],
1563 DEFAULT_OPTIONS,
1564 )
1565 .await,
1566 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1567 vec!["foo".into()]
1568 )))
1569 );
1570 }
1571
1572 #[tokio::test]
1573 async fn build_dynamic_input() {
1574 let command_runner = FakeCommandRunner::default();
1575 let file_system = FakeFileSystem::default();
1576
1577 file_system.write_file(
1578 "foo.dd",
1579 "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
1580 );
1581 file_system.write_file("bar", "");
1582
1583 run(
1584 &create_context(&command_runner, &Default::default(), &file_system),
1585 create_simple_config(
1586 vec![
1587 Build::new(
1588 vec!["foo".into()],
1589 vec![],
1590 Rule::new("touch foo", None).into(),
1591 vec![],
1592 vec!["foo.dd".into()],
1593 Some("foo.dd".into()),
1594 ),
1595 explicit_build(
1596 vec!["foo.dd".into()],
1597 Rule::new("touch foo.dd", None),
1598 vec![],
1599 ),
1600 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
1601 ],
1602 &["foo"],
1603 ),
1604 &[],
1605 DEFAULT_OPTIONS,
1606 )
1607 .await
1608 .unwrap();
1609
1610 assert_eq!(
1611 command_runner.commands(),
1612 ["touch foo.dd", "touch bar", "touch foo"]
1613 );
1614 }
1615
1616 #[tokio::test]
1617 async fn build_dynamic_input_declared_for_implicit_output() {
1618 let command_runner = FakeCommandRunner::default();
1619 let file_system = FakeFileSystem::default();
1620
1621 file_system.write_file(
1622 "foo.dd",
1623 "ninja_dyndep_version = 1\nbuild baz: dyndep | bar\n",
1624 );
1625 file_system.write_file("bar", "");
1626
1627 run(
1628 &create_context(&command_runner, &Default::default(), &file_system),
1629 create_simple_config(
1630 vec![
1631 Build::new(
1632 vec!["foo".into()],
1633 vec!["baz".into()],
1634 Rule::new("touch foo", None).into(),
1635 vec![],
1636 vec!["foo.dd".into()],
1637 Some("foo.dd".into()),
1638 ),
1639 explicit_build(
1640 vec!["foo.dd".into()],
1641 Rule::new("touch foo.dd", None),
1642 vec![],
1643 ),
1644 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
1645 ],
1646 &["foo"],
1647 ),
1648 &[],
1649 DEFAULT_OPTIONS,
1650 )
1651 .await
1652 .unwrap();
1653
1654 assert_eq!(
1655 command_runner.commands(),
1656 ["touch foo.dd", "touch bar", "touch foo"]
1657 );
1658 }
1659
1660 #[tokio::test]
1661 async fn fail_with_missing_dynamic_dependency() {
1662 let file_system = FakeFileSystem::default();
1663 let build = Build::new(
1664 vec!["foo".into()],
1665 vec![],
1666 Rule::new("touch foo", None).into(),
1667 vec![],
1668 vec![],
1669 Some("foo.dd".into()),
1670 );
1671
1672 file_system.write_file("foo.dd", "ninja_dyndep_version = 1\n");
1673
1674 assert_eq!(
1675 run(
1676 &create_context(&Default::default(), &Default::default(), &file_system),
1677 create_simple_config(vec![build.clone()], &["foo"]),
1678 &[],
1679 DEFAULT_OPTIONS,
1680 )
1681 .await,
1682 Err(BuildError::DynamicDependencyNotFound(build.into()))
1683 );
1684 }
1685
1686 #[tokio::test]
1687 async fn fail_with_unknown_output_in_dynamic_dependency() {
1688 let file_system = FakeFileSystem::default();
1689
1690 file_system.write_file(
1691 "foo.dd",
1692 "ninja_dyndep_version = 1\nbuild bar: dyndep | baz\n",
1693 );
1694
1695 assert_eq!(
1696 run(
1697 &create_context(&Default::default(), &Default::default(), &file_system),
1698 create_simple_config(
1699 vec![Build::new(
1700 vec!["foo".into()],
1701 vec![],
1702 Rule::new("touch foo", None).into(),
1703 vec![],
1704 vec![],
1705 Some("foo.dd".into()),
1706 )],
1707 &["foo"],
1708 ),
1709 &[],
1710 DEFAULT_OPTIONS,
1711 )
1712 .await,
1713 Err(BuildError::BuildGraph(BuildGraphError::OutputNotFound(
1714 "bar".into()
1715 )))
1716 );
1717 }
1718
1719 #[tokio::test]
1720 async fn detect_circular_dynamic_dependency() {
1721 let file_system = FakeFileSystem::default();
1722
1723 file_system.write_file(
1724 "foo.dd",
1725 "ninja_dyndep_version = 1\nbuild foo: dyndep | foo\n",
1726 );
1727
1728 assert_eq!(
1729 run(
1730 &create_context(&Default::default(), &Default::default(), &file_system),
1731 create_simple_config(
1732 vec![Build::new(
1733 vec!["foo".into()],
1734 vec![],
1735 Rule::new("touch foo", None).into(),
1736 vec![],
1737 vec![],
1738 Some("foo.dd".into()),
1739 )],
1740 &["foo"],
1741 ),
1742 &[],
1743 DEFAULT_OPTIONS,
1744 )
1745 .await,
1746 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1747 vec!["foo".into()]
1748 )))
1749 );
1750 }
1751
1752 #[tokio::test]
1753 async fn run_builds_concurrently() {
1754 let command_runner = FakeCommandRunner::default();
1755
1756 run(
1757 &create_context(&command_runner, &Default::default(), &Default::default()),
1758 create_simple_config(
1759 vec![
1760 pool_build("foo", None),
1761 pool_build("bar", None),
1762 pool_build("baz", None),
1763 ],
1764 &["foo", "bar", "baz"],
1765 ),
1766 &[],
1767 DEFAULT_OPTIONS,
1768 )
1769 .await
1770 .unwrap();
1771
1772 assert_eq!(command_runner.max_concurrency(), 3);
1773 }
1774
1775 #[tokio::test]
1776 async fn limit_concurrency_of_builds_in_pool() {
1777 let command_runner = FakeCommandRunner::default();
1778
1779 run(
1780 &create_context(&command_runner, &Default::default(), &Default::default()),
1781 create_pool_config(
1782 vec![
1783 pool_build("foo", limited_pool("qux")),
1784 pool_build("bar", limited_pool("qux")),
1785 pool_build("baz", limited_pool("qux")),
1786 ],
1787 &["foo", "bar", "baz"],
1788 &[("qux", 1)],
1789 ),
1790 &[],
1791 DEFAULT_OPTIONS,
1792 )
1793 .await
1794 .unwrap();
1795
1796 assert_eq!(command_runner.commands().len(), 3);
1797 assert_eq!(command_runner.max_concurrency(), 1);
1798 }
1799
1800 #[tokio::test]
1801 async fn limit_concurrency_of_builds_in_pool_with_depth_of_two() {
1802 let command_runner = FakeCommandRunner::default();
1803
1804 run(
1805 &create_context(&command_runner, &Default::default(), &Default::default()),
1806 create_pool_config(
1807 vec![
1808 pool_build("foo", limited_pool("quux")),
1809 pool_build("bar", limited_pool("quux")),
1810 pool_build("baz", limited_pool("quux")),
1811 pool_build("qux", limited_pool("quux")),
1812 ],
1813 &["foo", "bar", "baz", "qux"],
1814 &[("quux", 2)],
1815 ),
1816 &[],
1817 DEFAULT_OPTIONS,
1818 )
1819 .await
1820 .unwrap();
1821
1822 assert_eq!(command_runner.commands().len(), 4);
1823 assert_eq!(command_runner.max_concurrency(), 2);
1824 }
1825
1826 #[tokio::test]
1827 async fn run_builds_in_different_pools_concurrently() {
1828 let command_runner = FakeCommandRunner::default();
1829
1830 run(
1831 &create_context(&command_runner, &Default::default(), &Default::default()),
1832 create_pool_config(
1833 vec![
1834 pool_build("foo", limited_pool("baz")),
1835 pool_build("bar", limited_pool("qux")),
1836 ],
1837 &["foo", "bar"],
1838 &[("baz", 1), ("qux", 1)],
1839 ),
1840 &[],
1841 DEFAULT_OPTIONS,
1842 )
1843 .await
1844 .unwrap();
1845
1846 assert_eq!(command_runner.max_concurrency(), 2);
1847 }
1848
1849 #[tokio::test]
1850 async fn run_build_in_pool_of_huge_depth() {
1851 let command_runner = FakeCommandRunner::default();
1852
1853 run(
1854 &create_context(&command_runner, &Default::default(), &Default::default()),
1855 create_pool_config(
1856 vec![pool_build("foo", limited_pool("bar"))],
1857 &["foo"],
1858 &[("bar", usize::MAX)],
1859 ),
1860 &[],
1861 DEFAULT_OPTIONS,
1862 )
1863 .await
1864 .unwrap();
1865
1866 assert_eq!(command_runner.commands(), ["touch foo"]);
1867 }
1868
1869 #[tokio::test]
1870 async fn run_build_in_console_pool() {
1871 let command_runner = FakeCommandRunner::new(
1872 [(
1873 "touch foo".into(),
1874 Output {
1875 status: ExitStatus::default(),
1876 stdout: b"bar\n".into(),
1877 stderr: b"baz\n".into(),
1878 },
1879 )]
1880 .into_iter()
1881 .collect(),
1882 );
1883 let console = FakeConsole::default();
1884
1885 run(
1886 &create_context(&command_runner, &console, &Default::default()),
1887 create_simple_config(
1888 vec![explicit_build(
1889 vec!["foo".into()],
1890 Rule::new("touch foo", Some("build foo".into())).with_pool(Some(Pool::Console)),
1891 vec![],
1892 )],
1893 &["foo"],
1894 ),
1895 &[],
1896 DEFAULT_OPTIONS,
1897 )
1898 .await
1899 .unwrap();
1900
1901 assert_eq!(command_runner.commands(), Vec::<String>::new());
1902 assert_eq!(command_runner.console_commands(), ["touch foo"]);
1903 assert_eq!(console.stdout(), "");
1904 assert_eq!(console.stderr(), "build foo\n");
1905 }
1906
1907 #[tokio::test]
1908 async fn limit_concurrency_of_builds_in_console_pool() {
1909 let command_runner = FakeCommandRunner::default();
1910
1911 run(
1912 &create_context(&command_runner, &Default::default(), &Default::default()),
1913 create_simple_config(
1914 vec![
1915 pool_build("foo", Some(Pool::Console)),
1916 pool_build("bar", Some(Pool::Console)),
1917 pool_build("baz", Some(Pool::Console)),
1918 ],
1919 &["foo", "bar", "baz"],
1920 ),
1921 &[],
1922 DEFAULT_OPTIONS,
1923 )
1924 .await
1925 .unwrap();
1926
1927 assert_eq!(command_runner.console_commands().len(), 3);
1928 assert_eq!(command_runner.max_concurrency(), 1);
1929 }
1930
1931 #[tokio::test]
1932 async fn write_debug_log_of_failed_build_in_console_pool() {
1933 let command_runner =
1934 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1935 let console = FakeConsole::default();
1936
1937 assert_eq!(
1938 run(
1939 &create_context(&command_runner, &console, &Default::default()),
1940 create_simple_config(
1941 vec![explicit_build(
1942 vec!["foo".into()],
1943 Rule::new("exit 1", None).with_pool(Some(Pool::Console)),
1944 vec![],
1945 )],
1946 &["foo"],
1947 ),
1948 &[],
1949 RunOptions {
1950 debug: true,
1951 profile: false,
1952 },
1953 )
1954 .await,
1955 Err(BuildError::Build)
1956 );
1957 assert_eq!(command_runner.console_commands(), ["exit 1"]);
1958 assert_eq!(
1959 console.stderr(),
1960 "turtle: command: exit 1\nturtle: exit status: 1\n"
1961 );
1962 }
1963
1964 #[tokio::test]
1965 async fn lock_console_during_command_in_console_pool() {
1966 let command_runner = FakeCommandRunner::default();
1967 let context = create_run_context(&command_runner, &Default::default(), &[]);
1968 let rule = Rule::new("foo", None).with_pool(Some(Pool::Console));
1969 let mut future = pin!(run_rule(&context, &rule));
1970
1971 assert!(poll!(&mut future).is_pending());
1972 assert_eq!(command_runner.console_commands(), ["foo"]);
1973 assert!(context.application().console().try_lock().is_err());
1974
1975 future.await.unwrap();
1976
1977 assert!(context.application().console().try_lock().is_ok());
1978 }
1979
1980 #[tokio::test]
1981 async fn flush_console_before_command_in_console_pool() {
1982 let command_runner = FakeCommandRunner::default();
1983 let console = FakeConsole::default();
1984 let context = create_run_context(&command_runner, &console, &[]);
1985 let rule = Rule::new("foo", Some("bar".into())).with_pool(Some(Pool::Console));
1986 let mut future = pin!(run_rule(&context, &rule));
1987
1988 assert!(poll!(&mut future).is_pending());
1989 assert_eq!(command_runner.console_commands(), ["foo"]);
1990 assert_eq!(console.flushed_stderr(), "bar\n");
1991
1992 future.await.unwrap();
1993 }
1994
1995 #[tokio::test]
1996 async fn wait_for_console_before_command_in_console_pool() {
1997 let command_runner = FakeCommandRunner::default();
1998 let context = create_run_context(&command_runner, &Default::default(), &[]);
1999 let rule = Rule::new("foo", None).with_pool(Some(Pool::Console));
2000 let console = context.application().console().lock().await;
2001 let mut future = pin!(run_rule(&context, &rule));
2002
2003 for _ in 0..POLL_COUNT {
2004 assert!(poll!(&mut future).is_pending());
2005 }
2006
2007 assert_eq!(command_runner.console_commands(), Vec::<String>::new());
2008
2009 drop(console);
2010 future.await.unwrap();
2011
2012 assert_eq!(command_runner.console_commands(), ["foo"]);
2013 }
2014
2015 #[tokio::test]
2016 async fn run_command_concurrently_with_command_in_console_pool() {
2017 let command_runner = FakeCommandRunner::default();
2018 let context = create_run_context(&command_runner, &Default::default(), &[]);
2019 let foo = Rule::new("foo", None).with_pool(Some(Pool::Console));
2020 let bar = Rule::new("bar", None);
2021 let mut foo_future = pin!(run_rule(&context, &foo));
2022 let mut bar_future = pin!(run_rule(&context, &bar));
2023
2024 assert!(poll!(&mut foo_future).is_pending());
2025 assert!(poll!(&mut bar_future).is_pending());
2026 assert_eq!(command_runner.console_commands(), ["foo"]);
2027 assert_eq!(command_runner.commands(), ["bar"]);
2028
2029 foo_future.await.unwrap();
2030 bar_future.await.unwrap();
2031 }
2032
2033 #[tokio::test]
2034 async fn release_pool_before_waiting_for_console() {
2035 let command_runner = FakeCommandRunner::default();
2036 let context = create_run_context(&command_runner, &Default::default(), &[("baz", 1)]);
2037 let foo = Rule::new("foo", None).with_pool(limited_pool("baz"));
2038 let bar = Rule::new("bar", None).with_pool(limited_pool("baz"));
2039 let console = context.application().console().lock().await;
2040 let mut foo_future = pin!(run_rule(&context, &foo));
2041 let mut bar_future = pin!(run_rule(&context, &bar));
2042
2043 for _ in 0..POLL_COUNT {
2044 assert!(poll!(&mut foo_future).is_pending());
2045 }
2046
2047 for _ in 0..POLL_COUNT {
2048 assert!(poll!(&mut bar_future).is_pending());
2049 }
2050
2051 assert_eq!(command_runner.commands(), ["foo", "bar"]);
2052
2053 drop(console);
2054 foo_future.await.unwrap();
2055 bar_future.await.unwrap();
2056 }
2057
2058 #[tokio::test]
2059 async fn wait_for_pool_before_locking_console() {
2060 let command_runner = FakeCommandRunner::default();
2061 let console = FakeConsole::default();
2062 let context = create_run_context(&command_runner, &console, &[("bar", 1)]);
2063 let rule = Rule::new("foo", Some("foo".into())).with_pool(limited_pool("bar"));
2064 let permit = context.pool(rule.pool()).await.unwrap();
2065 let mut future = pin!(run_rule(&context, &rule));
2066
2067 for _ in 0..POLL_COUNT {
2068 assert!(poll!(&mut future).is_pending());
2069 }
2070
2071 assert_eq!(command_runner.commands(), Vec::<String>::new());
2072 assert_eq!(console.stderr(), "");
2073 assert!(context.application().console().try_lock().is_ok());
2074
2075 drop(permit);
2076 future.await.unwrap();
2077
2078 assert_eq!(command_runner.commands(), ["foo"]);
2079 assert_eq!(console.stderr(), "foo\n");
2080 }
2081
2082 #[tokio::test]
2083 async fn keep_pool_until_command_finishes_on_console_error() {
2084 let command_runner = FakeCommandRunner::default();
2085 let context = create_run_context(&command_runner, &FakeConsole::failing(), &[("baz", 1)]);
2086 let foo = Rule::new("foo", Some("foo".into())).with_pool(limited_pool("baz"));
2087 let bar = Rule::new("bar", None).with_pool(limited_pool("baz"));
2088 let mut foo_future = pin!(run_rule(&context, &foo));
2089 let mut bar_future = pin!(run_rule(&context, &bar));
2090
2091 assert!(poll!(&mut foo_future).is_pending());
2092 assert!(poll!(&mut bar_future).is_pending());
2093 assert_eq!(command_runner.commands(), ["foo"]);
2094 assert!(foo_future.await.is_err());
2095 }
2096
2097 #[test]
2098 fn classify_phony_and_file_inputs() {
2099 let build = explicit_build(
2100 vec!["foo".into()],
2101 Rule::new("", None),
2102 vec!["bar".into(), "baz".into(), "qux".into(), "bar".into()],
2103 );
2104 let context = RunContext::new(
2105 create_context(
2106 &Default::default(),
2107 &Default::default(),
2108 &Default::default(),
2109 ),
2110 create_simple_config(
2111 vec![
2112 build.clone(),
2113 Build::new(vec!["bar".into()], vec![], None, vec![], vec![], None),
2114 explicit_build(vec!["baz".into()], Rule::new("", None), vec![]),
2115 ],
2116 &[],
2117 ),
2118 BuildGraph::new(&Default::default()),
2119 DEFAULT_OPTIONS,
2120 );
2121
2122 assert_eq!(
2123 classify_inputs(
2124 &context,
2125 &build,
2126 &["quux".into(), "baz".into()],
2127 &["qux".into(), "corge".into()],
2128 ),
2129 (vec!["bar"], vec!["baz", "qux", "quux", "corge"])
2130 );
2131 }
2132}