1use crate::{utils, versions::patches::*};
7
8use displaydoc::Display;
9use flate2::read::GzDecoder;
10use fslock;
11use hex::ToHex;
12use reqwest;
13use sha2::{Digest, Sha256};
14use tar;
15use thiserror::Error;
16use tokio::{
17 fs,
18 io::{self, AsyncReadExt, AsyncWriteExt},
19 task,
20};
21
22use std::{
23 env,
24 path::{Path, PathBuf},
25};
26
27#[derive(Debug, Display, Error)]
29pub enum SummoningError {
30 Http(#[from] reqwest::Error),
32 Io(#[from] io::Error),
34 Checksum(String, String, String),
36 UnknownError(String),
38}
39
40#[derive(Clone, Debug)]
42pub struct CacheDir {
43 location: PathBuf,
44}
45
46impl CacheDir {
47 pub async fn get_or_create() -> Result<Self, SummoningError> {
52 let path = PathBuf::from(env::var("HOME").expect("$HOME should always be defined!"))
53 .join(".spack")
54 .join("summonings");
55 let p = path.clone();
56 task::spawn_blocking(move || utils::safe_create_dir_all_ioerror(&p))
57 .await
58 .unwrap()?;
59 Ok(Self { location: path })
60 }
61
62 pub fn location(&self) -> &Path { &self.location }
63
64 pub fn dirname(&self) -> String { PATCHES_SHA256SUM.encode_hex() }
66
67 pub fn unpacking_path(&self) -> PathBuf { self.location.join(PATCHES_TOPLEVEL_COMPONENT) }
69
70 pub fn tarball_path(&self) -> PathBuf { self.location.join(format!("{}.tar.gz", self.dirname())) }
72
73 pub fn repo_root(&self) -> PathBuf { self.unpacking_path().join(PATCHES_TOPLEVEL_COMPONENT) }
79
80 pub fn spack_script(&self) -> PathBuf { self.repo_root().join("bin").join("spack") }
82}
83
84struct SpackTarball {
85 downloaded_location: PathBuf,
86}
87
88impl SpackTarball {
89 pub fn downloaded_path(&self) -> &Path { self.downloaded_location.as_ref() }
90
91 async fn check_tarball_digest(
92 tgz_path: &Path,
93 tgz: &mut fs::File,
94 ) -> Result<Self, SummoningError> {
95 let mut tarball_bytes: Vec<u8> = vec![];
97 tgz.read_to_end(&mut tarball_bytes).await?;
98 let mut hasher = Sha256::new();
99 hasher.update(&tarball_bytes);
100 let checksum: [u8; 32] = hasher.finalize().into();
101 if checksum == PATCHES_SHA256SUM {
102 Ok(Self {
103 downloaded_location: tgz_path.to_path_buf(),
104 })
105 } else {
106 Err(SummoningError::Checksum(
107 format!("file://{}", tgz_path.display()),
108 PATCHES_SHA256SUM.encode_hex(),
109 checksum.encode_hex(),
110 ))
111 }
112 }
113
114 pub async fn fetch_spack_tarball(cache_dir: CacheDir) -> Result<Self, SummoningError> {
116 let tgz_path = cache_dir.tarball_path();
117
118 match fs::File::open(&tgz_path).await {
119 Ok(mut tgz) => Self::check_tarball_digest(&tgz_path, &mut tgz).await,
120 Err(e) if e.kind() == io::ErrorKind::NotFound => {
121 let lockfile_name: PathBuf = format!("{}.tgz.lock", cache_dir.dirname()).into();
123 let lockfile_path = cache_dir.location().join(lockfile_name);
124 let mut lockfile = task::spawn_blocking(move || fslock::LockFile::open(&lockfile_path))
125 .await
126 .unwrap()?;
127 let _lockfile = task::spawn_blocking(move || {
129 lockfile.lock_with_pid()?;
130 Ok::<_, io::Error>(lockfile)
131 })
132 .await
133 .unwrap()?;
134 if let Ok(mut tgz) = fs::File::open(&tgz_path).await {
138 return Self::check_tarball_digest(&tgz_path, &mut tgz).await;
140 }
141
142 eprintln!(
143 "downloading spack {} from {}...",
144 PATCHES_TOPLEVEL_COMPONENT, PATCHES_SPACK_URL,
145 );
146 let resp = reqwest::get(PATCHES_SPACK_URL).await?;
147 let tarball_bytes = resp.bytes().await?;
148 let mut hasher = Sha256::new();
149 hasher.update(&tarball_bytes);
150 let checksum: [u8; 32] = hasher.finalize().into();
151 if checksum == PATCHES_SHA256SUM {
152 let mut tgz = fs::File::create(&tgz_path).await?;
153 tgz.write_all(&tarball_bytes).await?;
154 tgz.sync_all().await?;
155 Ok(Self {
156 downloaded_location: tgz_path.to_path_buf(),
157 })
158 } else {
159 Err(SummoningError::Checksum(
160 PATCHES_SPACK_URL.to_string(),
161 PATCHES_SHA256SUM.encode_hex(),
162 checksum.encode_hex(),
163 ))
164 }
165 },
166 Err(e) => Err(e.into()),
167 }
168 }
169}
170
171#[derive(Debug, Clone)]
173pub struct SpackRepo {
174 pub script_path: PathBuf,
176 pub repo_path: PathBuf,
178 cache_dir: CacheDir,
179}
180
181impl SpackRepo {
182 pub(crate) fn cache_location(&self) -> &Path { self.cache_dir.location() }
183
184 pub(crate) fn unzip_archive(from: &Path, into: &Path) -> Result<Option<()>, SummoningError> {
185 match std::fs::File::open(from) {
186 Ok(tgz) => {
187 let gz_decoded = GzDecoder::new(tgz);
188 let mut archive = tar::Archive::new(gz_decoded);
189 Ok(Some(archive.unpack(into)?))
190 },
191 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
192 Err(e) => Err(e.into()),
193 }
194 }
195
196 async fn unzip_spack_archive(cache_dir: CacheDir) -> Result<Option<()>, SummoningError> {
197 let from = cache_dir.tarball_path();
198 let into = cache_dir.unpacking_path();
199 task::spawn_blocking(move || Self::unzip_archive(&from, &into))
200 .await
201 .unwrap()
202 }
203
204 pub(crate) async fn get_spack_script(cache_dir: CacheDir) -> Result<Self, SummoningError> {
205 let path = cache_dir.spack_script();
206 let _ = fs::File::open(&path).await?;
207 Ok(Self {
208 script_path: path,
209 repo_path: cache_dir.repo_root(),
210 cache_dir,
211 })
212 }
213
214 async fn ensure_unpacked(
215 current_link_path: PathBuf,
216 cache_dir: &CacheDir,
217 ) -> Result<(), SummoningError> {
218 match fs::read_dir(¤t_link_path).await {
219 Ok(_) => Ok(()),
220 Err(e) if e.kind() == io::ErrorKind::NotFound => {
221 let lockfile_name: PathBuf = format!("{}.lock", cache_dir.dirname()).into();
225 let lockfile_path = cache_dir.location().join(lockfile_name);
226 let mut lockfile = task::spawn_blocking(move || fslock::LockFile::open(&lockfile_path))
227 .await
228 .unwrap()?;
229 let _lockfile = task::spawn_blocking(move || {
231 lockfile.lock_with_pid()?;
232 Ok::<_, io::Error>(lockfile)
233 })
234 .await
235 .unwrap()?;
236
237 match fs::read_dir(¤t_link_path).await {
239 Ok(_) => Ok::<_, SummoningError>(()),
241 Err(e) if e.kind() == io::ErrorKind::NotFound => {
243 eprintln!("extracting spack {}...", PATCHES_TOPLEVEL_COMPONENT,);
244 assert!(Self::unzip_spack_archive(cache_dir.clone())
245 .await?
246 .is_some());
247 Ok(())
248 },
249 Err(e) => Err(e.into()),
250 }
251 },
252 Err(e) => Err(e.into()),
253 }
254 }
255
256 pub async fn summon(cache_dir: CacheDir) -> Result<Self, SummoningError> {
261 let spack_tarball = SpackTarball::fetch_spack_tarball(cache_dir.clone()).await?;
262 dbg!(spack_tarball.downloaded_path());
263
264 let current_link_path = cache_dir.unpacking_path();
265 Self::ensure_unpacked(current_link_path, &cache_dir).await?;
266
267 Self::get_spack_script(cache_dir).await
268 }
269}
270
271