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::{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<bool, 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<bool, BuildError> {
118 spawn(async move {
119 let (inputs_stale, _, output_metadata) = try_join!(
120 build_inputs(&context, build.inputs()),
121 build_inputs(&context, build.order_only_inputs()),
122 read_output_metadata(&context, &build),
123 )?;
124
125 let dynamic_inputs = if let Some(path) = build.dynamic_module() {
126 if build_input(context.clone(), path).await? {
127 return skip_build(&context, &build).await;
128 }
129
130 let config = load_dynamic_config(&context, path).await?;
131
132 build
133 .outputs()
134 .iter()
135 .chain(build.implicit_outputs())
136 .find_map(|output| config.outputs().get(output.as_ref()))
137 .map(|build| build.inputs())
138 .ok_or_else(|| BuildError::DynamicDependencyNotFound(build.clone()))?
139 } else {
140 &[]
141 };
142
143 let dynamic_inputs_stale = build_inputs(&context, dynamic_inputs).await?;
144
145 if build.rule().is_none() {
146 invalidate_outputs(&context, &build).await;
149 }
150
151 let dependencies = context.header_dependencies(build.id());
152
153 let dependencies_stale = try_join_all(
154 dependencies
155 .iter()
156 .filter_map(|dependency| context.config().outputs().get(dependency))
157 .map(|build| run_build(context.clone(), build)),
158 )
159 .await?
160 .contains(&true);
161
162 if inputs_stale || dynamic_inputs_stale || dependencies_stale {
163 return skip_build(&context, &build).await;
164 }
165
166 let header_dependencies =
167 filter_existing_header_dependencies(&context, dependencies).await?;
168
169 let (phony_inputs, file_inputs) =
170 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
171 let mut timestamp_hash =
172 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
173
174 if let Some(metadata) = &output_metadata
175 && Some(timestamp_hash)
176 == context
177 .build()
178 .database()
179 .get_hash(HashType::Timestamp, build.id())?
180 {
181 cache_output_metadata(&context, &build, metadata).await;
182
183 return Ok(false);
184 }
185
186 let mut content_hash =
187 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
188
189 if (output_metadata.is_some() || build.rule().is_none())
190 && context
191 .build()
192 .database()
193 .get_hash(HashType::Content, build.id())?
194 == Some(content_hash)
195 {
196 return Ok(false);
197 } else if context.options().dry_run {
198 return skip_build(&context, &build).await;
199 } else if let Some(rule) = build.rule() {
200 try_join_all(
201 build
202 .outputs()
203 .iter()
204 .chain(build.implicit_outputs())
205 .map(|path| prepare_directory(&context, path.as_ref())),
206 )
207 .await?;
208
209 let output = run_rule(&context, rule).await;
210
211 invalidate_outputs(&context, &build).await;
212
213 let new_header_dependencies =
214 read_header_dependencies(&context, rule, &output?).await?;
215
216 context
217 .build()
218 .database()
219 .set_header_dependencies(build.id(), &new_header_dependencies)?;
220
221 for output in build.outputs() {
222 context.build().database().set_output(output)?;
223
224 if let Some(source) = context.config().source_map().get(output) {
225 context.build().database().set_source(output, source)?;
226 }
227 }
228
229 if header_dependencies
230 .iter()
231 .copied()
232 .ne(&new_header_dependencies)
233 {
234 let header_dependencies =
235 filter_existing_header_dependencies(&context, &new_header_dependencies).await?;
236 let (phony_inputs, file_inputs) =
237 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
238
239 timestamp_hash =
240 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
241 content_hash =
242 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
243 }
244 }
245
246 context
247 .build()
248 .database()
249 .set_hash(HashType::Timestamp, build.id(), timestamp_hash)?;
250 context
251 .build()
252 .database()
253 .set_hash(HashType::Content, build.id(), content_hash)?;
254
255 Ok(false)
256 })
257 .await?
258}
259
260async fn build_inputs(context: &Arc<RunContext>, inputs: &[Arc<str>]) -> Result<bool, BuildError> {
261 Ok(try_join_all(
262 inputs
263 .iter()
264 .map(|input| build_input(context.clone(), input)),
265 )
266 .await?
267 .contains(&true))
268}
269
270async fn build_input(context: Arc<RunContext>, input: &Arc<str>) -> Result<bool, BuildError> {
271 if let Some(build) = context.config().outputs().get(input) {
272 run_build(context.clone(), build).await
273 } else {
274 check_file_existence(&context, input).await?;
275
276 Ok(false)
277 }
278}
279
280async fn load_dynamic_config<'a>(
281 context: &'a RunContext,
282 path: &str,
283) -> Result<&'a DynamicConfig, BuildError> {
284 context
285 .dynamic_config(path)
286 .get_or_try_init(|| async {
287 let config = compile_dynamic(
288 &parse_dynamic(
289 &context
290 .build()
291 .file_system()
292 .read_file_to_string(path.as_ref())
293 .await?,
294 )?,
295 context.build().path_pool(),
296 )?;
297
298 context
299 .build_graph()
300 .lock()
301 .await
302 .validate_dynamic(&config)
303 .map_err(|error| map_build_graph_error(context, &error))?;
304
305 Ok(config)
306 })
307 .await
308}
309
310async fn read_output_metadata(
311 context: &RunContext,
312 build: &Build,
313) -> Result<Option<Vec<Metadata>>, BuildError> {
314 Ok(try_join_all(
315 build
316 .outputs()
317 .iter()
318 .chain(build.implicit_outputs())
319 .map(|path| {
320 context
321 .build()
322 .file_system()
323 .metadata(path.as_ref().as_ref())
324 }),
325 )
326 .await?
327 .into_iter()
328 .collect())
329}
330
331async fn cache_output_metadata(context: &RunContext, build: &Build, metadata: &[Metadata]) {
332 if build.rule().is_none() {
334 return;
335 }
336
337 for (path, &metadata) in build
338 .outputs()
339 .iter()
340 .chain(build.implicit_outputs())
341 .zip(metadata)
342 {
343 context.file_cache().set_metadata(path, metadata).await;
344 }
345}
346
347async fn filter_existing_header_dependencies<'a>(
348 context: &RunContext,
349 dependencies: &'a [Arc<str>],
350) -> Result<Vec<&'a Arc<str>>, BuildError> {
351 let mut existing_dependencies = vec![];
352
353 for dependency in dependencies {
354 if context.file_cache().exists(dependency).await? {
355 existing_dependencies.push(dependency);
356 }
357 }
358
359 Ok(existing_dependencies)
360}
361
362async fn check_file_existence(context: &RunContext, path: &Arc<str>) -> Result<(), BuildError> {
363 if !context.file_cache().exists(path).await? {
364 return Err(BuildError::FileNotFound(
365 context
366 .build()
367 .database()
368 .get_source(path)?
369 .unwrap_or_else(|| path.as_ref().into()),
370 ));
371 }
372
373 Ok(())
374}
375
376async fn prepare_directory(context: &RunContext, path: impl AsRef<Path>) -> Result<(), BuildError> {
377 if let Some(directory) = path.as_ref().parent() {
378 context
379 .build()
380 .file_system()
381 .create_directory(directory)
382 .await?;
383 }
384
385 Ok(())
386}
387
388async fn invalidate_outputs(context: &RunContext, build: &Build) {
389 for output in build.outputs().iter().chain(build.implicit_outputs()) {
390 context.file_cache().invalidate(output).await;
391 }
392}
393
394fn classify_inputs<'a>(
395 context: &'a RunContext,
396 build: &'a Build,
397 dynamic_inputs: &'a [Arc<str>],
398 header_dependencies: &'a [&'a Arc<str>],
399) -> (Vec<&'a Arc<str>>, Vec<&'a Arc<str>>) {
400 let (phony_inputs, file_inputs) = build
401 .inputs()
402 .iter()
403 .chain(dynamic_inputs)
404 .unique()
405 .partition::<Vec<_>, _>(|&input| {
406 context
407 .config()
408 .outputs()
409 .get(input)
410 .map_or_default(|build| build.rule().is_none())
411 });
412
413 (
414 phony_inputs,
415 file_inputs
416 .into_iter()
417 .chain(header_dependencies.iter().copied())
418 .unique()
419 .collect(),
420 )
421}
422
423async fn skip_build(context: &RunContext, build: &Build) -> Result<bool, BuildError> {
424 if let Some(rule) = build.rule() {
425 let _guard = write_description(context, rule).await?;
426 }
427
428 Ok(true)
429}
430
431async fn run_rule(context: &RunContext, rule: &Rule) -> Result<Output, BuildError> {
432 let ((output, duration), mut console) = if rule.pool() == Some(&Pool::Console) {
433 let mut console = write_description(context, rule).await?;
434
435 console.flush().await?;
436
437 let time = Instant::now();
438 let status = context
439 .build()
440 .command_runner()
441 .run_with_console(rule.command())
442 .await?;
443
444 (
445 (
446 Output {
447 status,
448 stdout: vec![],
449 stderr: vec![],
450 },
451 Instant::now() - time,
452 ),
453 console,
454 )
455 } else {
456 let permit = context.pool(rule.pool()).await?;
457 let time = Instant::now();
458 let output = context.build().command_runner().run(rule.command()).await?;
459
460 drop(permit);
461
462 (
463 (output, Instant::now() - time),
464 write_description(context, rule).await?,
465 )
466 };
467
468 profile!(context, console, "duration: {} ms", duration.as_millis());
469
470 console
471 .write_stdout(&exclude_show_includes(rule, &output.stdout))
472 .await?;
473 console.write_stderr(&output.stderr).await?;
474
475 if !output.status.success() {
476 debug!(
477 context,
478 console,
479 "exit status: {}",
480 output
481 .status
482 .code()
483 .map(|code| code.to_string())
484 .unwrap_or_else(|| "-".into())
485 );
486
487 return Err(BuildError::Build);
488 }
489
490 Ok(output)
491}
492
493async fn write_description<'a>(
494 context: &'a RunContext,
495 rule: &Rule,
496) -> Result<MutexGuard<'a, dyn Console + Send + Sync>, BuildError> {
497 let mut console = context.build().console().lock().await;
498
499 if let Some(description) = rule.description() {
500 console.write_stdout(description.as_bytes()).await?;
501 console.write_stdout(b"\n").await?;
502 }
503
504 debug!(context, console, "command: {}", rule.command());
505
506 Ok(console)
507}
508
509fn map_build_graph_error(context: &RunContext, error: &BuildGraphError) -> BuildError {
510 match error {
511 BuildGraphError::CircularDependency(outputs) => {
512 match outputs
513 .iter()
514 .map(|output| {
515 Ok(context
516 .build()
517 .database()
518 .get_source(output)?
519 .map(|string| string.into())
520 .unwrap_or_else(|| output.clone()))
521 })
522 .collect::<Result<Vec<_>, BuildError>>()
523 {
524 Ok(outputs) => {
525 BuildGraphError::CircularDependency(outputs.into_iter().dedup().collect())
526 .into()
527 }
528 Err(error) => error,
529 }
530 }
531 BuildGraphError::OutputNotFound(_) => error.clone().into(),
532 }
533}
534
535#[cfg(test)]
536mod tests {
537 use super::*;
538 use crate::{
539 infrastructure::{FakeCommandRunner, FakeConsole, FakeDatabase, FakeFileSystem, FileError},
540 ir::HeaderDependency,
541 };
542 use core::{num::NonZeroUsize, pin::pin};
543 use futures::poll;
544 use pretty_assertions::{assert_eq, assert_ne};
545 use regex::Regex;
546 #[cfg(unix)]
547 use std::os::unix::process::ExitStatusExt;
548 #[cfg(windows)]
549 use std::os::windows::process::ExitStatusExt;
550 use std::{collections::HashMap, process::ExitStatus};
551 use tokio::{sync::Mutex, task::yield_now};
552
553 const DEFAULT_OPTIONS: RunOptions = RunOptions {
554 debug: false,
555 dry_run: false,
556 profile: false,
557 };
558 const POLL_COUNT: usize = 8;
559
560 fn create_context(
561 command_runner: &FakeCommandRunner,
562 console: &FakeConsole,
563 file_system: &FakeFileSystem,
564 ) -> Arc<Context> {
565 Context::new(
566 command_runner.clone(),
567 Mutex::new(console.clone()).into(),
568 FakeDatabase::default(),
569 file_system.clone(),
570 Default::default(),
571 )
572 .into()
573 }
574
575 fn create_outputs(builds: Vec<Build>) -> HashMap<Arc<str>, Arc<Build>> {
576 builds
577 .into_iter()
578 .map(Arc::new)
579 .flat_map(|build| {
580 build
581 .outputs()
582 .iter()
583 .chain(build.implicit_outputs())
584 .map(|output| (output.clone(), build.clone()))
585 .collect::<Vec<_>>()
586 })
587 .collect()
588 }
589
590 fn create_simple_config(builds: Vec<Build>, default_outputs: &[&str]) -> Arc<Config> {
591 create_pool_config(builds, default_outputs, &[])
592 }
593
594 fn create_pool_config(
595 builds: Vec<Build>,
596 default_outputs: &[&str],
597 pools: &[(&str, usize)],
598 ) -> Arc<Config> {
599 Config::new(
600 create_outputs(builds),
601 default_outputs
602 .iter()
603 .map(|&output| output.into())
604 .collect(),
605 Default::default(),
606 pools
607 .iter()
608 .map(|&(name, depth)| (name.into(), NonZeroUsize::new(depth).unwrap()))
609 .collect(),
610 None,
611 )
612 .into()
613 }
614
615 fn explicit_build(outputs: Vec<Arc<str>>, rule: Rule, inputs: Vec<Arc<str>>) -> Build {
616 Build::new(outputs, vec![], rule.into(), inputs, vec![], None)
617 }
618
619 fn failed_output() -> Output {
620 Output {
621 status: ExitStatus::from_raw(cfg_select! {
622 unix => 1 << 8,
623 windows => 1,
624 }),
625 stdout: vec![],
626 stderr: vec![],
627 }
628 }
629
630 fn limited_pool(name: &str) -> Option<Pool> {
631 Some(Pool::Limited(name.into()))
632 }
633
634 fn pool_build(output: &str, pool: Option<Pool>) -> Build {
635 explicit_build(
636 vec![output.into()],
637 Rule::new(format!("touch {output}"), None).with_pool(pool),
638 vec![],
639 )
640 }
641
642 fn count_metadata_requests(file_system: &FakeFileSystem, path: &str) -> usize {
643 file_system
644 .metadata_requests()
645 .iter()
646 .filter(|request| request.as_path() == Path::new(path))
647 .count()
648 }
649
650 fn create_run_context(
651 command_runner: &FakeCommandRunner,
652 console: &FakeConsole,
653 pools: &[(&str, usize)],
654 ) -> RunContext {
655 RunContext::new(
656 create_context(command_runner, console, &Default::default()),
657 create_pool_config(vec![], &[], pools),
658 BuildGraph::new(&Default::default()),
659 Default::default(),
660 DEFAULT_OPTIONS,
661 )
662 }
663
664 fn create_build_run_context(
665 command_runner: &FakeCommandRunner,
666 file_system: &FakeFileSystem,
667 builds: Vec<Build>,
668 ) -> Arc<RunContext> {
669 RunContext::new(
670 create_context(command_runner, &Default::default(), file_system),
671 create_simple_config(builds, &[]),
672 BuildGraph::new(&Default::default()),
673 Default::default(),
674 DEFAULT_OPTIONS,
675 )
676 .into()
677 }
678
679 #[tokio::test]
680 async fn build_nothing() {
681 let command_runner = FakeCommandRunner::default();
682
683 run(
684 &create_context(&command_runner, &Default::default(), &Default::default()),
685 create_simple_config(vec![], &[]),
686 &[],
687 DEFAULT_OPTIONS,
688 )
689 .await
690 .unwrap();
691
692 assert_eq!(command_runner.commands(), Vec::<String>::new());
693 }
694
695 #[tokio::test]
696 async fn build_default_output() {
697 let command_runner = FakeCommandRunner::default();
698
699 run(
700 &create_context(&command_runner, &Default::default(), &Default::default()),
701 create_simple_config(
702 vec![
703 explicit_build(
704 vec!["foo".into()],
705 Rule::new("touch foo".into(), None),
706 vec![],
707 ),
708 explicit_build(
709 vec!["bar".into()],
710 Rule::new("touch bar".into(), None),
711 vec![],
712 ),
713 ],
714 &["foo"],
715 ),
716 &[],
717 DEFAULT_OPTIONS,
718 )
719 .await
720 .unwrap();
721
722 assert_eq!(command_runner.commands(), ["touch foo"]);
723 }
724
725 #[tokio::test]
726 async fn build_specified_output() {
727 let command_runner = FakeCommandRunner::default();
728
729 run(
730 &create_context(&command_runner, &Default::default(), &Default::default()),
731 create_simple_config(
732 vec![
733 explicit_build(
734 vec!["foo".into()],
735 Rule::new("touch foo".into(), None),
736 vec![],
737 ),
738 explicit_build(
739 vec!["bar".into()],
740 Rule::new("touch bar".into(), None),
741 vec![],
742 ),
743 ],
744 &["foo"],
745 ),
746 &["bar".into()],
747 DEFAULT_OPTIONS,
748 )
749 .await
750 .unwrap();
751
752 assert_eq!(command_runner.commands(), ["touch bar"]);
753 }
754
755 #[tokio::test]
756 async fn build_duplicate_outputs() {
757 let command_runner = FakeCommandRunner::default();
758
759 run(
760 &create_context(&command_runner, &Default::default(), &Default::default()),
761 create_simple_config(
762 vec![explicit_build(
763 vec!["foo".into()],
764 Rule::new("touch foo".into(), None),
765 vec![],
766 )],
767 &[],
768 ),
769 &["foo".into(), "foo".into()],
770 DEFAULT_OPTIONS,
771 )
772 .await
773 .unwrap();
774
775 assert_eq!(command_runner.commands(), ["touch foo"]);
776 }
777
778 #[tokio::test]
779 async fn fail_with_unknown_default_output() {
780 assert_eq!(
781 run(
782 &create_context(
783 &Default::default(),
784 &Default::default(),
785 &Default::default()
786 ),
787 create_simple_config(vec![], &["foo"]),
788 &[],
789 DEFAULT_OPTIONS,
790 )
791 .await,
792 Err(BuildError::DefaultOutputNotFound("foo".into()))
793 );
794 }
795
796 #[tokio::test]
797 async fn fail_with_unknown_output() {
798 assert_eq!(
799 run(
800 &create_context(
801 &Default::default(),
802 &Default::default(),
803 &Default::default()
804 ),
805 create_simple_config(vec![], &[]),
806 &["foo".into()],
807 DEFAULT_OPTIONS,
808 )
809 .await,
810 Err(BuildError::OutputNotFound("foo".into()))
811 );
812 }
813
814 #[tokio::test]
815 async fn fail_with_unknown_output_after_known_output() {
816 let command_runner = FakeCommandRunner::default();
817
818 assert_eq!(
819 run(
820 &create_context(&command_runner, &Default::default(), &Default::default()),
821 create_simple_config(
822 vec![explicit_build(
823 vec!["foo".into()],
824 Rule::new("touch foo".into(), None),
825 vec![],
826 )],
827 &[],
828 ),
829 &["foo".into(), "bar".into()],
830 DEFAULT_OPTIONS,
831 )
832 .await,
833 Err(BuildError::OutputNotFound("bar".into()))
834 );
835
836 yield_now().await;
838
839 assert_eq!(command_runner.commands(), Vec::<String>::new());
840 }
841
842 #[tokio::test]
843 async fn build_shared_input_once() {
844 let command_runner = FakeCommandRunner::default();
845 let file_system = FakeFileSystem::default();
846
847 file_system.write_file("bar", "");
848 file_system.write_file("baz", "");
849
850 run(
851 &create_context(&command_runner, &Default::default(), &file_system),
852 create_simple_config(
853 vec![
854 explicit_build(
855 vec!["foo".into()],
856 Rule::new("touch foo".into(), None),
857 vec!["bar".into(), "baz".into()],
858 ),
859 explicit_build(
860 vec!["bar".into()],
861 Rule::new("touch bar".into(), None),
862 vec!["baz".into()],
863 ),
864 explicit_build(
865 vec!["baz".into()],
866 Rule::new("touch baz".into(), None),
867 vec![],
868 ),
869 ],
870 &["foo"],
871 ),
872 &[],
873 DEFAULT_OPTIONS,
874 )
875 .await
876 .unwrap();
877
878 assert_eq!(
879 command_runner.commands(),
880 ["touch baz", "touch bar", "touch foo"]
881 );
882 }
883
884 #[tokio::test]
885 async fn build_order_only_input() {
886 let command_runner = FakeCommandRunner::default();
887
888 run(
889 &create_context(&command_runner, &Default::default(), &Default::default()),
890 create_simple_config(
891 vec![
892 Build::new(
893 vec!["foo".into()],
894 vec![],
895 Rule::new("touch foo".into(), None).into(),
896 vec![],
897 vec!["bar".into()],
898 None,
899 ),
900 explicit_build(
901 vec!["bar".into()],
902 Rule::new("touch bar".into(), None),
903 vec![],
904 ),
905 ],
906 &["foo"],
907 ),
908 &[],
909 DEFAULT_OPTIONS,
910 )
911 .await
912 .unwrap();
913
914 assert_eq!(command_runner.commands(), ["touch bar", "touch foo"]);
915 }
916
917 #[tokio::test]
918 async fn build_multiple_outputs_of_build() {
919 let command_runner = FakeCommandRunner::default();
920 let file_system = FakeFileSystem::default();
921
922 file_system.write_file("foo", "");
923 file_system.write_file("bar", "");
924
925 run(
926 &create_context(&command_runner, &Default::default(), &file_system),
927 create_simple_config(
928 vec![
929 explicit_build(
930 vec!["foo".into(), "bar".into()],
931 Rule::new("touch foo bar".into(), None),
932 vec![],
933 ),
934 explicit_build(
935 vec!["baz".into()],
936 Rule::new("cat foo bar > baz".into(), None),
937 vec!["foo".into(), "bar".into()],
938 ),
939 ],
940 &["baz"],
941 ),
942 &[],
943 DEFAULT_OPTIONS,
944 )
945 .await
946 .unwrap();
947
948 assert_eq!(
949 command_runner.commands(),
950 ["touch foo bar", "cat foo bar > baz"]
951 );
952 }
953
954 #[tokio::test]
955 async fn fail_with_missing_input() {
956 let command_runner = FakeCommandRunner::default();
957
958 assert_eq!(
959 run(
960 &create_context(&command_runner, &Default::default(), &Default::default()),
961 create_simple_config(
962 vec![explicit_build(
963 vec!["foo".into()],
964 Rule::new("cp bar foo".into(), None),
965 vec!["bar".into()],
966 )],
967 &["foo"],
968 ),
969 &[],
970 DEFAULT_OPTIONS,
971 )
972 .await,
973 Err(BuildError::FileNotFound("bar".into()))
974 );
975 assert_eq!(command_runner.commands(), Vec::<String>::new());
976 }
977
978 #[tokio::test]
979 async fn fail_with_missing_order_only_input() {
980 assert_eq!(
981 run(
982 &create_context(
983 &Default::default(),
984 &Default::default(),
985 &Default::default()
986 ),
987 create_simple_config(
988 vec![Build::new(
989 vec!["foo".into()],
990 vec![],
991 Rule::new("touch foo".into(), None).into(),
992 vec![],
993 vec!["bar".into()],
994 None,
995 )],
996 &["foo"],
997 ),
998 &[],
999 DEFAULT_OPTIONS,
1000 )
1001 .await,
1002 Err(BuildError::FileNotFound("bar".into()))
1003 );
1004 }
1005
1006 #[tokio::test]
1007 async fn report_source_of_missing_input() {
1008 let context = create_context(
1009 &Default::default(),
1010 &Default::default(),
1011 &Default::default(),
1012 );
1013
1014 context.database().set_source("foo.o", "foo.c").unwrap();
1015
1016 assert_eq!(
1017 run(
1018 &context,
1019 create_simple_config(
1020 vec![explicit_build(
1021 vec!["foo".into()],
1022 Rule::new("cc foo.o".into(), None),
1023 vec!["foo.o".into()],
1024 )],
1025 &["foo"],
1026 ),
1027 &[],
1028 DEFAULT_OPTIONS,
1029 )
1030 .await,
1031 Err(BuildError::FileNotFound("foo.c".into()))
1032 );
1033 }
1034
1035 #[tokio::test]
1036 async fn detect_circular_dependency() {
1037 assert_eq!(
1038 run(
1039 &create_context(
1040 &Default::default(),
1041 &Default::default(),
1042 &Default::default()
1043 ),
1044 create_simple_config(
1045 vec![explicit_build(
1046 vec!["foo".into()],
1047 Rule::new("cp foo foo".into(), None),
1048 vec!["foo".into()],
1049 )],
1050 &["foo"],
1051 ),
1052 &[],
1053 DEFAULT_OPTIONS,
1054 )
1055 .await,
1056 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1057 vec!["foo".into()]
1058 )))
1059 );
1060 }
1061
1062 #[tokio::test]
1063 async fn report_sources_in_circular_dependency() {
1064 let context = create_context(
1065 &Default::default(),
1066 &Default::default(),
1067 &Default::default(),
1068 );
1069
1070 context.database().set_source("foo", "baz").unwrap();
1071 context.database().set_source("bar", "baz").unwrap();
1072
1073 assert_eq!(
1074 run(
1075 &context,
1076 create_simple_config(
1077 vec![
1078 explicit_build(
1079 vec!["foo".into()],
1080 Rule::new("cp bar foo".into(), None),
1081 vec!["bar".into()],
1082 ),
1083 explicit_build(
1084 vec!["bar".into()],
1085 Rule::new("cp foo bar".into(), None),
1086 vec!["foo".into()],
1087 ),
1088 ],
1089 &["foo"],
1090 ),
1091 &[],
1092 DEFAULT_OPTIONS,
1093 )
1094 .await,
1095 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1096 vec!["baz".into()]
1097 )))
1098 );
1099 }
1100
1101 #[tokio::test]
1102 async fn prepare_output_directory() {
1103 let context = create_context(
1104 &Default::default(),
1105 &Default::default(),
1106 &Default::default(),
1107 );
1108
1109 run(
1110 &context,
1111 create_simple_config(
1112 vec![explicit_build(
1113 vec!["foo/bar".into()],
1114 Rule::new("touch foo/bar".into(), None),
1115 vec![],
1116 )],
1117 &["foo/bar"],
1118 ),
1119 &[],
1120 DEFAULT_OPTIONS,
1121 )
1122 .await
1123 .unwrap();
1124
1125 assert!(context.file_system().exists("foo".as_ref()).await.unwrap());
1126 }
1127
1128 #[tokio::test]
1129 async fn record_output_with_source() {
1130 let file_system = FakeFileSystem::default();
1131 let context = create_context(&Default::default(), &Default::default(), &file_system);
1132
1133 file_system.write_file("foo.c", "");
1134
1135 run(
1136 &context,
1137 Config::new(
1138 create_outputs(vec![explicit_build(
1139 vec!["foo.o".into()],
1140 Rule::new("cc foo.c".into(), None),
1141 vec!["foo.c".into()],
1142 )]),
1143 ["foo.o".into()].into_iter().collect(),
1144 [("foo.o".into(), "foo.c".into())].into_iter().collect(),
1145 Default::default(),
1146 None,
1147 )
1148 .into(),
1149 &[],
1150 DEFAULT_OPTIONS,
1151 )
1152 .await
1153 .unwrap();
1154
1155 assert_eq!(context.database().get_outputs().unwrap(), ["foo.o"]);
1156 assert_eq!(
1157 context.database().get_source("foo.o").unwrap(),
1158 Some("foo.c".into())
1159 );
1160 }
1161
1162 #[tokio::test]
1163 async fn skip_up_to_date_build() {
1164 let command_runner = FakeCommandRunner::default();
1165 let file_system = FakeFileSystem::default();
1166 let context = create_context(&command_runner, &Default::default(), &file_system);
1167 let config = create_simple_config(
1168 vec![explicit_build(
1169 vec!["foo".into()],
1170 Rule::new("cp bar foo".into(), None),
1171 vec!["bar".into()],
1172 )],
1173 &["foo"],
1174 );
1175
1176 file_system.write_file("foo", "");
1177 file_system.write_file("bar", "");
1178
1179 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1180 .await
1181 .unwrap();
1182 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1183
1184 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1185 }
1186
1187 #[tokio::test]
1188 async fn skip_build_on_timestamp_update_of_input() {
1189 let command_runner = FakeCommandRunner::default();
1190 let file_system = FakeFileSystem::default();
1191 let context = create_context(&command_runner, &Default::default(), &file_system);
1192 let config = create_simple_config(
1193 vec![explicit_build(
1194 vec!["foo".into()],
1195 Rule::new("cp bar foo".into(), None),
1196 vec!["bar".into()],
1197 )],
1198 &["foo"],
1199 );
1200
1201 file_system.write_file("foo", "");
1202 file_system.write_file("bar", "");
1203
1204 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1205 .await
1206 .unwrap();
1207
1208 file_system.write_file("bar", "");
1209
1210 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1211
1212 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1213 }
1214
1215 #[tokio::test]
1216 async fn rebuild_on_content_update_of_input() {
1217 let command_runner = FakeCommandRunner::default();
1218 let file_system = FakeFileSystem::default();
1219 let context = create_context(&command_runner, &Default::default(), &file_system);
1220 let config = create_simple_config(
1221 vec![explicit_build(
1222 vec!["foo".into()],
1223 Rule::new("cp bar foo".into(), None),
1224 vec!["bar".into()],
1225 )],
1226 &["foo"],
1227 );
1228
1229 file_system.write_file("foo", "");
1230 file_system.write_file("bar", "");
1231
1232 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1233 .await
1234 .unwrap();
1235
1236 file_system.write_file("bar", "bar");
1237
1238 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1239
1240 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1241 }
1242
1243 #[tokio::test]
1244 async fn rebuild_on_update_of_phony_input() {
1245 let command_runner = FakeCommandRunner::default();
1246 let file_system = FakeFileSystem::default();
1247 let context = create_context(&command_runner, &Default::default(), &file_system);
1248 let config = create_simple_config(
1249 vec![
1250 explicit_build(
1251 vec!["foo".into()],
1252 Rule::new("cp bar foo".into(), None),
1253 vec!["bar".into()],
1254 ),
1255 Build::new(
1256 vec!["bar".into()],
1257 vec![],
1258 None,
1259 vec!["baz".into()],
1260 vec![],
1261 None,
1262 ),
1263 ],
1264 &["foo"],
1265 );
1266
1267 file_system.write_file("foo", "");
1268 file_system.write_file("baz", "");
1269
1270 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1271 .await
1272 .unwrap();
1273
1274 file_system.write_file("baz", "baz");
1275
1276 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1277
1278 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1279 }
1280
1281 #[tokio::test]
1282 async fn rebuild_missing_output() {
1283 let command_runner = FakeCommandRunner::default();
1284 let file_system = FakeFileSystem::default();
1285 let context = create_context(&command_runner, &Default::default(), &file_system);
1286 let config = create_simple_config(
1287 vec![explicit_build(
1288 vec!["foo".into()],
1289 Rule::new("cp bar foo".into(), None),
1290 vec!["bar".into()],
1291 )],
1292 &["foo"],
1293 );
1294
1295 file_system.write_file("bar", "");
1296
1297 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1298 .await
1299 .unwrap();
1300 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1301
1302 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1303 }
1304
1305 #[tokio::test]
1306 async fn rebuild_missing_implicit_output() {
1307 let command_runner = FakeCommandRunner::default();
1308 let file_system = FakeFileSystem::default();
1309 let context = create_context(&command_runner, &Default::default(), &file_system);
1310 let config = create_simple_config(
1311 vec![Build::new(
1312 vec!["foo".into()],
1313 vec!["baz".into()],
1314 Rule::new("cp bar foo".into(), None).into(),
1315 vec!["bar".into()],
1316 vec![],
1317 None,
1318 )],
1319 &["foo"],
1320 );
1321
1322 file_system.write_file("foo", "");
1323 file_system.write_file("bar", "");
1324
1325 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1326 .await
1327 .unwrap();
1328 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1329
1330 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1331 }
1332
1333 #[tokio::test]
1334 async fn rerun_failed_build() {
1335 let command_runner =
1336 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1337 let file_system = FakeFileSystem::default();
1338 let context = create_context(&command_runner, &Default::default(), &file_system);
1339 let config = create_simple_config(
1340 vec![explicit_build(
1341 vec!["foo".into()],
1342 Rule::new("exit 1".into(), None),
1343 vec![],
1344 )],
1345 &["foo"],
1346 );
1347
1348 file_system.write_file("foo", "");
1349
1350 assert_eq!(
1351 run(&context, config.clone(), &[], DEFAULT_OPTIONS).await,
1352 Err(BuildError::Build)
1353 );
1354 assert_eq!(
1355 run(&context, config, &[], DEFAULT_OPTIONS).await,
1356 Err(BuildError::Build)
1357 );
1358 assert_eq!(command_runner.commands(), ["exit 1", "exit 1"]);
1359 }
1360
1361 #[tokio::test]
1362 async fn invalidate_file_cache_of_outputs_after_command() {
1363 let command_runner = FakeCommandRunner::default();
1364 let file_system = FakeFileSystem::default();
1365 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1366 let build = Build::new(
1367 vec!["foo".into()],
1368 vec!["bar".into()],
1369 Rule::new("touch foo bar".into(), None).into(),
1370 vec![],
1371 vec![],
1372 None,
1373 )
1374 .into();
1375 let mut future = pin!(run_build(context.clone(), &build));
1376
1377 file_system.write_file("foo", "1");
1378 file_system.write_file("bar", "1");
1379
1380 assert!(poll!(&mut future).is_pending());
1381
1382 yield_now().await;
1384
1385 assert_eq!(command_runner.commands(), ["touch foo bar"]);
1386
1387 let foo_hash = context.file_cache().content_hash(&"foo".into()).await;
1388 let bar_hash = context.file_cache().content_hash(&"bar".into()).await;
1389
1390 file_system.write_file("foo", "2");
1391 file_system.write_file("bar", "2");
1392 future.await.unwrap();
1393
1394 assert_ne!(
1395 context.file_cache().content_hash(&"foo".into()).await,
1396 foo_hash
1397 );
1398 assert_ne!(
1399 context.file_cache().content_hash(&"bar".into()).await,
1400 bar_hash
1401 );
1402 }
1403
1404 #[tokio::test]
1405 async fn invalidate_file_cache_of_output_after_failed_command() {
1406 let command_runner =
1407 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1408 let file_system = FakeFileSystem::default();
1409 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1410 let build =
1411 explicit_build(vec!["foo".into()], Rule::new("exit 1".into(), None), vec![]).into();
1412 let mut future = pin!(run_build(context.clone(), &build));
1413
1414 file_system.write_file("foo", "1");
1415
1416 assert!(poll!(&mut future).is_pending());
1417
1418 yield_now().await;
1420
1421 assert_eq!(command_runner.commands(), ["exit 1"]);
1422
1423 let hash = context.file_cache().content_hash(&"foo".into()).await;
1424
1425 file_system.write_file("foo", "2");
1426
1427 assert_eq!(future.await, Err(BuildError::Build));
1428 assert_ne!(context.file_cache().content_hash(&"foo".into()).await, hash);
1429 }
1430
1431 #[tokio::test]
1432 async fn invalidate_file_cache_of_phony_output_after_inputs() {
1433 let command_runner = FakeCommandRunner::default();
1434 let file_system = FakeFileSystem::default();
1435 let build = Build::new(
1436 vec!["foo".into()],
1437 vec![],
1438 None,
1439 vec!["bar".into()],
1440 vec![],
1441 None,
1442 );
1443 let context = create_build_run_context(
1444 &command_runner,
1445 &file_system,
1446 vec![
1447 build.clone(),
1448 explicit_build(
1449 vec!["bar".into()],
1450 Rule::new("touch bar".into(), None),
1451 vec![],
1452 ),
1453 ],
1454 );
1455 let build = build.into();
1456 let mut future = pin!(run_build(context.clone(), &build));
1457
1458 file_system.write_file("foo", "1");
1459 file_system.write_file("bar", "");
1460
1461 assert!(poll!(&mut future).is_pending());
1462
1463 yield_now().await;
1465
1466 assert_eq!(command_runner.commands(), ["touch bar"]);
1467
1468 let hash = context.file_cache().content_hash(&"foo".into()).await;
1469
1470 file_system.write_file("foo", "2");
1471 future.await.unwrap();
1472
1473 assert_ne!(context.file_cache().content_hash(&"foo".into()).await, hash);
1474 }
1475
1476 #[tokio::test]
1477 async fn keep_file_cache_of_other_file_after_command() {
1478 let command_runner = FakeCommandRunner::default();
1479 let file_system = FakeFileSystem::default();
1480 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1481
1482 file_system.write_file("bar", "1");
1483
1484 let hash = context.file_cache().content_hash(&"bar".into()).await;
1485
1486 file_system.write_file("bar", "2");
1487 run_build(
1488 context.clone(),
1489 &explicit_build(
1490 vec!["foo".into()],
1491 Rule::new("touch foo".into(), None),
1492 vec![],
1493 )
1494 .into(),
1495 )
1496 .await
1497 .unwrap();
1498
1499 assert_eq!(command_runner.commands(), ["touch foo"]);
1500 assert_eq!(context.file_cache().content_hash(&"bar".into()).await, hash);
1501 }
1502
1503 #[tokio::test]
1504 async fn query_output_while_building_input() {
1505 let file_system = FakeFileSystem::default();
1506
1507 file_system.write_file("foo", "");
1508 file_system.write_file("bar", "");
1509 file_system.write_file("baz", "");
1510
1511 run(
1512 &create_context(&Default::default(), &Default::default(), &file_system),
1513 create_simple_config(
1514 vec![
1515 explicit_build(
1516 vec!["foo".into()],
1517 Rule::new("cp bar foo".into(), None),
1518 vec!["bar".into()],
1519 ),
1520 explicit_build(
1521 vec!["bar".into()],
1522 Rule::new("cp baz bar".into(), None),
1523 vec!["baz".into()],
1524 ),
1525 ],
1526 &["foo"],
1527 ),
1528 &[],
1529 DEFAULT_OPTIONS,
1530 )
1531 .await
1532 .unwrap();
1533
1534 assert_eq!(file_system.metadata_requests().first(), Some(&"foo".into()));
1535 }
1536
1537 #[tokio::test]
1538 async fn query_output_once_for_up_to_date_build() {
1539 let file_system = FakeFileSystem::default();
1540 let context = create_context(&Default::default(), &Default::default(), &file_system);
1541 let config = create_simple_config(
1542 vec![
1543 explicit_build(
1544 vec!["foo".into()],
1545 Rule::new("cp bar foo".into(), None),
1546 vec!["bar".into()],
1547 ),
1548 explicit_build(
1549 vec!["bar".into()],
1550 Rule::new("cp baz bar".into(), None),
1551 vec!["baz".into()],
1552 ),
1553 ],
1554 &["foo"],
1555 );
1556
1557 file_system.write_file("foo", "");
1558 file_system.write_file("bar", "");
1559 file_system.write_file("baz", "");
1560
1561 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1562 .await
1563 .unwrap();
1564
1565 let count = count_metadata_requests(&file_system, "bar");
1566
1567 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1568
1569 assert_eq!(count_metadata_requests(&file_system, "bar"), count + 1);
1570 }
1571
1572 #[tokio::test]
1573 async fn query_implicit_output_once_for_up_to_date_build() {
1574 let file_system = FakeFileSystem::default();
1575 let context = create_context(&Default::default(), &Default::default(), &file_system);
1576 let config = create_simple_config(
1577 vec![
1578 explicit_build(
1579 vec!["foo".into()],
1580 Rule::new("cp qux foo".into(), None),
1581 vec!["qux".into()],
1582 ),
1583 Build::new(
1584 vec!["bar".into()],
1585 vec!["qux".into()],
1586 Rule::new("cp baz bar".into(), None).into(),
1587 vec!["baz".into()],
1588 vec![],
1589 None,
1590 ),
1591 ],
1592 &["foo"],
1593 );
1594
1595 file_system.write_file("foo", "");
1596 file_system.write_file("bar", "");
1597 file_system.write_file("baz", "");
1598 file_system.write_file("qux", "");
1599
1600 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1601 .await
1602 .unwrap();
1603
1604 let count = count_metadata_requests(&file_system, "qux");
1605
1606 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1607
1608 assert_eq!(count_metadata_requests(&file_system, "qux"), count + 1);
1609 }
1610
1611 #[tokio::test]
1612 async fn query_output_again_on_timestamp_update_of_input() {
1613 let file_system = FakeFileSystem::default();
1614 let context = create_context(&Default::default(), &Default::default(), &file_system);
1615 let config = create_simple_config(
1616 vec![
1617 explicit_build(
1618 vec!["foo".into()],
1619 Rule::new("cp bar foo".into(), None),
1620 vec!["bar".into()],
1621 ),
1622 explicit_build(
1623 vec!["bar".into()],
1624 Rule::new("cp baz bar".into(), None),
1625 vec!["baz".into()],
1626 ),
1627 ],
1628 &["foo"],
1629 );
1630
1631 file_system.write_file("foo", "");
1632 file_system.write_file("bar", "");
1633 file_system.write_file("baz", "");
1634
1635 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1636 .await
1637 .unwrap();
1638
1639 file_system.write_file("baz", "");
1640
1641 let count = count_metadata_requests(&file_system, "bar");
1642
1643 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1644
1645 assert_eq!(count_metadata_requests(&file_system, "bar"), count + 2);
1646 }
1647
1648 #[tokio::test]
1649 async fn query_output_again_after_command() {
1650 let file_system = FakeFileSystem::default();
1651
1652 file_system.write_file("foo", "");
1653 file_system.write_file("bar", "");
1654 file_system.write_file("baz", "");
1655
1656 run(
1657 &create_context(&Default::default(), &Default::default(), &file_system),
1658 create_simple_config(
1659 vec![
1660 explicit_build(
1661 vec!["foo".into()],
1662 Rule::new("cp bar foo".into(), None),
1663 vec!["bar".into()],
1664 ),
1665 explicit_build(
1666 vec!["bar".into()],
1667 Rule::new("cp baz bar".into(), None),
1668 vec!["baz".into()],
1669 ),
1670 ],
1671 &["foo"],
1672 ),
1673 &[],
1674 DEFAULT_OPTIONS,
1675 )
1676 .await
1677 .unwrap();
1678
1679 assert_eq!(count_metadata_requests(&file_system, "bar"), 2);
1680 }
1681
1682 #[tokio::test]
1683 async fn query_output_again_for_up_to_date_phony_build() {
1684 let file_system = FakeFileSystem::default();
1685 let context = create_context(&Default::default(), &Default::default(), &file_system);
1686 let config = create_simple_config(
1687 vec![
1688 explicit_build(
1689 vec!["foo.o".into()],
1690 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1691 HeaderDependency::Make {
1692 path: "foo.d".into(),
1693 },
1694 )),
1695 vec!["foo.c".into()],
1696 ),
1697 Build::new(
1698 vec!["foo.h".into()],
1699 vec![],
1700 None,
1701 vec!["foo.h.in".into()],
1702 vec![],
1703 None,
1704 ),
1705 ],
1706 &["foo.o", "foo.h"],
1707 );
1708
1709 file_system.write_file("foo.o", "");
1710 file_system.write_file("foo.c", "");
1711 file_system.write_file("foo.h", "");
1712 file_system.write_file("foo.h.in", "");
1713 file_system.write_file("foo.d", "foo.o: foo.h\n");
1714
1715 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1716 .await
1717 .unwrap();
1718
1719 let count = count_metadata_requests(&file_system, "foo.h");
1720
1721 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1722
1723 assert_eq!(count_metadata_requests(&file_system, "foo.h"), count + 2);
1724 }
1725
1726 #[tokio::test]
1727 async fn read_no_input_on_timestamp_update_of_input_of_phony_input() {
1728 let file_system = FakeFileSystem::default();
1729 let context = create_context(&Default::default(), &Default::default(), &file_system);
1730 let config = create_simple_config(
1731 vec![
1732 explicit_build(
1733 vec!["foo".into()],
1734 Rule::new("cp bar foo".into(), None),
1735 vec!["bar".into(), "qux".into()],
1736 ),
1737 Build::new(
1738 vec!["bar".into()],
1739 vec![],
1740 None,
1741 vec!["baz".into()],
1742 vec![],
1743 None,
1744 ),
1745 ],
1746 &["foo"],
1747 );
1748
1749 file_system.write_file("foo", "");
1750 file_system.write_file("baz", "");
1751 file_system.write_file("qux", "");
1752
1753 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1754 .await
1755 .unwrap();
1756
1757 file_system.write_file("baz", "");
1758
1759 let count = file_system.read_requests().len();
1760
1761 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1762
1763 assert_eq!(file_system.read_requests()[count..], [Path::new("baz")]);
1764 }
1765
1766 #[tokio::test]
1767 async fn fail_with_output_metadata_error() {
1768 let command_runner = FakeCommandRunner::default();
1769 let file_system = FakeFileSystem::default();
1770
1771 file_system.write_file("bar", "");
1772 file_system.fail_metadata("foo");
1773
1774 assert_eq!(
1775 run(
1776 &create_context(&command_runner, &Default::default(), &file_system),
1777 create_simple_config(
1778 vec![explicit_build(
1779 vec!["foo".into()],
1780 Rule::new("cp bar foo".into(), None),
1781 vec!["bar".into()],
1782 )],
1783 &["foo"],
1784 ),
1785 &[],
1786 DEFAULT_OPTIONS,
1787 )
1788 .await,
1789 Err(BuildError::File(FileError::new("metadata failure")))
1790 );
1791 assert_eq!(command_runner.commands(), Vec::<String>::new());
1792 }
1793
1794 #[tokio::test]
1795 async fn write_command_output() {
1796 let console = FakeConsole::default();
1797
1798 run(
1799 &create_context(
1800 &FakeCommandRunner::new(
1801 [(
1802 "touch foo".into(),
1803 Output {
1804 status: ExitStatus::default(),
1805 stdout: b"bar\n".into(),
1806 stderr: b"baz\n".into(),
1807 },
1808 )]
1809 .into_iter()
1810 .collect(),
1811 ),
1812 &console,
1813 &Default::default(),
1814 ),
1815 create_simple_config(
1816 vec![explicit_build(
1817 vec!["foo".into()],
1818 Rule::new("touch foo".into(), Some("build foo".into())),
1819 vec![],
1820 )],
1821 &["foo"],
1822 ),
1823 &[],
1824 DEFAULT_OPTIONS,
1825 )
1826 .await
1827 .unwrap();
1828
1829 assert_eq!(console.stdout(), "build foo\nbar\n");
1830 assert_eq!(console.stderr(), "baz\n");
1831 }
1832
1833 #[tokio::test]
1834 async fn write_debug_log_of_failed_build() {
1835 let console = FakeConsole::default();
1836
1837 assert_eq!(
1838 run(
1839 &create_context(
1840 &FakeCommandRunner::new(
1841 [("exit 1".into(), failed_output())].into_iter().collect(),
1842 ),
1843 &console,
1844 &Default::default(),
1845 ),
1846 create_simple_config(
1847 vec![explicit_build(
1848 vec!["foo".into()],
1849 Rule::new("exit 1".into(), None),
1850 vec![],
1851 )],
1852 &["foo"],
1853 ),
1854 &[],
1855 RunOptions {
1856 debug: true,
1857 dry_run: false,
1858 profile: false,
1859 },
1860 )
1861 .await,
1862 Err(BuildError::Build)
1863 );
1864 assert_eq!(
1865 console.stderr(),
1866 "turtle: command: exit 1\nturtle: exit status: 1\n"
1867 );
1868 }
1869
1870 #[tokio::test]
1871 async fn write_profile_log() {
1872 let console = FakeConsole::default();
1873
1874 run(
1875 &create_context(&Default::default(), &console, &Default::default()),
1876 create_simple_config(
1877 vec![explicit_build(
1878 vec!["foo".into()],
1879 Rule::new("touch foo".into(), None),
1880 vec![],
1881 )],
1882 &["foo"],
1883 ),
1884 &[],
1885 RunOptions {
1886 debug: false,
1887 dry_run: false,
1888 profile: true,
1889 },
1890 )
1891 .await
1892 .unwrap();
1893
1894 assert!(
1895 Regex::new(r"^turtle: duration: \d+ ms\n$")
1896 .unwrap()
1897 .is_match(&console.stderr())
1898 );
1899 }
1900
1901 #[tokio::test]
1902 async fn rebuild_on_update_of_header_dependency() {
1903 let command_runner = FakeCommandRunner::default();
1904 let file_system = FakeFileSystem::default();
1905 let context = create_context(&command_runner, &Default::default(), &file_system);
1906 let config = create_simple_config(
1907 vec![explicit_build(
1908 vec!["foo.o".into()],
1909 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1910 HeaderDependency::Make {
1911 path: "foo.d".into(),
1912 },
1913 )),
1914 vec!["foo.c".into()],
1915 )],
1916 &["foo.o"],
1917 );
1918
1919 file_system.write_file("foo.o", "");
1920 file_system.write_file("foo.c", "");
1921 file_system.write_file("foo.h", "");
1922 file_system.write_file("foo.d", "foo.o: foo.h\n");
1923
1924 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1925 .await
1926 .unwrap();
1927 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1928 .await
1929 .unwrap();
1930
1931 file_system.write_file("foo.h", "foo");
1932
1933 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1934
1935 assert_eq!(command_runner.commands(), ["cc foo.c", "cc foo.c"]);
1936 }
1937
1938 #[tokio::test]
1939 async fn read_and_remove_gcc_depfile() {
1940 let file_system = FakeFileSystem::default();
1941 let context = create_context(&Default::default(), &Default::default(), &file_system);
1942 let build = explicit_build(
1943 vec!["foo.o".into()],
1944 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
1945 HeaderDependency::Gcc {
1946 path: "foo.d".into(),
1947 },
1948 )),
1949 vec!["foo.c".into()],
1950 );
1951
1952 file_system.write_file("foo.c", "");
1953 file_system.write_file("foo.h", "");
1954 file_system.write_file("foo.d", "foo.o: foo.h\n");
1955
1956 run(
1957 &context,
1958 create_simple_config(vec![build.clone()], &["foo.o"]),
1959 &[],
1960 DEFAULT_OPTIONS,
1961 )
1962 .await
1963 .unwrap();
1964
1965 assert_eq!(
1966 context
1967 .database()
1968 .get_header_dependencies(build.id())
1969 .unwrap(),
1970 ["foo.h".into()]
1971 );
1972 assert!(
1973 !context
1974 .file_system()
1975 .exists("foo.d".as_ref())
1976 .await
1977 .unwrap()
1978 );
1979 }
1980
1981 #[tokio::test]
1982 async fn read_msvc_header_dependencies() {
1983 let console = FakeConsole::default();
1984 let file_system = FakeFileSystem::default();
1985 let context = create_context(
1986 &FakeCommandRunner::new(
1987 [(
1988 "cl foo.c".into(),
1989 Output {
1990 status: ExitStatus::default(),
1991 stdout: b"Note: including file: foo.h\nfoo.c\n".into(),
1992 stderr: vec![],
1993 },
1994 )]
1995 .into_iter()
1996 .collect(),
1997 ),
1998 &console,
1999 &file_system,
2000 );
2001 let build = explicit_build(
2002 vec!["foo.obj".into()],
2003 Rule::new("cl foo.c".into(), None).with_header_dependency(Some(
2004 HeaderDependency::Msvc {
2005 prefix: "Note: including file: ".into(),
2006 },
2007 )),
2008 vec!["foo.c".into()],
2009 );
2010
2011 file_system.write_file("foo.c", "");
2012 file_system.write_file("foo.h", "");
2013
2014 run(
2015 &context,
2016 create_simple_config(vec![build.clone()], &["foo.obj"]),
2017 &[],
2018 DEFAULT_OPTIONS,
2019 )
2020 .await
2021 .unwrap();
2022
2023 assert_eq!(
2024 context
2025 .database()
2026 .get_header_dependencies(build.id())
2027 .unwrap(),
2028 ["foo.h".into()]
2029 );
2030 assert_eq!(console.stdout(), "foo.c\n");
2031 }
2032
2033 #[tokio::test]
2034 async fn intern_header_dependencies() {
2035 let file_system = FakeFileSystem::default();
2036 let context = create_context(&Default::default(), &Default::default(), &file_system);
2037 let build = explicit_build(
2038 vec!["foo.o".into()],
2039 Rule::new("cc foo.c".into(), None).with_header_dependency(Some(
2040 HeaderDependency::Gcc {
2041 path: "foo.d".into(),
2042 },
2043 )),
2044 vec!["foo.c".into()],
2045 );
2046
2047 file_system.write_file("foo.c", "");
2048 file_system.write_file("foo.h", "");
2049 file_system.write_file("foo.d", "foo.o: ./foo.h\n");
2050
2051 run(
2052 &context,
2053 create_simple_config(vec![build.clone()], &["foo.o"]),
2054 &[],
2055 DEFAULT_OPTIONS,
2056 )
2057 .await
2058 .unwrap();
2059
2060 assert!(Arc::ptr_eq(
2061 &context
2062 .database()
2063 .get_header_dependencies(build.id())
2064 .unwrap()[0],
2065 &context.path_pool().intern("foo.h")
2066 ));
2067 }
2068
2069 #[tokio::test]
2070 async fn build_generated_header_dependency() {
2071 let command_runner = FakeCommandRunner::default();
2072 let file_system = FakeFileSystem::default();
2073 let context = create_context(&command_runner, &Default::default(), &file_system);
2074 let build = explicit_build(
2075 vec!["foo.o".into()],
2076 Rule::new("cc foo.c".into(), None),
2077 vec!["foo.c".into()],
2078 );
2079
2080 context
2081 .database()
2082 .set_header_dependencies(build.id(), &["foo.h".into()])
2083 .unwrap();
2084 file_system.write_file("foo.c", "");
2085 file_system.write_file("foo.h", "");
2086
2087 run(
2088 &context,
2089 create_simple_config(
2090 vec![
2091 build,
2092 explicit_build(
2093 vec!["foo.h".into()],
2094 Rule::new("touch foo.h".into(), None),
2095 vec![],
2096 ),
2097 ],
2098 &["foo.o"],
2099 ),
2100 &[],
2101 DEFAULT_OPTIONS,
2102 )
2103 .await
2104 .unwrap();
2105
2106 assert_eq!(command_runner.commands(), ["touch foo.h", "cc foo.c"]);
2107 }
2108
2109 #[tokio::test]
2110 async fn ignore_missing_header_dependency() {
2111 let file_system = FakeFileSystem::default();
2112 let context = create_context(&Default::default(), &Default::default(), &file_system);
2113 let build = explicit_build(
2114 vec!["foo.o".into()],
2115 Rule::new("cc foo.c".into(), None),
2116 vec!["foo.c".into()],
2117 );
2118
2119 context
2120 .database()
2121 .set_header_dependencies(build.id(), &["foo.h".into()])
2122 .unwrap();
2123 file_system.write_file("foo.c", "");
2124
2125 run(
2126 &context,
2127 create_simple_config(vec![build], &["foo.o"]),
2128 &[],
2129 DEFAULT_OPTIONS,
2130 )
2131 .await
2132 .unwrap();
2133 }
2134
2135 #[tokio::test]
2136 async fn read_header_dependencies_once() {
2137 let database = FakeDatabase::default();
2138 let file_system = FakeFileSystem::default();
2139 let context = Arc::new(Context::new(
2140 FakeCommandRunner::default(),
2141 Mutex::new(FakeConsole::default()).into(),
2142 database.clone(),
2143 file_system.clone(),
2144 Default::default(),
2145 ));
2146 let build = explicit_build(
2147 vec!["foo.o".into()],
2148 Rule::new("cc foo.c".into(), None),
2149 vec!["foo.c".into()],
2150 );
2151
2152 context
2153 .database()
2154 .set_header_dependencies(build.id(), &["foo.h".into()])
2155 .unwrap();
2156 file_system.write_file("foo.c", "");
2157 file_system.write_file("foo.h", "");
2158
2159 run(
2160 &context,
2161 create_simple_config(vec![build.clone()], &["foo.o"]),
2162 &[],
2163 DEFAULT_OPTIONS,
2164 )
2165 .await
2166 .unwrap();
2167
2168 assert_eq!(database.header_dependency_requests(), [build.id()]);
2169 }
2170
2171 #[tokio::test]
2172 async fn detect_circular_header_dependency() {
2173 let context = create_context(
2174 &Default::default(),
2175 &Default::default(),
2176 &Default::default(),
2177 );
2178 let build = explicit_build(
2179 vec!["foo".into()],
2180 Rule::new("touch foo".into(), None),
2181 vec![],
2182 );
2183
2184 context
2185 .database()
2186 .set_header_dependencies(build.id(), &["foo".into()])
2187 .unwrap();
2188
2189 assert_eq!(
2190 run(
2191 &context,
2192 create_simple_config(vec![build], &["foo"]),
2193 &[],
2194 DEFAULT_OPTIONS,
2195 )
2196 .await,
2197 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2198 vec!["foo".into()]
2199 )))
2200 );
2201 }
2202
2203 #[tokio::test]
2204 async fn build_dynamic_input() {
2205 let command_runner = FakeCommandRunner::default();
2206 let file_system = FakeFileSystem::default();
2207
2208 file_system.write_file(
2209 "foo.dd",
2210 "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
2211 );
2212 file_system.write_file("bar", "");
2213
2214 run(
2215 &create_context(&command_runner, &Default::default(), &file_system),
2216 create_simple_config(
2217 vec![
2218 Build::new(
2219 vec!["foo".into()],
2220 vec![],
2221 Rule::new("touch foo".into(), None).into(),
2222 vec![],
2223 vec!["foo.dd".into()],
2224 Some("foo.dd".into()),
2225 ),
2226 explicit_build(
2227 vec!["foo.dd".into()],
2228 Rule::new("touch foo.dd".into(), None),
2229 vec![],
2230 ),
2231 explicit_build(
2232 vec!["bar".into()],
2233 Rule::new("touch bar".into(), None),
2234 vec![],
2235 ),
2236 ],
2237 &["foo"],
2238 ),
2239 &[],
2240 DEFAULT_OPTIONS,
2241 )
2242 .await
2243 .unwrap();
2244
2245 assert_eq!(
2246 command_runner.commands(),
2247 ["touch foo.dd", "touch bar", "touch foo"]
2248 );
2249 }
2250
2251 #[tokio::test]
2252 async fn build_dynamic_input_declared_for_implicit_output() {
2253 let command_runner = FakeCommandRunner::default();
2254 let file_system = FakeFileSystem::default();
2255
2256 file_system.write_file(
2257 "foo.dd",
2258 "ninja_dyndep_version = 1\nbuild baz: dyndep | bar\n",
2259 );
2260 file_system.write_file("bar", "");
2261
2262 run(
2263 &create_context(&command_runner, &Default::default(), &file_system),
2264 create_simple_config(
2265 vec![
2266 Build::new(
2267 vec!["foo".into()],
2268 vec!["baz".into()],
2269 Rule::new("touch foo".into(), None).into(),
2270 vec![],
2271 vec!["foo.dd".into()],
2272 Some("foo.dd".into()),
2273 ),
2274 explicit_build(
2275 vec!["foo.dd".into()],
2276 Rule::new("touch foo.dd".into(), None),
2277 vec![],
2278 ),
2279 explicit_build(
2280 vec!["bar".into()],
2281 Rule::new("touch bar".into(), None),
2282 vec![],
2283 ),
2284 ],
2285 &["foo"],
2286 ),
2287 &[],
2288 DEFAULT_OPTIONS,
2289 )
2290 .await
2291 .unwrap();
2292
2293 assert_eq!(
2294 command_runner.commands(),
2295 ["touch foo.dd", "touch bar", "touch foo"]
2296 );
2297 }
2298
2299 #[tokio::test]
2300 async fn build_dynamic_inputs_of_builds_sharing_dynamic_module() {
2301 let command_runner = FakeCommandRunner::default();
2302 let file_system = FakeFileSystem::default();
2303
2304 file_system.write_file(
2305 "foo.dd",
2306 "ninja_dyndep_version = 1\nbuild foo: dyndep | baz\nbuild bar: dyndep | qux\n",
2307 );
2308 file_system.write_file("foo", "");
2309 file_system.write_file("baz", "");
2310 file_system.write_file("qux", "");
2311
2312 run(
2313 &create_context(&command_runner, &Default::default(), &file_system),
2314 create_simple_config(
2315 vec![
2316 Build::new(
2317 vec!["foo".into()],
2318 vec![],
2319 Rule::new("touch foo".into(), None).into(),
2320 vec![],
2321 vec!["foo.dd".into()],
2322 Some("foo.dd".into()),
2323 ),
2324 Build::new(
2325 vec!["bar".into()],
2326 vec![],
2327 Rule::new("touch bar".into(), None).into(),
2328 vec!["foo".into()],
2329 vec!["foo.dd".into()],
2330 Some("foo.dd".into()),
2331 ),
2332 explicit_build(
2333 vec!["baz".into()],
2334 Rule::new("touch baz".into(), None),
2335 vec![],
2336 ),
2337 explicit_build(
2338 vec!["qux".into()],
2339 Rule::new("touch qux".into(), None),
2340 vec![],
2341 ),
2342 ],
2343 &["bar"],
2344 ),
2345 &[],
2346 DEFAULT_OPTIONS,
2347 )
2348 .await
2349 .unwrap();
2350
2351 assert_eq!(
2352 command_runner.commands(),
2353 ["touch baz", "touch foo", "touch qux", "touch bar"]
2354 );
2355 }
2356
2357 #[tokio::test]
2358 async fn read_dynamic_module_once() {
2359 let file_system = FakeFileSystem::default();
2360
2361 file_system.write_file(
2362 "foo.dd",
2363 "ninja_dyndep_version = 1\nbuild foo: dyndep\nbuild bar: dyndep\n",
2364 );
2365
2366 run(
2367 &create_context(&Default::default(), &Default::default(), &file_system),
2368 create_simple_config(
2369 ["foo", "bar"]
2370 .into_iter()
2371 .map(|output| {
2372 Build::new(
2373 vec![output.into()],
2374 vec![],
2375 Rule::new(format!("touch {output}"), None).into(),
2376 vec![],
2377 vec!["foo.dd".into()],
2378 Some("foo.dd".into()),
2379 )
2380 })
2381 .collect(),
2382 &["foo", "bar"],
2383 ),
2384 &[],
2385 DEFAULT_OPTIONS,
2386 )
2387 .await
2388 .unwrap();
2389
2390 assert_eq!(file_system.read_requests(), [Path::new("foo.dd")]);
2391 }
2392
2393 #[tokio::test]
2394 async fn intern_dynamic_input() {
2395 let file_system = FakeFileSystem::default();
2396 let config = create_simple_config(
2397 vec![Build::new(
2398 vec!["foo".into()],
2399 vec![],
2400 Rule::new("touch foo".into(), None).into(),
2401 vec![],
2402 vec!["foo.dd".into()],
2403 Some("foo.dd".into()),
2404 )],
2405 &[],
2406 );
2407 let context = RunContext::new(
2408 create_context(&Default::default(), &Default::default(), &file_system),
2409 config.clone(),
2410 BuildGraph::new(config.outputs()),
2411 Default::default(),
2412 DEFAULT_OPTIONS,
2413 );
2414
2415 file_system.write_file(
2416 "foo.dd",
2417 "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
2418 );
2419
2420 assert!(Arc::ptr_eq(
2421 &load_dynamic_config(&context, "foo.dd")
2422 .await
2423 .unwrap()
2424 .outputs()["foo"]
2425 .inputs()[0],
2426 &context.build().path_pool().intern("bar")
2427 ));
2428 }
2429
2430 #[tokio::test]
2431 async fn fail_with_missing_dynamic_dependency() {
2432 let file_system = FakeFileSystem::default();
2433 let build = Build::new(
2434 vec!["foo".into()],
2435 vec![],
2436 Rule::new("touch foo".into(), None).into(),
2437 vec![],
2438 vec![],
2439 Some("foo.dd".into()),
2440 );
2441
2442 file_system.write_file("foo.dd", "ninja_dyndep_version = 1\n");
2443
2444 assert_eq!(
2445 run(
2446 &create_context(&Default::default(), &Default::default(), &file_system),
2447 create_simple_config(vec![build.clone()], &["foo"]),
2448 &[],
2449 DEFAULT_OPTIONS,
2450 )
2451 .await,
2452 Err(BuildError::DynamicDependencyNotFound(build.into()))
2453 );
2454 }
2455
2456 #[tokio::test]
2457 async fn fail_with_unknown_output_in_dynamic_dependency() {
2458 let file_system = FakeFileSystem::default();
2459
2460 file_system.write_file(
2461 "foo.dd",
2462 "ninja_dyndep_version = 1\nbuild bar: dyndep | baz\n",
2463 );
2464
2465 assert_eq!(
2466 run(
2467 &create_context(&Default::default(), &Default::default(), &file_system),
2468 create_simple_config(
2469 vec![Build::new(
2470 vec!["foo".into()],
2471 vec![],
2472 Rule::new("touch foo".into(), None).into(),
2473 vec![],
2474 vec![],
2475 Some("foo.dd".into()),
2476 )],
2477 &["foo"],
2478 ),
2479 &[],
2480 DEFAULT_OPTIONS,
2481 )
2482 .await,
2483 Err(BuildError::BuildGraph(BuildGraphError::OutputNotFound(
2484 "bar".into()
2485 )))
2486 );
2487 }
2488
2489 #[tokio::test]
2490 async fn detect_circular_dynamic_dependency() {
2491 let file_system = FakeFileSystem::default();
2492
2493 file_system.write_file(
2494 "foo.dd",
2495 "ninja_dyndep_version = 1\nbuild foo: dyndep | foo\n",
2496 );
2497
2498 assert_eq!(
2499 run(
2500 &create_context(&Default::default(), &Default::default(), &file_system),
2501 create_simple_config(
2502 vec![Build::new(
2503 vec!["foo".into()],
2504 vec![],
2505 Rule::new("touch foo".into(), None).into(),
2506 vec![],
2507 vec![],
2508 Some("foo.dd".into()),
2509 )],
2510 &["foo"],
2511 ),
2512 &[],
2513 DEFAULT_OPTIONS,
2514 )
2515 .await,
2516 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2517 vec!["foo".into()]
2518 )))
2519 );
2520 }
2521
2522 #[tokio::test]
2523 async fn run_builds_concurrently() {
2524 let command_runner = FakeCommandRunner::default();
2525
2526 run(
2527 &create_context(&command_runner, &Default::default(), &Default::default()),
2528 create_simple_config(
2529 vec![
2530 pool_build("foo", None),
2531 pool_build("bar", None),
2532 pool_build("baz", None),
2533 ],
2534 &["foo", "bar", "baz"],
2535 ),
2536 &[],
2537 DEFAULT_OPTIONS,
2538 )
2539 .await
2540 .unwrap();
2541
2542 assert_eq!(command_runner.max_concurrency(), 3);
2543 }
2544
2545 #[tokio::test]
2546 async fn limit_concurrency_of_builds_in_pool() {
2547 let command_runner = FakeCommandRunner::default();
2548
2549 run(
2550 &create_context(&command_runner, &Default::default(), &Default::default()),
2551 create_pool_config(
2552 vec![
2553 pool_build("foo", limited_pool("qux")),
2554 pool_build("bar", limited_pool("qux")),
2555 pool_build("baz", limited_pool("qux")),
2556 ],
2557 &["foo", "bar", "baz"],
2558 &[("qux", 1)],
2559 ),
2560 &[],
2561 DEFAULT_OPTIONS,
2562 )
2563 .await
2564 .unwrap();
2565
2566 assert_eq!(command_runner.commands().len(), 3);
2567 assert_eq!(command_runner.max_concurrency(), 1);
2568 }
2569
2570 #[tokio::test]
2571 async fn limit_concurrency_of_builds_in_pool_with_depth_of_two() {
2572 let command_runner = FakeCommandRunner::default();
2573
2574 run(
2575 &create_context(&command_runner, &Default::default(), &Default::default()),
2576 create_pool_config(
2577 vec![
2578 pool_build("foo", limited_pool("quux")),
2579 pool_build("bar", limited_pool("quux")),
2580 pool_build("baz", limited_pool("quux")),
2581 pool_build("qux", limited_pool("quux")),
2582 ],
2583 &["foo", "bar", "baz", "qux"],
2584 &[("quux", 2)],
2585 ),
2586 &[],
2587 DEFAULT_OPTIONS,
2588 )
2589 .await
2590 .unwrap();
2591
2592 assert_eq!(command_runner.commands().len(), 4);
2593 assert_eq!(command_runner.max_concurrency(), 2);
2594 }
2595
2596 #[tokio::test]
2597 async fn run_builds_in_different_pools_concurrently() {
2598 let command_runner = FakeCommandRunner::default();
2599
2600 run(
2601 &create_context(&command_runner, &Default::default(), &Default::default()),
2602 create_pool_config(
2603 vec![
2604 pool_build("foo", limited_pool("baz")),
2605 pool_build("bar", limited_pool("qux")),
2606 ],
2607 &["foo", "bar"],
2608 &[("baz", 1), ("qux", 1)],
2609 ),
2610 &[],
2611 DEFAULT_OPTIONS,
2612 )
2613 .await
2614 .unwrap();
2615
2616 assert_eq!(command_runner.max_concurrency(), 2);
2617 }
2618
2619 #[tokio::test]
2620 async fn run_build_in_pool_of_huge_depth() {
2621 let command_runner = FakeCommandRunner::default();
2622
2623 run(
2624 &create_context(&command_runner, &Default::default(), &Default::default()),
2625 create_pool_config(
2626 vec![pool_build("foo", limited_pool("bar"))],
2627 &["foo"],
2628 &[("bar", usize::MAX)],
2629 ),
2630 &[],
2631 DEFAULT_OPTIONS,
2632 )
2633 .await
2634 .unwrap();
2635
2636 assert_eq!(command_runner.commands(), ["touch foo"]);
2637 }
2638
2639 #[tokio::test]
2640 async fn run_build_in_console_pool() {
2641 let command_runner = FakeCommandRunner::new(
2642 [(
2643 "touch foo".into(),
2644 Output {
2645 status: ExitStatus::default(),
2646 stdout: b"bar\n".into(),
2647 stderr: b"baz\n".into(),
2648 },
2649 )]
2650 .into_iter()
2651 .collect(),
2652 );
2653 let console = FakeConsole::default();
2654
2655 run(
2656 &create_context(&command_runner, &console, &Default::default()),
2657 create_simple_config(
2658 vec![explicit_build(
2659 vec!["foo".into()],
2660 Rule::new("touch foo".into(), Some("build foo".into()))
2661 .with_pool(Some(Pool::Console)),
2662 vec![],
2663 )],
2664 &["foo"],
2665 ),
2666 &[],
2667 DEFAULT_OPTIONS,
2668 )
2669 .await
2670 .unwrap();
2671
2672 assert_eq!(command_runner.commands(), Vec::<String>::new());
2673 assert_eq!(command_runner.console_commands(), ["touch foo"]);
2674 assert_eq!(console.stdout(), "build foo\n");
2675 assert_eq!(console.stderr(), "");
2676 }
2677
2678 #[tokio::test]
2679 async fn limit_concurrency_of_builds_in_console_pool() {
2680 let command_runner = FakeCommandRunner::default();
2681
2682 run(
2683 &create_context(&command_runner, &Default::default(), &Default::default()),
2684 create_simple_config(
2685 vec![
2686 pool_build("foo", Some(Pool::Console)),
2687 pool_build("bar", Some(Pool::Console)),
2688 pool_build("baz", Some(Pool::Console)),
2689 ],
2690 &["foo", "bar", "baz"],
2691 ),
2692 &[],
2693 DEFAULT_OPTIONS,
2694 )
2695 .await
2696 .unwrap();
2697
2698 assert_eq!(command_runner.console_commands().len(), 3);
2699 assert_eq!(command_runner.max_concurrency(), 1);
2700 }
2701
2702 #[tokio::test]
2703 async fn write_debug_log_of_failed_build_in_console_pool() {
2704 let command_runner =
2705 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
2706 let console = FakeConsole::default();
2707
2708 assert_eq!(
2709 run(
2710 &create_context(&command_runner, &console, &Default::default()),
2711 create_simple_config(
2712 vec![explicit_build(
2713 vec!["foo".into()],
2714 Rule::new("exit 1".into(), None).with_pool(Some(Pool::Console)),
2715 vec![],
2716 )],
2717 &["foo"],
2718 ),
2719 &[],
2720 RunOptions {
2721 debug: true,
2722 dry_run: false,
2723 profile: false,
2724 },
2725 )
2726 .await,
2727 Err(BuildError::Build)
2728 );
2729 assert_eq!(command_runner.console_commands(), ["exit 1"]);
2730 assert_eq!(
2731 console.stderr(),
2732 "turtle: command: exit 1\nturtle: exit status: 1\n"
2733 );
2734 }
2735
2736 #[tokio::test]
2737 async fn lock_console_during_command_in_console_pool() {
2738 let command_runner = FakeCommandRunner::default();
2739 let context = create_run_context(&command_runner, &Default::default(), &[]);
2740 let rule = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
2741 let mut future = pin!(run_rule(&context, &rule));
2742
2743 assert!(poll!(&mut future).is_pending());
2744 assert_eq!(command_runner.console_commands(), ["foo"]);
2745 assert!(context.build().console().try_lock().is_err());
2746
2747 future.await.unwrap();
2748
2749 assert!(context.build().console().try_lock().is_ok());
2750 }
2751
2752 #[tokio::test]
2753 async fn flush_console_before_command_in_console_pool() {
2754 let command_runner = FakeCommandRunner::default();
2755 let console = FakeConsole::default();
2756 let context = create_run_context(&command_runner, &console, &[]);
2757 let rule = Rule::new("foo".into(), Some("bar".into())).with_pool(Some(Pool::Console));
2758 let mut future = pin!(run_rule(&context, &rule));
2759
2760 assert!(poll!(&mut future).is_pending());
2761 assert_eq!(command_runner.console_commands(), ["foo"]);
2762 assert_eq!(console.flushed_stdout(), "bar\n");
2763
2764 future.await.unwrap();
2765 }
2766
2767 #[tokio::test]
2768 async fn wait_for_console_before_command_in_console_pool() {
2769 let command_runner = FakeCommandRunner::default();
2770 let context = create_run_context(&command_runner, &Default::default(), &[]);
2771 let rule = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
2772 let console = context.build().console().lock().await;
2773 let mut future = pin!(run_rule(&context, &rule));
2774
2775 for _ in 0..POLL_COUNT {
2776 assert!(poll!(&mut future).is_pending());
2777 }
2778
2779 assert_eq!(command_runner.console_commands(), Vec::<String>::new());
2780
2781 drop(console);
2782 future.await.unwrap();
2783
2784 assert_eq!(command_runner.console_commands(), ["foo"]);
2785 }
2786
2787 #[tokio::test]
2788 async fn run_command_concurrently_with_command_in_console_pool() {
2789 let command_runner = FakeCommandRunner::default();
2790 let context = create_run_context(&command_runner, &Default::default(), &[]);
2791 let foo = Rule::new("foo".into(), None).with_pool(Some(Pool::Console));
2792 let bar = Rule::new("bar".into(), None);
2793 let mut foo_future = pin!(run_rule(&context, &foo));
2794 let mut bar_future = pin!(run_rule(&context, &bar));
2795
2796 assert!(poll!(&mut foo_future).is_pending());
2797 assert!(poll!(&mut bar_future).is_pending());
2798 assert_eq!(command_runner.console_commands(), ["foo"]);
2799 assert_eq!(command_runner.commands(), ["bar"]);
2800
2801 foo_future.await.unwrap();
2802 bar_future.await.unwrap();
2803 }
2804
2805 #[tokio::test]
2806 async fn run_command_without_locking_console() {
2807 let command_runner = FakeCommandRunner::default();
2808 let console = FakeConsole::default();
2809 let context = create_run_context(&command_runner, &console, &[]);
2810 let rule = Rule::new("foo".into(), Some("bar".into()));
2811 let mut future = pin!(run_rule(&context, &rule));
2812
2813 assert!(poll!(&mut future).is_pending());
2814 assert_eq!(command_runner.commands(), ["foo"]);
2815 assert!(context.build().console().try_lock().is_ok());
2816 assert_eq!(console.stdout(), "");
2817
2818 future.await.unwrap();
2819
2820 assert_eq!(console.stdout(), "bar\n");
2821 }
2822
2823 #[tokio::test]
2824 async fn write_output_while_another_command_runs() {
2825 let command_runner = FakeCommandRunner::default();
2826 let console = FakeConsole::default();
2827 let context = create_run_context(&command_runner, &console, &[]);
2828 let foo = Rule::new("foo".into(), Some("foo".into()));
2829 let bar = Rule::new("bar".into(), Some("bar".into()));
2830 let mut foo_future = pin!(run_rule(&context, &foo));
2831
2832 assert!(poll!(&mut foo_future).is_pending());
2833
2834 run_rule(&context, &bar).await.unwrap();
2835
2836 assert_eq!(command_runner.commands(), ["foo", "bar"]);
2837 assert_eq!(console.stdout(), "bar\n");
2838
2839 foo_future.await.unwrap();
2840
2841 assert_eq!(console.stdout(), "bar\nfoo\n");
2842 }
2843
2844 #[tokio::test]
2845 async fn release_pool_before_waiting_for_console() {
2846 let command_runner = FakeCommandRunner::default();
2847 let context = create_run_context(&command_runner, &Default::default(), &[("baz", 1)]);
2848 let foo = Rule::new("foo".into(), None).with_pool(limited_pool("baz"));
2849 let bar = Rule::new("bar".into(), None).with_pool(limited_pool("baz"));
2850 let console = context.build().console().lock().await;
2851 let mut foo_future = pin!(run_rule(&context, &foo));
2852 let mut bar_future = pin!(run_rule(&context, &bar));
2853
2854 for _ in 0..POLL_COUNT {
2855 assert!(poll!(&mut foo_future).is_pending());
2856 }
2857
2858 for _ in 0..POLL_COUNT {
2859 assert!(poll!(&mut bar_future).is_pending());
2860 }
2861
2862 assert_eq!(command_runner.commands(), ["foo", "bar"]);
2863
2864 drop(console);
2865 foo_future.await.unwrap();
2866 bar_future.await.unwrap();
2867 }
2868
2869 #[tokio::test]
2870 async fn wait_for_pool_before_locking_console() {
2871 let command_runner = FakeCommandRunner::default();
2872 let console = FakeConsole::default();
2873 let context = create_run_context(&command_runner, &console, &[("bar", 1)]);
2874 let rule = Rule::new("foo".into(), Some("foo".into())).with_pool(limited_pool("bar"));
2875 let permit = context.pool(rule.pool()).await.unwrap();
2876 let mut future = pin!(run_rule(&context, &rule));
2877
2878 for _ in 0..POLL_COUNT {
2879 assert!(poll!(&mut future).is_pending());
2880 }
2881
2882 assert_eq!(command_runner.commands(), Vec::<String>::new());
2883 assert_eq!(console.stdout(), "");
2884 assert!(context.build().console().try_lock().is_ok());
2885
2886 drop(permit);
2887 future.await.unwrap();
2888
2889 assert_eq!(command_runner.commands(), ["foo"]);
2890 assert_eq!(console.stdout(), "foo\n");
2891 }
2892
2893 #[tokio::test]
2894 async fn keep_pool_until_command_finishes_on_console_error() {
2895 let command_runner = FakeCommandRunner::default();
2896 let context = create_run_context(&command_runner, &FakeConsole::failing(), &[("baz", 1)]);
2897 let foo = Rule::new("foo".into(), Some("foo".into())).with_pool(limited_pool("baz"));
2898 let bar = Rule::new("bar".into(), None).with_pool(limited_pool("baz"));
2899 let mut foo_future = pin!(run_rule(&context, &foo));
2900 let mut bar_future = pin!(run_rule(&context, &bar));
2901
2902 assert!(poll!(&mut foo_future).is_pending());
2903 assert!(poll!(&mut bar_future).is_pending());
2904 assert_eq!(command_runner.commands(), ["foo"]);
2905 assert!(foo_future.await.is_err());
2906 }
2907
2908 #[test]
2909 fn classify_phony_and_file_inputs() {
2910 let build = explicit_build(
2911 vec!["foo".into()],
2912 Rule::new("".into(), None),
2913 vec!["bar".into(), "baz".into(), "qux".into(), "bar".into()],
2914 );
2915 let context = RunContext::new(
2916 create_context(
2917 &Default::default(),
2918 &Default::default(),
2919 &Default::default(),
2920 ),
2921 create_simple_config(
2922 vec![
2923 build.clone(),
2924 Build::new(vec!["bar".into()], vec![], None, vec![], vec![], None),
2925 explicit_build(vec!["baz".into()], Rule::new("".into(), None), vec![]),
2926 ],
2927 &[],
2928 ),
2929 BuildGraph::new(&Default::default()),
2930 Default::default(),
2931 DEFAULT_OPTIONS,
2932 );
2933
2934 assert_eq!(
2935 classify_inputs(
2936 &context,
2937 &build,
2938 &["quux".into(), "baz".into()],
2939 &[&"qux".into(), &"corge".into()],
2940 ),
2941 (
2942 vec![&"bar".into()],
2943 vec![
2944 &"baz".into(),
2945 &"qux".into(),
2946 &"quux".into(),
2947 &"corge".into()
2948 ]
2949 )
2950 );
2951 }
2952
2953 mod dry_run {
2954 use super::*;
2955 use pretty_assertions::assert_eq;
2956 use std::path::PathBuf;
2957
2958 const OPTIONS: RunOptions = RunOptions {
2959 dry_run: true,
2960 ..DEFAULT_OPTIONS
2961 };
2962
2963 fn create_database_context(
2964 console: &FakeConsole,
2965 database: &FakeDatabase,
2966 file_system: &FakeFileSystem,
2967 ) -> Arc<Context> {
2968 Context::new(
2969 FakeCommandRunner::default(),
2970 Mutex::new(console.clone()).into(),
2971 database.clone(),
2972 file_system.clone(),
2973 Default::default(),
2974 )
2975 .into()
2976 }
2977
2978 fn described_rule(output: &str) -> Rule {
2979 Rule::new(format!("touch {output}"), Some(format!("build {output}")))
2980 }
2981
2982 fn described_build(output: &str, inputs: &[&str]) -> Build {
2983 explicit_build(
2984 vec![output.into()],
2985 described_rule(output),
2986 inputs.iter().map(|&input| input.into()).collect(),
2987 )
2988 }
2989
2990 fn phony_build(output: &str, inputs: &[&str]) -> Build {
2991 Build::new(
2992 vec![output.into()],
2993 vec![],
2994 None,
2995 inputs.iter().map(|&input| input.into()).collect(),
2996 vec![],
2997 None,
2998 )
2999 }
3000
3001 #[tokio::test]
3002 async fn run_no_command() {
3003 let command_runner = FakeCommandRunner::default();
3004
3005 run(
3006 &create_context(&command_runner, &Default::default(), &Default::default()),
3007 create_simple_config(vec![described_build("foo", &[])], &["foo"]),
3008 &[],
3009 OPTIONS,
3010 )
3011 .await
3012 .unwrap();
3013
3014 assert_eq!(command_runner.commands(), Vec::<String>::new());
3015 }
3016
3017 #[tokio::test]
3018 async fn write_description() {
3019 let console = FakeConsole::default();
3020
3021 run(
3022 &create_context(&Default::default(), &console, &Default::default()),
3023 create_simple_config(vec![described_build("foo", &[])], &["foo"]),
3024 &[],
3025 OPTIONS,
3026 )
3027 .await
3028 .unwrap();
3029
3030 assert_eq!(console.stdout(), "build foo\n");
3031 assert_eq!(console.stderr(), "");
3032 }
3033
3034 #[tokio::test]
3035 async fn write_debug_log() {
3036 let console = FakeConsole::default();
3037
3038 run(
3039 &create_context(&Default::default(), &console, &Default::default()),
3040 create_simple_config(vec![described_build("foo", &[])], &["foo"]),
3041 &[],
3042 RunOptions {
3043 debug: true,
3044 ..OPTIONS
3045 },
3046 )
3047 .await
3048 .unwrap();
3049
3050 assert_eq!(console.stdout(), "build foo\n");
3051 assert_eq!(console.stderr(), "turtle: command: touch foo\n");
3052 }
3053
3054 #[tokio::test]
3055 async fn prepare_no_output_directory() {
3056 let context = create_context(
3057 &Default::default(),
3058 &Default::default(),
3059 &Default::default(),
3060 );
3061
3062 run(
3063 &context,
3064 create_simple_config(vec![described_build("foo/bar", &[])], &["foo/bar"]),
3065 &[],
3066 OPTIONS,
3067 )
3068 .await
3069 .unwrap();
3070
3071 assert!(!context.file_system().exists("foo".as_ref()).await.unwrap());
3072 }
3073
3074 #[tokio::test]
3075 async fn record_no_output() {
3076 let file_system = FakeFileSystem::default();
3077 let context = create_context(&Default::default(), &Default::default(), &file_system);
3078
3079 file_system.write_file("foo.c", "");
3080
3081 run(
3082 &context,
3083 Config::new(
3084 create_outputs(vec![described_build("foo.o", &["foo.c"])]),
3085 ["foo.o".into()].into_iter().collect(),
3086 [("foo.o".into(), "foo.c".into())].into_iter().collect(),
3087 Default::default(),
3088 None,
3089 )
3090 .into(),
3091 &[],
3092 OPTIONS,
3093 )
3094 .await
3095 .unwrap();
3096
3097 assert_eq!(
3098 context.database().get_outputs().unwrap(),
3099 Vec::<String>::new()
3100 );
3101 assert_eq!(context.database().get_source("foo.o").unwrap(), None);
3102 }
3103
3104 #[tokio::test]
3105 async fn run_command_after_dry_run() {
3106 let command_runner = FakeCommandRunner::default();
3107 let file_system = FakeFileSystem::default();
3108 let context = create_context(&command_runner, &Default::default(), &file_system);
3109 let config = create_simple_config(
3110 vec![
3111 described_build("foo", &["bar"]),
3112 phony_build("bar", &["baz"]),
3113 ],
3114 &["foo"],
3115 );
3116
3117 file_system.write_file("foo", "");
3118 file_system.write_file("baz", "");
3119
3120 run(&context, config.clone(), &[], OPTIONS).await.unwrap();
3121 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
3122
3123 assert_eq!(command_runner.commands(), ["touch foo"]);
3124 }
3125
3126 #[tokio::test]
3127 async fn write_no_description_of_up_to_date_build() {
3128 let console = FakeConsole::default();
3129 let database = FakeDatabase::default();
3130 let file_system = FakeFileSystem::default();
3131 let config = create_simple_config(vec![described_build("foo", &["bar"])], &["foo"]);
3132
3133 file_system.write_file("foo", "");
3134 file_system.write_file("bar", "");
3135
3136 run(
3137 &create_database_context(&Default::default(), &database, &file_system),
3138 config.clone(),
3139 &[],
3140 DEFAULT_OPTIONS,
3141 )
3142 .await
3143 .unwrap();
3144 run(
3145 &create_database_context(&console, &database, &file_system),
3146 config,
3147 &[],
3148 OPTIONS,
3149 )
3150 .await
3151 .unwrap();
3152
3153 assert_eq!(console.stdout(), "");
3154 }
3155
3156 #[tokio::test]
3157 async fn write_description_on_content_update_of_input() {
3158 let console = FakeConsole::default();
3159 let database = FakeDatabase::default();
3160 let file_system = FakeFileSystem::default();
3161 let config = create_simple_config(vec![described_build("foo", &["bar"])], &["foo"]);
3162
3163 file_system.write_file("foo", "");
3164 file_system.write_file("bar", "");
3165
3166 run(
3167 &create_database_context(&Default::default(), &database, &file_system),
3168 config.clone(),
3169 &[],
3170 DEFAULT_OPTIONS,
3171 )
3172 .await
3173 .unwrap();
3174
3175 file_system.write_file("bar", "bar");
3176
3177 run(
3178 &create_database_context(&console, &database, &file_system),
3179 config,
3180 &[],
3181 OPTIONS,
3182 )
3183 .await
3184 .unwrap();
3185
3186 assert_eq!(console.stdout(), "build foo\n");
3187 }
3188
3189 #[tokio::test]
3190 async fn write_description_of_missing_output() {
3191 let console = FakeConsole::default();
3192 let database = FakeDatabase::default();
3193 let file_system = FakeFileSystem::default();
3194 let config = create_simple_config(vec![described_build("foo", &["bar"])], &["foo"]);
3195
3196 file_system.write_file("bar", "");
3197
3198 run(
3199 &create_database_context(&Default::default(), &database, &file_system),
3200 config.clone(),
3201 &[],
3202 DEFAULT_OPTIONS,
3203 )
3204 .await
3205 .unwrap();
3206 run(
3207 &create_database_context(&console, &database, &file_system),
3208 config,
3209 &[],
3210 OPTIONS,
3211 )
3212 .await
3213 .unwrap();
3214
3215 assert_eq!(console.stdout(), "build foo\n");
3216 }
3217
3218 #[tokio::test]
3219 async fn write_description_of_dependent_of_missing_output() {
3220 let console = FakeConsole::default();
3221 let file_system = FakeFileSystem::default();
3222
3223 file_system.write_file("baz", "");
3224
3225 run(
3226 &create_context(&Default::default(), &console, &file_system),
3227 create_simple_config(
3228 vec![
3229 described_build("foo", &["bar"]),
3230 described_build("bar", &["baz"]),
3231 ],
3232 &["foo"],
3233 ),
3234 &[],
3235 OPTIONS,
3236 )
3237 .await
3238 .unwrap();
3239
3240 assert_eq!(console.stdout(), "build bar\nbuild foo\n");
3241 }
3242
3243 #[tokio::test]
3244 async fn write_description_of_dependent_of_stale_build() {
3245 let console = FakeConsole::default();
3246 let database = FakeDatabase::default();
3247 let file_system = FakeFileSystem::default();
3248 let config = create_simple_config(
3249 vec![
3250 described_build("foo", &["bar"]),
3251 described_build("bar", &["baz"]),
3252 ],
3253 &["foo"],
3254 );
3255
3256 file_system.write_file("foo", "");
3257 file_system.write_file("bar", "");
3258 file_system.write_file("baz", "");
3259
3260 run(
3261 &create_database_context(&Default::default(), &database, &file_system),
3262 config.clone(),
3263 &[],
3264 DEFAULT_OPTIONS,
3265 )
3266 .await
3267 .unwrap();
3268
3269 file_system.write_file("baz", "baz");
3270
3271 run(
3272 &create_database_context(&console, &database, &file_system),
3273 config,
3274 &[],
3275 OPTIONS,
3276 )
3277 .await
3278 .unwrap();
3279
3280 assert_eq!(console.stdout(), "build bar\nbuild foo\n");
3281 }
3282
3283 #[tokio::test]
3284 async fn write_no_description_of_dependent_of_stale_order_only_input() {
3285 let console = FakeConsole::default();
3286 let database = FakeDatabase::default();
3287 let file_system = FakeFileSystem::default();
3288 let config = create_simple_config(
3289 vec![
3290 Build::new(
3291 vec!["foo".into()],
3292 vec![],
3293 described_rule("foo").into(),
3294 vec![],
3295 vec!["bar".into()],
3296 None,
3297 ),
3298 described_build("bar", &["baz"]),
3299 ],
3300 &["foo"],
3301 );
3302
3303 file_system.write_file("foo", "");
3304 file_system.write_file("bar", "");
3305 file_system.write_file("baz", "");
3306
3307 run(
3308 &create_database_context(&Default::default(), &database, &file_system),
3309 config.clone(),
3310 &[],
3311 DEFAULT_OPTIONS,
3312 )
3313 .await
3314 .unwrap();
3315
3316 file_system.write_file("baz", "baz");
3317
3318 run(
3319 &create_database_context(&console, &database, &file_system),
3320 config,
3321 &[],
3322 OPTIONS,
3323 )
3324 .await
3325 .unwrap();
3326
3327 assert_eq!(console.stdout(), "build bar\n");
3328 }
3329
3330 #[tokio::test]
3331 async fn write_no_description_of_dependent_of_up_to_date_phony_input() {
3332 let console = FakeConsole::default();
3333 let database = FakeDatabase::default();
3334 let file_system = FakeFileSystem::default();
3335 let config = create_simple_config(
3336 vec![
3337 described_build("foo", &["bar"]),
3338 phony_build("bar", &["baz"]),
3339 ],
3340 &["foo"],
3341 );
3342
3343 file_system.write_file("foo", "");
3344 file_system.write_file("baz", "");
3345
3346 run(
3347 &create_database_context(&Default::default(), &database, &file_system),
3348 config.clone(),
3349 &[],
3350 DEFAULT_OPTIONS,
3351 )
3352 .await
3353 .unwrap();
3354 run(
3355 &create_database_context(&console, &database, &file_system),
3356 config,
3357 &[],
3358 OPTIONS,
3359 )
3360 .await
3361 .unwrap();
3362
3363 assert_eq!(console.stdout(), "");
3364 }
3365
3366 #[tokio::test]
3367 async fn write_description_on_update_of_phony_input() {
3368 let console = FakeConsole::default();
3369 let database = FakeDatabase::default();
3370 let file_system = FakeFileSystem::default();
3371 let config = create_simple_config(
3372 vec![
3373 described_build("foo", &["bar"]),
3374 phony_build("bar", &["baz"]),
3375 ],
3376 &["foo"],
3377 );
3378
3379 file_system.write_file("foo", "");
3380 file_system.write_file("baz", "");
3381
3382 run(
3383 &create_database_context(&Default::default(), &database, &file_system),
3384 config.clone(),
3385 &[],
3386 DEFAULT_OPTIONS,
3387 )
3388 .await
3389 .unwrap();
3390
3391 file_system.write_file("baz", "baz");
3392
3393 run(
3394 &create_database_context(&console, &database, &file_system),
3395 config,
3396 &[],
3397 OPTIONS,
3398 )
3399 .await
3400 .unwrap();
3401
3402 assert_eq!(console.stdout(), "build foo\n");
3403 }
3404
3405 #[tokio::test]
3406 async fn write_description_of_dependent_of_phony_input_without_input() {
3407 let console = FakeConsole::default();
3408 let database = FakeDatabase::default();
3409 let file_system = FakeFileSystem::default();
3410 let config = create_simple_config(
3411 vec![described_build("foo", &["bar"]), phony_build("bar", &[])],
3412 &["foo"],
3413 );
3414
3415 file_system.write_file("foo", "");
3416
3417 run(
3418 &create_database_context(&Default::default(), &database, &file_system),
3419 config.clone(),
3420 &[],
3421 DEFAULT_OPTIONS,
3422 )
3423 .await
3424 .unwrap();
3425 run(
3426 &create_database_context(&console, &database, &file_system),
3427 config,
3428 &[],
3429 OPTIONS,
3430 )
3431 .await
3432 .unwrap();
3433
3434 assert_eq!(console.stdout(), "build foo\n");
3435 }
3436
3437 #[tokio::test]
3438 async fn write_description_of_dependent_of_phony_input_with_stale_input() {
3439 let console = FakeConsole::default();
3440 let database = FakeDatabase::default();
3441 let file_system = FakeFileSystem::default();
3442 let config = create_simple_config(
3443 vec![
3444 described_build("foo", &["bar"]),
3445 phony_build("bar", &["baz"]),
3446 described_build("baz", &["qux"]),
3447 ],
3448 &["foo"],
3449 );
3450
3451 file_system.write_file("foo", "");
3452 file_system.write_file("baz", "");
3453 file_system.write_file("qux", "");
3454
3455 run(
3456 &create_database_context(&Default::default(), &database, &file_system),
3457 config.clone(),
3458 &[],
3459 DEFAULT_OPTIONS,
3460 )
3461 .await
3462 .unwrap();
3463
3464 file_system.write_file("qux", "qux");
3465
3466 run(
3467 &create_database_context(&console, &database, &file_system),
3468 config,
3469 &[],
3470 OPTIONS,
3471 )
3472 .await
3473 .unwrap();
3474
3475 assert_eq!(console.stdout(), "build baz\nbuild foo\n");
3476 }
3477
3478 #[tokio::test]
3479 async fn write_description_of_dependent_of_stale_header_dependency() {
3480 let console = FakeConsole::default();
3481 let database = FakeDatabase::default();
3482 let file_system = FakeFileSystem::default();
3483 let config = create_simple_config(
3484 vec![
3485 explicit_build(
3486 vec!["foo.o".into()],
3487 described_rule("foo.o").with_header_dependency(Some(
3488 HeaderDependency::Make {
3489 path: "foo.d".into(),
3490 },
3491 )),
3492 vec!["foo.c".into()],
3493 ),
3494 described_build("foo.h", &["foo.h.in"]),
3495 ],
3496 &["foo.o", "foo.h"],
3497 );
3498
3499 file_system.write_file("foo.o", "");
3500 file_system.write_file("foo.c", "");
3501 file_system.write_file("foo.d", "foo.o: foo.h\n");
3502 file_system.write_file("foo.h", "");
3503 file_system.write_file("foo.h.in", "");
3504
3505 run(
3506 &create_database_context(&Default::default(), &database, &file_system),
3507 config.clone(),
3508 &[],
3509 DEFAULT_OPTIONS,
3510 )
3511 .await
3512 .unwrap();
3513
3514 file_system.write_file("foo.h.in", "foo");
3515
3516 run(
3517 &create_database_context(&console, &database, &file_system),
3518 config,
3519 &[],
3520 OPTIONS,
3521 )
3522 .await
3523 .unwrap();
3524
3525 assert_eq!(console.stdout(), "build foo.h\nbuild foo.o\n");
3526 }
3527
3528 #[tokio::test]
3529 async fn write_description_of_dependent_of_stale_dynamic_input() {
3530 let console = FakeConsole::default();
3531 let database = FakeDatabase::default();
3532 let file_system = FakeFileSystem::default();
3533 let config = create_simple_config(
3534 vec![
3535 Build::new(
3536 vec!["foo".into()],
3537 vec![],
3538 described_rule("foo").into(),
3539 vec![],
3540 vec!["foo.dd".into()],
3541 Some("foo.dd".into()),
3542 ),
3543 described_build("bar", &["baz"]),
3544 ],
3545 &["foo"],
3546 );
3547
3548 file_system.write_file(
3549 "foo.dd",
3550 "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
3551 );
3552 file_system.write_file("foo", "");
3553 file_system.write_file("bar", "");
3554 file_system.write_file("baz", "");
3555
3556 run(
3557 &create_database_context(&Default::default(), &database, &file_system),
3558 config.clone(),
3559 &[],
3560 DEFAULT_OPTIONS,
3561 )
3562 .await
3563 .unwrap();
3564
3565 file_system.write_file("baz", "baz");
3566
3567 run(
3568 &create_database_context(&console, &database, &file_system),
3569 config,
3570 &[],
3571 OPTIONS,
3572 )
3573 .await
3574 .unwrap();
3575
3576 assert_eq!(console.stdout(), "build bar\nbuild foo\n");
3577 }
3578
3579 #[tokio::test]
3580 async fn write_description_of_dependent_of_stale_dynamic_module() {
3581 let console = FakeConsole::default();
3582 let file_system = FakeFileSystem::default();
3583
3584 run(
3585 &create_context(&Default::default(), &console, &file_system),
3586 create_simple_config(
3587 vec![
3588 Build::new(
3589 vec!["foo".into()],
3590 vec![],
3591 described_rule("foo").into(),
3592 vec![],
3593 vec!["foo.dd".into()],
3594 Some("foo.dd".into()),
3595 ),
3596 described_build("foo.dd", &[]),
3597 ],
3598 &["foo"],
3599 ),
3600 &[],
3601 OPTIONS,
3602 )
3603 .await
3604 .unwrap();
3605
3606 assert_eq!(console.stdout(), "build foo.dd\nbuild foo\n");
3607 assert_eq!(file_system.read_requests(), Vec::<PathBuf>::new());
3608 }
3609
3610 #[tokio::test]
3611 async fn fail_with_missing_input() {
3612 assert_eq!(
3613 run(
3614 &create_context(
3615 &Default::default(),
3616 &Default::default(),
3617 &Default::default()
3618 ),
3619 create_simple_config(vec![described_build("foo", &["bar"])], &["foo"]),
3620 &[],
3621 OPTIONS,
3622 )
3623 .await,
3624 Err(BuildError::FileNotFound("bar".into()))
3625 );
3626 }
3627 }
3628}