1mod context;
2mod file_cache;
3mod hash;
4mod header_dependency;
5mod log;
6mod options;
7
8use self::{
9 context::RunContext,
10 hash::{calculate_content_hash, calculate_timestamp_hash},
11 header_dependency::{exclude_show_includes, read_header_dependencies},
12 log::{debug, profile},
13};
14use crate::{
15 build_graph::{BuildGraph, BuildGraphError},
16 compile::compile_dynamic,
17 context::Context,
18 error::BuildError,
19 hash_type::HashType,
20 infrastructure::{Console, Metadata},
21 ir::{Build, Config, DynamicConfig, Pool, Rule},
22 parse::parse_dynamic,
23};
24use alloc::sync::Arc;
25use async_recursion::async_recursion;
26use futures::future::{FutureExt, try_join_all};
27use itertools::Itertools;
28pub use options::RunOptions;
29use std::{collections::HashMap, path::Path, process::Output};
30use tokio::{join, spawn, sync::MutexGuard, time::Instant, try_join};
31
32pub async fn run(
34 context: &Arc<Context>,
35 config: Arc<Config>,
36 outputs: &[String],
37 options: RunOptions,
38) -> Result<(), BuildError> {
39 let mut graph = BuildGraph::new(config.outputs());
40 let mut header_dependencies = HashMap::new();
41
42 for build in config
43 .outputs()
44 .values()
45 .filter(|build| build.rule().is_some())
46 .unique_by(|build| build.id())
47 {
48 let dependencies = context.database().get_header_dependencies(build.id())?;
49
50 graph.add_header_dependencies(&build.outputs()[0], &dependencies);
51 header_dependencies.insert(build.id(), dependencies);
52 }
53
54 let context = Arc::new(RunContext::new(
55 context.clone(),
56 config,
57 graph,
58 header_dependencies,
59 options,
60 ));
61
62 context
63 .build_graph()
64 .lock()
65 .await
66 .validate()
67 .map_err(|error| map_build_graph_error(&context, &error))?;
68
69 try_join_all(
70 if outputs.is_empty() {
71 context
72 .config()
73 .default_outputs()
74 .iter()
75 .map(|output| {
76 context
77 .config()
78 .outputs()
79 .get(output.as_ref())
80 .ok_or_else(|| BuildError::DefaultOutputNotFound(output.clone()))
81 })
82 .collect::<Result<Vec<_>, _>>()?
83 } else {
84 outputs
85 .iter()
86 .map(|output| {
87 context
88 .config()
89 .outputs()
90 .get(output.as_str())
91 .ok_or_else(|| BuildError::OutputNotFound(output.clone()))
92 })
93 .collect::<Result<Vec<_>, _>>()?
94 }
95 .into_iter()
96 .map(|build| run_build(context.clone(), build)),
97 )
98 .await?;
99
100 Ok(())
101}
102
103#[async_recursion]
104async fn run_build(context: Arc<RunContext>, build: &Arc<Build>) -> Result<(), BuildError> {
105 let future = context
107 .build_futures()
108 .entry_async(build.id())
109 .await
110 .or_insert_with(|| spawn_build(context.clone(), build.clone()).boxed().shared())
111 .get()
112 .clone();
113
114 future.await
115}
116
117async fn spawn_build(context: Arc<RunContext>, build: Arc<Build>) -> Result<(), BuildError> {
118 spawn(async move {
119 let (_, output_metadata) = try_join!(
120 try_join_all(
121 build
122 .inputs()
123 .iter()
124 .chain(build.order_only_inputs())
125 .map(|input| build_input(context.clone(), input)),
126 ),
127 get_output_metadata(&context, &build),
128 )?;
129
130 let dynamic_inputs = if let Some(path) = build.dynamic_module() {
131 let config = load_dynamic_config(&context, path).await?;
132
133 build
134 .outputs()
135 .iter()
136 .chain(build.implicit_outputs())
137 .find_map(|output| config.outputs().get(output.as_ref()))
138 .map(|build| build.inputs())
139 .ok_or_else(|| BuildError::DynamicDependencyNotFound(build.clone()))?
140 } else {
141 &[]
142 };
143
144 try_join_all(
145 dynamic_inputs
146 .iter()
147 .map(|input| build_input(context.clone(), input)),
148 )
149 .await?;
150
151 if build.rule().is_none() {
152 invalidate_outputs(&context, &build).await;
155 }
156
157 let dependencies = context.header_dependencies(build.id());
158
159 try_join_all(
160 dependencies
161 .iter()
162 .filter_map(|dependency| context.config().outputs().get(dependency))
163 .map(|build| run_build(context.clone(), build)),
164 )
165 .await?;
166
167 let header_dependencies =
168 filter_existing_header_dependencies(&context, dependencies).await?;
169
170 let (phony_inputs, file_inputs) =
171 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
172 let mut timestamp_hash =
173 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
174
175 if let Some(metadata) = &output_metadata
176 && Some(timestamp_hash)
177 == context
178 .build()
179 .database()
180 .get_hash(HashType::Timestamp, build.id())?
181 {
182 cache_output_metadata(&context, &build, metadata).await;
183
184 return Ok(());
185 }
186
187 let mut content_hash =
188 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
189
190 if output_metadata.is_some()
191 && Some(content_hash)
192 == context
193 .build()
194 .database()
195 .get_hash(HashType::Content, build.id())?
196 {
197 return Ok(());
198 } else if let Some(rule) = build.rule() {
199 try_join_all(
200 build
201 .outputs()
202 .iter()
203 .chain(build.implicit_outputs())
204 .map(|path| prepare_directory(&context, path.as_ref())),
205 )
206 .await?;
207
208 let output = run_rule(&context, rule).await;
209
210 invalidate_outputs(&context, &build).await;
211
212 let new_header_dependencies =
213 read_header_dependencies(&context, rule, &output?).await?;
214
215 context
216 .build()
217 .database()
218 .set_header_dependencies(build.id(), &new_header_dependencies)?;
219
220 for output in build.outputs() {
221 context.build().database().set_output(output)?;
222
223 if let Some(source) = context.config().source_map().get(output) {
224 context.build().database().set_source(output, source)?;
225 }
226 }
227
228 if header_dependencies
229 .iter()
230 .copied()
231 .ne(&new_header_dependencies)
232 {
233 let header_dependencies =
234 filter_existing_header_dependencies(&context, &new_header_dependencies).await?;
235 let (phony_inputs, file_inputs) =
236 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
237
238 timestamp_hash =
239 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
240 content_hash =
241 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
242 }
243 }
244
245 context
246 .build()
247 .database()
248 .set_hash(HashType::Timestamp, build.id(), timestamp_hash)?;
249 context
250 .build()
251 .database()
252 .set_hash(HashType::Content, build.id(), content_hash)?;
253
254 Ok(())
255 })
256 .await?
257}
258
259async fn build_input(context: Arc<RunContext>, input: &Arc<str>) -> Result<(), BuildError> {
260 if let Some(build) = context.config().outputs().get(input) {
261 run_build(context.clone(), build).await
262 } else {
263 check_file_existence(&context, input).await
264 }
265}
266
267async fn load_dynamic_config<'a>(
268 context: &'a RunContext,
269 path: &str,
270) -> Result<&'a DynamicConfig, BuildError> {
271 context
272 .dynamic_config(path)
273 .get_or_try_init(|| async {
274 let config = compile_dynamic(
275 &parse_dynamic(
276 &context
277 .build()
278 .file_system()
279 .read_file_to_string(path.as_ref())
280 .await?,
281 )?,
282 context.build().path_pool(),
283 )?;
284
285 context
286 .build_graph()
287 .lock()
288 .await
289 .validate_dynamic(&config)
290 .map_err(|error| map_build_graph_error(context, &error))?;
291
292 Ok(config)
293 })
294 .await
295}
296
297async fn get_output_metadata(
298 context: &RunContext,
299 build: &Build,
300) -> Result<Option<Vec<Metadata>>, BuildError> {
301 Ok(try_join_all(
302 build
303 .outputs()
304 .iter()
305 .chain(build.implicit_outputs())
306 .map(|path| {
307 context
308 .build()
309 .file_system()
310 .metadata(path.as_ref().as_ref())
311 }),
312 )
313 .await?
314 .into_iter()
315 .collect())
316}
317
318async fn cache_output_metadata(context: &RunContext, build: &Build, metadata: &[Metadata]) {
319 if build.rule().is_none() {
321 return;
322 }
323
324 for (path, &metadata) in build
325 .outputs()
326 .iter()
327 .chain(build.implicit_outputs())
328 .zip(metadata)
329 {
330 context.file_cache().set_metadata(path, metadata).await;
331 }
332}
333
334async fn filter_existing_header_dependencies<'a>(
335 context: &RunContext,
336 dependencies: &'a [Arc<str>],
337) -> Result<Vec<&'a Arc<str>>, BuildError> {
338 let mut existing_dependencies = vec![];
339
340 for dependency in dependencies {
341 if context.file_cache().exists(dependency).await? {
342 existing_dependencies.push(dependency);
343 }
344 }
345
346 Ok(existing_dependencies)
347}
348
349async fn check_file_existence(context: &RunContext, path: &Arc<str>) -> Result<(), BuildError> {
350 if !context.file_cache().exists(path).await? {
351 return Err(BuildError::FileNotFound(
352 context
353 .build()
354 .database()
355 .get_source(path)?
356 .unwrap_or_else(|| path.as_ref().into()),
357 ));
358 }
359
360 Ok(())
361}
362
363async fn prepare_directory(context: &RunContext, path: impl AsRef<Path>) -> Result<(), BuildError> {
364 if let Some(directory) = path.as_ref().parent() {
365 context
366 .build()
367 .file_system()
368 .create_directory(directory)
369 .await?;
370 }
371
372 Ok(())
373}
374
375async fn invalidate_outputs(context: &RunContext, build: &Build) {
376 for output in build.outputs().iter().chain(build.implicit_outputs()) {
377 context.file_cache().invalidate(output).await;
378 }
379}
380
381fn classify_inputs<'a>(
382 context: &'a RunContext,
383 build: &'a Build,
384 dynamic_inputs: &'a [Arc<str>],
385 header_dependencies: &'a [&'a Arc<str>],
386) -> (Vec<&'a Arc<str>>, Vec<&'a Arc<str>>) {
387 let (phony_inputs, file_inputs) = build
388 .inputs()
389 .iter()
390 .chain(dynamic_inputs)
391 .unique()
392 .partition::<Vec<_>, _>(|&input| {
393 context
394 .config()
395 .outputs()
396 .get(input)
397 .map_or_default(|build| build.rule().is_none())
398 });
399
400 (
401 phony_inputs,
402 file_inputs
403 .into_iter()
404 .chain(header_dependencies.iter().copied())
405 .unique()
406 .collect(),
407 )
408}
409
410async fn run_rule(context: &RunContext, rule: &Rule) -> Result<Output, BuildError> {
411 let ((output, duration), mut console) = if rule.pool() == Some(&Pool::Console) {
412 let mut console = write_description(context, rule).await?;
413
414 console.flush().await?;
415
416 let time = Instant::now();
417 let status = context
418 .build()
419 .command_runner()
420 .run_with_console(rule.command())
421 .await?;
422
423 (
424 (
425 Output {
426 status,
427 stdout: vec![],
428 stderr: vec![],
429 },
430 Instant::now() - time,
431 ),
432 console,
433 )
434 } else {
435 let permit = context.pool(rule.pool()).await?;
436 let (output, console) = join!(
437 async {
438 let time = Instant::now();
439 let output = context.build().command_runner().run(rule.command()).await?;
440
441 drop(permit);
442
443 Ok::<_, BuildError>((output, Instant::now() - time))
444 },
445 write_description(context, rule)
446 );
447
448 (output?, console?)
449 };
450
451 profile!(context, console, "duration: {} ms", duration.as_millis());
452
453 console
454 .write_stdout(&exclude_show_includes(rule, &output.stdout))
455 .await?;
456 console.write_stderr(&output.stderr).await?;
457
458 if !output.status.success() {
459 debug!(
460 context,
461 console,
462 "exit status: {}",
463 output
464 .status
465 .code()
466 .map(|code| code.to_string())
467 .unwrap_or_else(|| "-".into())
468 );
469
470 return Err(BuildError::Build);
471 }
472
473 Ok(output)
474}
475
476async fn write_description<'a>(
477 context: &'a RunContext,
478 rule: &Rule,
479) -> Result<MutexGuard<'a, dyn Console + Send + Sync>, BuildError> {
480 let mut console = context.build().console().lock().await;
481
482 if let Some(description) = rule.description() {
483 console.write_stderr(description.as_bytes()).await?;
484 console.write_stderr(b"\n").await?;
485 }
486
487 debug!(context, console, "command: {}", rule.command());
488
489 Ok(console)
490}
491
492fn map_build_graph_error(context: &RunContext, error: &BuildGraphError) -> BuildError {
493 match error {
494 BuildGraphError::CircularDependency(outputs) => {
495 match outputs
496 .iter()
497 .map(|output| {
498 Ok(context
499 .build()
500 .database()
501 .get_source(output)?
502 .map(|string| string.into())
503 .unwrap_or_else(|| output.clone()))
504 })
505 .collect::<Result<Vec<_>, BuildError>>()
506 {
507 Ok(outputs) => {
508 BuildGraphError::CircularDependency(outputs.into_iter().dedup().collect())
509 .into()
510 }
511 Err(error) => error,
512 }
513 }
514 BuildGraphError::OutputNotFound(_) => error.clone().into(),
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use crate::{
522 infrastructure::{FakeCommandRunner, FakeConsole, FakeDatabase, FakeFileSystem, FileError},
523 ir::HeaderDependency,
524 };
525 use core::{num::NonZeroUsize, pin::pin};
526 use futures::poll;
527 use pretty_assertions::{assert_eq, assert_ne};
528 use regex::Regex;
529 #[cfg(unix)]
530 use std::os::unix::process::ExitStatusExt;
531 #[cfg(windows)]
532 use std::os::windows::process::ExitStatusExt;
533 use std::{collections::HashMap, process::ExitStatus};
534 use tokio::{sync::Mutex, task::yield_now};
535
536 const DEFAULT_OPTIONS: RunOptions = RunOptions {
537 debug: false,
538 profile: false,
539 };
540 const POLL_COUNT: usize = 8;
541
542 fn create_context(
543 command_runner: &FakeCommandRunner,
544 console: &FakeConsole,
545 file_system: &FakeFileSystem,
546 ) -> Arc<Context> {
547 Context::new(
548 command_runner.clone(),
549 Mutex::new(console.clone()).into(),
550 FakeDatabase::default(),
551 file_system.clone(),
552 Default::default(),
553 )
554 .into()
555 }
556
557 fn create_outputs(builds: Vec<Build>) -> HashMap<Arc<str>, Arc<Build>> {
558 builds
559 .into_iter()
560 .map(Arc::new)
561 .flat_map(|build| {
562 build
563 .outputs()
564 .iter()
565 .chain(build.implicit_outputs())
566 .map(|output| (output.clone(), build.clone()))
567 .collect::<Vec<_>>()
568 })
569 .collect()
570 }
571
572 fn create_simple_config(builds: Vec<Build>, default_outputs: &[&str]) -> Arc<Config> {
573 create_pool_config(builds, default_outputs, &[])
574 }
575
576 fn create_pool_config(
577 builds: Vec<Build>,
578 default_outputs: &[&str],
579 pools: &[(&str, usize)],
580 ) -> Arc<Config> {
581 Config::new(
582 create_outputs(builds),
583 default_outputs
584 .iter()
585 .map(|&output| output.into())
586 .collect(),
587 Default::default(),
588 pools
589 .iter()
590 .map(|&(name, depth)| (name.into(), NonZeroUsize::new(depth).unwrap()))
591 .collect(),
592 None,
593 )
594 .into()
595 }
596
597 fn explicit_build(outputs: Vec<Arc<str>>, rule: Rule, inputs: Vec<Arc<str>>) -> Build {
598 Build::new(outputs, vec![], rule.into(), inputs, vec![], None)
599 }
600
601 fn failed_output() -> Output {
602 Output {
603 status: ExitStatus::from_raw(cfg_select! {
604 unix => 1 << 8,
605 windows => 1,
606 }),
607 stdout: vec![],
608 stderr: vec![],
609 }
610 }
611
612 fn limited_pool(name: &str) -> Option<Pool> {
613 Some(Pool::Limited(name.into()))
614 }
615
616 fn pool_build(output: &str, pool: Option<Pool>) -> Build {
617 explicit_build(
618 vec![output.into()],
619 Rule::new(format!("touch {output}"), None).with_pool(pool),
620 vec![],
621 )
622 }
623
624 fn count_metadata_requests(file_system: &FakeFileSystem, path: &str) -> usize {
625 file_system
626 .metadata_requests()
627 .iter()
628 .filter(|request| request.as_path() == Path::new(path))
629 .count()
630 }
631
632 fn create_run_context(
633 command_runner: &FakeCommandRunner,
634 console: &FakeConsole,
635 pools: &[(&str, usize)],
636 ) -> RunContext {
637 RunContext::new(
638 create_context(command_runner, console, &Default::default()),
639 create_pool_config(vec![], &[], pools),
640 BuildGraph::new(&Default::default()),
641 Default::default(),
642 DEFAULT_OPTIONS,
643 )
644 }
645
646 fn create_build_run_context(
647 command_runner: &FakeCommandRunner,
648 file_system: &FakeFileSystem,
649 builds: Vec<Build>,
650 ) -> Arc<RunContext> {
651 RunContext::new(
652 create_context(command_runner, &Default::default(), file_system),
653 create_simple_config(builds, &[]),
654 BuildGraph::new(&Default::default()),
655 Default::default(),
656 DEFAULT_OPTIONS,
657 )
658 .into()
659 }
660
661 #[tokio::test]
662 async fn build_nothing() {
663 let command_runner = FakeCommandRunner::default();
664
665 run(
666 &create_context(&command_runner, &Default::default(), &Default::default()),
667 create_simple_config(vec![], &[]),
668 &[],
669 DEFAULT_OPTIONS,
670 )
671 .await
672 .unwrap();
673
674 assert_eq!(command_runner.commands(), Vec::<String>::new());
675 }
676
677 #[tokio::test]
678 async fn build_default_output() {
679 let command_runner = FakeCommandRunner::default();
680
681 run(
682 &create_context(&command_runner, &Default::default(), &Default::default()),
683 create_simple_config(
684 vec![
685 explicit_build(
686 vec!["foo".into()],
687 Rule::new("touch foo".into(), None),
688 vec![],
689 ),
690 explicit_build(
691 vec!["bar".into()],
692 Rule::new("touch bar".into(), None),
693 vec![],
694 ),
695 ],
696 &["foo"],
697 ),
698 &[],
699 DEFAULT_OPTIONS,
700 )
701 .await
702 .unwrap();
703
704 assert_eq!(command_runner.commands(), ["touch foo"]);
705 }
706
707 #[tokio::test]
708 async fn build_specified_output() {
709 let command_runner = FakeCommandRunner::default();
710
711 run(
712 &create_context(&command_runner, &Default::default(), &Default::default()),
713 create_simple_config(
714 vec![
715 explicit_build(
716 vec!["foo".into()],
717 Rule::new("touch foo".into(), None),
718 vec![],
719 ),
720 explicit_build(
721 vec!["bar".into()],
722 Rule::new("touch bar".into(), None),
723 vec![],
724 ),
725 ],
726 &["foo"],
727 ),
728 &["bar".into()],
729 DEFAULT_OPTIONS,
730 )
731 .await
732 .unwrap();
733
734 assert_eq!(command_runner.commands(), ["touch bar"]);
735 }
736
737 #[tokio::test]
738 async fn build_duplicate_outputs() {
739 let command_runner = FakeCommandRunner::default();
740
741 run(
742 &create_context(&command_runner, &Default::default(), &Default::default()),
743 create_simple_config(
744 vec![explicit_build(
745 vec!["foo".into()],
746 Rule::new("touch foo".into(), None),
747 vec![],
748 )],
749 &[],
750 ),
751 &["foo".into(), "foo".into()],
752 DEFAULT_OPTIONS,
753 )
754 .await
755 .unwrap();
756
757 assert_eq!(command_runner.commands(), ["touch foo"]);
758 }
759
760 #[tokio::test]
761 async fn fail_with_unknown_default_output() {
762 assert_eq!(
763 run(
764 &create_context(
765 &Default::default(),
766 &Default::default(),
767 &Default::default()
768 ),
769 create_simple_config(vec![], &["foo"]),
770 &[],
771 DEFAULT_OPTIONS,
772 )
773 .await,
774 Err(BuildError::DefaultOutputNotFound("foo".into()))
775 );
776 }
777
778 #[tokio::test]
779 async fn fail_with_unknown_output() {
780 assert_eq!(
781 run(
782 &create_context(
783 &Default::default(),
784 &Default::default(),
785 &Default::default()
786 ),
787 create_simple_config(vec![], &[]),
788 &["foo".into()],
789 DEFAULT_OPTIONS,
790 )
791 .await,
792 Err(BuildError::OutputNotFound("foo".into()))
793 );
794 }
795
796 #[tokio::test]
797 async fn fail_with_unknown_output_after_known_output() {
798 let command_runner = FakeCommandRunner::default();
799
800 assert_eq!(
801 run(
802 &create_context(&command_runner, &Default::default(), &Default::default()),
803 create_simple_config(
804 vec![explicit_build(
805 vec!["foo".into()],
806 Rule::new("touch foo".into(), None),
807 vec![],
808 )],
809 &[],
810 ),
811 &["foo".into(), "bar".into()],
812 DEFAULT_OPTIONS,
813 )
814 .await,
815 Err(BuildError::OutputNotFound("bar".into()))
816 );
817
818 yield_now().await;
820
821 assert_eq!(command_runner.commands(), Vec::<String>::new());
822 }
823
824 #[tokio::test]
825 async fn build_shared_input_once() {
826 let command_runner = FakeCommandRunner::default();
827 let file_system = FakeFileSystem::default();
828
829 file_system.write_file("bar", "");
830 file_system.write_file("baz", "");
831
832 run(
833 &create_context(&command_runner, &Default::default(), &file_system),
834 create_simple_config(
835 vec![
836 explicit_build(
837 vec!["foo".into()],
838 Rule::new("touch foo".into(), None),
839 vec!["bar".into(), "baz".into()],
840 ),
841 explicit_build(
842 vec!["bar".into()],
843 Rule::new("touch bar".into(), None),
844 vec!["baz".into()],
845 ),
846 explicit_build(
847 vec!["baz".into()],
848 Rule::new("touch baz".into(), None),
849 vec![],
850 ),
851 ],
852 &["foo"],
853 ),
854 &[],
855 DEFAULT_OPTIONS,
856 )
857 .await
858 .unwrap();
859
860 assert_eq!(
861 command_runner.commands(),
862 ["touch baz", "touch bar", "touch foo"]
863 );
864 }
865
866 #[tokio::test]
867 async fn build_order_only_input() {
868 let command_runner = FakeCommandRunner::default();
869
870 run(
871 &create_context(&command_runner, &Default::default(), &Default::default()),
872 create_simple_config(
873 vec![
874 Build::new(
875 vec!["foo".into()],
876 vec![],
877 Rule::new("touch foo".into(), None).into(),
878 vec![],
879 vec!["bar".into()],
880 None,
881 ),
882 explicit_build(
883 vec!["bar".into()],
884 Rule::new("touch bar".into(), None),
885 vec![],
886 ),
887 ],
888 &["foo"],
889 ),
890 &[],
891 DEFAULT_OPTIONS,
892 )
893 .await
894 .unwrap();
895
896 assert_eq!(command_runner.commands(), ["touch bar", "touch foo"]);
897 }
898
899 #[tokio::test]
900 async fn build_multiple_outputs_of_build() {
901 let command_runner = FakeCommandRunner::default();
902 let file_system = FakeFileSystem::default();
903
904 file_system.write_file("foo", "");
905 file_system.write_file("bar", "");
906
907 run(
908 &create_context(&command_runner, &Default::default(), &file_system),
909 create_simple_config(
910 vec![
911 explicit_build(
912 vec!["foo".into(), "bar".into()],
913 Rule::new("touch foo bar".into(), None),
914 vec![],
915 ),
916 explicit_build(
917 vec!["baz".into()],
918 Rule::new("cat foo bar > baz".into(), None),
919 vec!["foo".into(), "bar".into()],
920 ),
921 ],
922 &["baz"],
923 ),
924 &[],
925 DEFAULT_OPTIONS,
926 )
927 .await
928 .unwrap();
929
930 assert_eq!(
931 command_runner.commands(),
932 ["touch foo bar", "cat foo bar > baz"]
933 );
934 }
935
936 #[tokio::test]
937 async fn fail_with_missing_input() {
938 let command_runner = FakeCommandRunner::default();
939
940 assert_eq!(
941 run(
942 &create_context(&command_runner, &Default::default(), &Default::default()),
943 create_simple_config(
944 vec![explicit_build(
945 vec!["foo".into()],
946 Rule::new("cp bar foo".into(), None),
947 vec!["bar".into()],
948 )],
949 &["foo"],
950 ),
951 &[],
952 DEFAULT_OPTIONS,
953 )
954 .await,
955 Err(BuildError::FileNotFound("bar".into()))
956 );
957 assert_eq!(command_runner.commands(), Vec::<String>::new());
958 }
959
960 #[tokio::test]
961 async fn fail_with_missing_order_only_input() {
962 assert_eq!(
963 run(
964 &create_context(
965 &Default::default(),
966 &Default::default(),
967 &Default::default()
968 ),
969 create_simple_config(
970 vec![Build::new(
971 vec!["foo".into()],
972 vec![],
973 Rule::new("touch foo".into(), None).into(),
974 vec![],
975 vec!["bar".into()],
976 None,
977 )],
978 &["foo"],
979 ),
980 &[],
981 DEFAULT_OPTIONS,
982 )
983 .await,
984 Err(BuildError::FileNotFound("bar".into()))
985 );
986 }
987
988 #[tokio::test]
989 async fn report_source_of_missing_input() {
990 let context = create_context(
991 &Default::default(),
992 &Default::default(),
993 &Default::default(),
994 );
995
996 context.database().set_source("foo.o", "foo.c").unwrap();
997
998 assert_eq!(
999 run(
1000 &context,
1001 create_simple_config(
1002 vec![explicit_build(
1003 vec!["foo".into()],
1004 Rule::new("cc foo.o".into(), None),
1005 vec!["foo.o".into()],
1006 )],
1007 &["foo"],
1008 ),
1009 &[],
1010 DEFAULT_OPTIONS,
1011 )
1012 .await,
1013 Err(BuildError::FileNotFound("foo.c".into()))
1014 );
1015 }
1016
1017 #[tokio::test]
1018 async fn detect_circular_dependency() {
1019 assert_eq!(
1020 run(
1021 &create_context(
1022 &Default::default(),
1023 &Default::default(),
1024 &Default::default()
1025 ),
1026 create_simple_config(
1027 vec![explicit_build(
1028 vec!["foo".into()],
1029 Rule::new("cp foo foo".into(), None),
1030 vec!["foo".into()],
1031 )],
1032 &["foo"],
1033 ),
1034 &[],
1035 DEFAULT_OPTIONS,
1036 )
1037 .await,
1038 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1039 vec!["foo".into()]
1040 )))
1041 );
1042 }
1043
1044 #[tokio::test]
1045 async fn report_sources_in_circular_dependency() {
1046 let context = create_context(
1047 &Default::default(),
1048 &Default::default(),
1049 &Default::default(),
1050 );
1051
1052 context.database().set_source("foo", "baz").unwrap();
1053 context.database().set_source("bar", "baz").unwrap();
1054
1055 assert_eq!(
1056 run(
1057 &context,
1058 create_simple_config(
1059 vec![
1060 explicit_build(
1061 vec!["foo".into()],
1062 Rule::new("cp bar foo".into(), None),
1063 vec!["bar".into()],
1064 ),
1065 explicit_build(
1066 vec!["bar".into()],
1067 Rule::new("cp foo bar".into(), None),
1068 vec!["foo".into()],
1069 ),
1070 ],
1071 &["foo"],
1072 ),
1073 &[],
1074 DEFAULT_OPTIONS,
1075 )
1076 .await,
1077 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1078 vec!["baz".into()]
1079 )))
1080 );
1081 }
1082
1083 #[tokio::test]
1084 async fn prepare_output_directory() {
1085 let context = create_context(
1086 &Default::default(),
1087 &Default::default(),
1088 &Default::default(),
1089 );
1090
1091 run(
1092 &context,
1093 create_simple_config(
1094 vec![explicit_build(
1095 vec!["foo/bar".into()],
1096 Rule::new("touch foo/bar".into(), None),
1097 vec![],
1098 )],
1099 &["foo/bar"],
1100 ),
1101 &[],
1102 DEFAULT_OPTIONS,
1103 )
1104 .await
1105 .unwrap();
1106
1107 assert!(context.file_system().exists("foo".as_ref()).await.unwrap());
1108 }
1109
1110 #[tokio::test]
1111 async fn record_output_with_source() {
1112 let file_system = FakeFileSystem::default();
1113 let context = create_context(&Default::default(), &Default::default(), &file_system);
1114
1115 file_system.write_file("foo.c", "");
1116
1117 run(
1118 &context,
1119 Config::new(
1120 create_outputs(vec![explicit_build(
1121 vec!["foo.o".into()],
1122 Rule::new("cc foo.c".into(), None),
1123 vec!["foo.c".into()],
1124 )]),
1125 ["foo.o".into()].into_iter().collect(),
1126 [("foo.o".into(), "foo.c".into())].into_iter().collect(),
1127 Default::default(),
1128 None,
1129 )
1130 .into(),
1131 &[],
1132 DEFAULT_OPTIONS,
1133 )
1134 .await
1135 .unwrap();
1136
1137 assert_eq!(context.database().get_outputs().unwrap(), ["foo.o"]);
1138 assert_eq!(
1139 context.database().get_source("foo.o").unwrap(),
1140 Some("foo.c".into())
1141 );
1142 }
1143
1144 #[tokio::test]
1145 async fn skip_up_to_date_build() {
1146 let command_runner = FakeCommandRunner::default();
1147 let file_system = FakeFileSystem::default();
1148 let context = create_context(&command_runner, &Default::default(), &file_system);
1149 let config = create_simple_config(
1150 vec![explicit_build(
1151 vec!["foo".into()],
1152 Rule::new("cp bar foo".into(), None),
1153 vec!["bar".into()],
1154 )],
1155 &["foo"],
1156 );
1157
1158 file_system.write_file("foo", "");
1159 file_system.write_file("bar", "");
1160
1161 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1162 .await
1163 .unwrap();
1164 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1165
1166 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1167 }
1168
1169 #[tokio::test]
1170 async fn skip_build_on_timestamp_update_of_input() {
1171 let command_runner = FakeCommandRunner::default();
1172 let file_system = FakeFileSystem::default();
1173 let context = create_context(&command_runner, &Default::default(), &file_system);
1174 let config = create_simple_config(
1175 vec![explicit_build(
1176 vec!["foo".into()],
1177 Rule::new("cp bar foo".into(), None),
1178 vec!["bar".into()],
1179 )],
1180 &["foo"],
1181 );
1182
1183 file_system.write_file("foo", "");
1184 file_system.write_file("bar", "");
1185
1186 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1187 .await
1188 .unwrap();
1189
1190 file_system.write_file("bar", "");
1191
1192 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1193
1194 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1195 }
1196
1197 #[tokio::test]
1198 async fn rebuild_on_content_update_of_input() {
1199 let command_runner = FakeCommandRunner::default();
1200 let file_system = FakeFileSystem::default();
1201 let context = create_context(&command_runner, &Default::default(), &file_system);
1202 let config = create_simple_config(
1203 vec![explicit_build(
1204 vec!["foo".into()],
1205 Rule::new("cp bar foo".into(), None),
1206 vec!["bar".into()],
1207 )],
1208 &["foo"],
1209 );
1210
1211 file_system.write_file("foo", "");
1212 file_system.write_file("bar", "");
1213
1214 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1215 .await
1216 .unwrap();
1217
1218 file_system.write_file("bar", "bar");
1219
1220 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1221
1222 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1223 }
1224
1225 #[tokio::test]
1226 async fn rebuild_on_update_of_phony_input() {
1227 let command_runner = FakeCommandRunner::default();
1228 let file_system = FakeFileSystem::default();
1229 let context = create_context(&command_runner, &Default::default(), &file_system);
1230 let config = create_simple_config(
1231 vec![
1232 explicit_build(
1233 vec!["foo".into()],
1234 Rule::new("cp bar foo".into(), None),
1235 vec!["bar".into()],
1236 ),
1237 Build::new(
1238 vec!["bar".into()],
1239 vec![],
1240 None,
1241 vec!["baz".into()],
1242 vec![],
1243 None,
1244 ),
1245 ],
1246 &["foo"],
1247 );
1248
1249 file_system.write_file("foo", "");
1250 file_system.write_file("baz", "");
1251
1252 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1253 .await
1254 .unwrap();
1255
1256 file_system.write_file("baz", "baz");
1257
1258 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1259
1260 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1261 }
1262
1263 #[tokio::test]
1264 async fn rebuild_missing_output() {
1265 let command_runner = FakeCommandRunner::default();
1266 let file_system = FakeFileSystem::default();
1267 let context = create_context(&command_runner, &Default::default(), &file_system);
1268 let config = create_simple_config(
1269 vec![explicit_build(
1270 vec!["foo".into()],
1271 Rule::new("cp bar foo".into(), None),
1272 vec!["bar".into()],
1273 )],
1274 &["foo"],
1275 );
1276
1277 file_system.write_file("bar", "");
1278
1279 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1280 .await
1281 .unwrap();
1282 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1283
1284 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1285 }
1286
1287 #[tokio::test]
1288 async fn rebuild_missing_implicit_output() {
1289 let command_runner = FakeCommandRunner::default();
1290 let file_system = FakeFileSystem::default();
1291 let context = create_context(&command_runner, &Default::default(), &file_system);
1292 let config = create_simple_config(
1293 vec![Build::new(
1294 vec!["foo".into()],
1295 vec!["baz".into()],
1296 Rule::new("cp bar foo".into(), None).into(),
1297 vec!["bar".into()],
1298 vec![],
1299 None,
1300 )],
1301 &["foo"],
1302 );
1303
1304 file_system.write_file("foo", "");
1305 file_system.write_file("bar", "");
1306
1307 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1308 .await
1309 .unwrap();
1310 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1311
1312 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1313 }
1314
1315 #[tokio::test]
1316 async fn rerun_failed_build() {
1317 let command_runner =
1318 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1319 let file_system = FakeFileSystem::default();
1320 let context = create_context(&command_runner, &Default::default(), &file_system);
1321 let config = create_simple_config(
1322 vec![explicit_build(
1323 vec!["foo".into()],
1324 Rule::new("exit 1".into(), None),
1325 vec![],
1326 )],
1327 &["foo"],
1328 );
1329
1330 file_system.write_file("foo", "");
1331
1332 assert_eq!(
1333 run(&context, config.clone(), &[], DEFAULT_OPTIONS).await,
1334 Err(BuildError::Build)
1335 );
1336 assert_eq!(
1337 run(&context, config, &[], DEFAULT_OPTIONS).await,
1338 Err(BuildError::Build)
1339 );
1340 assert_eq!(command_runner.commands(), ["exit 1", "exit 1"]);
1341 }
1342
1343 #[tokio::test]
1344 async fn invalidate_file_cache_of_outputs_after_command() {
1345 let command_runner = FakeCommandRunner::default();
1346 let file_system = FakeFileSystem::default();
1347 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1348 let build = Build::new(
1349 vec!["foo".into()],
1350 vec!["bar".into()],
1351 Rule::new("touch foo bar".into(), None).into(),
1352 vec![],
1353 vec![],
1354 None,
1355 )
1356 .into();
1357 let mut future = pin!(run_build(context.clone(), &build));
1358
1359 file_system.write_file("foo", "1");
1360 file_system.write_file("bar", "1");
1361
1362 assert!(poll!(&mut future).is_pending());
1363
1364 yield_now().await;
1366
1367 assert_eq!(command_runner.commands(), ["touch foo bar"]);
1368
1369 let foo_hash = context.file_cache().content_hash(&"foo".into()).await;
1370 let bar_hash = context.file_cache().content_hash(&"bar".into()).await;
1371
1372 file_system.write_file("foo", "2");
1373 file_system.write_file("bar", "2");
1374 future.await.unwrap();
1375
1376 assert_ne!(
1377 context.file_cache().content_hash(&"foo".into()).await,
1378 foo_hash
1379 );
1380 assert_ne!(
1381 context.file_cache().content_hash(&"bar".into()).await,
1382 bar_hash
1383 );
1384 }
1385
1386 #[tokio::test]
1387 async fn invalidate_file_cache_of_output_after_failed_command() {
1388 let command_runner =
1389 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1390 let file_system = FakeFileSystem::default();
1391 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1392 let build =
1393 explicit_build(vec!["foo".into()], Rule::new("exit 1".into(), None), vec![]).into();
1394 let mut future = pin!(run_build(context.clone(), &build));
1395
1396 file_system.write_file("foo", "1");
1397
1398 assert!(poll!(&mut future).is_pending());
1399
1400 yield_now().await;
1402
1403 assert_eq!(command_runner.commands(), ["exit 1"]);
1404
1405 let hash = context.file_cache().content_hash(&"foo".into()).await;
1406
1407 file_system.write_file("foo", "2");
1408
1409 assert_eq!(future.await, Err(BuildError::Build));
1410 assert_ne!(context.file_cache().content_hash(&"foo".into()).await, hash);
1411 }
1412
1413 #[tokio::test]
1414 async fn invalidate_file_cache_of_phony_output_after_inputs() {
1415 let command_runner = FakeCommandRunner::default();
1416 let file_system = FakeFileSystem::default();
1417 let build = Build::new(
1418 vec!["foo".into()],
1419 vec![],
1420 None,
1421 vec!["bar".into()],
1422 vec![],
1423 None,
1424 );
1425 let context = create_build_run_context(
1426 &command_runner,
1427 &file_system,
1428 vec![
1429 build.clone(),
1430 explicit_build(
1431 vec!["bar".into()],
1432 Rule::new("touch bar".into(), None),
1433 vec![],
1434 ),
1435 ],
1436 );
1437 let build = build.into();
1438 let mut future = pin!(run_build(context.clone(), &build));
1439
1440 file_system.write_file("foo", "1");
1441 file_system.write_file("bar", "");
1442
1443 assert!(poll!(&mut future).is_pending());
1444
1445 yield_now().await;
1447
1448 assert_eq!(command_runner.commands(), ["touch bar"]);
1449
1450 let hash = context.file_cache().content_hash(&"foo".into()).await;
1451
1452 file_system.write_file("foo", "2");
1453 future.await.unwrap();
1454
1455 assert_ne!(context.file_cache().content_hash(&"foo".into()).await, hash);
1456 }
1457
1458 #[tokio::test]
1459 async fn keep_file_cache_of_other_file_after_command() {
1460 let command_runner = FakeCommandRunner::default();
1461 let file_system = FakeFileSystem::default();
1462 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1463
1464 file_system.write_file("bar", "1");
1465
1466 let hash = context.file_cache().content_hash(&"bar".into()).await;
1467
1468 file_system.write_file("bar", "2");
1469 run_build(
1470 context.clone(),
1471 &explicit_build(
1472 vec!["foo".into()],
1473 Rule::new("touch foo".into(), None),
1474 vec![],
1475 )
1476 .into(),
1477 )
1478 .await
1479 .unwrap();
1480
1481 assert_eq!(command_runner.commands(), ["touch foo"]);
1482 assert_eq!(context.file_cache().content_hash(&"bar".into()).await, hash);
1483 }
1484
1485 #[tokio::test]
1486 async fn query_output_while_building_input() {
1487 let file_system = FakeFileSystem::default();
1488
1489 file_system.write_file("foo", "");
1490 file_system.write_file("bar", "");
1491 file_system.write_file("baz", "");
1492
1493 run(
1494 &create_context(&Default::default(), &Default::default(), &file_system),
1495 create_simple_config(
1496 vec![
1497 explicit_build(
1498 vec!["foo".into()],
1499 Rule::new("cp bar foo".into(), None),
1500 vec!["bar".into()],
1501 ),
1502 explicit_build(
1503 vec!["bar".into()],
1504 Rule::new("cp baz bar".into(), None),
1505 vec!["baz".into()],
1506 ),
1507 ],
1508 &["foo"],
1509 ),
1510 &[],
1511 DEFAULT_OPTIONS,
1512 )
1513 .await
1514 .unwrap();
1515
1516 assert_eq!(file_system.metadata_requests().first(), Some(&"foo".into()));
1517 }
1518
1519 #[tokio::test]
1520 async fn query_output_once_for_up_to_date_build() {
1521 let file_system = FakeFileSystem::default();
1522 let context = create_context(&Default::default(), &Default::default(), &file_system);
1523 let config = create_simple_config(
1524 vec![
1525 explicit_build(
1526 vec!["foo".into()],
1527 Rule::new("cp bar foo".into(), None),
1528 vec!["bar".into()],
1529 ),
1530 explicit_build(
1531 vec!["bar".into()],
1532 Rule::new("cp baz bar".into(), None),
1533 vec!["baz".into()],
1534 ),
1535 ],
1536 &["foo"],
1537 );
1538
1539 file_system.write_file("foo", "");
1540 file_system.write_file("bar", "");
1541 file_system.write_file("baz", "");
1542
1543 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1544 .await
1545 .unwrap();
1546
1547 let count = count_metadata_requests(&file_system, "bar");
1548
1549 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1550
1551 assert_eq!(count_metadata_requests(&file_system, "bar"), count + 1);
1552 }
1553
1554 #[tokio::test]
1555 async fn query_implicit_output_once_for_up_to_date_build() {
1556 let file_system = FakeFileSystem::default();
1557 let context = create_context(&Default::default(), &Default::default(), &file_system);
1558 let config = create_simple_config(
1559 vec![
1560 explicit_build(
1561 vec!["foo".into()],
1562 Rule::new("cp qux foo".into(), None),
1563 vec!["qux".into()],
1564 ),
1565 Build::new(
1566 vec!["bar".into()],
1567 vec!["qux".into()],
1568 Rule::new("cp baz bar".into(), None).into(),
1569 vec!["baz".into()],
1570 vec![],
1571 None,
1572 ),
1573 ],
1574 &["foo"],
1575 );
1576
1577 file_system.write_file("foo", "");
1578 file_system.write_file("bar", "");
1579 file_system.write_file("baz", "");
1580 file_system.write_file("qux", "");
1581
1582 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1583 .await
1584 .unwrap();
1585
1586 let count = count_metadata_requests(&file_system, "qux");
1587
1588 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1589
1590 assert_eq!(count_metadata_requests(&file_system, "qux"), count + 1);
1591 }
1592
1593 #[tokio::test]
1594 async fn query_output_again_on_timestamp_update_of_input() {
1595 let file_system = FakeFileSystem::default();
1596 let context = create_context(&Default::default(), &Default::default(), &file_system);
1597 let config = create_simple_config(
1598 vec![
1599 explicit_build(
1600 vec!["foo".into()],
1601 Rule::new("cp bar foo".into(), None),
1602 vec!["bar".into()],
1603 ),
1604 explicit_build(
1605 vec!["bar".into()],
1606 Rule::new("cp baz bar".into(), None),
1607 vec!["baz".into()],
1608 ),
1609 ],
1610 &["foo"],
1611 );
1612
1613 file_system.write_file("foo", "");
1614 file_system.write_file("bar", "");
1615 file_system.write_file("baz", "");
1616
1617 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1618 .await
1619 .unwrap();
1620
1621 file_system.write_file("baz", "");
1622
1623 let count = count_metadata_requests(&file_system, "bar");
1624
1625 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1626
1627 assert_eq!(count_metadata_requests(&file_system, "bar"), count + 2);
1628 }
1629
1630 #[tokio::test]
1631 async fn query_output_again_after_command() {
1632 let file_system = FakeFileSystem::default();
1633
1634 file_system.write_file("foo", "");
1635 file_system.write_file("bar", "");
1636 file_system.write_file("baz", "");
1637
1638 run(
1639 &create_context(&Default::default(), &Default::default(), &file_system),
1640 create_simple_config(
1641 vec![
1642 explicit_build(
1643 vec!["foo".into()],
1644 Rule::new("cp bar foo".into(), None),
1645 vec!["bar".into()],
1646 ),
1647 explicit_build(
1648 vec!["bar".into()],
1649 Rule::new("cp baz bar".into(), None),
1650 vec!["baz".into()],
1651 ),
1652 ],
1653 &["foo"],
1654 ),
1655 &[],
1656 DEFAULT_OPTIONS,
1657 )
1658 .await
1659 .unwrap();
1660
1661 assert_eq!(count_metadata_requests(&file_system, "bar"), 2);
1662 }
1663
1664 #[tokio::test]
1665 async fn query_output_again_for_up_to_date_phony_build() {
1666 let file_system = FakeFileSystem::default();
1667 let context = create_context(&Default::default(), &Default::default(), &file_system);
1668 let config = create_simple_config(
1669 vec![
1670 explicit_build(
1671 vec!["foo.o".into()],
1672 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1673 HeaderDependency::Make {
1674 path: "foo.d".into(),
1675 },
1676 )),
1677 vec!["foo.c".into()],
1678 ),
1679 Build::new(
1680 vec!["foo.h".into()],
1681 vec![],
1682 None,
1683 vec!["foo.h.in".into()],
1684 vec![],
1685 None,
1686 ),
1687 ],
1688 &["foo.o", "foo.h"],
1689 );
1690
1691 file_system.write_file("foo.o", "");
1692 file_system.write_file("foo.c", "");
1693 file_system.write_file("foo.h", "");
1694 file_system.write_file("foo.h.in", "");
1695 file_system.write_file("foo.d", "foo.o: foo.h\n");
1696
1697 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1698 .await
1699 .unwrap();
1700
1701 let count = count_metadata_requests(&file_system, "foo.h");
1702
1703 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1704
1705 assert_eq!(count_metadata_requests(&file_system, "foo.h"), count + 2);
1706 }
1707
1708 #[tokio::test]
1709 async fn fail_with_output_metadata_error() {
1710 let command_runner = FakeCommandRunner::default();
1711 let file_system = FakeFileSystem::default();
1712
1713 file_system.write_file("bar", "");
1714 file_system.fail_metadata("foo");
1715
1716 assert_eq!(
1717 run(
1718 &create_context(&command_runner, &Default::default(), &file_system),
1719 create_simple_config(
1720 vec![explicit_build(
1721 vec!["foo".into()],
1722 Rule::new("cp bar foo".into(), None),
1723 vec!["bar".into()],
1724 )],
1725 &["foo"],
1726 ),
1727 &[],
1728 DEFAULT_OPTIONS,
1729 )
1730 .await,
1731 Err(BuildError::File(FileError::new("metadata failure")))
1732 );
1733 assert_eq!(command_runner.commands(), Vec::<String>::new());
1734 }
1735
1736 #[tokio::test]
1737 async fn write_command_output() {
1738 let console = FakeConsole::default();
1739
1740 run(
1741 &create_context(
1742 &FakeCommandRunner::new(
1743 [(
1744 "touch foo".into(),
1745 Output {
1746 status: ExitStatus::default(),
1747 stdout: b"bar\n".into(),
1748 stderr: b"baz\n".into(),
1749 },
1750 )]
1751 .into_iter()
1752 .collect(),
1753 ),
1754 &console,
1755 &Default::default(),
1756 ),
1757 create_simple_config(
1758 vec![explicit_build(
1759 vec!["foo".into()],
1760 Rule::new("touch foo".into(), Some("build foo".into())),
1761 vec![],
1762 )],
1763 &["foo"],
1764 ),
1765 &[],
1766 DEFAULT_OPTIONS,
1767 )
1768 .await
1769 .unwrap();
1770
1771 assert_eq!(console.stdout(), "bar\n");
1772 assert_eq!(console.stderr(), "build foo\nbaz\n");
1773 }
1774
1775 #[tokio::test]
1776 async fn write_debug_log_of_failed_build() {
1777 let console = FakeConsole::default();
1778
1779 assert_eq!(
1780 run(
1781 &create_context(
1782 &FakeCommandRunner::new(
1783 [("exit 1".into(), failed_output())].into_iter().collect(),
1784 ),
1785 &console,
1786 &Default::default(),
1787 ),
1788 create_simple_config(
1789 vec![explicit_build(
1790 vec!["foo".into()],
1791 Rule::new("exit 1".into(), None),
1792 vec![],
1793 )],
1794 &["foo"],
1795 ),
1796 &[],
1797 RunOptions {
1798 debug: true,
1799 profile: false,
1800 },
1801 )
1802 .await,
1803 Err(BuildError::Build)
1804 );
1805 assert_eq!(
1806 console.stderr(),
1807 "turtle: command: exit 1\nturtle: exit status: 1\n"
1808 );
1809 }
1810
1811 #[tokio::test]
1812 async fn write_profile_log() {
1813 let console = FakeConsole::default();
1814
1815 run(
1816 &create_context(&Default::default(), &console, &Default::default()),
1817 create_simple_config(
1818 vec![explicit_build(
1819 vec!["foo".into()],
1820 Rule::new("touch foo".into(), None),
1821 vec![],
1822 )],
1823 &["foo"],
1824 ),
1825 &[],
1826 RunOptions {
1827 debug: false,
1828 profile: true,
1829 },
1830 )
1831 .await
1832 .unwrap();
1833
1834 assert!(
1835 Regex::new(r"^turtle: duration: \d+ ms\n$")
1836 .unwrap()
1837 .is_match(&console.stderr())
1838 );
1839 }
1840
1841 #[tokio::test]
1842 async fn rebuild_on_update_of_header_dependency() {
1843 let command_runner = FakeCommandRunner::default();
1844 let file_system = FakeFileSystem::default();
1845 let context = create_context(&command_runner, &Default::default(), &file_system);
1846 let config = create_simple_config(
1847 vec![explicit_build(
1848 vec!["foo.o".into()],
1849 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1850 HeaderDependency::Make {
1851 path: "foo.d".into(),
1852 },
1853 )),
1854 vec!["foo.c".into()],
1855 )],
1856 &["foo.o"],
1857 );
1858
1859 file_system.write_file("foo.o", "");
1860 file_system.write_file("foo.c", "");
1861 file_system.write_file("foo.h", "");
1862 file_system.write_file("foo.d", "foo.o: foo.h\n");
1863
1864 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1865 .await
1866 .unwrap();
1867 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1868 .await
1869 .unwrap();
1870
1871 file_system.write_file("foo.h", "foo");
1872
1873 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1874
1875 assert_eq!(command_runner.commands(), ["cc foo.c", "cc foo.c"]);
1876 }
1877
1878 #[tokio::test]
1879 async fn read_and_remove_gcc_depfile() {
1880 let file_system = FakeFileSystem::default();
1881 let context = create_context(&Default::default(), &Default::default(), &file_system);
1882 let build = explicit_build(
1883 vec!["foo.o".into()],
1884 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1885 HeaderDependency::Gcc {
1886 path: "foo.d".into(),
1887 },
1888 )),
1889 vec!["foo.c".into()],
1890 );
1891
1892 file_system.write_file("foo.c", "");
1893 file_system.write_file("foo.h", "");
1894 file_system.write_file("foo.d", "foo.o: foo.h\n");
1895
1896 run(
1897 &context,
1898 create_simple_config(vec![build.clone()], &["foo.o"]),
1899 &[],
1900 DEFAULT_OPTIONS,
1901 )
1902 .await
1903 .unwrap();
1904
1905 assert_eq!(
1906 context
1907 .database()
1908 .get_header_dependencies(build.id())
1909 .unwrap(),
1910 ["foo.h".into()]
1911 );
1912 assert!(
1913 !context
1914 .file_system()
1915 .exists("foo.d".as_ref())
1916 .await
1917 .unwrap()
1918 );
1919 }
1920
1921 #[tokio::test]
1922 async fn read_msvc_header_dependencies() {
1923 let console = FakeConsole::default();
1924 let file_system = FakeFileSystem::default();
1925 let context = create_context(
1926 &FakeCommandRunner::new(
1927 [(
1928 "cl foo.c".into(),
1929 Output {
1930 status: ExitStatus::default(),
1931 stdout: b"Note: including file: foo.h\nfoo.c\n".into(),
1932 stderr: vec![],
1933 },
1934 )]
1935 .into_iter()
1936 .collect(),
1937 ),
1938 &console,
1939 &file_system,
1940 );
1941 let build = explicit_build(
1942 vec!["foo.obj".into()],
1943 Rule::new("cl foo.c".into(), None).with_header_dependency(Some(
1944 HeaderDependency::Msvc {
1945 prefix: "Note: including file: ".into(),
1946 },
1947 )),
1948 vec!["foo.c".into()],
1949 );
1950
1951 file_system.write_file("foo.c", "");
1952 file_system.write_file("foo.h", "");
1953
1954 run(
1955 &context,
1956 create_simple_config(vec![build.clone()], &["foo.obj"]),
1957 &[],
1958 DEFAULT_OPTIONS,
1959 )
1960 .await
1961 .unwrap();
1962
1963 assert_eq!(
1964 context
1965 .database()
1966 .get_header_dependencies(build.id())
1967 .unwrap(),
1968 ["foo.h".into()]
1969 );
1970 assert_eq!(console.stdout(), "foo.c\n");
1971 }
1972
1973 #[tokio::test]
1974 async fn intern_header_dependencies() {
1975 let file_system = FakeFileSystem::default();
1976 let context = create_context(&Default::default(), &Default::default(), &file_system);
1977 let build = explicit_build(
1978 vec!["foo.o".into()],
1979 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1980 HeaderDependency::Gcc {
1981 path: "foo.d".into(),
1982 },
1983 )),
1984 vec!["foo.c".into()],
1985 );
1986
1987 file_system.write_file("foo.c", "");
1988 file_system.write_file("foo.h", "");
1989 file_system.write_file("foo.d", "foo.o: ./foo.h\n");
1990
1991 run(
1992 &context,
1993 create_simple_config(vec![build.clone()], &["foo.o"]),
1994 &[],
1995 DEFAULT_OPTIONS,
1996 )
1997 .await
1998 .unwrap();
1999
2000 assert!(Arc::ptr_eq(
2001 &context
2002 .database()
2003 .get_header_dependencies(build.id())
2004 .unwrap()[0],
2005 &context.path_pool().intern("foo.h")
2006 ));
2007 }
2008
2009 #[tokio::test]
2010 async fn build_generated_header_dependency() {
2011 let command_runner = FakeCommandRunner::default();
2012 let file_system = FakeFileSystem::default();
2013 let context = create_context(&command_runner, &Default::default(), &file_system);
2014 let build = explicit_build(
2015 vec!["foo.o".into()],
2016 Rule::new("cc foo.c".into(), None),
2017 vec!["foo.c".into()],
2018 );
2019
2020 context
2021 .database()
2022 .set_header_dependencies(build.id(), &["foo.h".into()])
2023 .unwrap();
2024 file_system.write_file("foo.c", "");
2025 file_system.write_file("foo.h", "");
2026
2027 run(
2028 &context,
2029 create_simple_config(
2030 vec![
2031 build,
2032 explicit_build(
2033 vec!["foo.h".into()],
2034 Rule::new("touch foo.h".into(), None),
2035 vec![],
2036 ),
2037 ],
2038 &["foo.o"],
2039 ),
2040 &[],
2041 DEFAULT_OPTIONS,
2042 )
2043 .await
2044 .unwrap();
2045
2046 assert_eq!(command_runner.commands(), ["touch foo.h", "cc foo.c"]);
2047 }
2048
2049 #[tokio::test]
2050 async fn ignore_missing_header_dependency() {
2051 let file_system = FakeFileSystem::default();
2052 let context = create_context(&Default::default(), &Default::default(), &file_system);
2053 let build = explicit_build(
2054 vec!["foo.o".into()],
2055 Rule::new("cc foo.c".into(), None),
2056 vec!["foo.c".into()],
2057 );
2058
2059 context
2060 .database()
2061 .set_header_dependencies(build.id(), &["foo.h".into()])
2062 .unwrap();
2063 file_system.write_file("foo.c", "");
2064
2065 run(
2066 &context,
2067 create_simple_config(vec![build], &["foo.o"]),
2068 &[],
2069 DEFAULT_OPTIONS,
2070 )
2071 .await
2072 .unwrap();
2073 }
2074
2075 #[tokio::test]
2076 async fn read_header_dependencies_once() {
2077 let database = FakeDatabase::default();
2078 let file_system = FakeFileSystem::default();
2079 let context = Arc::new(Context::new(
2080 FakeCommandRunner::default(),
2081 Mutex::new(FakeConsole::default()).into(),
2082 database.clone(),
2083 file_system.clone(),
2084 Default::default(),
2085 ));
2086 let build = explicit_build(
2087 vec!["foo.o".into()],
2088 Rule::new("cc foo.c".into(), None),
2089 vec!["foo.c".into()],
2090 );
2091
2092 context
2093 .database()
2094 .set_header_dependencies(build.id(), &["foo.h".into()])
2095 .unwrap();
2096 file_system.write_file("foo.c", "");
2097 file_system.write_file("foo.h", "");
2098
2099 run(
2100 &context,
2101 create_simple_config(vec![build.clone()], &["foo.o"]),
2102 &[],
2103 DEFAULT_OPTIONS,
2104 )
2105 .await
2106 .unwrap();
2107
2108 assert_eq!(database.header_dependency_requests(), [build.id()]);
2109 }
2110
2111 #[tokio::test]
2112 async fn detect_circular_header_dependency() {
2113 let context = create_context(
2114 &Default::default(),
2115 &Default::default(),
2116 &Default::default(),
2117 );
2118 let build = explicit_build(
2119 vec!["foo".into()],
2120 Rule::new("touch foo".into(), None),
2121 vec![],
2122 );
2123
2124 context
2125 .database()
2126 .set_header_dependencies(build.id(), &["foo".into()])
2127 .unwrap();
2128
2129 assert_eq!(
2130 run(
2131 &context,
2132 create_simple_config(vec![build], &["foo"]),
2133 &[],
2134 DEFAULT_OPTIONS,
2135 )
2136 .await,
2137 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2138 vec!["foo".into()]
2139 )))
2140 );
2141 }
2142
2143 #[tokio::test]
2144 async fn build_dynamic_input() {
2145 let command_runner = FakeCommandRunner::default();
2146 let file_system = FakeFileSystem::default();
2147
2148 file_system.write_file(
2149 "foo.dd",
2150 "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
2151 );
2152 file_system.write_file("bar", "");
2153
2154 run(
2155 &create_context(&command_runner, &Default::default(), &file_system),
2156 create_simple_config(
2157 vec![
2158 Build::new(
2159 vec!["foo".into()],
2160 vec![],
2161 Rule::new("touch foo".into(), None).into(),
2162 vec![],
2163 vec!["foo.dd".into()],
2164 Some("foo.dd".into()),
2165 ),
2166 explicit_build(
2167 vec!["foo.dd".into()],
2168 Rule::new("touch foo.dd".into(), None),
2169 vec![],
2170 ),
2171 explicit_build(
2172 vec!["bar".into()],
2173 Rule::new("touch bar".into(), None),
2174 vec![],
2175 ),
2176 ],
2177 &["foo"],
2178 ),
2179 &[],
2180 DEFAULT_OPTIONS,
2181 )
2182 .await
2183 .unwrap();
2184
2185 assert_eq!(
2186 command_runner.commands(),
2187 ["touch foo.dd", "touch bar", "touch foo"]
2188 );
2189 }
2190
2191 #[tokio::test]
2192 async fn build_dynamic_input_declared_for_implicit_output() {
2193 let command_runner = FakeCommandRunner::default();
2194 let file_system = FakeFileSystem::default();
2195
2196 file_system.write_file(
2197 "foo.dd",
2198 "ninja_dyndep_version = 1\nbuild baz: dyndep | bar\n",
2199 );
2200 file_system.write_file("bar", "");
2201
2202 run(
2203 &create_context(&command_runner, &Default::default(), &file_system),
2204 create_simple_config(
2205 vec![
2206 Build::new(
2207 vec!["foo".into()],
2208 vec!["baz".into()],
2209 Rule::new("touch foo".into(), None).into(),
2210 vec![],
2211 vec!["foo.dd".into()],
2212 Some("foo.dd".into()),
2213 ),
2214 explicit_build(
2215 vec!["foo.dd".into()],
2216 Rule::new("touch foo.dd".into(), None),
2217 vec![],
2218 ),
2219 explicit_build(
2220 vec!["bar".into()],
2221 Rule::new("touch bar".into(), None),
2222 vec![],
2223 ),
2224 ],
2225 &["foo"],
2226 ),
2227 &[],
2228 DEFAULT_OPTIONS,
2229 )
2230 .await
2231 .unwrap();
2232
2233 assert_eq!(
2234 command_runner.commands(),
2235 ["touch foo.dd", "touch bar", "touch foo"]
2236 );
2237 }
2238
2239 #[tokio::test]
2240 async fn build_dynamic_inputs_of_builds_sharing_dynamic_module() {
2241 let command_runner = FakeCommandRunner::default();
2242 let file_system = FakeFileSystem::default();
2243
2244 file_system.write_file(
2245 "foo.dd",
2246 "ninja_dyndep_version = 1\nbuild foo: dyndep | baz\nbuild bar: dyndep | qux\n",
2247 );
2248 file_system.write_file("foo", "");
2249 file_system.write_file("baz", "");
2250 file_system.write_file("qux", "");
2251
2252 run(
2253 &create_context(&command_runner, &Default::default(), &file_system),
2254 create_simple_config(
2255 vec![
2256 Build::new(
2257 vec!["foo".into()],
2258 vec![],
2259 Rule::new("touch foo".into(), None).into(),
2260 vec![],
2261 vec!["foo.dd".into()],
2262 Some("foo.dd".into()),
2263 ),
2264 Build::new(
2265 vec!["bar".into()],
2266 vec![],
2267 Rule::new("touch bar".into(), None).into(),
2268 vec!["foo".into()],
2269 vec!["foo.dd".into()],
2270 Some("foo.dd".into()),
2271 ),
2272 explicit_build(
2273 vec!["baz".into()],
2274 Rule::new("touch baz".into(), None),
2275 vec![],
2276 ),
2277 explicit_build(
2278 vec!["qux".into()],
2279 Rule::new("touch qux".into(), None),
2280 vec![],
2281 ),
2282 ],
2283 &["bar"],
2284 ),
2285 &[],
2286 DEFAULT_OPTIONS,
2287 )
2288 .await
2289 .unwrap();
2290
2291 assert_eq!(
2292 command_runner.commands(),
2293 ["touch baz", "touch foo", "touch qux", "touch bar"]
2294 );
2295 }
2296
2297 #[tokio::test]
2298 async fn read_dynamic_module_once() {
2299 let file_system = FakeFileSystem::default();
2300
2301 file_system.write_file(
2302 "foo.dd",
2303 "ninja_dyndep_version = 1\nbuild foo: dyndep\nbuild bar: dyndep\n",
2304 );
2305
2306 run(
2307 &create_context(&Default::default(), &Default::default(), &file_system),
2308 create_simple_config(
2309 ["foo", "bar"]
2310 .into_iter()
2311 .map(|output| {
2312 Build::new(
2313 vec![output.into()],
2314 vec![],
2315 Rule::new(format!("touch {output}"), None).into(),
2316 vec![],
2317 vec!["foo.dd".into()],
2318 Some("foo.dd".into()),
2319 )
2320 })
2321 .collect(),
2322 &["foo", "bar"],
2323 ),
2324 &[],
2325 DEFAULT_OPTIONS,
2326 )
2327 .await
2328 .unwrap();
2329
2330 assert_eq!(file_system.read_requests(), [Path::new("foo.dd")]);
2331 }
2332
2333 #[tokio::test]
2334 async fn intern_dynamic_input() {
2335 let file_system = FakeFileSystem::default();
2336 let config = create_simple_config(
2337 vec![Build::new(
2338 vec!["foo".into()],
2339 vec![],
2340 Rule::new("touch foo".into(), None).into(),
2341 vec![],
2342 vec!["foo.dd".into()],
2343 Some("foo.dd".into()),
2344 )],
2345 &[],
2346 );
2347 let context = RunContext::new(
2348 create_context(&Default::default(), &Default::default(), &file_system),
2349 config.clone(),
2350 BuildGraph::new(config.outputs()),
2351 Default::default(),
2352 DEFAULT_OPTIONS,
2353 );
2354
2355 file_system.write_file(
2356 "foo.dd",
2357 "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
2358 );
2359
2360 assert!(Arc::ptr_eq(
2361 &load_dynamic_config(&context, "foo.dd")
2362 .await
2363 .unwrap()
2364 .outputs()["foo"]
2365 .inputs()[0],
2366 &context.build().path_pool().intern("bar")
2367 ));
2368 }
2369
2370 #[tokio::test]
2371 async fn fail_with_missing_dynamic_dependency() {
2372 let file_system = FakeFileSystem::default();
2373 let build = Build::new(
2374 vec!["foo".into()],
2375 vec![],
2376 Rule::new("touch foo".into(), None).into(),
2377 vec![],
2378 vec![],
2379 Some("foo.dd".into()),
2380 );
2381
2382 file_system.write_file("foo.dd", "ninja_dyndep_version = 1\n");
2383
2384 assert_eq!(
2385 run(
2386 &create_context(&Default::default(), &Default::default(), &file_system),
2387 create_simple_config(vec![build.clone()], &["foo"]),
2388 &[],
2389 DEFAULT_OPTIONS,
2390 )
2391 .await,
2392 Err(BuildError::DynamicDependencyNotFound(build.into()))
2393 );
2394 }
2395
2396 #[tokio::test]
2397 async fn fail_with_unknown_output_in_dynamic_dependency() {
2398 let file_system = FakeFileSystem::default();
2399
2400 file_system.write_file(
2401 "foo.dd",
2402 "ninja_dyndep_version = 1\nbuild bar: dyndep | baz\n",
2403 );
2404
2405 assert_eq!(
2406 run(
2407 &create_context(&Default::default(), &Default::default(), &file_system),
2408 create_simple_config(
2409 vec![Build::new(
2410 vec!["foo".into()],
2411 vec![],
2412 Rule::new("touch foo".into(), None).into(),
2413 vec![],
2414 vec![],
2415 Some("foo.dd".into()),
2416 )],
2417 &["foo"],
2418 ),
2419 &[],
2420 DEFAULT_OPTIONS,
2421 )
2422 .await,
2423 Err(BuildError::BuildGraph(BuildGraphError::OutputNotFound(
2424 "bar".into()
2425 )))
2426 );
2427 }
2428
2429 #[tokio::test]
2430 async fn detect_circular_dynamic_dependency() {
2431 let file_system = FakeFileSystem::default();
2432
2433 file_system.write_file(
2434 "foo.dd",
2435 "ninja_dyndep_version = 1\nbuild foo: dyndep | foo\n",
2436 );
2437
2438 assert_eq!(
2439 run(
2440 &create_context(&Default::default(), &Default::default(), &file_system),
2441 create_simple_config(
2442 vec![Build::new(
2443 vec!["foo".into()],
2444 vec![],
2445 Rule::new("touch foo".into(), None).into(),
2446 vec![],
2447 vec![],
2448 Some("foo.dd".into()),
2449 )],
2450 &["foo"],
2451 ),
2452 &[],
2453 DEFAULT_OPTIONS,
2454 )
2455 .await,
2456 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2457 vec!["foo".into()]
2458 )))
2459 );
2460 }
2461
2462 #[tokio::test]
2463 async fn run_builds_concurrently() {
2464 let command_runner = FakeCommandRunner::default();
2465
2466 run(
2467 &create_context(&command_runner, &Default::default(), &Default::default()),
2468 create_simple_config(
2469 vec![
2470 pool_build("foo", None),
2471 pool_build("bar", None),
2472 pool_build("baz", None),
2473 ],
2474 &["foo", "bar", "baz"],
2475 ),
2476 &[],
2477 DEFAULT_OPTIONS,
2478 )
2479 .await
2480 .unwrap();
2481
2482 assert_eq!(command_runner.max_concurrency(), 3);
2483 }
2484
2485 #[tokio::test]
2486 async fn limit_concurrency_of_builds_in_pool() {
2487 let command_runner = FakeCommandRunner::default();
2488
2489 run(
2490 &create_context(&command_runner, &Default::default(), &Default::default()),
2491 create_pool_config(
2492 vec![
2493 pool_build("foo", limited_pool("qux")),
2494 pool_build("bar", limited_pool("qux")),
2495 pool_build("baz", limited_pool("qux")),
2496 ],
2497 &["foo", "bar", "baz"],
2498 &[("qux", 1)],
2499 ),
2500 &[],
2501 DEFAULT_OPTIONS,
2502 )
2503 .await
2504 .unwrap();
2505
2506 assert_eq!(command_runner.commands().len(), 3);
2507 assert_eq!(command_runner.max_concurrency(), 1);
2508 }
2509
2510 #[tokio::test]
2511 async fn limit_concurrency_of_builds_in_pool_with_depth_of_two() {
2512 let command_runner = FakeCommandRunner::default();
2513
2514 run(
2515 &create_context(&command_runner, &Default::default(), &Default::default()),
2516 create_pool_config(
2517 vec![
2518 pool_build("foo", limited_pool("quux")),
2519 pool_build("bar", limited_pool("quux")),
2520 pool_build("baz", limited_pool("quux")),
2521 pool_build("qux", limited_pool("quux")),
2522 ],
2523 &["foo", "bar", "baz", "qux"],
2524 &[("quux", 2)],
2525 ),
2526 &[],
2527 DEFAULT_OPTIONS,
2528 )
2529 .await
2530 .unwrap();
2531
2532 assert_eq!(command_runner.commands().len(), 4);
2533 assert_eq!(command_runner.max_concurrency(), 2);
2534 }
2535
2536 #[tokio::test]
2537 async fn run_builds_in_different_pools_concurrently() {
2538 let command_runner = FakeCommandRunner::default();
2539
2540 run(
2541 &create_context(&command_runner, &Default::default(), &Default::default()),
2542 create_pool_config(
2543 vec![
2544 pool_build("foo", limited_pool("baz")),
2545 pool_build("bar", limited_pool("qux")),
2546 ],
2547 &["foo", "bar"],
2548 &[("baz", 1), ("qux", 1)],
2549 ),
2550 &[],
2551 DEFAULT_OPTIONS,
2552 )
2553 .await
2554 .unwrap();
2555
2556 assert_eq!(command_runner.max_concurrency(), 2);
2557 }
2558
2559 #[tokio::test]
2560 async fn run_build_in_pool_of_huge_depth() {
2561 let command_runner = FakeCommandRunner::default();
2562
2563 run(
2564 &create_context(&command_runner, &Default::default(), &Default::default()),
2565 create_pool_config(
2566 vec![pool_build("foo", limited_pool("bar"))],
2567 &["foo"],
2568 &[("bar", usize::MAX)],
2569 ),
2570 &[],
2571 DEFAULT_OPTIONS,
2572 )
2573 .await
2574 .unwrap();
2575
2576 assert_eq!(command_runner.commands(), ["touch foo"]);
2577 }
2578
2579 #[tokio::test]
2580 async fn run_build_in_console_pool() {
2581 let command_runner = FakeCommandRunner::new(
2582 [(
2583 "touch foo".into(),
2584 Output {
2585 status: ExitStatus::default(),
2586 stdout: b"bar\n".into(),
2587 stderr: b"baz\n".into(),
2588 },
2589 )]
2590 .into_iter()
2591 .collect(),
2592 );
2593 let console = FakeConsole::default();
2594
2595 run(
2596 &create_context(&command_runner, &console, &Default::default()),
2597 create_simple_config(
2598 vec![explicit_build(
2599 vec!["foo".into()],
2600 Rule::new("touch foo".into(), Some("build foo".into()))
2601 .with_pool(Some(Pool::Console)),
2602 vec![],
2603 )],
2604 &["foo"],
2605 ),
2606 &[],
2607 DEFAULT_OPTIONS,
2608 )
2609 .await
2610 .unwrap();
2611
2612 assert_eq!(command_runner.commands(), Vec::<String>::new());
2613 assert_eq!(command_runner.console_commands(), ["touch foo"]);
2614 assert_eq!(console.stdout(), "");
2615 assert_eq!(console.stderr(), "build foo\n");
2616 }
2617
2618 #[tokio::test]
2619 async fn limit_concurrency_of_builds_in_console_pool() {
2620 let command_runner = FakeCommandRunner::default();
2621
2622 run(
2623 &create_context(&command_runner, &Default::default(), &Default::default()),
2624 create_simple_config(
2625 vec![
2626 pool_build("foo", Some(Pool::Console)),
2627 pool_build("bar", Some(Pool::Console)),
2628 pool_build("baz", Some(Pool::Console)),
2629 ],
2630 &["foo", "bar", "baz"],
2631 ),
2632 &[],
2633 DEFAULT_OPTIONS,
2634 )
2635 .await
2636 .unwrap();
2637
2638 assert_eq!(command_runner.console_commands().len(), 3);
2639 assert_eq!(command_runner.max_concurrency(), 1);
2640 }
2641
2642 #[tokio::test]
2643 async fn write_debug_log_of_failed_build_in_console_pool() {
2644 let command_runner =
2645 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
2646 let console = FakeConsole::default();
2647
2648 assert_eq!(
2649 run(
2650 &create_context(&command_runner, &console, &Default::default()),
2651 create_simple_config(
2652 vec![explicit_build(
2653 vec!["foo".into()],
2654 Rule::new("exit 1".into(), None).with_pool(Some(Pool::Console)),
2655 vec![],
2656 )],
2657 &["foo"],
2658 ),
2659 &[],
2660 RunOptions {
2661 debug: true,
2662 profile: false,
2663 },
2664 )
2665 .await,
2666 Err(BuildError::Build)
2667 );
2668 assert_eq!(command_runner.console_commands(), ["exit 1"]);
2669 assert_eq!(
2670 console.stderr(),
2671 "turtle: command: exit 1\nturtle: exit status: 1\n"
2672 );
2673 }
2674
2675 #[tokio::test]
2676 async fn lock_console_during_command_in_console_pool() {
2677 let command_runner = FakeCommandRunner::default();
2678 let context = create_run_context(&command_runner, &Default::default(), &[]);
2679 let rule = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
2680 let mut future = pin!(run_rule(&context, &rule));
2681
2682 assert!(poll!(&mut future).is_pending());
2683 assert_eq!(command_runner.console_commands(), ["foo"]);
2684 assert!(context.build().console().try_lock().is_err());
2685
2686 future.await.unwrap();
2687
2688 assert!(context.build().console().try_lock().is_ok());
2689 }
2690
2691 #[tokio::test]
2692 async fn flush_console_before_command_in_console_pool() {
2693 let command_runner = FakeCommandRunner::default();
2694 let console = FakeConsole::default();
2695 let context = create_run_context(&command_runner, &console, &[]);
2696 let rule = Rule::new("foo".into(), Some("bar".into())).with_pool(Some(Pool::Console));
2697 let mut future = pin!(run_rule(&context, &rule));
2698
2699 assert!(poll!(&mut future).is_pending());
2700 assert_eq!(command_runner.console_commands(), ["foo"]);
2701 assert_eq!(console.flushed_stderr(), "bar\n");
2702
2703 future.await.unwrap();
2704 }
2705
2706 #[tokio::test]
2707 async fn wait_for_console_before_command_in_console_pool() {
2708 let command_runner = FakeCommandRunner::default();
2709 let context = create_run_context(&command_runner, &Default::default(), &[]);
2710 let rule = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
2711 let console = context.build().console().lock().await;
2712 let mut future = pin!(run_rule(&context, &rule));
2713
2714 for _ in 0..POLL_COUNT {
2715 assert!(poll!(&mut future).is_pending());
2716 }
2717
2718 assert_eq!(command_runner.console_commands(), Vec::<String>::new());
2719
2720 drop(console);
2721 future.await.unwrap();
2722
2723 assert_eq!(command_runner.console_commands(), ["foo"]);
2724 }
2725
2726 #[tokio::test]
2727 async fn run_command_concurrently_with_command_in_console_pool() {
2728 let command_runner = FakeCommandRunner::default();
2729 let context = create_run_context(&command_runner, &Default::default(), &[]);
2730 let foo = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
2731 let bar = Rule::new("bar".into(), None);
2732 let mut foo_future = pin!(run_rule(&context, &foo));
2733 let mut bar_future = pin!(run_rule(&context, &bar));
2734
2735 assert!(poll!(&mut foo_future).is_pending());
2736 assert!(poll!(&mut bar_future).is_pending());
2737 assert_eq!(command_runner.console_commands(), ["foo"]);
2738 assert_eq!(command_runner.commands(), ["bar"]);
2739
2740 foo_future.await.unwrap();
2741 bar_future.await.unwrap();
2742 }
2743
2744 #[tokio::test]
2745 async fn release_pool_before_waiting_for_console() {
2746 let command_runner = FakeCommandRunner::default();
2747 let context = create_run_context(&command_runner, &Default::default(), &[("baz", 1)]);
2748 let foo = Rule::new("foo".into(), None).with_pool(limited_pool("baz"));
2749 let bar = Rule::new("bar".into(), None).with_pool(limited_pool("baz"));
2750 let console = context.build().console().lock().await;
2751 let mut foo_future = pin!(run_rule(&context, &foo));
2752 let mut bar_future = pin!(run_rule(&context, &bar));
2753
2754 for _ in 0..POLL_COUNT {
2755 assert!(poll!(&mut foo_future).is_pending());
2756 }
2757
2758 for _ in 0..POLL_COUNT {
2759 assert!(poll!(&mut bar_future).is_pending());
2760 }
2761
2762 assert_eq!(command_runner.commands(), ["foo", "bar"]);
2763
2764 drop(console);
2765 foo_future.await.unwrap();
2766 bar_future.await.unwrap();
2767 }
2768
2769 #[tokio::test]
2770 async fn wait_for_pool_before_locking_console() {
2771 let command_runner = FakeCommandRunner::default();
2772 let console = FakeConsole::default();
2773 let context = create_run_context(&command_runner, &console, &[("bar", 1)]);
2774 let rule = Rule::new("foo".into(), Some("foo".into())).with_pool(limited_pool("bar"));
2775 let permit = context.pool(rule.pool()).await.unwrap();
2776 let mut future = pin!(run_rule(&context, &rule));
2777
2778 for _ in 0..POLL_COUNT {
2779 assert!(poll!(&mut future).is_pending());
2780 }
2781
2782 assert_eq!(command_runner.commands(), Vec::<String>::new());
2783 assert_eq!(console.stderr(), "");
2784 assert!(context.build().console().try_lock().is_ok());
2785
2786 drop(permit);
2787 future.await.unwrap();
2788
2789 assert_eq!(command_runner.commands(), ["foo"]);
2790 assert_eq!(console.stderr(), "foo\n");
2791 }
2792
2793 #[tokio::test]
2794 async fn keep_pool_until_command_finishes_on_console_error() {
2795 let command_runner = FakeCommandRunner::default();
2796 let context = create_run_context(&command_runner, &FakeConsole::failing(), &[("baz", 1)]);
2797 let foo = Rule::new("foo".into(), Some("foo".into())).with_pool(limited_pool("baz"));
2798 let bar = Rule::new("bar".into(), None).with_pool(limited_pool("baz"));
2799 let mut foo_future = pin!(run_rule(&context, &foo));
2800 let mut bar_future = pin!(run_rule(&context, &bar));
2801
2802 assert!(poll!(&mut foo_future).is_pending());
2803 assert!(poll!(&mut bar_future).is_pending());
2804 assert_eq!(command_runner.commands(), ["foo"]);
2805 assert!(foo_future.await.is_err());
2806 }
2807
2808 #[test]
2809 fn classify_phony_and_file_inputs() {
2810 let build = explicit_build(
2811 vec!["foo".into()],
2812 Rule::new("".into(), None),
2813 vec!["bar".into(), "baz".into(), "qux".into(), "bar".into()],
2814 );
2815 let context = RunContext::new(
2816 create_context(
2817 &Default::default(),
2818 &Default::default(),
2819 &Default::default(),
2820 ),
2821 create_simple_config(
2822 vec![
2823 build.clone(),
2824 Build::new(vec!["bar".into()], vec![], None, vec![], vec![], None),
2825 explicit_build(vec!["baz".into()], Rule::new("".into(), None), vec![]),
2826 ],
2827 &[],
2828 ),
2829 BuildGraph::new(&Default::default()),
2830 Default::default(),
2831 DEFAULT_OPTIONS,
2832 );
2833
2834 assert_eq!(
2835 classify_inputs(
2836 &context,
2837 &build,
2838 &["quux".into(), "baz".into()],
2839 &[&"qux".into(), &"corge".into()],
2840 ),
2841 (
2842 vec![&"bar".into()],
2843 vec![
2844 &"baz".into(),
2845 &"qux".into(),
2846 &"quux".into(),
2847 &"corge".into()
2848 ]
2849 )
2850 );
2851 }
2852}