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