From b848cfebb688f1fa0566debec404bd42d49eef89 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Fri, 15 Aug 2025 09:02:54 +0200 Subject: [PATCH] Ignore MSI installers with no cabs (#160) * Ignore MSI installers with no cabs At least aarch64, but possibly more, reference MSI installers that don't actually reference any cabinet files. These are utterly fucking worthless, so we just ignore them instead of show an error. VS install is such a garbage fire. * Fix lints * Rustfmt * Update CHANGELOG --- .github/workflows/rust-ci.yml | 2 +- CHANGELOG.md | 4 + src/ctx.rs | 30 +++--- src/download.rs | 14 ++- src/splat.rs | 23 ++--- src/unpack.rs | 18 ++-- tests/compiles.rs | 169 ++++++++++++++++++++++++++++++++++ 7 files changed, 219 insertions(+), 41 deletions(-) diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 42d6cef..5fa7a94 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -43,7 +43,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable # Add the actual target we compile for in the test - - run: rustup target add x86_64-pc-windows-msvc + - run: rustup target add x86_64-pc-windows-msvc aarch64-pc-windows-msvc - name: symlinks run: | set -eux diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a43d98..133c3d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Fixed +- [PR#160](https://github.com/Jake-Shadle/xwin/pull/160) resolved [#126](https://github.com/Jake-Shadle/xwin/issues/126) by ignoring MSI installers that don't reference any cabinet files. Why do such utterly fucking useless installers exist? Because fuck me I guess. +- [PR#159](https://github.com/Jake-Shadle/xwin/pull/159) fixed an issue where enabling native TLS was not actually doing anything. No one actually reported this not working which makes me think no one is using this feature. :) + ## [0.6.6] - 2025-06-19 ### Fixed - [PR#143](https://github.com/Jake-Shadle/xwin/pull/142) is a second attempt to resolve [#141](https://github.com/Jake-Shadle/xwin/issues/141) by switching to a new `3.0.0-rc1` version of ureq that might not have the same issue, as well as adding support for retries for EOF I/O errors seen by users which can be configured via `--http-retry` or `XWIN_HTTP_RETRY`. diff --git a/src/ctx.rs b/src/ctx.rs index 1e29af5..220ba64 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -366,6 +366,11 @@ impl Ctx { return Ok(None); } + let Some(payload_contents) = payload_contents else { + wi.progress.abandon_with_message("MSI with no cabs"); + return Ok(None); + }; + let ft = crate::unpack::unpack(self.clone(), &wi, payload_contents)?; if let crate::Ops::Unpack = ops { @@ -482,19 +487,18 @@ impl Ctx { unpack_dir.push(".unpack"); - if let Ok(unpack) = std::fs::read(&unpack_dir) { - if let Ok(um) = serde_json::from_slice::(&unpack) { - if payload.sha256 == um.sha256 { - tracing::debug!("already unpacked"); - unpack_dir.pop(); - return Ok(Unpack::Present { - output_dir: unpack_dir, - compressed: um.compressed, - decompressed: um.decompressed, - num_files: um.num_files, - }); - } - } + if let Ok(unpack) = std::fs::read(&unpack_dir) + && let Ok(um) = serde_json::from_slice::(&unpack) + && payload.sha256 == um.sha256 + { + tracing::debug!("already unpacked"); + unpack_dir.pop(); + return Ok(Unpack::Present { + output_dir: unpack_dir, + compressed: um.compressed, + decompressed: um.decompressed, + num_files: um.num_files, + }); } unpack_dir.pop(); diff --git a/src/download.rs b/src/download.rs index a94e996..f477287 100644 --- a/src/download.rs +++ b/src/download.rs @@ -30,7 +30,7 @@ pub(crate) fn download( ctx: Arc, pkgs: Arc>, item: &crate::WorkItem, -) -> Result { +) -> Result, Error> { item.progress.set_message("📥 downloading.."); let contents = ctx.get_and_validate( @@ -70,7 +70,7 @@ pub(crate) fn download( download_cabs(ctx, &cabs, item, contents) } - Some("vsix") => Ok(PayloadContents::Vsix(contents)), + Some("vsix") => Ok(Some(PayloadContents::Vsix(contents))), ext => anyhow::bail!("unknown extension {ext:?}"), }; @@ -86,7 +86,7 @@ fn download_cabs( cabs: &[Cab], msi: &crate::WorkItem, msi_content: bytes::Bytes, -) -> Result { +) -> Result, Error> { use rayon::prelude::*; let msi_filename = &msi.payload.filename; @@ -136,6 +136,10 @@ fn download_cabs( }) .collect(); + if cab_files.is_empty() { + return Ok(None); + } + let cabs = cab_files .into_par_iter() .map( @@ -151,8 +155,8 @@ fn download_cabs( ) .collect::, _>>()?; - Ok(PayloadContents::Msi { + Ok(Some(PayloadContents::Msi { msi: msi_content, cabs, - }) + })) } diff --git a/src/splat.rs b/src/splat.rs index c28d239..5c3edee 100644 --- a/src/splat.rs +++ b/src/splat.rs @@ -408,7 +408,7 @@ pub(crate) fn splat( mappings } PayloadKind::VcrDebug => { - if vcrd_version.is_some() { + if let Some(version) = vcrd_version { let mut src = src.clone(); let mut target = roots.vcrd.clone(); @@ -420,7 +420,7 @@ pub(crate) fn splat( }); }; - target.push(vcrd_version.unwrap()); + target.push(version); target.push("bin"); target.push(item.payload.target_arch.unwrap().as_str()); @@ -588,18 +588,15 @@ pub(crate) fn splat( if !include_debug_libs && (mapping.kind == PayloadKind::CrtLibs || mapping.kind == PayloadKind::Ucrt) + && let Some(stripped) = fname_str.strip_suffix(".lib") + && (stripped.ends_with('d') + || stripped.ends_with("d_netcore") + || stripped + .strip_suffix(|c: char| c.is_ascii_digit()) + .is_some_and(|fname| fname.ends_with('d'))) { - if let Some(stripped) = fname_str.strip_suffix(".lib") { - if stripped.ends_with('d') - || stripped.ends_with("d_netcore") - || stripped - .strip_suffix(|c: char| c.is_ascii_digit()) - .is_some_and(|fname| fname.ends_with('d')) - { - tracing::debug!("skipping {fname}"); - continue; - } - } + tracing::debug!("skipping {fname}"); + continue; } tar.push(fname); diff --git a/src/unpack.rs b/src/unpack.rs index 5a18ad1..e1cf6a5 100644 --- a/src/unpack.rs +++ b/src/unpack.rs @@ -176,11 +176,11 @@ pub(crate) fn unpack( fs_path.push(comp); } - if let Some(parent) = fs_path.parent() { - if !parent.exists() { - std::fs::create_dir_all(parent) - .with_context(|| format!("unable to create unpack dir '{parent}'"))?; - } + if let Some(parent) = fs_path.parent() + && !parent.exists() + { + std::fs::create_dir_all(parent) + .with_context(|| format!("unable to create unpack dir '{parent}'"))?; } let mut dest = std::fs::File::create(&fs_path).with_context(|| { @@ -526,10 +526,10 @@ pub(crate) fn unpack( let unpack_path = output_dir.join(&file.name); - if let Some(parent) = unpack_path.parent() { - if !parent.exists() { - std::fs::create_dir_all(parent)?; - } + if let Some(parent) = unpack_path.parent() + && !parent.exists() + { + std::fs::create_dir_all(parent)?; } let unpacked_file = std::fs::File::create(&unpack_path)?; diff --git a/tests/compiles.rs b/tests/compiles.rs index 57ded3b..f9ff3cb 100644 --- a/tests/compiles.rs +++ b/tests/compiles.rs @@ -290,3 +290,172 @@ fn verify_compiles_minimized() { assert!(cmd.status().unwrap().success()); } + +#[test] +fn verify_compiles_aarch64() { + let ctx = xwin::Ctx::with_dir( + xwin::PathBuf::from(".xwin-cache/compiles-aarch64"), + xwin::util::ProgressTarget::Hidden, + ureq::agent(), + 0, + ) + .unwrap(); + + let ctx = std::sync::Arc::new(ctx); + + let hidden = indicatif::ProgressBar::hidden(); + + // TODO: Bump to CI to 17 once github actions isn't using an ancient version, + // we could install in the action run, but not really worth it since I can + // test locally + let manifest_version = if std::env::var_os("CI").is_some() { + "16" + } else { + "17" + }; + + let manifest = + xwin::manifest::get_manifest(&ctx, manifest_version, "release", hidden.clone()).unwrap(); + let pkg_manifest = + xwin::manifest::get_package_manifest(&ctx, &manifest, hidden.clone()).unwrap(); + + let pruned = xwin::prune_pkg_list( + &pkg_manifest, + xwin::Arch::Aarch64 as u32, + xwin::Variant::Desktop as u32, + false, + false, + None, + None, + ) + .unwrap(); + + #[derive(Debug)] + enum Style { + Default, + WinSysRoot, + } + + for style in [Style::Default, Style::WinSysRoot] { + let output_dir = ctx.work_dir.join(format!("{style:?}")); + if !output_dir.exists() { + std::fs::create_dir_all(&output_dir).unwrap(); + } + + if !cfg!(target_os = "windows") && matches!(style, Style::WinSysRoot) { + continue; + } + + let op = xwin::Ops::Splat(xwin::SplatConfig { + include_debug_libs: false, + include_debug_symbols: false, + enable_symlinks: matches!(style, Style::Default), + preserve_ms_arch_notation: matches!(style, Style::WinSysRoot), + use_winsysroot_style: matches!(style, Style::WinSysRoot), + map: None, + copy: true, + output: output_dir.clone(), + }); + + ctx.clone() + .execute( + pkg_manifest.packages.clone(), + pruned + .payloads + .clone() + .into_iter() + .map(|payload| xwin::WorkItem { + progress: hidden.clone(), + payload: std::sync::Arc::new(payload), + }) + .collect(), + pruned.crt_version.clone(), + pruned.sdk_version.clone(), + pruned.vcr_version.clone(), + xwin::Arch::Aarch64 as u32, + xwin::Variant::Desktop as u32, + op, + ) + .unwrap(); + + if xwin::Path::new("tests/xwin-test/target").exists() { + std::fs::remove_dir_all("tests/xwin-test/target").expect("failed to remove target dir"); + } + + let mut cmd = std::process::Command::new("cargo"); + cmd.args([ + "build", + "--target", + "aarch64-pc-windows-msvc", + "--manifest-path", + "tests/xwin-test/Cargo.toml", + ]); + + let od = xwin::util::canonicalize(&output_dir).unwrap(); + + let includes = match style { + Style::Default => { + cmd.env("RUSTFLAGS", format!("-C linker=lld-link -Lnative={od}/crt/lib/aarch64 -Lnative={od}/sdk/lib/um/aarch64 -Lnative={od}/sdk/lib/ucrt/aarch64")); + format!( + "-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc{od}/crt/include /imsvc{od}/sdk/include/ucrt /imsvc{od}/sdk/include/um /imsvc{od}/sdk/include/shared" + ) + } + Style::WinSysRoot => { + const SEP: char = '\x1F'; + cmd.env("CARGO_ENCODED_RUSTFLAGS", format!("-C{SEP}linker=lld-link{SEP}-Lnative={od}/VC/Tools/MSVC/{crt_version}/Lib/x64{SEP}-Lnative={od}/Windows Kits/10/Lib/{sdk_version}/um/x64{SEP}-Lnative={od}/Windows Kits/10/Lib/{sdk_version}/ucrt/x64", crt_version = &pruned.crt_version, sdk_version = &pruned.sdk_version)); + + format!("-Wno-unused-command-line-argument -fuse-ld=lld-link /winsysroot {od}") + } + }; + + let cc_env = [ + ("CC_aarch64_pc_windows_msvc", "clang-cl"), + ("CXX_aarch64_pc_windows_msvc", "clang-cl"), + ("AR_aarch64_pc_windows_msvc", "llvm-lib"), + ("CFLAGS_aarch64_pc_windows_msvc", &includes), + ("CXXFLAGS_aarch64_pc_windows_msvc", &includes), + ]; + + cmd.envs(cc_env); + + assert!(cmd.status().unwrap().success()); + + // Ignore the /vctoolsdir /winsdkdir test below on CI since it fails, I'm assuming + // due to the clang version in GHA being outdated, but don't have the will to + // look into it now + if !matches!(style, Style::Default) || std::env::var("CI").is_ok() { + return; + } + + std::fs::remove_dir_all("tests/xwin-test/target").expect("failed to remove target dir"); + + let mut cmd = std::process::Command::new("cargo"); + cmd.args([ + "build", + "--target", + "aarch64-pc-windows-msvc", + "--manifest-path", + "tests/xwin-test/Cargo.toml", + ]); + + let includes = format!( + "-Wno-unused-command-line-argument -fuse-ld=lld-link /vctoolsdir {od}/crt /winsdkdir {od}/sdk" + ); + let libs = format!( + "-C linker=lld-link -Lnative={od}/crt/lib/aarch64 -Lnative={od}/sdk/lib/um/aarch64 -Lnative={od}/sdk/lib/ucrt/aarch64" + ); + + let cc_env = [ + ("CC_aarch4_pc_windows_msvc", "clang-cl"), + ("CXX_aarch64_pc_windows_msvc", "clang-cl"), + ("AR_aarch64_pc_windows_msvc", "llvm-lib"), + ("CFLAGS_aarch64_pc_windows_msvc", &includes), + ("CXXFLAGS_aarch64_pc_windows_msvc", &includes), + ("RUSTFLAGS", &libs), + ]; + + cmd.envs(cc_env); + + assert!(cmd.status().unwrap().success()); + } +}