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()); + } +}