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