From caf1dadaaac556dc32ea071bff46545672167bb4 Mon Sep 17 00:00:00 2001 From: Yeoh Joer Date: Mon, 14 Jul 2025 18:25:42 +0800 Subject: [PATCH 1/5] feat: support extracting 7z archives for static backends --- src/file.rs | 30 ++++++++++++++++++++++++++++-- src/plugins/core/ruby_windows.rs | 2 +- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/file.rs b/src/file.rs index bcb6f89154..7d084c504e 100644 --- a/src/file.rs +++ b/src/file.rs @@ -645,6 +645,8 @@ pub enum TarFormat { TarZst, #[strum(serialize = "zip")] Zip, + #[strum(serialize = "7z")] + SevenZip, #[strum(serialize = "raw")] Raw, } @@ -657,6 +659,7 @@ impl TarFormat { "bz2" | "tbz2" => TarFormat::TarBz2, "zst" | "tzst" => TarFormat::TarZst, "zip" => TarFormat::Zip, + "7z" => TarFormat::SevenZip, _ => TarFormat::Raw, } } @@ -684,7 +687,17 @@ pub fn untar(archive: &Path, dest: &Path, opts: &TarOptions) -> Result<()> { strip_components: opts.strip_components, }, ); + } else if format == TarFormat::SevenZip { + #[cfg(windows)] + return un7z( + archive, + dest, + &SevenZipOptions { + strip_components: opts.strip_components, + }, + ); } + debug!("tar -xf {} -C {}", archive.display(), dest.display()); if let Some(pr) = &opts.pr { pr.set_message(format!( @@ -738,6 +751,7 @@ fn open_tar(format: TarFormat, archive: &Path) -> Result> TarFormat::TarBz2 => Box::new(BzDecoder::new(f)), TarFormat::TarZst => Box::new(zstd::stream::read::Decoder::new(f)?), TarFormat::Zip => bail!("zip format not supported"), + TarFormat::SevenZip => bail!("7z format not supported"), TarFormat::Auto => match archive.extension().and_then(|s| s.to_str()) { Some("xz") => open_tar(TarFormat::TarXz, archive)?, Some("bz2") => open_tar(TarFormat::TarBz2, archive)?, @@ -824,10 +838,22 @@ pub fn un_pkg(archive: &Path, dest: &Path) -> Result<()> { Ok(()) } +#[derive(Default)] +pub struct SevenZipOptions { + pub strip_components: usize, +} + #[cfg(windows)] -pub fn un7z(archive: &Path, dest: &Path) -> Result<()> { +pub fn un7z(archive: &Path, dest: &Path, opts: &SevenZipOptions) -> Result<()> { sevenz_rust::decompress_file(archive, dest) - .wrap_err_with(|| format!("failed to extract 7z archive: {}", display_path(archive))) + .wrap_err_with(|| format!("failed to extract 7z archive: {}", display_path(archive)))?; + + strip_archive_path_components(dest, opts.strip_components).wrap_err_with(|| { + format!( + "failed to strip path components from 7z archive: {}", + display_path(archive) + ) + }) } pub fn split_file_name(path: &Path) -> (String, String) { diff --git a/src/plugins/core/ruby_windows.rs b/src/plugins/core/ruby_windows.rs index 21be9a3d25..567164cd5c 100644 --- a/src/plugins/core/ruby_windows.rs +++ b/src/plugins/core/ruby_windows.rs @@ -134,7 +134,7 @@ impl RubyPlugin { let filename = tarball_path.file_name().unwrap().to_string_lossy(); ctx.pr.set_message(format!("extract {filename}")); file::remove_all(tv.install_path())?; - file::un7z(tarball_path, &tv.download_path())?; + file::un7z(tarball_path, &tv.download_path(), &Default::default())?; file::rename( tv.download_path() .join(format!("rubyinstaller-{}-1-{arch}", tv.version)), From 3b336b469cb9fcf20d5c0f12b071715f466d2c3a Mon Sep 17 00:00:00 2001 From: Yeoh Joer Date: Mon, 14 Jul 2025 21:15:21 +0800 Subject: [PATCH 2/5] feat: support should_strip_components --- src/file.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/file.rs b/src/file.rs index 7d084c504e..b5240e6804 100644 --- a/src/file.rs +++ b/src/file.rs @@ -838,6 +838,7 @@ pub fn un_pkg(archive: &Path, dest: &Path) -> Result<()> { Ok(()) } +#[cfg(windows)] #[derive(Default)] pub struct SevenZipOptions { pub strip_components: usize, @@ -943,10 +944,42 @@ pub fn inspect_zip_contents(archive: &Path) -> Result> { Ok(top_level_components.into_iter().collect()) } +/// Adapted from inspect_tar_contents for 7z archives +#[cfg(windows)] +pub fn inspect_7z_contents(archive: &Path) -> Result> { + let sevenz = sevenz_rust::SevenZReader::open(archive, sevenz_rust::Password::empty())?; + let mut top_level_components = std::collections::HashMap::new(); + + for file in &sevenz.archive().files { + let path = PathBuf::from(file.name()); + + if let Some(first_component) = path.components().next() { + let name = first_component.as_os_str().to_string_lossy().to_string(); + let is_directory = file.is_directory() || path.components().count() > 1; + + let existing = top_level_components.entry(name.clone()).or_insert(false); + *existing = *existing || is_directory; + } + } + + Ok(top_level_components.into_iter().collect()) +} + /// Determines if strip_components=1 should be applied based on archive structure pub fn should_strip_components(archive: &Path, format: TarFormat) -> Result { let top_level_entries = match format { TarFormat::Zip => inspect_zip_contents(archive)?, + TarFormat::SevenZip => { + #[cfg(windows)] + { + inspect_7z_contents(archive)? + } + + #[cfg(not(windows))] + { + bail!("7z format not supported on this platform"); + } + } _ => inspect_tar_contents(archive, format)?, }; From 23b61818db5738d35588ef59e72aa766b97f3e8b Mon Sep 17 00:00:00 2001 From: Yeoh Joer Date: Tue, 15 Jul 2025 05:04:56 +0800 Subject: [PATCH 3/5] test: add an integration test for 7z archives --- e2e-win/7z.Tests.ps1 | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 e2e-win/7z.Tests.ps1 diff --git a/e2e-win/7z.Tests.ps1 b/e2e-win/7z.Tests.ps1 new file mode 100644 index 0000000000..b5a713fab1 --- /dev/null +++ b/e2e-win/7z.Tests.ps1 @@ -0,0 +1,20 @@ +Describe '7z' { + BeforeAll { + $cfg = ".\mise.local.toml" + $content = @" +[tools] +"github:ip7z/7zip" = { version = "25.00", asset_pattern = "*-extra.7z" } +"@ + $content | Out-File $cfg + Get-Content $cfg + } + + AfterAll { + Remove-Item $cfg -ErrorAction Ignore + } + + It 'executes 7za 25.00' { + mise install + mise x -- 7za | Out-String | Should -Match "7-Zip \(a\) 25\.00" + } +} From ed09d051a89e39edc0dc72c09d77e7064a831a3e Mon Sep 17 00:00:00 2001 From: Yeoh Joer Date: Tue, 15 Jul 2025 18:17:48 +0800 Subject: [PATCH 4/5] test: add another integration test where the automatic strip components take place --- e2e-win/7z.Tests.ps1 | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/e2e-win/7z.Tests.ps1 b/e2e-win/7z.Tests.ps1 index b5a713fab1..8a506e482d 100644 --- a/e2e-win/7z.Tests.ps1 +++ b/e2e-win/7z.Tests.ps1 @@ -15,6 +15,27 @@ Describe '7z' { It 'executes 7za 25.00' { mise install - mise x -- 7za | Out-String | Should -Match "7-Zip \(a\) 25\.00" + mise x github:ip7z/7zip -- 7za | Out-String | Should -Match "7-Zip \(a\) 25\.00" + } +} + +Describe '7z-strip-components' { + BeforeAll { + $cfg = ".\mise.local.toml" + $content = @" +[tools] +"http:ip7z/7zip" = { version = "25.00", url = "https://mise.jdx.dev/test-fixtures/7z2500-extra.7z" } +"@ + $content | Out-File $cfg + Get-Content $cfg + } + + AfterAll { + Remove-Item $cfg -ErrorAction Ignore + } + + It 'executes 7za 25.00' { + mise install + mise x http:ip7z/7zip -- 7za | Out-String | Should -Match "7-Zip \(a\) 25\.00" } } From b67227618491d5d2538fd7b1f12992bca36552d3 Mon Sep 17 00:00:00 2001 From: Yeoh Joer Date: Tue, 15 Jul 2025 21:03:24 +0800 Subject: [PATCH 5/5] refactor: panic when encountering 7z archives on non-windows platforms --- src/file.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/file.rs b/src/file.rs index b5240e6804..932f43d378 100644 --- a/src/file.rs +++ b/src/file.rs @@ -965,21 +965,16 @@ pub fn inspect_7z_contents(archive: &Path) -> Result> { Ok(top_level_components.into_iter().collect()) } +#[cfg(not(windows))] +pub fn inspect_7z_contents(_archive: &Path) -> Result> { + unimplemented!("7z format not supported on this platform") +} + /// Determines if strip_components=1 should be applied based on archive structure pub fn should_strip_components(archive: &Path, format: TarFormat) -> Result { let top_level_entries = match format { TarFormat::Zip => inspect_zip_contents(archive)?, - TarFormat::SevenZip => { - #[cfg(windows)] - { - inspect_7z_contents(archive)? - } - - #[cfg(not(windows))] - { - bail!("7z format not supported on this platform"); - } - } + TarFormat::SevenZip => inspect_7z_contents(archive)?, _ => inspect_tar_contents(archive, format)?, };