1use flowey::node::prelude::*;
5
6const FLOWEY_INFO_DIR: &str = ".flowey_info";
7const FLOWEY_EXTRACT_DIR: &str = "extracted";
8
9#[derive(Clone)]
10#[non_exhaustive]
11pub struct ExtractZipDeps<C = VarNotClaimed> {
12 persistent_dir: Option<ReadVar<PathBuf, C>>,
13 bsdtar_installed: ReadVar<SideEffect, C>,
14}
15
16impl ClaimVar for ExtractZipDeps {
17 type Claimed = ExtractZipDeps<VarClaimed>;
18
19 fn claim(self, ctx: &mut StepCtx<'_>) -> Self::Claimed {
20 let Self {
21 persistent_dir,
22 bsdtar_installed,
23 } = self;
24 ExtractZipDeps {
25 persistent_dir: persistent_dir.claim(ctx),
26 bsdtar_installed: bsdtar_installed.claim(ctx),
27 }
28 }
29}
30
31#[track_caller]
32pub fn extract_zip_if_new_deps(ctx: &mut NodeCtx<'_>) -> ExtractZipDeps {
33 let platform = ctx.platform();
34 ExtractZipDeps {
35 persistent_dir: ctx.persistent_dir(),
36 bsdtar_installed: ctx.reqv(|v| crate::install_dist_pkg::Request::Install {
37 package_names: match platform {
38 FlowPlatform::Linux(linux_distribution) => match linux_distribution {
39 FlowPlatformLinuxDistro::Fedora => {
40 vec!["bsdtar".into()]
41 }
42 FlowPlatformLinuxDistro::Ubuntu => vec!["libarchive-tools".into()],
43 FlowPlatformLinuxDistro::AzureLinux | FlowPlatformLinuxDistro::Arch => {
44 vec!["libarchive".into()]
45 }
46 FlowPlatformLinuxDistro::Nix => vec![],
47 FlowPlatformLinuxDistro::Unknown => vec![],
48 },
49 _ => {
50 vec![]
51 }
52 },
53 done: v,
54 }),
55 }
56}
57
58pub fn extract_zip_if_new(
66 rt: &mut RustRuntimeServices<'_>,
67 deps: ExtractZipDeps<VarClaimed>,
68 file: &Path,
69 file_version: &str,
70) -> anyhow::Result<PathBuf> {
71 let ExtractZipDeps {
72 persistent_dir,
73 bsdtar_installed: _,
74 } = deps;
75
76 let root_dir = match persistent_dir {
77 Some(dir) => rt.read(dir),
78 None => rt.sh.current_dir(),
79 };
80
81 let bsdtar = crate::_util::bsdtar_name(rt);
82 extract_archive_if_new(rt, &root_dir, file, file_version, bsdtar)
83}
84
85pub fn extract_tar_gz_if_new(
98 rt: &mut RustRuntimeServices<'_>,
99 persistent_dir: Option<&Path>,
100 file: &Path,
101 file_version: &str,
102) -> anyhow::Result<PathBuf> {
103 let root_dir = match persistent_dir {
104 Some(dir) => dir.to_path_buf(),
105 None => rt.sh.current_dir(),
106 };
107
108 extract_archive_if_new(rt, &root_dir, file, file_version, "tar")
109}
110
111#[derive(Clone)]
112#[non_exhaustive]
113pub struct ExtractTarBz2Deps<C = VarNotClaimed> {
114 persistent_dir: Option<ReadVar<PathBuf, C>>,
115 bzip2_installed: ReadVar<SideEffect, C>,
116}
117
118impl ClaimVar for ExtractTarBz2Deps {
119 type Claimed = ExtractTarBz2Deps<VarClaimed>;
120
121 fn claim(self, ctx: &mut StepCtx<'_>) -> Self::Claimed {
122 let Self {
123 persistent_dir,
124 bzip2_installed,
125 } = self;
126 ExtractTarBz2Deps {
127 persistent_dir: persistent_dir.claim(ctx),
128 bzip2_installed: bzip2_installed.claim(ctx),
129 }
130 }
131}
132
133#[track_caller]
134pub fn extract_tar_bz2_if_new_deps(ctx: &mut NodeCtx<'_>) -> ExtractTarBz2Deps {
135 ExtractTarBz2Deps {
136 persistent_dir: ctx.persistent_dir(),
137 bzip2_installed: ctx.reqv(|v| crate::install_dist_pkg::Request::Install {
138 package_names: vec!["bzip2".into()],
139 done: v,
140 }),
141 }
142}
143
144pub fn extract_tar_bz2_if_new(
152 rt: &mut RustRuntimeServices<'_>,
153 deps: ExtractTarBz2Deps<VarClaimed>,
154 file: &Path,
155 file_version: &str,
156) -> anyhow::Result<PathBuf> {
157 let ExtractTarBz2Deps {
158 persistent_dir,
159 bzip2_installed: _,
160 } = deps;
161
162 let root_dir = match persistent_dir {
163 Some(dir) => rt.read(dir),
164 None => rt.sh.current_dir(),
165 };
166
167 extract_archive_if_new(rt, &root_dir, file, file_version, "tar")
168}
169
170fn extract_archive_if_new(
171 rt: &mut RustRuntimeServices<'_>,
172 root_dir: &Path,
173 file: &Path,
174 file_version: &str,
175 tar: &str,
176) -> anyhow::Result<PathBuf> {
177 let current_dir = rt.sh.current_dir();
178 let file = current_dir.join(file).absolute()?;
179 let root_dir = current_dir.join(root_dir).absolute()?;
180 extract_if_new(&root_dir, &file, file_version, |extract_dir| {
181 let _dir = rt.sh.push_dir(extract_dir);
182 flowey::shell_cmd!(rt, "{tar} -xf {file}").run()?;
183 Ok(())
184 })
185}
186
187fn extract_if_new(
188 root_dir: &Path,
189 file: &Path,
190 file_version: &str,
191 extract: impl FnOnce(&Path) -> anyhow::Result<()>,
192) -> anyhow::Result<PathBuf> {
193 let filename = file
194 .file_name()
195 .with_context(|| format!("archive path has no filename: {}", file.display()))?;
196 let extract_dir = root_dir.join(FLOWEY_EXTRACT_DIR).join(filename);
197 let pkg_info_dir = root_dir.join(FLOWEY_INFO_DIR);
198 let pkg_info_file = pkg_info_dir.join(filename);
199
200 let cached_version = match fs_err::read_to_string(&pkg_info_file) {
201 Ok(info) => Some(info),
202 Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
203 Err(err) => return Err(err).context("failed to read archive extraction version"),
204 };
205 let extracted = match fs_err::metadata(&extract_dir) {
206 Ok(metadata) => {
207 anyhow::ensure!(
208 metadata.is_dir(),
209 "archive extraction path is not a directory: {}",
210 extract_dir.display()
211 );
212 true
213 }
214 Err(err) if err.kind() == std::io::ErrorKind::NotFound => false,
215 Err(err) => return Err(err).context("failed to inspect archive extraction directory"),
216 };
217
218 if extracted && cached_version.as_deref() == Some(file_version) {
219 log::info!("already extracted!");
220 return Ok(extract_dir);
221 }
222
223 if cached_version.is_some() {
226 fs_err::remove_file(&pkg_info_file)?;
227 }
228 if extracted {
229 fs_err::remove_dir_all(&extract_dir)?;
230 }
231 fs_err::create_dir_all(&extract_dir)?;
232 extract(&extract_dir)
233 .with_context(|| format!("failed to extract archive {}", file.display()))?;
234 fs_err::create_dir_all(&pkg_info_dir)?;
235 fs_err::write(pkg_info_file, file_version)?;
236
237 Ok(extract_dir)
238}