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