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