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