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#[derive(Clone)]
118#[non_exhaustive]
119pub struct ExtractTarBz2Deps<C = VarNotClaimed> {
120    persistent_dir: Option<ReadVar<PathBuf, C>>,
121    bzip2_installed: ReadVar<SideEffect, C>,
122}
123
124impl ClaimVar for ExtractTarBz2Deps {
125    type Claimed = ExtractTarBz2Deps<VarClaimed>;
126
127    fn claim(self, ctx: &mut StepCtx<'_>) -> Self::Claimed {
128        let Self {
129            persistent_dir,
130            bzip2_installed,
131        } = self;
132        ExtractTarBz2Deps {
133            persistent_dir: persistent_dir.claim(ctx),
134            bzip2_installed: bzip2_installed.claim(ctx),
135        }
136    }
137}
138
139#[track_caller]
140pub fn extract_tar_bz2_if_new_deps(ctx: &mut NodeCtx<'_>) -> ExtractTarBz2Deps {
141    ExtractTarBz2Deps {
142        persistent_dir: ctx.persistent_dir(),
143        bzip2_installed: ctx.reqv(|v| crate::install_dist_pkg::Request::Install {
144            package_names: vec!["bzip2".into()],
145            done: v,
146        }),
147    }
148}
149
150/// Extracts the given `file` into `persistent_dir` (or into
151/// [`std::env::current_dir()`], if no persistent dir is available).
152///
153/// To avoid redundant unzips between pipeline runs, callers must provide a
154/// `file_version` string that identifies the current file. If the previous run
155/// already unzipped a zip with the given `file_version`, this function will
156/// return nearly instantaneously.
157pub fn extract_tar_bz2_if_new(
158    rt: &mut RustRuntimeServices<'_>,
159    deps: ExtractTarBz2Deps<VarClaimed>,
160    file: &Path,
161    file_version: &str,
162) -> anyhow::Result<PathBuf> {
163    let ExtractTarBz2Deps {
164        persistent_dir,
165        bzip2_installed: _,
166    } = deps;
167
168    let root_dir = match persistent_dir {
169        Some(dir) => rt.read(dir),
170        None => rt.sh.current_dir(),
171    };
172
173    let filename = file.file_name().expect("tar.bz2 file was not a file");
174    let extract_dir = root_dir.join(FLOWEY_EXTRACT_DIR).join(filename);
175    fs_err::create_dir_all(&extract_dir)?;
176
177    let pkg_info_dir = root_dir.join(FLOWEY_INFO_DIR);
178    fs_err::create_dir_all(&pkg_info_dir)?;
179    let pkg_info_file = pkg_info_dir.join(filename);
180
181    let mut already_extracted = false;
182    if let Ok(info) = fs_err::read_to_string(&pkg_info_file) {
183        if info == file_version {
184            already_extracted = true;
185        }
186    }
187
188    if !already_extracted {
189        rt.sh.change_dir(&extract_dir);
190
191        // clear out any old version that was present
192        //
193        // FUTURE: maybe reconsider this approach, and keep
194        // old versions lying around, to make branch
195        // switching easier?
196        fs_err::remove_dir_all(&extract_dir)?;
197        fs_err::create_dir(&extract_dir)?;
198
199        // windows builds past Windows 10 build 17063 come with tar installed
200        flowey::shell_cmd!(rt, "tar -xf {file}").run()?;
201
202        fs_err::write(pkg_info_file, file_version)?;
203    } else {
204        log::info!("already extracted!");
205    }
206
207    Ok(extract_dir)
208}