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 => vec!["bsdtar".into()],
40                    FlowPlatformLinuxDistro::Ubuntu => vec!["libarchive-tools".into()],
41                    FlowPlatformLinuxDistro::Unknown => vec![],
42                },
43                _ => {
44                    vec![]
45                }
46            },
47            done: v,
48        }),
49    }
50}
51
52/// Extracts the given `file` into `persistent_dir` (or into
53/// [`std::env::current_dir()`], if no persistent dir is available).
54///
55/// To avoid redundant unzips between pipeline runs, callers must provide a
56/// `file_version` string that identifies the current file. If the
57/// previous run already unzipped a zip with the given `file_version`, this
58/// function will return nearly instantaneously.
59pub fn extract_zip_if_new(
60    rt: &mut RustRuntimeServices<'_>,
61    deps: ExtractZipDeps<VarClaimed>,
62    file: &Path,
63    file_version: &str,
64) -> anyhow::Result<PathBuf> {
65    let ExtractZipDeps {
66        persistent_dir,
67        bsdtar_installed: _,
68    } = deps;
69
70    let sh = xshell::Shell::new()?;
71
72    let root_dir = match persistent_dir {
73        Some(dir) => rt.read(dir),
74        None => sh.current_dir(),
75    };
76
77    let filename = file.file_name().expect("zip file was not a file");
78    let extract_dir = root_dir.join(FLOWEY_EXTRACT_DIR).join(filename);
79    fs_err::create_dir_all(&extract_dir)?;
80
81    let pkg_info_dir = root_dir.join(FLOWEY_INFO_DIR);
82    fs_err::create_dir_all(&pkg_info_dir)?;
83    let pkg_info_file = pkg_info_dir.join(filename);
84
85    let mut already_extracted = false;
86    if let Ok(info) = fs_err::read_to_string(&pkg_info_file) {
87        if info == file_version {
88            already_extracted = true;
89        }
90    }
91
92    if !already_extracted {
93        // clear out any old version that was present
94        //
95        // FUTURE: maybe reconsider this approach, and keep
96        // old versions lying around, to make branch
97        // switching easier?
98        fs_err::remove_dir_all(&extract_dir)?;
99        fs_err::create_dir(&extract_dir)?;
100
101        sh.change_dir(&extract_dir);
102
103        let bsdtar = crate::_util::bsdtar_name(rt);
104        xshell::cmd!(sh, "{bsdtar} -xf {file}").run()?;
105        fs_err::write(pkg_info_file, file_version)?;
106    } else {
107        log::info!("already extracted!");
108    }
109
110    Ok(extract_dir)
111}
112
113#[derive(Clone)]
114#[non_exhaustive]
115pub struct ExtractTarBz2Deps<C = VarNotClaimed> {
116    persistent_dir: Option<ReadVar<PathBuf, C>>,
117    lbzip2_installed: ReadVar<SideEffect, C>,
118}
119
120impl ClaimVar for ExtractTarBz2Deps {
121    type Claimed = ExtractTarBz2Deps<VarClaimed>;
122
123    fn claim(self, ctx: &mut StepCtx<'_>) -> Self::Claimed {
124        let Self {
125            persistent_dir,
126            lbzip2_installed,
127        } = self;
128        ExtractTarBz2Deps {
129            persistent_dir: persistent_dir.claim(ctx),
130            lbzip2_installed: lbzip2_installed.claim(ctx),
131        }
132    }
133}
134
135#[track_caller]
136pub fn extract_tar_bz2_if_new_deps(ctx: &mut NodeCtx<'_>) -> ExtractTarBz2Deps {
137    ExtractTarBz2Deps {
138        persistent_dir: ctx.persistent_dir(),
139        lbzip2_installed: ctx.reqv(|v| crate::install_dist_pkg::Request::Install {
140            package_names: vec!["lbzip2".into()],
141            done: v,
142        }),
143    }
144}
145
146/// Extracts the given `file` into `persistent_dir` (or into
147/// [`std::env::current_dir()`], if no persistent dir is available).
148///
149/// To avoid redundant unzips between pipeline runs, callers must provide a
150/// `file_version` string that identifies the current file. If the previous run
151/// already unzipped a zip with the given `file_version`, this function will
152/// return nearly instantaneously.
153pub fn extract_tar_bz2_if_new(
154    rt: &mut RustRuntimeServices<'_>,
155    deps: ExtractTarBz2Deps<VarClaimed>,
156    file: &Path,
157    file_version: &str,
158) -> anyhow::Result<PathBuf> {
159    let ExtractTarBz2Deps {
160        persistent_dir,
161        lbzip2_installed: _,
162    } = deps;
163
164    let sh = xshell::Shell::new()?;
165
166    let root_dir = match persistent_dir {
167        Some(dir) => rt.read(dir),
168        None => sh.current_dir(),
169    };
170
171    let filename = file.file_name().expect("tar.bz2 file was not a file");
172    let extract_dir = root_dir.join(FLOWEY_EXTRACT_DIR).join(filename);
173    fs_err::create_dir_all(&extract_dir)?;
174
175    let pkg_info_dir = root_dir.join(FLOWEY_INFO_DIR);
176    fs_err::create_dir_all(&pkg_info_dir)?;
177    let pkg_info_file = pkg_info_dir.join(filename);
178
179    let mut already_extracted = false;
180    if let Ok(info) = fs_err::read_to_string(&pkg_info_file) {
181        if info == file_version {
182            already_extracted = true;
183        }
184    }
185
186    if !already_extracted {
187        sh.change_dir(&extract_dir);
188
189        // clear out any old version that was present
190        //
191        // FUTURE: maybe reconsider this approach, and keep
192        // old versions lying around, to make branch
193        // switching easier?
194        fs_err::remove_dir_all(&extract_dir)?;
195        fs_err::create_dir(&extract_dir)?;
196
197        // windows builds past Windows 10 build 17063 come with tar installed
198        xshell::cmd!(sh, "tar -xf {file}").run()?;
199
200        fs_err::write(pkg_info_file, file_version)?;
201    } else {
202        log::info!("already extracted!");
203    }
204
205    Ok(extract_dir)
206}