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