summaryrefslogtreecommitdiff
path: root/src/command.rs
blob: 8e8a3127348e68375487be260725b4f5fb36c131 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use crate::{
	http::{self, Ext},
	nix,
};

use anyhow::Result;
use futures::{stream, StreamExt, TryStreamExt};
use indicatif::{ProgressBar, ProgressStyle};
use tracing::instrument;

pub trait Run {
	async fn run(&self) -> Result<()>;
}

impl Run for crate::Cli {
	#[instrument(skip(self))]
	async fn run(&self) -> Result<()> {
		let store_paths = if let Some(installables) = self.installables.clone() {
			resolve_installables(installables).await?
		} else if let Some(configuration) = &self.configuration {
			println!("ā“ Indexing requisites of configuration closure");
			nix::configuration_closure_paths(configuration)?
		} else {
			println!("ā“ Indexing all installables of flake `{}`", self.flake);
			let installables = nix::all_flake_installables(&self.flake)?;
			resolve_installables(installables).await?
		};

		check_store_paths(&self.binary_cache, &store_paths, self.show_missing).await?;

		Ok(())
	}
}

#[instrument(skip(installables))]
async fn resolve_installables(installables: Vec<String>) -> Result<Vec<String>> {
	println!(
		"šŸ”ƒ Attempting to evaluate {} installable(s)",
		installables.len()
	);

	// Find our outputs concurrently
	let progress_bar = ProgressBar::new(installables.len() as u64).with_style(progress_style()?);
	let out_paths: Vec<String> = stream::iter(&installables)
		.map(|installable| {
			let progress_bar = &progress_bar;
			async move {
				progress_bar.inc(1);
				let out_path = nix::out_path(installable)?;

				anyhow::Ok(out_path)
			}
		})
		.buffer_unordered(num_cpus::get()) // try not to explode computers
		.try_collect()
		.await?;

	println!("āœ… Evaluated {} installable(s)!", out_paths.len());

	Ok(out_paths)
}

#[allow(clippy::cast_precision_loss)]
#[instrument(skip(store_paths))]
async fn check_store_paths(
	binary_cache: &str,
	store_paths: &Vec<String>,
	show_missing: bool,
) -> Result<()> {
	let num_store_paths = store_paths.len();
	println!("šŸŒ”ļø Checking for {num_store_paths} store path(s) in {binary_cache}",);

	let http = <http::Client as http::Ext>::default();
	let progress_bar = ProgressBar::new(num_store_paths as u64).with_style(progress_style()?);
	let uncached_paths: Vec<&str> = stream::iter(store_paths)
		// Check the cache for all of our paths
		.map(|store_path| {
			let http = &http;
			let progress_bar = &progress_bar;
			async move {
				let has_store_path = http.has_store_path(binary_cache, store_path).await?;
				progress_bar.inc(1);

				anyhow::Ok((has_store_path, store_path.as_str()))
			}
		})
		.buffer_unordered(100)
		// Filter out misses
		.try_filter_map(|(has_store_path, store_path)| async move {
			Ok((!has_store_path).then_some(store_path))
		})
		.try_collect()
		.await?;

	let num_uncached = uncached_paths.len();
	let num_cached = num_store_paths - num_uncached;

	println!(
		"ā˜€ļø {:.2}% of paths available ({} out of {})",
		(num_cached as f32 / num_store_paths as f32) * 100.0,
		num_cached,
		num_store_paths,
	);

	if show_missing {
		println!(
			"\nā›ˆļø  Found {num_uncached} uncached paths:\n{}",
			uncached_paths.join("\n")
		);
	}

	Ok(())
}

pub fn progress_style() -> Result<ProgressStyle> {
	Ok(
		ProgressStyle::with_template("[{elapsed_precise}] {bar:40} {pos:>7}/{len:7} {msg}")?
			.progress_chars("##-"),
	)
}