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_dependencies},
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, 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::{join, 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_dependencies = 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 dependencies = context.database().get_header_dependencies(build.id())?;
49
50 graph.add_header_dependencies(&build.outputs()[0], &dependencies);
51 header_dependencies.insert(build.id(), dependencies);
52 }
53
54 let context = Arc::new(RunContext::new(
55 context.clone(),
56 config,
57 graph,
58 header_dependencies,
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 let result = 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 context.build().database().flush().await?;
101
102 result.map(|_| ())
103}
104
105#[async_recursion]
106async fn run_build(context: Arc<RunContext>, build: &Arc<Build>) -> Result<(), BuildError> {
107 let future = context
109 .build_futures()
110 .entry_async(build.id())
111 .await
112 .or_insert_with(|| spawn_build(context.clone(), build.clone()).boxed().shared())
113 .get()
114 .clone();
115
116 future.await
117}
118
119async fn spawn_build(context: Arc<RunContext>, build: Arc<Build>) -> Result<(), BuildError> {
120 spawn(async move {
121 let (_, output_metadata) = try_join!(
122 try_join_all(
123 build
124 .inputs()
125 .iter()
126 .chain(build.order_only_inputs())
127 .map(|input| build_input(context.clone(), input)),
128 ),
129 get_output_metadata(&context, &build),
130 )?;
131
132 let dynamic_config = if let Some(dynamic_module) = build.dynamic_module() {
134 let config = compile_dynamic(&parse_dynamic(
135 &context
136 .build()
137 .file_system()
138 .read_file_to_string(dynamic_module.as_ref().as_ref())
139 .await?,
140 )?)?;
141
142 context
143 .build_graph()
144 .lock()
145 .await
146 .validate_dynamic(&config)
147 .map_err(|error| map_build_graph_error(&context, &error))?;
148
149 Some(config)
150 } else {
151 None
152 };
153
154 let dynamic_inputs = if let Some(config) = &dynamic_config {
155 build
156 .outputs()
157 .iter()
158 .chain(build.implicit_outputs())
159 .find_map(|output| config.outputs().get(output.as_ref()))
160 .map(|build| build.inputs())
161 .ok_or_else(|| BuildError::DynamicDependencyNotFound(build.clone()))?
162 } else {
163 &[]
164 };
165
166 try_join_all(
167 dynamic_inputs
168 .iter()
169 .map(|input| build_input(context.clone(), input)),
170 )
171 .await?;
172
173 if build.rule().is_none() {
174 invalidate_outputs(&context, &build).await;
177 }
178
179 let dependencies = context.header_dependencies(build.id());
180
181 try_join_all(
182 dependencies
183 .iter()
184 .filter_map(|dependency| context.config().outputs().get(dependency.as_str()))
185 .map(|build| run_build(context.clone(), build)),
186 )
187 .await?;
188
189 let header_dependencies =
190 filter_existing_header_dependencies(&context, dependencies).await?;
191
192 let (phony_inputs, file_inputs) =
193 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
194 let mut timestamp_hash =
195 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
196
197 if let Some(metadata) = &output_metadata
198 && Some(timestamp_hash)
199 == context
200 .build()
201 .database()
202 .get_hash(HashType::Timestamp, build.id())?
203 {
204 cache_output_metadata(&context, &build, metadata).await;
205
206 return Ok(());
207 }
208
209 let mut content_hash =
210 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
211
212 if output_metadata.is_some()
213 && Some(content_hash)
214 == context
215 .build()
216 .database()
217 .get_hash(HashType::Content, build.id())?
218 {
219 return Ok(());
220 } else if let Some(rule) = build.rule() {
221 try_join_all(
222 build
223 .outputs()
224 .iter()
225 .chain(build.implicit_outputs())
226 .map(|path| prepare_directory(&context, path.as_ref())),
227 )
228 .await?;
229
230 let output = run_rule(&context, rule).await;
231
232 invalidate_outputs(&context, &build).await;
233
234 let new_header_dependencies =
235 read_header_dependencies(&context, rule, &output?).await?;
236
237 context
238 .build()
239 .database()
240 .set_header_dependencies(build.id(), &new_header_dependencies)?;
241
242 for output in build.outputs() {
243 context.build().database().set_output(output)?;
244
245 if let Some(source) = context.config().source_map().get(output) {
246 context.build().database().set_source(output, source)?;
247 }
248 }
249
250 if header_dependencies != new_header_dependencies {
251 let header_dependencies =
252 filter_existing_header_dependencies(&context, &new_header_dependencies).await?;
253 let (phony_inputs, file_inputs) =
254 classify_inputs(&context, &build, dynamic_inputs, &header_dependencies);
255
256 timestamp_hash =
257 calculate_timestamp_hash(&context, &build, &file_inputs, &phony_inputs).await?;
258 content_hash =
259 calculate_content_hash(&context, &build, &file_inputs, &phony_inputs).await?;
260 }
261 }
262
263 context
264 .build()
265 .database()
266 .set_hash(HashType::Timestamp, build.id(), timestamp_hash)?;
267 context
268 .build()
269 .database()
270 .set_hash(HashType::Content, build.id(), content_hash)?;
271
272 Ok(())
273 })
274 .await?
275}
276
277async fn build_input(context: Arc<RunContext>, input: &str) -> Result<(), BuildError> {
278 if let Some(build) = context.config().outputs().get(input) {
279 run_build(context.clone(), build).await
280 } else {
281 check_file_existence(&context, input).await
282 }
283}
284
285async fn get_output_metadata(
286 context: &RunContext,
287 build: &Build,
288) -> Result<Option<Vec<Metadata>>, BuildError> {
289 Ok(try_join_all(
290 build
291 .outputs()
292 .iter()
293 .chain(build.implicit_outputs())
294 .map(|path| {
295 context
296 .build()
297 .file_system()
298 .metadata(path.as_ref().as_ref())
299 }),
300 )
301 .await?
302 .into_iter()
303 .collect())
304}
305
306async fn cache_output_metadata(context: &RunContext, build: &Build, metadata: &[Metadata]) {
307 if build.rule().is_none() {
309 return;
310 }
311
312 for (path, &metadata) in build
313 .outputs()
314 .iter()
315 .chain(build.implicit_outputs())
316 .zip(metadata)
317 {
318 context
319 .file_cache()
320 .set_metadata(path.as_ref().as_ref(), metadata)
321 .await;
322 }
323}
324
325async fn filter_existing_header_dependencies<'a>(
326 context: &RunContext,
327 dependencies: &'a [String],
328) -> Result<Vec<&'a str>, BuildError> {
329 let mut existing_dependencies = vec![];
330
331 for dependency in dependencies {
332 if context.file_cache().exists(dependency.as_ref()).await? {
333 existing_dependencies.push(dependency.as_str());
334 }
335 }
336
337 Ok(existing_dependencies)
338}
339
340async fn check_file_existence(context: &RunContext, path: &str) -> Result<(), BuildError> {
341 if !context.file_cache().exists(path.as_ref()).await? {
342 return Err(BuildError::FileNotFound(
343 context
344 .build()
345 .database()
346 .get_source(path)?
347 .unwrap_or_else(|| path.into()),
348 ));
349 }
350
351 Ok(())
352}
353
354async fn prepare_directory(context: &RunContext, path: impl AsRef<Path>) -> Result<(), BuildError> {
355 if let Some(directory) = path.as_ref().parent() {
356 context
357 .build()
358 .file_system()
359 .create_directory(directory)
360 .await?;
361 }
362
363 Ok(())
364}
365
366async fn invalidate_outputs(context: &RunContext, build: &Build) {
367 for output in build.outputs().iter().chain(build.implicit_outputs()) {
368 context
369 .file_cache()
370 .invalidate(output.as_ref().as_ref())
371 .await;
372 }
373}
374
375fn classify_inputs<'a>(
376 context: &'a RunContext,
377 build: &'a Build,
378 dynamic_inputs: &'a [Arc<str>],
379 header_dependencies: &'a [&'a str],
380) -> (Vec<&'a str>, Vec<&'a str>) {
381 let (phony_inputs, file_inputs) = build
382 .inputs()
383 .iter()
384 .chain(dynamic_inputs)
385 .map(AsRef::as_ref)
386 .unique()
387 .partition::<Vec<_>, _>(|&input| {
388 context
389 .config()
390 .outputs()
391 .get(input)
392 .map_or_default(|build| build.rule().is_none())
393 });
394
395 (
396 phony_inputs,
397 file_inputs
398 .into_iter()
399 .chain(header_dependencies.iter().copied())
400 .unique()
401 .collect(),
402 )
403}
404
405async fn run_rule(context: &RunContext, rule: &Rule) -> Result<Output, BuildError> {
406 let ((output, duration), mut console) = if rule.pool() == Some(&Pool::Console) {
407 let mut console = write_description(context, rule).await?;
408
409 console.flush().await?;
410
411 let time = Instant::now();
412 let status = context
413 .build()
414 .command_runner()
415 .run_with_console(rule.command())
416 .await?;
417
418 (
419 (
420 Output {
421 status,
422 stdout: vec![],
423 stderr: vec![],
424 },
425 Instant::now() - time,
426 ),
427 console,
428 )
429 } else {
430 let permit = context.pool(rule.pool()).await?;
431 let (output, console) = join!(
432 async {
433 let time = Instant::now();
434 let output = context.build().command_runner().run(rule.command()).await?;
435
436 drop(permit);
437
438 Ok::<_, BuildError>((output, Instant::now() - time))
439 },
440 write_description(context, rule)
441 );
442
443 (output?, console?)
444 };
445
446 profile!(context, console, "duration: {} ms", duration.as_millis());
447
448 console
449 .write_stdout(&exclude_show_includes(rule, &output.stdout))
450 .await?;
451 console.write_stderr(&output.stderr).await?;
452
453 if !output.status.success() {
454 debug!(
455 context,
456 console,
457 "exit status: {}",
458 output
459 .status
460 .code()
461 .map(|code| code.to_string())
462 .unwrap_or_else(|| "-".into())
463 );
464
465 return Err(BuildError::Build);
466 }
467
468 Ok(output)
469}
470
471async fn write_description<'a>(
472 context: &'a RunContext,
473 rule: &Rule,
474) -> Result<MutexGuard<'a, dyn Console + Send + Sync>, BuildError> {
475 let mut console = context.build().console().lock().await;
476
477 if let Some(description) = rule.description() {
478 console.write_stderr(description.as_bytes()).await?;
479 console.write_stderr(b"\n").await?;
480 }
481
482 debug!(context, console, "command: {}", rule.command());
483
484 Ok(console)
485}
486
487fn map_build_graph_error(context: &RunContext, error: &BuildGraphError) -> BuildError {
488 match error {
489 BuildGraphError::CircularDependency(outputs) => {
490 match outputs
491 .iter()
492 .map(|output| {
493 Ok(context
494 .build()
495 .database()
496 .get_source(output)?
497 .map(|string| string.into())
498 .unwrap_or_else(|| output.clone()))
499 })
500 .collect::<Result<Vec<_>, BuildError>>()
501 {
502 Ok(outputs) => {
503 BuildGraphError::CircularDependency(outputs.into_iter().dedup().collect())
504 .into()
505 }
506 Err(error) => error,
507 }
508 }
509 BuildGraphError::OutputNotFound(_) => error.clone().into(),
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 use crate::{
517 infrastructure::{FakeCommandRunner, FakeConsole, FakeDatabase, FakeFileSystem, FileError},
518 ir::HeaderDependency,
519 };
520 use core::{num::NonZeroUsize, pin::pin};
521 use futures::poll;
522 use pretty_assertions::{assert_eq, assert_ne};
523 use regex::Regex;
524 #[cfg(unix)]
525 use std::os::unix::process::ExitStatusExt;
526 #[cfg(windows)]
527 use std::os::windows::process::ExitStatusExt;
528 use std::{collections::HashMap, process::ExitStatus};
529 use tokio::{sync::Mutex, task::yield_now};
530
531 const DEFAULT_OPTIONS: RunOptions = RunOptions {
532 debug: false,
533 profile: false,
534 };
535 const POLL_COUNT: usize = 8;
536
537 fn create_context(
538 command_runner: &FakeCommandRunner,
539 console: &FakeConsole,
540 file_system: &FakeFileSystem,
541 ) -> Arc<Context> {
542 Context::new(
543 command_runner.clone(),
544 Mutex::new(console.clone()).into(),
545 FakeDatabase::default(),
546 file_system.clone(),
547 )
548 .into()
549 }
550
551 fn create_outputs(builds: Vec<Build>) -> HashMap<Arc<str>, Arc<Build>> {
552 builds
553 .into_iter()
554 .map(Arc::new)
555 .flat_map(|build| {
556 build
557 .outputs()
558 .iter()
559 .chain(build.implicit_outputs())
560 .map(|output| (output.clone(), build.clone()))
561 .collect::<Vec<_>>()
562 })
563 .collect()
564 }
565
566 fn create_simple_config(builds: Vec<Build>, default_outputs: &[&str]) -> Arc<Config> {
567 create_pool_config(builds, default_outputs, &[])
568 }
569
570 fn create_pool_config(
571 builds: Vec<Build>,
572 default_outputs: &[&str],
573 pools: &[(&str, usize)],
574 ) -> Arc<Config> {
575 Config::new(
576 create_outputs(builds),
577 default_outputs
578 .iter()
579 .map(|&output| output.into())
580 .collect(),
581 Default::default(),
582 pools
583 .iter()
584 .map(|&(name, depth)| (name.into(), NonZeroUsize::new(depth).unwrap()))
585 .collect(),
586 None,
587 )
588 .into()
589 }
590
591 fn explicit_build(outputs: Vec<Arc<str>>, rule: Rule, inputs: Vec<Arc<str>>) -> Build {
592 Build::new(outputs, vec![], rule.into(), inputs, vec![], None)
593 }
594
595 fn failed_output() -> Output {
596 Output {
597 status: ExitStatus::from_raw(cfg_select! {
598 unix => 1 << 8,
599 windows => 1,
600 }),
601 stdout: vec![],
602 stderr: vec![],
603 }
604 }
605
606 fn limited_pool(name: &str) -> Option<Pool> {
607 Some(Pool::Limited(name.into()))
608 }
609
610 fn pool_build(output: &str, pool: Option<Pool>) -> Build {
611 explicit_build(
612 vec![output.into()],
613 Rule::new(format!("touch {output}"), None).with_pool(pool),
614 vec![],
615 )
616 }
617
618 fn count_metadata_requests(file_system: &FakeFileSystem, path: &str) -> usize {
619 file_system
620 .metadata_requests()
621 .iter()
622 .filter(|request| request.as_path() == Path::new(path))
623 .count()
624 }
625
626 fn create_run_context(
627 command_runner: &FakeCommandRunner,
628 console: &FakeConsole,
629 pools: &[(&str, usize)],
630 ) -> RunContext {
631 RunContext::new(
632 create_context(command_runner, console, &Default::default()),
633 create_pool_config(vec![], &[], pools),
634 BuildGraph::new(&Default::default()),
635 Default::default(),
636 DEFAULT_OPTIONS,
637 )
638 }
639
640 fn create_build_run_context(
641 command_runner: &FakeCommandRunner,
642 file_system: &FakeFileSystem,
643 builds: Vec<Build>,
644 ) -> Arc<RunContext> {
645 RunContext::new(
646 create_context(command_runner, &Default::default(), file_system),
647 create_simple_config(builds, &[]),
648 BuildGraph::new(&Default::default()),
649 Default::default(),
650 DEFAULT_OPTIONS,
651 )
652 .into()
653 }
654
655 #[tokio::test]
656 async fn build_nothing() {
657 let command_runner = FakeCommandRunner::default();
658
659 run(
660 &create_context(&command_runner, &Default::default(), &Default::default()),
661 create_simple_config(vec![], &[]),
662 &[],
663 DEFAULT_OPTIONS,
664 )
665 .await
666 .unwrap();
667
668 assert_eq!(command_runner.commands(), Vec::<String>::new());
669 }
670
671 #[tokio::test]
672 async fn build_default_output() {
673 let command_runner = FakeCommandRunner::default();
674
675 run(
676 &create_context(&command_runner, &Default::default(), &Default::default()),
677 create_simple_config(
678 vec![
679 explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]),
680 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
681 ],
682 &["foo"],
683 ),
684 &[],
685 DEFAULT_OPTIONS,
686 )
687 .await
688 .unwrap();
689
690 assert_eq!(command_runner.commands(), ["touch foo"]);
691 }
692
693 #[tokio::test]
694 async fn build_specified_output() {
695 let command_runner = FakeCommandRunner::default();
696
697 run(
698 &create_context(&command_runner, &Default::default(), &Default::default()),
699 create_simple_config(
700 vec![
701 explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]),
702 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
703 ],
704 &["foo"],
705 ),
706 &["bar".into()],
707 DEFAULT_OPTIONS,
708 )
709 .await
710 .unwrap();
711
712 assert_eq!(command_runner.commands(), ["touch bar"]);
713 }
714
715 #[tokio::test]
716 async fn build_duplicate_outputs() {
717 let command_runner = FakeCommandRunner::default();
718
719 run(
720 &create_context(&command_runner, &Default::default(), &Default::default()),
721 create_simple_config(
722 vec![explicit_build(
723 vec!["foo".into()],
724 Rule::new("touch foo", None),
725 vec![],
726 )],
727 &[],
728 ),
729 &["foo".into(), "foo".into()],
730 DEFAULT_OPTIONS,
731 )
732 .await
733 .unwrap();
734
735 assert_eq!(command_runner.commands(), ["touch foo"]);
736 }
737
738 #[tokio::test]
739 async fn fail_with_unknown_default_output() {
740 assert_eq!(
741 run(
742 &create_context(
743 &Default::default(),
744 &Default::default(),
745 &Default::default()
746 ),
747 create_simple_config(vec![], &["foo"]),
748 &[],
749 DEFAULT_OPTIONS,
750 )
751 .await,
752 Err(BuildError::DefaultOutputNotFound("foo".into()))
753 );
754 }
755
756 #[tokio::test]
757 async fn fail_with_unknown_output() {
758 assert_eq!(
759 run(
760 &create_context(
761 &Default::default(),
762 &Default::default(),
763 &Default::default()
764 ),
765 create_simple_config(vec![], &[]),
766 &["foo".into()],
767 DEFAULT_OPTIONS,
768 )
769 .await,
770 Err(BuildError::OutputNotFound("foo".into()))
771 );
772 }
773
774 #[tokio::test]
775 async fn fail_with_unknown_output_after_known_output() {
776 let command_runner = FakeCommandRunner::default();
777
778 assert_eq!(
779 run(
780 &create_context(&command_runner, &Default::default(), &Default::default()),
781 create_simple_config(
782 vec![explicit_build(
783 vec!["foo".into()],
784 Rule::new("touch foo", None),
785 vec![],
786 )],
787 &[],
788 ),
789 &["foo".into(), "bar".into()],
790 DEFAULT_OPTIONS,
791 )
792 .await,
793 Err(BuildError::OutputNotFound("bar".into()))
794 );
795
796 yield_now().await;
798
799 assert_eq!(command_runner.commands(), Vec::<String>::new());
800 }
801
802 #[tokio::test]
803 async fn build_shared_input_once() {
804 let command_runner = FakeCommandRunner::default();
805 let file_system = FakeFileSystem::default();
806
807 file_system.write_file("bar", "");
808 file_system.write_file("baz", "");
809
810 run(
811 &create_context(&command_runner, &Default::default(), &file_system),
812 create_simple_config(
813 vec![
814 explicit_build(
815 vec!["foo".into()],
816 Rule::new("touch foo", None),
817 vec!["bar".into(), "baz".into()],
818 ),
819 explicit_build(
820 vec!["bar".into()],
821 Rule::new("touch bar", None),
822 vec!["baz".into()],
823 ),
824 explicit_build(vec!["baz".into()], Rule::new("touch baz", None), vec![]),
825 ],
826 &["foo"],
827 ),
828 &[],
829 DEFAULT_OPTIONS,
830 )
831 .await
832 .unwrap();
833
834 assert_eq!(
835 command_runner.commands(),
836 ["touch baz", "touch bar", "touch foo"]
837 );
838 }
839
840 #[tokio::test]
841 async fn build_order_only_input() {
842 let command_runner = FakeCommandRunner::default();
843
844 run(
845 &create_context(&command_runner, &Default::default(), &Default::default()),
846 create_simple_config(
847 vec![
848 Build::new(
849 vec!["foo".into()],
850 vec![],
851 Rule::new("touch foo", None).into(),
852 vec![],
853 vec!["bar".into()],
854 None,
855 ),
856 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
857 ],
858 &["foo"],
859 ),
860 &[],
861 DEFAULT_OPTIONS,
862 )
863 .await
864 .unwrap();
865
866 assert_eq!(command_runner.commands(), ["touch bar", "touch foo"]);
867 }
868
869 #[tokio::test]
870 async fn build_multiple_outputs_of_build() {
871 let command_runner = FakeCommandRunner::default();
872 let file_system = FakeFileSystem::default();
873
874 file_system.write_file("foo", "");
875 file_system.write_file("bar", "");
876
877 run(
878 &create_context(&command_runner, &Default::default(), &file_system),
879 create_simple_config(
880 vec![
881 explicit_build(
882 vec!["foo".into(), "bar".into()],
883 Rule::new("touch foo bar", None),
884 vec![],
885 ),
886 explicit_build(
887 vec!["baz".into()],
888 Rule::new("cat foo bar > baz", None),
889 vec!["foo".into(), "bar".into()],
890 ),
891 ],
892 &["baz"],
893 ),
894 &[],
895 DEFAULT_OPTIONS,
896 )
897 .await
898 .unwrap();
899
900 assert_eq!(
901 command_runner.commands(),
902 ["touch foo bar", "cat foo bar > baz"]
903 );
904 }
905
906 #[tokio::test]
907 async fn fail_with_missing_input() {
908 let command_runner = FakeCommandRunner::default();
909
910 assert_eq!(
911 run(
912 &create_context(&command_runner, &Default::default(), &Default::default()),
913 create_simple_config(
914 vec![explicit_build(
915 vec!["foo".into()],
916 Rule::new("cp bar foo", None),
917 vec!["bar".into()],
918 )],
919 &["foo"],
920 ),
921 &[],
922 DEFAULT_OPTIONS,
923 )
924 .await,
925 Err(BuildError::FileNotFound("bar".into()))
926 );
927 assert_eq!(command_runner.commands(), Vec::<String>::new());
928 }
929
930 #[tokio::test]
931 async fn fail_with_missing_order_only_input() {
932 assert_eq!(
933 run(
934 &create_context(
935 &Default::default(),
936 &Default::default(),
937 &Default::default()
938 ),
939 create_simple_config(
940 vec![Build::new(
941 vec!["foo".into()],
942 vec![],
943 Rule::new("touch foo", None).into(),
944 vec![],
945 vec!["bar".into()],
946 None,
947 )],
948 &["foo"],
949 ),
950 &[],
951 DEFAULT_OPTIONS,
952 )
953 .await,
954 Err(BuildError::FileNotFound("bar".into()))
955 );
956 }
957
958 #[tokio::test]
959 async fn report_source_of_missing_input() {
960 let context = create_context(
961 &Default::default(),
962 &Default::default(),
963 &Default::default(),
964 );
965
966 context.database().set_source("foo.o", "foo.c").unwrap();
967
968 assert_eq!(
969 run(
970 &context,
971 create_simple_config(
972 vec![explicit_build(
973 vec!["foo".into()],
974 Rule::new("cc foo.o", None),
975 vec!["foo.o".into()],
976 )],
977 &["foo"],
978 ),
979 &[],
980 DEFAULT_OPTIONS,
981 )
982 .await,
983 Err(BuildError::FileNotFound("foo.c".into()))
984 );
985 }
986
987 #[tokio::test]
988 async fn detect_circular_dependency() {
989 assert_eq!(
990 run(
991 &create_context(
992 &Default::default(),
993 &Default::default(),
994 &Default::default()
995 ),
996 create_simple_config(
997 vec![explicit_build(
998 vec!["foo".into()],
999 Rule::new("cp foo foo", None),
1000 vec!["foo".into()],
1001 )],
1002 &["foo"],
1003 ),
1004 &[],
1005 DEFAULT_OPTIONS,
1006 )
1007 .await,
1008 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1009 vec!["foo".into()]
1010 )))
1011 );
1012 }
1013
1014 #[tokio::test]
1015 async fn report_sources_in_circular_dependency() {
1016 let context = create_context(
1017 &Default::default(),
1018 &Default::default(),
1019 &Default::default(),
1020 );
1021
1022 context.database().set_source("foo", "baz").unwrap();
1023 context.database().set_source("bar", "baz").unwrap();
1024
1025 assert_eq!(
1026 run(
1027 &context,
1028 create_simple_config(
1029 vec![
1030 explicit_build(
1031 vec!["foo".into()],
1032 Rule::new("cp bar foo", None),
1033 vec!["bar".into()],
1034 ),
1035 explicit_build(
1036 vec!["bar".into()],
1037 Rule::new("cp foo bar", None),
1038 vec!["foo".into()],
1039 ),
1040 ],
1041 &["foo"],
1042 ),
1043 &[],
1044 DEFAULT_OPTIONS,
1045 )
1046 .await,
1047 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
1048 vec!["baz".into()]
1049 )))
1050 );
1051 }
1052
1053 #[tokio::test]
1054 async fn prepare_output_directory() {
1055 let context = create_context(
1056 &Default::default(),
1057 &Default::default(),
1058 &Default::default(),
1059 );
1060
1061 run(
1062 &context,
1063 create_simple_config(
1064 vec![explicit_build(
1065 vec!["foo/bar".into()],
1066 Rule::new("touch foo/bar", None),
1067 vec![],
1068 )],
1069 &["foo/bar"],
1070 ),
1071 &[],
1072 DEFAULT_OPTIONS,
1073 )
1074 .await
1075 .unwrap();
1076
1077 assert!(context.file_system().exists("foo".as_ref()).await.unwrap());
1078 }
1079
1080 #[tokio::test]
1081 async fn record_output_with_source() {
1082 let file_system = FakeFileSystem::default();
1083 let context = create_context(&Default::default(), &Default::default(), &file_system);
1084
1085 file_system.write_file("foo.c", "");
1086
1087 run(
1088 &context,
1089 Config::new(
1090 create_outputs(vec![explicit_build(
1091 vec!["foo.o".into()],
1092 Rule::new("cc foo.c", None),
1093 vec!["foo.c".into()],
1094 )]),
1095 ["foo.o".into()].into_iter().collect(),
1096 [("foo.o".into(), "foo.c".into())].into_iter().collect(),
1097 Default::default(),
1098 None,
1099 )
1100 .into(),
1101 &[],
1102 DEFAULT_OPTIONS,
1103 )
1104 .await
1105 .unwrap();
1106
1107 assert_eq!(context.database().get_outputs().unwrap(), ["foo.o"]);
1108 assert_eq!(
1109 context.database().get_source("foo.o").unwrap(),
1110 Some("foo.c".into())
1111 );
1112 }
1113
1114 #[tokio::test]
1115 async fn skip_up_to_date_build() {
1116 let command_runner = FakeCommandRunner::default();
1117 let file_system = FakeFileSystem::default();
1118 let context = create_context(&command_runner, &Default::default(), &file_system);
1119 let config = create_simple_config(
1120 vec![explicit_build(
1121 vec!["foo".into()],
1122 Rule::new("cp bar foo", None),
1123 vec!["bar".into()],
1124 )],
1125 &["foo"],
1126 );
1127
1128 file_system.write_file("foo", "");
1129 file_system.write_file("bar", "");
1130
1131 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1132 .await
1133 .unwrap();
1134 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1135
1136 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1137 }
1138
1139 #[tokio::test]
1140 async fn skip_build_on_timestamp_update_of_input() {
1141 let command_runner = FakeCommandRunner::default();
1142 let file_system = FakeFileSystem::default();
1143 let context = create_context(&command_runner, &Default::default(), &file_system);
1144 let config = create_simple_config(
1145 vec![explicit_build(
1146 vec!["foo".into()],
1147 Rule::new("cp bar foo", None),
1148 vec!["bar".into()],
1149 )],
1150 &["foo"],
1151 );
1152
1153 file_system.write_file("foo", "");
1154 file_system.write_file("bar", "");
1155
1156 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1157 .await
1158 .unwrap();
1159
1160 file_system.write_file("bar", "");
1161
1162 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1163
1164 assert_eq!(command_runner.commands(), ["cp bar foo"]);
1165 }
1166
1167 #[tokio::test]
1168 async fn rebuild_on_content_update_of_input() {
1169 let command_runner = FakeCommandRunner::default();
1170 let file_system = FakeFileSystem::default();
1171 let context = create_context(&command_runner, &Default::default(), &file_system);
1172 let config = create_simple_config(
1173 vec![explicit_build(
1174 vec!["foo".into()],
1175 Rule::new("cp bar foo", None),
1176 vec!["bar".into()],
1177 )],
1178 &["foo"],
1179 );
1180
1181 file_system.write_file("foo", "");
1182 file_system.write_file("bar", "");
1183
1184 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1185 .await
1186 .unwrap();
1187
1188 file_system.write_file("bar", "bar");
1189
1190 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1191
1192 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1193 }
1194
1195 #[tokio::test]
1196 async fn rebuild_on_update_of_phony_input() {
1197 let command_runner = FakeCommandRunner::default();
1198 let file_system = FakeFileSystem::default();
1199 let context = create_context(&command_runner, &Default::default(), &file_system);
1200 let config = create_simple_config(
1201 vec![
1202 explicit_build(
1203 vec!["foo".into()],
1204 Rule::new("cp bar foo", None),
1205 vec!["bar".into()],
1206 ),
1207 Build::new(
1208 vec!["bar".into()],
1209 vec![],
1210 None,
1211 vec!["baz".into()],
1212 vec![],
1213 None,
1214 ),
1215 ],
1216 &["foo"],
1217 );
1218
1219 file_system.write_file("foo", "");
1220 file_system.write_file("baz", "");
1221
1222 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1223 .await
1224 .unwrap();
1225
1226 file_system.write_file("baz", "baz");
1227
1228 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1229
1230 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1231 }
1232
1233 #[tokio::test]
1234 async fn rebuild_missing_output() {
1235 let command_runner = FakeCommandRunner::default();
1236 let file_system = FakeFileSystem::default();
1237 let context = create_context(&command_runner, &Default::default(), &file_system);
1238 let config = create_simple_config(
1239 vec![explicit_build(
1240 vec!["foo".into()],
1241 Rule::new("cp bar foo", None),
1242 vec!["bar".into()],
1243 )],
1244 &["foo"],
1245 );
1246
1247 file_system.write_file("bar", "");
1248
1249 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1250 .await
1251 .unwrap();
1252 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1253
1254 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1255 }
1256
1257 #[tokio::test]
1258 async fn rebuild_missing_implicit_output() {
1259 let command_runner = FakeCommandRunner::default();
1260 let file_system = FakeFileSystem::default();
1261 let context = create_context(&command_runner, &Default::default(), &file_system);
1262 let config = create_simple_config(
1263 vec![Build::new(
1264 vec!["foo".into()],
1265 vec!["baz".into()],
1266 Rule::new("cp bar foo", None).into(),
1267 vec!["bar".into()],
1268 vec![],
1269 None,
1270 )],
1271 &["foo"],
1272 );
1273
1274 file_system.write_file("foo", "");
1275 file_system.write_file("bar", "");
1276
1277 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1278 .await
1279 .unwrap();
1280 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1281
1282 assert_eq!(command_runner.commands(), ["cp bar foo", "cp bar foo"]);
1283 }
1284
1285 #[tokio::test]
1286 async fn rerun_failed_build() {
1287 let command_runner =
1288 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
1289 let file_system = FakeFileSystem::default();
1290 let context = create_context(&command_runner, &Default::default(), &file_system);
1291 let config = create_simple_config(
1292 vec![explicit_build(
1293 vec!["foo".into()],
1294 Rule::new("exit 1", None),
1295 vec![],
1296 )],
1297 &["foo"],
1298 );
1299
1300 file_system.write_file("foo", "");
1301
1302 assert_eq!(
1303 run(&context, config.clone(), &[], DEFAULT_OPTIONS).await,
1304 Err(BuildError::Build)
1305 );
1306 assert_eq!(
1307 run(&context, config, &[], DEFAULT_OPTIONS).await,
1308 Err(BuildError::Build)
1309 );
1310 assert_eq!(command_runner.commands(), ["exit 1", "exit 1"]);
1311 }
1312
1313 #[tokio::test]
1314 async fn invalidate_file_cache_of_outputs_after_command() {
1315 let command_runner = FakeCommandRunner::default();
1316 let file_system = FakeFileSystem::default();
1317 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1318 let build = Build::new(
1319 vec!["foo".into()],
1320 vec!["bar".into()],
1321 Rule::new("touch foo bar", None).into(),
1322 vec![],
1323 vec![],
1324 None,
1325 )
1326 .into();
1327 let mut future = pin!(run_build(context.clone(), &build));
1328
1329 file_system.write_file("foo", "1");
1330 file_system.write_file("bar", "1");
1331
1332 assert!(poll!(&mut future).is_pending());
1333
1334 yield_now().await;
1336
1337 assert_eq!(command_runner.commands(), ["touch foo bar"]);
1338
1339 let foo_hash = context.file_cache().content_hash("foo".as_ref()).await;
1340 let bar_hash = context.file_cache().content_hash("bar".as_ref()).await;
1341
1342 file_system.write_file("foo", "2");
1343 file_system.write_file("bar", "2");
1344 future.await.unwrap();
1345
1346 assert_ne!(
1347 context.file_cache().content_hash("foo".as_ref()).await,
1348 foo_hash
1349 );
1350 assert_ne!(
1351 context.file_cache().content_hash("bar".as_ref()).await,
1352 bar_hash
1353 );
1354 }
1355
1356 #[tokio::test]
1357 async fn invalidate_file_cache_of_output_after_failed_command() {
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_build_run_context(&command_runner, &file_system, vec![]);
1362 let build = explicit_build(vec!["foo".into()], Rule::new("exit 1", None), vec![]).into();
1363 let mut future = pin!(run_build(context.clone(), &build));
1364
1365 file_system.write_file("foo", "1");
1366
1367 assert!(poll!(&mut future).is_pending());
1368
1369 yield_now().await;
1371
1372 assert_eq!(command_runner.commands(), ["exit 1"]);
1373
1374 let hash = context.file_cache().content_hash("foo".as_ref()).await;
1375
1376 file_system.write_file("foo", "2");
1377
1378 assert_eq!(future.await, Err(BuildError::Build));
1379 assert_ne!(
1380 context.file_cache().content_hash("foo".as_ref()).await,
1381 hash
1382 );
1383 }
1384
1385 #[tokio::test]
1386 async fn invalidate_file_cache_of_phony_output_after_inputs() {
1387 let command_runner = FakeCommandRunner::default();
1388 let file_system = FakeFileSystem::default();
1389 let build = Build::new(
1390 vec!["foo".into()],
1391 vec![],
1392 None,
1393 vec!["bar".into()],
1394 vec![],
1395 None,
1396 );
1397 let context = create_build_run_context(
1398 &command_runner,
1399 &file_system,
1400 vec![
1401 build.clone(),
1402 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
1403 ],
1404 );
1405 let build = build.into();
1406 let mut future = pin!(run_build(context.clone(), &build));
1407
1408 file_system.write_file("foo", "1");
1409 file_system.write_file("bar", "");
1410
1411 assert!(poll!(&mut future).is_pending());
1412
1413 yield_now().await;
1415
1416 assert_eq!(command_runner.commands(), ["touch bar"]);
1417
1418 let hash = context.file_cache().content_hash("foo".as_ref()).await;
1419
1420 file_system.write_file("foo", "2");
1421 future.await.unwrap();
1422
1423 assert_ne!(
1424 context.file_cache().content_hash("foo".as_ref()).await,
1425 hash
1426 );
1427 }
1428
1429 #[tokio::test]
1430 async fn keep_file_cache_of_other_file_after_command() {
1431 let command_runner = FakeCommandRunner::default();
1432 let file_system = FakeFileSystem::default();
1433 let context = create_build_run_context(&command_runner, &file_system, vec![]);
1434
1435 file_system.write_file("bar", "1");
1436
1437 let hash = context.file_cache().content_hash("bar".as_ref()).await;
1438
1439 file_system.write_file("bar", "2");
1440 run_build(
1441 context.clone(),
1442 &explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]).into(),
1443 )
1444 .await
1445 .unwrap();
1446
1447 assert_eq!(command_runner.commands(), ["touch foo"]);
1448 assert_eq!(
1449 context.file_cache().content_hash("bar".as_ref()).await,
1450 hash
1451 );
1452 }
1453
1454 #[tokio::test]
1455 async fn query_output_while_building_input() {
1456 let file_system = FakeFileSystem::default();
1457
1458 file_system.write_file("foo", "");
1459 file_system.write_file("bar", "");
1460 file_system.write_file("baz", "");
1461
1462 run(
1463 &create_context(&Default::default(), &Default::default(), &file_system),
1464 create_simple_config(
1465 vec![
1466 explicit_build(
1467 vec!["foo".into()],
1468 Rule::new("cp bar foo", None),
1469 vec!["bar".into()],
1470 ),
1471 explicit_build(
1472 vec!["bar".into()],
1473 Rule::new("cp baz bar", None),
1474 vec!["baz".into()],
1475 ),
1476 ],
1477 &["foo"],
1478 ),
1479 &[],
1480 DEFAULT_OPTIONS,
1481 )
1482 .await
1483 .unwrap();
1484
1485 assert_eq!(file_system.metadata_requests().first(), Some(&"foo".into()));
1486 }
1487
1488 #[tokio::test]
1489 async fn query_output_once_for_up_to_date_build() {
1490 let file_system = FakeFileSystem::default();
1491 let context = create_context(&Default::default(), &Default::default(), &file_system);
1492 let config = create_simple_config(
1493 vec![
1494 explicit_build(
1495 vec!["foo".into()],
1496 Rule::new("cp bar foo", None),
1497 vec!["bar".into()],
1498 ),
1499 explicit_build(
1500 vec!["bar".into()],
1501 Rule::new("cp baz bar", None),
1502 vec!["baz".into()],
1503 ),
1504 ],
1505 &["foo"],
1506 );
1507
1508 file_system.write_file("foo", "");
1509 file_system.write_file("bar", "");
1510 file_system.write_file("baz", "");
1511
1512 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1513 .await
1514 .unwrap();
1515
1516 let count = count_metadata_requests(&file_system, "bar");
1517
1518 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1519
1520 assert_eq!(count_metadata_requests(&file_system, "bar"), count + 1);
1521 }
1522
1523 #[tokio::test]
1524 async fn query_implicit_output_once_for_up_to_date_build() {
1525 let file_system = FakeFileSystem::default();
1526 let context = create_context(&Default::default(), &Default::default(), &file_system);
1527 let config = create_simple_config(
1528 vec![
1529 explicit_build(
1530 vec!["foo".into()],
1531 Rule::new("cp qux foo", None),
1532 vec!["qux".into()],
1533 ),
1534 Build::new(
1535 vec!["bar".into()],
1536 vec!["qux".into()],
1537 Rule::new("cp baz bar", None).into(),
1538 vec!["baz".into()],
1539 vec![],
1540 None,
1541 ),
1542 ],
1543 &["foo"],
1544 );
1545
1546 file_system.write_file("foo", "");
1547 file_system.write_file("bar", "");
1548 file_system.write_file("baz", "");
1549 file_system.write_file("qux", "");
1550
1551 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1552 .await
1553 .unwrap();
1554
1555 let count = count_metadata_requests(&file_system, "qux");
1556
1557 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1558
1559 assert_eq!(count_metadata_requests(&file_system, "qux"), count + 1);
1560 }
1561
1562 #[tokio::test]
1563 async fn query_output_again_on_timestamp_update_of_input() {
1564 let file_system = FakeFileSystem::default();
1565 let context = create_context(&Default::default(), &Default::default(), &file_system);
1566 let config = create_simple_config(
1567 vec![
1568 explicit_build(
1569 vec!["foo".into()],
1570 Rule::new("cp bar foo", None),
1571 vec!["bar".into()],
1572 ),
1573 explicit_build(
1574 vec!["bar".into()],
1575 Rule::new("cp baz bar", None),
1576 vec!["baz".into()],
1577 ),
1578 ],
1579 &["foo"],
1580 );
1581
1582 file_system.write_file("foo", "");
1583 file_system.write_file("bar", "");
1584 file_system.write_file("baz", "");
1585
1586 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1587 .await
1588 .unwrap();
1589
1590 file_system.write_file("baz", "");
1591
1592 let count = count_metadata_requests(&file_system, "bar");
1593
1594 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1595
1596 assert_eq!(count_metadata_requests(&file_system, "bar"), count + 2);
1597 }
1598
1599 #[tokio::test]
1600 async fn query_output_again_after_command() {
1601 let file_system = FakeFileSystem::default();
1602
1603 file_system.write_file("foo", "");
1604 file_system.write_file("bar", "");
1605 file_system.write_file("baz", "");
1606
1607 run(
1608 &create_context(&Default::default(), &Default::default(), &file_system),
1609 create_simple_config(
1610 vec![
1611 explicit_build(
1612 vec!["foo".into()],
1613 Rule::new("cp bar foo", None),
1614 vec!["bar".into()],
1615 ),
1616 explicit_build(
1617 vec!["bar".into()],
1618 Rule::new("cp baz bar", None),
1619 vec!["baz".into()],
1620 ),
1621 ],
1622 &["foo"],
1623 ),
1624 &[],
1625 DEFAULT_OPTIONS,
1626 )
1627 .await
1628 .unwrap();
1629
1630 assert_eq!(count_metadata_requests(&file_system, "bar"), 2);
1631 }
1632
1633 #[tokio::test]
1634 async fn query_output_again_for_up_to_date_phony_build() {
1635 let file_system = FakeFileSystem::default();
1636 let context = create_context(&Default::default(), &Default::default(), &file_system);
1637 let config = create_simple_config(
1638 vec![
1639 explicit_build(
1640 vec!["foo.o".into()],
1641 Rule::new("cc foo.c", None).with_header_dependency(Some(
1642 HeaderDependency::Make {
1643 path: "foo.d".into(),
1644 },
1645 )),
1646 vec!["foo.c".into()],
1647 ),
1648 Build::new(
1649 vec!["foo.h".into()],
1650 vec![],
1651 None,
1652 vec!["foo.h.in".into()],
1653 vec![],
1654 None,
1655 ),
1656 ],
1657 &["foo.o", "foo.h"],
1658 );
1659
1660 file_system.write_file("foo.o", "");
1661 file_system.write_file("foo.c", "");
1662 file_system.write_file("foo.h", "");
1663 file_system.write_file("foo.h.in", "");
1664 file_system.write_file("foo.d", "foo.o: foo.h\n");
1665
1666 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1667 .await
1668 .unwrap();
1669
1670 let count = count_metadata_requests(&file_system, "foo.h");
1671
1672 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1673
1674 assert_eq!(count_metadata_requests(&file_system, "foo.h"), count + 2);
1675 }
1676
1677 #[tokio::test]
1678 async fn fail_with_output_metadata_error() {
1679 let command_runner = FakeCommandRunner::default();
1680 let file_system = FakeFileSystem::default();
1681
1682 file_system.write_file("bar", "");
1683 file_system.fail_metadata("foo");
1684
1685 assert_eq!(
1686 run(
1687 &create_context(&command_runner, &Default::default(), &file_system),
1688 create_simple_config(
1689 vec![explicit_build(
1690 vec!["foo".into()],
1691 Rule::new("cp bar foo", None),
1692 vec!["bar".into()],
1693 )],
1694 &["foo"],
1695 ),
1696 &[],
1697 DEFAULT_OPTIONS,
1698 )
1699 .await,
1700 Err(BuildError::File(FileError::new("metadata failure")))
1701 );
1702 assert_eq!(command_runner.commands(), Vec::<String>::new());
1703 }
1704
1705 #[tokio::test]
1706 async fn write_command_output() {
1707 let console = FakeConsole::default();
1708
1709 run(
1710 &create_context(
1711 &FakeCommandRunner::new(
1712 [(
1713 "touch foo".into(),
1714 Output {
1715 status: ExitStatus::default(),
1716 stdout: b"bar\n".into(),
1717 stderr: b"baz\n".into(),
1718 },
1719 )]
1720 .into_iter()
1721 .collect(),
1722 ),
1723 &console,
1724 &Default::default(),
1725 ),
1726 create_simple_config(
1727 vec![explicit_build(
1728 vec!["foo".into()],
1729 Rule::new("touch foo", Some("build foo".into())),
1730 vec![],
1731 )],
1732 &["foo"],
1733 ),
1734 &[],
1735 DEFAULT_OPTIONS,
1736 )
1737 .await
1738 .unwrap();
1739
1740 assert_eq!(console.stdout(), "bar\n");
1741 assert_eq!(console.stderr(), "build foo\nbaz\n");
1742 }
1743
1744 #[tokio::test]
1745 async fn write_debug_log_of_failed_build() {
1746 let console = FakeConsole::default();
1747
1748 assert_eq!(
1749 run(
1750 &create_context(
1751 &FakeCommandRunner::new(
1752 [("exit 1".into(), failed_output())].into_iter().collect(),
1753 ),
1754 &console,
1755 &Default::default(),
1756 ),
1757 create_simple_config(
1758 vec![explicit_build(
1759 vec!["foo".into()],
1760 Rule::new("exit 1", None),
1761 vec![],
1762 )],
1763 &["foo"],
1764 ),
1765 &[],
1766 RunOptions {
1767 debug: true,
1768 profile: false,
1769 },
1770 )
1771 .await,
1772 Err(BuildError::Build)
1773 );
1774 assert_eq!(
1775 console.stderr(),
1776 "turtle: command: exit 1\nturtle: exit status: 1\n"
1777 );
1778 }
1779
1780 #[tokio::test]
1781 async fn write_profile_log() {
1782 let console = FakeConsole::default();
1783
1784 run(
1785 &create_context(&Default::default(), &console, &Default::default()),
1786 create_simple_config(
1787 vec![explicit_build(
1788 vec!["foo".into()],
1789 Rule::new("touch foo", None),
1790 vec![],
1791 )],
1792 &["foo"],
1793 ),
1794 &[],
1795 RunOptions {
1796 debug: false,
1797 profile: true,
1798 },
1799 )
1800 .await
1801 .unwrap();
1802
1803 assert!(
1804 Regex::new(r"^turtle: duration: \d+ ms\n$")
1805 .unwrap()
1806 .is_match(&console.stderr())
1807 );
1808 }
1809
1810 #[tokio::test]
1811 async fn rebuild_on_update_of_header_dependency() {
1812 let command_runner = FakeCommandRunner::default();
1813 let file_system = FakeFileSystem::default();
1814 let context = create_context(&command_runner, &Default::default(), &file_system);
1815 let config = create_simple_config(
1816 vec![explicit_build(
1817 vec!["foo.o".into()],
1818 Rule::new("cc foo.c", None).with_header_dependency(Some(HeaderDependency::Make {
1819 path: "foo.d".into(),
1820 })),
1821 vec!["foo.c".into()],
1822 )],
1823 &["foo.o"],
1824 );
1825
1826 file_system.write_file("foo.o", "");
1827 file_system.write_file("foo.c", "");
1828 file_system.write_file("foo.h", "");
1829 file_system.write_file("foo.d", "foo.o: foo.h\n");
1830
1831 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1832 .await
1833 .unwrap();
1834 run(&context, config.clone(), &[], DEFAULT_OPTIONS)
1835 .await
1836 .unwrap();
1837
1838 file_system.write_file("foo.h", "foo");
1839
1840 run(&context, config, &[], DEFAULT_OPTIONS).await.unwrap();
1841
1842 assert_eq!(command_runner.commands(), ["cc foo.c", "cc foo.c"]);
1843 }
1844
1845 #[tokio::test]
1846 async fn read_and_remove_gcc_depfile() {
1847 let file_system = FakeFileSystem::default();
1848 let context = create_context(&Default::default(), &Default::default(), &file_system);
1849 let build = explicit_build(
1850 vec!["foo.o".into()],
1851 Rule::new("cc foo.c", None).with_header_dependency(Some(HeaderDependency::Gcc {
1852 path: "foo.d".into(),
1853 })),
1854 vec!["foo.c".into()],
1855 );
1856
1857 file_system.write_file("foo.c", "");
1858 file_system.write_file("foo.h", "");
1859 file_system.write_file("foo.d", "foo.o: foo.h\n");
1860
1861 run(
1862 &context,
1863 create_simple_config(vec![build.clone()], &["foo.o"]),
1864 &[],
1865 DEFAULT_OPTIONS,
1866 )
1867 .await
1868 .unwrap();
1869
1870 assert_eq!(
1871 context
1872 .database()
1873 .get_header_dependencies(build.id())
1874 .unwrap(),
1875 ["foo.h"]
1876 );
1877 assert!(
1878 !context
1879 .file_system()
1880 .exists("foo.d".as_ref())
1881 .await
1882 .unwrap()
1883 );
1884 }
1885
1886 #[tokio::test]
1887 async fn read_msvc_header_dependencies() {
1888 let console = FakeConsole::default();
1889 let file_system = FakeFileSystem::default();
1890 let context = create_context(
1891 &FakeCommandRunner::new(
1892 [(
1893 "cl foo.c".into(),
1894 Output {
1895 status: ExitStatus::default(),
1896 stdout: b"Note: including file: foo.h\nfoo.c\n".into(),
1897 stderr: vec![],
1898 },
1899 )]
1900 .into_iter()
1901 .collect(),
1902 ),
1903 &console,
1904 &file_system,
1905 );
1906 let build = explicit_build(
1907 vec!["foo.obj".into()],
1908 Rule::new("cl foo.c", None).with_header_dependency(Some(HeaderDependency::Msvc {
1909 prefix: "Note: including file: ".into(),
1910 })),
1911 vec!["foo.c".into()],
1912 );
1913
1914 file_system.write_file("foo.c", "");
1915 file_system.write_file("foo.h", "");
1916
1917 run(
1918 &context,
1919 create_simple_config(vec![build.clone()], &["foo.obj"]),
1920 &[],
1921 DEFAULT_OPTIONS,
1922 )
1923 .await
1924 .unwrap();
1925
1926 assert_eq!(
1927 context
1928 .database()
1929 .get_header_dependencies(build.id())
1930 .unwrap(),
1931 ["foo.h"]
1932 );
1933 assert_eq!(console.stdout(), "foo.c\n");
1934 }
1935
1936 #[tokio::test]
1937 async fn build_generated_header_dependency() {
1938 let command_runner = FakeCommandRunner::default();
1939 let file_system = FakeFileSystem::default();
1940 let context = create_context(&command_runner, &Default::default(), &file_system);
1941 let build = explicit_build(
1942 vec!["foo.o".into()],
1943 Rule::new("cc foo.c", None),
1944 vec!["foo.c".into()],
1945 );
1946
1947 context
1948 .database()
1949 .set_header_dependencies(build.id(), &["foo.h".into()])
1950 .unwrap();
1951 file_system.write_file("foo.c", "");
1952 file_system.write_file("foo.h", "");
1953
1954 run(
1955 &context,
1956 create_simple_config(
1957 vec![
1958 build,
1959 explicit_build(vec!["foo.h".into()], Rule::new("touch foo.h", None), vec![]),
1960 ],
1961 &["foo.o"],
1962 ),
1963 &[],
1964 DEFAULT_OPTIONS,
1965 )
1966 .await
1967 .unwrap();
1968
1969 assert_eq!(command_runner.commands(), ["touch foo.h", "cc foo.c"]);
1970 }
1971
1972 #[tokio::test]
1973 async fn ignore_missing_header_dependency() {
1974 let file_system = FakeFileSystem::default();
1975 let context = create_context(&Default::default(), &Default::default(), &file_system);
1976 let build = explicit_build(
1977 vec!["foo.o".into()],
1978 Rule::new("cc foo.c", None),
1979 vec!["foo.c".into()],
1980 );
1981
1982 context
1983 .database()
1984 .set_header_dependencies(build.id(), &["foo.h".into()])
1985 .unwrap();
1986 file_system.write_file("foo.c", "");
1987
1988 run(
1989 &context,
1990 create_simple_config(vec![build], &["foo.o"]),
1991 &[],
1992 DEFAULT_OPTIONS,
1993 )
1994 .await
1995 .unwrap();
1996 }
1997
1998 #[tokio::test]
1999 async fn read_header_dependencies_once() {
2000 let database = FakeDatabase::default();
2001 let file_system = FakeFileSystem::default();
2002 let context = Arc::new(Context::new(
2003 FakeCommandRunner::default(),
2004 Mutex::new(FakeConsole::default()).into(),
2005 database.clone(),
2006 file_system.clone(),
2007 ));
2008 let build = explicit_build(
2009 vec!["foo.o".into()],
2010 Rule::new("cc foo.c", None),
2011 vec!["foo.c".into()],
2012 );
2013
2014 context
2015 .database()
2016 .set_header_dependencies(build.id(), &["foo.h".into()])
2017 .unwrap();
2018 file_system.write_file("foo.c", "");
2019 file_system.write_file("foo.h", "");
2020
2021 run(
2022 &context,
2023 create_simple_config(vec![build.clone()], &["foo.o"]),
2024 &[],
2025 DEFAULT_OPTIONS,
2026 )
2027 .await
2028 .unwrap();
2029
2030 assert_eq!(database.header_dependency_requests(), [build.id()]);
2031 }
2032
2033 #[tokio::test]
2034 async fn detect_circular_header_dependency() {
2035 let context = create_context(
2036 &Default::default(),
2037 &Default::default(),
2038 &Default::default(),
2039 );
2040 let build = explicit_build(vec!["foo".into()], Rule::new("touch foo", None), vec![]);
2041
2042 context
2043 .database()
2044 .set_header_dependencies(build.id(), &["foo".into()])
2045 .unwrap();
2046
2047 assert_eq!(
2048 run(
2049 &context,
2050 create_simple_config(vec![build], &["foo"]),
2051 &[],
2052 DEFAULT_OPTIONS,
2053 )
2054 .await,
2055 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2056 vec!["foo".into()]
2057 )))
2058 );
2059 }
2060
2061 #[tokio::test]
2062 async fn build_dynamic_input() {
2063 let command_runner = FakeCommandRunner::default();
2064 let file_system = FakeFileSystem::default();
2065
2066 file_system.write_file(
2067 "foo.dd",
2068 "ninja_dyndep_version = 1\nbuild foo: dyndep | bar\n",
2069 );
2070 file_system.write_file("bar", "");
2071
2072 run(
2073 &create_context(&command_runner, &Default::default(), &file_system),
2074 create_simple_config(
2075 vec![
2076 Build::new(
2077 vec!["foo".into()],
2078 vec![],
2079 Rule::new("touch foo", None).into(),
2080 vec![],
2081 vec!["foo.dd".into()],
2082 Some("foo.dd".into()),
2083 ),
2084 explicit_build(
2085 vec!["foo.dd".into()],
2086 Rule::new("touch foo.dd", None),
2087 vec![],
2088 ),
2089 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
2090 ],
2091 &["foo"],
2092 ),
2093 &[],
2094 DEFAULT_OPTIONS,
2095 )
2096 .await
2097 .unwrap();
2098
2099 assert_eq!(
2100 command_runner.commands(),
2101 ["touch foo.dd", "touch bar", "touch foo"]
2102 );
2103 }
2104
2105 #[tokio::test]
2106 async fn build_dynamic_input_declared_for_implicit_output() {
2107 let command_runner = FakeCommandRunner::default();
2108 let file_system = FakeFileSystem::default();
2109
2110 file_system.write_file(
2111 "foo.dd",
2112 "ninja_dyndep_version = 1\nbuild baz: dyndep | bar\n",
2113 );
2114 file_system.write_file("bar", "");
2115
2116 run(
2117 &create_context(&command_runner, &Default::default(), &file_system),
2118 create_simple_config(
2119 vec![
2120 Build::new(
2121 vec!["foo".into()],
2122 vec!["baz".into()],
2123 Rule::new("touch foo", None).into(),
2124 vec![],
2125 vec!["foo.dd".into()],
2126 Some("foo.dd".into()),
2127 ),
2128 explicit_build(
2129 vec!["foo.dd".into()],
2130 Rule::new("touch foo.dd", None),
2131 vec![],
2132 ),
2133 explicit_build(vec!["bar".into()], Rule::new("touch bar", None), vec![]),
2134 ],
2135 &["foo"],
2136 ),
2137 &[],
2138 DEFAULT_OPTIONS,
2139 )
2140 .await
2141 .unwrap();
2142
2143 assert_eq!(
2144 command_runner.commands(),
2145 ["touch foo.dd", "touch bar", "touch foo"]
2146 );
2147 }
2148
2149 #[tokio::test]
2150 async fn fail_with_missing_dynamic_dependency() {
2151 let file_system = FakeFileSystem::default();
2152 let build = Build::new(
2153 vec!["foo".into()],
2154 vec![],
2155 Rule::new("touch foo", None).into(),
2156 vec![],
2157 vec![],
2158 Some("foo.dd".into()),
2159 );
2160
2161 file_system.write_file("foo.dd", "ninja_dyndep_version = 1\n");
2162
2163 assert_eq!(
2164 run(
2165 &create_context(&Default::default(), &Default::default(), &file_system),
2166 create_simple_config(vec![build.clone()], &["foo"]),
2167 &[],
2168 DEFAULT_OPTIONS,
2169 )
2170 .await,
2171 Err(BuildError::DynamicDependencyNotFound(build.into()))
2172 );
2173 }
2174
2175 #[tokio::test]
2176 async fn fail_with_unknown_output_in_dynamic_dependency() {
2177 let file_system = FakeFileSystem::default();
2178
2179 file_system.write_file(
2180 "foo.dd",
2181 "ninja_dyndep_version = 1\nbuild bar: dyndep | baz\n",
2182 );
2183
2184 assert_eq!(
2185 run(
2186 &create_context(&Default::default(), &Default::default(), &file_system),
2187 create_simple_config(
2188 vec![Build::new(
2189 vec!["foo".into()],
2190 vec![],
2191 Rule::new("touch foo", None).into(),
2192 vec![],
2193 vec![],
2194 Some("foo.dd".into()),
2195 )],
2196 &["foo"],
2197 ),
2198 &[],
2199 DEFAULT_OPTIONS,
2200 )
2201 .await,
2202 Err(BuildError::BuildGraph(BuildGraphError::OutputNotFound(
2203 "bar".into()
2204 )))
2205 );
2206 }
2207
2208 #[tokio::test]
2209 async fn detect_circular_dynamic_dependency() {
2210 let file_system = FakeFileSystem::default();
2211
2212 file_system.write_file(
2213 "foo.dd",
2214 "ninja_dyndep_version = 1\nbuild foo: dyndep | foo\n",
2215 );
2216
2217 assert_eq!(
2218 run(
2219 &create_context(&Default::default(), &Default::default(), &file_system),
2220 create_simple_config(
2221 vec![Build::new(
2222 vec!["foo".into()],
2223 vec![],
2224 Rule::new("touch foo", None).into(),
2225 vec![],
2226 vec![],
2227 Some("foo.dd".into()),
2228 )],
2229 &["foo"],
2230 ),
2231 &[],
2232 DEFAULT_OPTIONS,
2233 )
2234 .await,
2235 Err(BuildError::BuildGraph(BuildGraphError::CircularDependency(
2236 vec!["foo".into()]
2237 )))
2238 );
2239 }
2240
2241 #[tokio::test]
2242 async fn run_builds_concurrently() {
2243 let command_runner = FakeCommandRunner::default();
2244
2245 run(
2246 &create_context(&command_runner, &Default::default(), &Default::default()),
2247 create_simple_config(
2248 vec![
2249 pool_build("foo", None),
2250 pool_build("bar", None),
2251 pool_build("baz", None),
2252 ],
2253 &["foo", "bar", "baz"],
2254 ),
2255 &[],
2256 DEFAULT_OPTIONS,
2257 )
2258 .await
2259 .unwrap();
2260
2261 assert_eq!(command_runner.max_concurrency(), 3);
2262 }
2263
2264 #[tokio::test]
2265 async fn limit_concurrency_of_builds_in_pool() {
2266 let command_runner = FakeCommandRunner::default();
2267
2268 run(
2269 &create_context(&command_runner, &Default::default(), &Default::default()),
2270 create_pool_config(
2271 vec![
2272 pool_build("foo", limited_pool("qux")),
2273 pool_build("bar", limited_pool("qux")),
2274 pool_build("baz", limited_pool("qux")),
2275 ],
2276 &["foo", "bar", "baz"],
2277 &[("qux", 1)],
2278 ),
2279 &[],
2280 DEFAULT_OPTIONS,
2281 )
2282 .await
2283 .unwrap();
2284
2285 assert_eq!(command_runner.commands().len(), 3);
2286 assert_eq!(command_runner.max_concurrency(), 1);
2287 }
2288
2289 #[tokio::test]
2290 async fn limit_concurrency_of_builds_in_pool_with_depth_of_two() {
2291 let command_runner = FakeCommandRunner::default();
2292
2293 run(
2294 &create_context(&command_runner, &Default::default(), &Default::default()),
2295 create_pool_config(
2296 vec![
2297 pool_build("foo", limited_pool("quux")),
2298 pool_build("bar", limited_pool("quux")),
2299 pool_build("baz", limited_pool("quux")),
2300 pool_build("qux", limited_pool("quux")),
2301 ],
2302 &["foo", "bar", "baz", "qux"],
2303 &[("quux", 2)],
2304 ),
2305 &[],
2306 DEFAULT_OPTIONS,
2307 )
2308 .await
2309 .unwrap();
2310
2311 assert_eq!(command_runner.commands().len(), 4);
2312 assert_eq!(command_runner.max_concurrency(), 2);
2313 }
2314
2315 #[tokio::test]
2316 async fn run_builds_in_different_pools_concurrently() {
2317 let command_runner = FakeCommandRunner::default();
2318
2319 run(
2320 &create_context(&command_runner, &Default::default(), &Default::default()),
2321 create_pool_config(
2322 vec![
2323 pool_build("foo", limited_pool("baz")),
2324 pool_build("bar", limited_pool("qux")),
2325 ],
2326 &["foo", "bar"],
2327 &[("baz", 1), ("qux", 1)],
2328 ),
2329 &[],
2330 DEFAULT_OPTIONS,
2331 )
2332 .await
2333 .unwrap();
2334
2335 assert_eq!(command_runner.max_concurrency(), 2);
2336 }
2337
2338 #[tokio::test]
2339 async fn run_build_in_pool_of_huge_depth() {
2340 let command_runner = FakeCommandRunner::default();
2341
2342 run(
2343 &create_context(&command_runner, &Default::default(), &Default::default()),
2344 create_pool_config(
2345 vec![pool_build("foo", limited_pool("bar"))],
2346 &["foo"],
2347 &[("bar", usize::MAX)],
2348 ),
2349 &[],
2350 DEFAULT_OPTIONS,
2351 )
2352 .await
2353 .unwrap();
2354
2355 assert_eq!(command_runner.commands(), ["touch foo"]);
2356 }
2357
2358 #[tokio::test]
2359 async fn run_build_in_console_pool() {
2360 let command_runner = FakeCommandRunner::new(
2361 [(
2362 "touch foo".into(),
2363 Output {
2364 status: ExitStatus::default(),
2365 stdout: b"bar\n".into(),
2366 stderr: b"baz\n".into(),
2367 },
2368 )]
2369 .into_iter()
2370 .collect(),
2371 );
2372 let console = FakeConsole::default();
2373
2374 run(
2375 &create_context(&command_runner, &console, &Default::default()),
2376 create_simple_config(
2377 vec![explicit_build(
2378 vec!["foo".into()],
2379 Rule::new("touch foo", Some("build foo".into())).with_pool(Some(Pool::Console)),
2380 vec![],
2381 )],
2382 &["foo"],
2383 ),
2384 &[],
2385 DEFAULT_OPTIONS,
2386 )
2387 .await
2388 .unwrap();
2389
2390 assert_eq!(command_runner.commands(), Vec::<String>::new());
2391 assert_eq!(command_runner.console_commands(), ["touch foo"]);
2392 assert_eq!(console.stdout(), "");
2393 assert_eq!(console.stderr(), "build foo\n");
2394 }
2395
2396 #[tokio::test]
2397 async fn limit_concurrency_of_builds_in_console_pool() {
2398 let command_runner = FakeCommandRunner::default();
2399
2400 run(
2401 &create_context(&command_runner, &Default::default(), &Default::default()),
2402 create_simple_config(
2403 vec![
2404 pool_build("foo", Some(Pool::Console)),
2405 pool_build("bar", Some(Pool::Console)),
2406 pool_build("baz", Some(Pool::Console)),
2407 ],
2408 &["foo", "bar", "baz"],
2409 ),
2410 &[],
2411 DEFAULT_OPTIONS,
2412 )
2413 .await
2414 .unwrap();
2415
2416 assert_eq!(command_runner.console_commands().len(), 3);
2417 assert_eq!(command_runner.max_concurrency(), 1);
2418 }
2419
2420 #[tokio::test]
2421 async fn write_debug_log_of_failed_build_in_console_pool() {
2422 let command_runner =
2423 FakeCommandRunner::new([("exit 1".into(), failed_output())].into_iter().collect());
2424 let console = FakeConsole::default();
2425
2426 assert_eq!(
2427 run(
2428 &create_context(&command_runner, &console, &Default::default()),
2429 create_simple_config(
2430 vec![explicit_build(
2431 vec!["foo".into()],
2432 Rule::new("exit 1", None).with_pool(Some(Pool::Console)),
2433 vec![],
2434 )],
2435 &["foo"],
2436 ),
2437 &[],
2438 RunOptions {
2439 debug: true,
2440 profile: false,
2441 },
2442 )
2443 .await,
2444 Err(BuildError::Build)
2445 );
2446 assert_eq!(command_runner.console_commands(), ["exit 1"]);
2447 assert_eq!(
2448 console.stderr(),
2449 "turtle: command: exit 1\nturtle: exit status: 1\n"
2450 );
2451 }
2452
2453 #[tokio::test]
2454 async fn lock_console_during_command_in_console_pool() {
2455 let command_runner = FakeCommandRunner::default();
2456 let context = create_run_context(&command_runner, &Default::default(), &[]);
2457 let rule = Rule::new("foo", None).with_pool(Some(Pool::Console));
2458 let mut future = pin!(run_rule(&context, &rule));
2459
2460 assert!(poll!(&mut future).is_pending());
2461 assert_eq!(command_runner.console_commands(), ["foo"]);
2462 assert!(context.build().console().try_lock().is_err());
2463
2464 future.await.unwrap();
2465
2466 assert!(context.build().console().try_lock().is_ok());
2467 }
2468
2469 #[tokio::test]
2470 async fn flush_console_before_command_in_console_pool() {
2471 let command_runner = FakeCommandRunner::default();
2472 let console = FakeConsole::default();
2473 let context = create_run_context(&command_runner, &console, &[]);
2474 let rule = Rule::new("foo", Some("bar".into())).with_pool(Some(Pool::Console));
2475 let mut future = pin!(run_rule(&context, &rule));
2476
2477 assert!(poll!(&mut future).is_pending());
2478 assert_eq!(command_runner.console_commands(), ["foo"]);
2479 assert_eq!(console.flushed_stderr(), "bar\n");
2480
2481 future.await.unwrap();
2482 }
2483
2484 #[tokio::test]
2485 async fn wait_for_console_before_command_in_console_pool() {
2486 let command_runner = FakeCommandRunner::default();
2487 let context = create_run_context(&command_runner, &Default::default(), &[]);
2488 let rule = Rule::new("foo", None).with_pool(Some(Pool::Console));
2489 let console = context.build().console().lock().await;
2490 let mut future = pin!(run_rule(&context, &rule));
2491
2492 for _ in 0..POLL_COUNT {
2493 assert!(poll!(&mut future).is_pending());
2494 }
2495
2496 assert_eq!(command_runner.console_commands(), Vec::<String>::new());
2497
2498 drop(console);
2499 future.await.unwrap();
2500
2501 assert_eq!(command_runner.console_commands(), ["foo"]);
2502 }
2503
2504 #[tokio::test]
2505 async fn run_command_concurrently_with_command_in_console_pool() {
2506 let command_runner = FakeCommandRunner::default();
2507 let context = create_run_context(&command_runner, &Default::default(), &[]);
2508 let foo = Rule::new("foo", None).with_pool(Some(Pool::Console));
2509 let bar = Rule::new("bar", None);
2510 let mut foo_future = pin!(run_rule(&context, &foo));
2511 let mut bar_future = pin!(run_rule(&context, &bar));
2512
2513 assert!(poll!(&mut foo_future).is_pending());
2514 assert!(poll!(&mut bar_future).is_pending());
2515 assert_eq!(command_runner.console_commands(), ["foo"]);
2516 assert_eq!(command_runner.commands(), ["bar"]);
2517
2518 foo_future.await.unwrap();
2519 bar_future.await.unwrap();
2520 }
2521
2522 #[tokio::test]
2523 async fn release_pool_before_waiting_for_console() {
2524 let command_runner = FakeCommandRunner::default();
2525 let context = create_run_context(&command_runner, &Default::default(), &[("baz", 1)]);
2526 let foo = Rule::new("foo", None).with_pool(limited_pool("baz"));
2527 let bar = Rule::new("bar", None).with_pool(limited_pool("baz"));
2528 let console = context.build().console().lock().await;
2529 let mut foo_future = pin!(run_rule(&context, &foo));
2530 let mut bar_future = pin!(run_rule(&context, &bar));
2531
2532 for _ in 0..POLL_COUNT {
2533 assert!(poll!(&mut foo_future).is_pending());
2534 }
2535
2536 for _ in 0..POLL_COUNT {
2537 assert!(poll!(&mut bar_future).is_pending());
2538 }
2539
2540 assert_eq!(command_runner.commands(), ["foo", "bar"]);
2541
2542 drop(console);
2543 foo_future.await.unwrap();
2544 bar_future.await.unwrap();
2545 }
2546
2547 #[tokio::test]
2548 async fn wait_for_pool_before_locking_console() {
2549 let command_runner = FakeCommandRunner::default();
2550 let console = FakeConsole::default();
2551 let context = create_run_context(&command_runner, &console, &[("bar", 1)]);
2552 let rule = Rule::new("foo", Some("foo".into())).with_pool(limited_pool("bar"));
2553 let permit = context.pool(rule.pool()).await.unwrap();
2554 let mut future = pin!(run_rule(&context, &rule));
2555
2556 for _ in 0..POLL_COUNT {
2557 assert!(poll!(&mut future).is_pending());
2558 }
2559
2560 assert_eq!(command_runner.commands(), Vec::<String>::new());
2561 assert_eq!(console.stderr(), "");
2562 assert!(context.build().console().try_lock().is_ok());
2563
2564 drop(permit);
2565 future.await.unwrap();
2566
2567 assert_eq!(command_runner.commands(), ["foo"]);
2568 assert_eq!(console.stderr(), "foo\n");
2569 }
2570
2571 #[tokio::test]
2572 async fn keep_pool_until_command_finishes_on_console_error() {
2573 let command_runner = FakeCommandRunner::default();
2574 let context = create_run_context(&command_runner, &FakeConsole::failing(), &[("baz", 1)]);
2575 let foo = Rule::new("foo", Some("foo".into())).with_pool(limited_pool("baz"));
2576 let bar = Rule::new("bar", None).with_pool(limited_pool("baz"));
2577 let mut foo_future = pin!(run_rule(&context, &foo));
2578 let mut bar_future = pin!(run_rule(&context, &bar));
2579
2580 assert!(poll!(&mut foo_future).is_pending());
2581 assert!(poll!(&mut bar_future).is_pending());
2582 assert_eq!(command_runner.commands(), ["foo"]);
2583 assert!(foo_future.await.is_err());
2584 }
2585
2586 #[test]
2587 fn classify_phony_and_file_inputs() {
2588 let build = explicit_build(
2589 vec!["foo".into()],
2590 Rule::new("", None),
2591 vec!["bar".into(), "baz".into(), "qux".into(), "bar".into()],
2592 );
2593 let context = RunContext::new(
2594 create_context(
2595 &Default::default(),
2596 &Default::default(),
2597 &Default::default(),
2598 ),
2599 create_simple_config(
2600 vec![
2601 build.clone(),
2602 Build::new(vec!["bar".into()], vec![], None, vec![], vec![], None),
2603 explicit_build(vec!["baz".into()], Rule::new("", None), vec![]),
2604 ],
2605 &[],
2606 ),
2607 BuildGraph::new(&Default::default()),
2608 Default::default(),
2609 DEFAULT_OPTIONS,
2610 );
2611
2612 assert_eq!(
2613 classify_inputs(
2614 &context,
2615 &build,
2616 &["quux".into(), "baz".into()],
2617 &["qux", "corge"],
2618 ),
2619 (vec!["bar"], vec!["baz", "qux", "quux", "corge"])
2620 );
2621 }
2622}