Skip to main content

flowey_lib_common/_util/
extract.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use 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
58/// Extracts the given `file` into `persistent_dir` (or into
59/// [`std::env::current_dir()`], if no persistent dir is available).
60///
61/// To avoid redundant unzips between pipeline runs, callers must provide a
62/// `file_version` string that identifies the current file. If the
63/// previous run already unzipped a zip with the given `file_version`, this
64/// function will return nearly instantaneously.
65pub 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 filename = file.file_name().expect("zip file was not a file");
82    let extract_dir = root_dir.join(FLOWEY_EXTRACT_DIR).join(filename);
83    fs_err::create_dir_all(&extract_dir)?;
84
85    let pkg_info_dir = root_dir.join(FLOWEY_INFO_DIR);
86    fs_err::create_dir_all(&pkg_info_dir)?;
87    let pkg_info_file = pkg_info_dir.join(filename);
88
89    let mut already_extracted = false;
90    if let Ok(info) = fs_err::read_to_string(&pkg_info_file) {
91        if info == file_version {
92            already_extracted = true;
93        }
94    }
95
96    if !already_extracted {
97        // clear out any old version that was present
98        //
99        // FUTURE: maybe reconsider this approach, and keep
100        // old versions lying around, to make branch
101        // switching easier?
102        fs_err::remove_dir_all(&extract_dir)?;
103        fs_err::create_dir(&extract_dir)?;
104
105        rt.sh.change_dir(&extract_dir);
106
107        let bsdtar = crate::_util::bsdtar_name(rt);
108        flowey::shell_cmd!(rt, "{bsdtar} -xf {file}").run()?;
109        fs_err::write(pkg_info_file, file_version)?;
110    } else {
111        log::info!("already extracted!");
112    }
113
114    Ok(extract_dir)
115}
116
117/// Extracts the given `.tar.gz` `file` into `persistent_dir` (or into
118/// [`std::env::current_dir()`], if no persistent dir is available).
119///
120/// Unlike `.tar.bz2`, `.tar.gz` is handled natively by every platform's `tar`,
121/// so this helper has no install-package dependency to track. The caller
122/// resolves the persistent dir itself and passes it (already read) as
123/// `persistent_dir` — no `Deps` struct needed.
124///
125/// To avoid redundant extracts between pipeline runs, callers must provide a
126/// `file_version` string that identifies the current file. If the previous
127/// run already extracted an archive with the given `file_version`, this
128/// function will return nearly instantaneously.
129pub fn extract_tar_gz_if_new(
130    rt: &mut RustRuntimeServices<'_>,
131    persistent_dir: Option<&Path>,
132    file: &Path,
133    file_version: &str,
134) -> anyhow::Result<PathBuf> {
135    let root_dir = match persistent_dir {
136        Some(dir) => dir.to_path_buf(),
137        None => rt.sh.current_dir(),
138    };
139
140    let filename = file.file_name().expect("tar.gz file was not a file");
141    let extract_dir = root_dir.join(FLOWEY_EXTRACT_DIR).join(filename);
142    fs_err::create_dir_all(&extract_dir)?;
143
144    let pkg_info_dir = root_dir.join(FLOWEY_INFO_DIR);
145    fs_err::create_dir_all(&pkg_info_dir)?;
146    let pkg_info_file = pkg_info_dir.join(filename);
147
148    let mut already_extracted = false;
149    if let Ok(info) = fs_err::read_to_string(&pkg_info_file) {
150        if info == file_version {
151            already_extracted = true;
152        }
153    }
154
155    if !already_extracted {
156        // clear out any old version that was present
157        fs_err::remove_dir_all(&extract_dir)?;
158        fs_err::create_dir(&extract_dir)?;
159
160        rt.sh.change_dir(&extract_dir);
161
162        // windows builds past Windows 10 build 17063 come with tar installed,
163        // and `tar -xf` auto-detects gzip compression on all platforms
164        flowey::shell_cmd!(rt, "tar -xf {file}").run()?;
165
166        fs_err::write(pkg_info_file, file_version)?;
167    } else {
168        log::info!("already extracted!");
169    }
170
171    Ok(extract_dir)
172}
173
174#[derive(Clone)]
175#[non_exhaustive]
176pub struct ExtractTarBz2Deps<C = VarNotClaimed> {
177    persistent_dir: Option<ReadVar<PathBuf, C>>,
178    bzip2_installed: ReadVar<SideEffect, C>,
179}
180
181impl ClaimVar for ExtractTarBz2Deps {
182    type Claimed = ExtractTarBz2Deps<VarClaimed>;
183
184    fn claim(self, ctx: &mut StepCtx<'_>) -> Self::Claimed {
185        let Self {
186            persistent_dir,
187            bzip2_installed,
188        } = self;
189        ExtractTarBz2Deps {
190            persistent_dir: persistent_dir.claim(ctx),
191            bzip2_installed: bzip2_installed.claim(ctx),
192        }
193    }
194}
195
196#[track_caller]
197pub fn extract_tar_bz2_if_new_deps(ctx: &mut NodeCtx<'_>) -> ExtractTarBz2Deps {
198    ExtractTarBz2Deps {
199        persistent_dir: ctx.persistent_dir(),
200        bzip2_installed: ctx.reqv(|v| crate::install_dist_pkg::Request::Install {
201            package_names: vec!["bzip2".into()],
202            done: v,
203        }),
204    }
205}
206
207/// Extracts the given `file` into `persistent_dir` (or into
208/// [`std::env::current_dir()`], if no persistent dir is available).
209///
210/// To avoid redundant unzips between pipeline runs, callers must provide a
211/// `file_version` string that identifies the current file. If the previous run
212/// already unzipped a zip with the given `file_version`, this function will
213/// return nearly instantaneously.
214pub fn extract_tar_bz2_if_new(
215    rt: &mut RustRuntimeServices<'_>,
216    deps: ExtractTarBz2Deps<VarClaimed>,
217    file: &Path,
218    file_version: &str,
219) -> anyhow::Result<PathBuf> {
220    let ExtractTarBz2Deps {
221        persistent_dir,
222        bzip2_installed: _,
223    } = deps;
224
225    let root_dir = match persistent_dir {
226        Some(dir) => rt.read(dir),
227        None => rt.sh.current_dir(),
228    };
229
230    let filename = file.file_name().expect("tar.bz2 file was not a file");
231    let extract_dir = root_dir.join(FLOWEY_EXTRACT_DIR).join(filename);
232    fs_err::create_dir_all(&extract_dir)?;
233
234    let pkg_info_dir = root_dir.join(FLOWEY_INFO_DIR);
235    fs_err::create_dir_all(&pkg_info_dir)?;
236    let pkg_info_file = pkg_info_dir.join(filename);
237
238    let mut already_extracted = false;
239    if let Ok(info) = fs_err::read_to_string(&pkg_info_file) {
240        if info == file_version {
241            already_extracted = true;
242        }
243    }
244
245    if !already_extracted {
246        rt.sh.change_dir(&extract_dir);
247
248        // clear out any old version that was present
249        //
250        // FUTURE: maybe reconsider this approach, and keep
251        // old versions lying around, to make branch
252        // switching easier?
253        fs_err::remove_dir_all(&extract_dir)?;
254        fs_err::create_dir(&extract_dir)?;
255
256        // windows builds past Windows 10 build 17063 come with tar installed
257        flowey::shell_cmd!(rt, "tar -xf {file}").run()?;
258
259        fs_err::write(pkg_info_file, file_version)?;
260    } else {
261        log::info!("already extracted!");
262    }
263
264    Ok(extract_dir)
265}