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::Nix => vec![],
43                    FlowPlatformLinuxDistro::Unknown => vec![],
44                },
45                _ => {
46                    vec![]
47                }
48            },
49            done: v,
50        }),
51    }
52}
53
54/// Extracts the given `file` into `persistent_dir` (or into
55/// [`std::env::current_dir()`], if no persistent dir is available).
56///
57/// To avoid redundant unzips between pipeline runs, callers must provide a
58/// `file_version` string that identifies the current file. If the
59/// previous run already unzipped a zip with the given `file_version`, this
60/// function will return nearly instantaneously.
61pub fn extract_zip_if_new(
62    rt: &mut RustRuntimeServices<'_>,
63    deps: ExtractZipDeps<VarClaimed>,
64    file: &Path,
65    file_version: &str,
66) -> anyhow::Result<PathBuf> {
67    let ExtractZipDeps {
68        persistent_dir,
69        bsdtar_installed: _,
70    } = deps;
71
72    let root_dir = match persistent_dir {
73        Some(dir) => rt.read(dir),
74        None => rt.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        rt.sh.change_dir(&extract_dir);
102
103        let bsdtar = crate::_util::bsdtar_name(rt);
104        flowey::shell_cmd!(rt, "{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    bzip2_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            bzip2_installed,
127        } = self;
128        ExtractTarBz2Deps {
129            persistent_dir: persistent_dir.claim(ctx),
130            bzip2_installed: bzip2_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        bzip2_installed: ctx.reqv(|v| crate::install_dist_pkg::Request::Install {
140            package_names: vec!["bzip2".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        bzip2_installed: _,
162    } = deps;
163
164    let root_dir = match persistent_dir {
165        Some(dir) => rt.read(dir),
166        None => rt.sh.current_dir(),
167    };
168
169    let filename = file.file_name().expect("tar.bz2 file was not a file");
170    let extract_dir = root_dir.join(FLOWEY_EXTRACT_DIR).join(filename);
171    fs_err::create_dir_all(&extract_dir)?;
172
173    let pkg_info_dir = root_dir.join(FLOWEY_INFO_DIR);
174    fs_err::create_dir_all(&pkg_info_dir)?;
175    let pkg_info_file = pkg_info_dir.join(filename);
176
177    let mut already_extracted = false;
178    if let Ok(info) = fs_err::read_to_string(&pkg_info_file) {
179        if info == file_version {
180            already_extracted = true;
181        }
182    }
183
184    if !already_extracted {
185        rt.sh.change_dir(&extract_dir);
186
187        // clear out any old version that was present
188        //
189        // FUTURE: maybe reconsider this approach, and keep
190        // old versions lying around, to make branch
191        // switching easier?
192        fs_err::remove_dir_all(&extract_dir)?;
193        fs_err::create_dir(&extract_dir)?;
194
195        // windows builds past Windows 10 build 17063 come with tar installed
196        flowey::shell_cmd!(rt, "tar -xf {file}").run()?;
197
198        fs_err::write(pkg_info_file, file_version)?;
199    } else {
200        log::info!("already extracted!");
201    }
202
203    Ok(extract_dir)
204}