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