From ead35f44a5a555c3e476ff8a05587ede76671f2a Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Fri, 8 May 2026 20:27:53 +0800 Subject: [PATCH 01/15] Run Windows backend tests without native Foundry startup Windows CI needs to execute the Rust backend unit tests, not stop at --no-run, but the Foundry WinML SDK linked into the default Windows build exits the GitHub Windows Server test process before the Rust harness starts with STATUS_ENTRYPOINT_NOT_FOUND. The product default remains unchanged: the default feature set still includes Foundry Local runtime support and cargo check continues to cover that linked shape.\n\nFor the Windows unit-test gate, build the lib tests without the native Foundry runtime feature so the harness can start and exercise coordinator, hotkey, insertion, recorder, persistence, and command tests on Windows. The Foundry runtime implementation now has an explicit unavailable stub for Windows builds that disable the feature, matching the existing non-Windows fallback shape.\n\nConstraint: GitHub Windows Server runners cannot currently execute the default Foundry/WinML-linked test harness before Rust tests start.\n\nRejected: Keep --no-run permanently | it leaves Windows-specific unit regressions unexecuted.\n\nRejected: Install Windows App Runtime and Foundry DLLs in CI | the harness still failed before running tests.\n\nRejected: Put Foundry DLLs on PATH | it risks loader pollution and did not resolve the startup failure.\n\nConfidence: medium\n\nScope-risk: moderate\n\nDirective: Keep default product builds on foundry-local-runtime unless the local ASR product decision changes; the no-default Windows CI command is specifically a unit-test harness escape hatch.\n\nTested: git diff --check; cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib; cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol.\n\nNot-tested: Windows GitHub runner execution after the no-Foundry test-harness split before PR CI. --- .github/workflows/ci.yml | 37 ++++++++++++++++--- openless-all/app/src-tauri/Cargo.toml | 5 ++- .../src/asr/local/foundry_runtime.rs | 37 +++++++++++++------ 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f7453e3..34726c2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,10 @@ jobs: - os: macos-latest label: macOS preflight: false - - os: windows-latest + # windows-latest 当前指向 Windows Server 2025;Rust lib test harness + # 在该镜像启动时会遇到 STATUS_ENTRYPOINT_NOT_FOUND。CI 先固定到 + # Windows Server 2022,确保真正执行后端单测,而不是退回 --no-run。 + - os: windows-2022 label: Windows preflight: true - os: ubuntu-latest @@ -95,12 +98,34 @@ jobs: if: runner.os != 'Windows' run: cargo test --manifest-path src-tauri/Cargo.toml --lib - - name: Compile Rust backend unit tests (Windows) - # Windows runner 能链接 lib test binary,但干净镜像缺少可选 native runtime - # DLL entrypoint 时,进程会在 test harness 启动前退出。这里保留 cfg/link - # 覆盖;共享单测在 macOS / Linux 上实际执行。 + - name: Run Rust backend unit tests (Windows) if: runner.os == 'Windows' - run: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-run + run: | + # Foundry/WinML SDK 在 GitHub Windows Server runner 上会让 test + # harness 启动前因 native entrypoint 退出;产品默认构建仍通过 + # cargo check 覆盖该链接形态,单测门禁用无 native Foundry 链接的 + # 构建来真正执行 Windows 后端测试。 + cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol + $testExit = $LASTEXITCODE + if ($testExit -ne 0) { + $testExe = Get-ChildItem -Path "src-tauri\target\debug\deps" -Filter "openless_lib-*.exe" | + Sort-Object LastWriteTime -Descending | + Select-Object -First 1 + if ($testExe) { + Write-Host "[ci] failed test exe: $($testExe.FullName)" + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + if (Test-Path $vswhere) { + $dumpbin = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -find "VC\Tools\MSVC\**\bin\Hostx64\x64\dumpbin.exe" 2>$null | Select-Object -First 1 + if ($dumpbin -and (Test-Path $dumpbin)) { + Write-Host "[ci] dumpbin dependents" + & $dumpbin /dependents $testExe.FullName + Write-Host "[ci] dumpbin imports summary" + & $dumpbin /imports $testExe.FullName | Select-String -Pattern "DLL Name|api-ms|VCRUNTIME|WebView2|Foundry|onnx|Mdd|WaitOnAddress|GetThreadDescription|LocalFree" + } + } + } + exit $testExit + } - name: Verify version sync across all 5 files # 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。 diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index eead6568..132ddecb 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -78,7 +78,7 @@ objc2-app-kit = "0.2" libc = "0.2" [target.'cfg(target_os = "windows")'.dependencies] -foundry-local-sdk = { version = "1.1.0", features = ["winml"] } +foundry-local-sdk = { version = "1.1.0", features = ["winml"], optional = true } raw-window-handle = "0.6" windows = { version = "0.58", features = [ "Win32_Foundation", @@ -103,5 +103,6 @@ winreg = "0.52" window-vibrancy = "0.7" [features] -default = ["custom-protocol"] +default = ["custom-protocol", "foundry-local-runtime"] custom-protocol = ["tauri/custom-protocol"] +foundry-local-runtime = ["dep:foundry-local-sdk"] diff --git a/openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs b/openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs index 7a762fcb..3f8676fb 100644 --- a/openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs +++ b/openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs @@ -1,4 +1,4 @@ -#[cfg(target_os = "windows")] +#[cfg(all(target_os = "windows", feature = "foundry-local-runtime"))] #[allow(dead_code)] mod imp { use std::path::{Path, PathBuf}; @@ -579,23 +579,27 @@ mod imp { } } -#[cfg(target_os = "windows")] +#[cfg(all(target_os = "windows", feature = "foundry-local-runtime"))] pub use imp::FoundryLocalRuntime; -#[cfg(not(target_os = "windows"))] -pub struct FoundryLocalRuntime; +#[cfg(any(not(target_os = "windows"), all(target_os = "windows", not(feature = "foundry-local-runtime"))))] +pub struct FoundryLocalRuntime { + cancel_prepare: std::sync::atomic::AtomicBool, +} -#[cfg(not(target_os = "windows"))] +#[cfg(any(not(target_os = "windows"), all(target_os = "windows", not(feature = "foundry-local-runtime"))))] impl Default for FoundryLocalRuntime { fn default() -> Self { Self::new() } } -#[cfg(not(target_os = "windows"))] +#[cfg(any(not(target_os = "windows"), all(target_os = "windows", not(feature = "foundry-local-runtime"))))] impl FoundryLocalRuntime { pub fn new() -> Self { - Self + Self { + cancel_prepare: std::sync::atomic::AtomicBool::new(false), + } } pub async fn status_snapshot( @@ -605,7 +609,7 @@ impl FoundryLocalRuntime { ) -> super::foundry::FoundryRuntimeStatus { let mut status = super::foundry::FoundryRuntimeStatus::unavailable( active_model.to_string(), - "Foundry Local Whisper is only available on Windows", + "Foundry Local Whisper is unavailable in this build", ); status.runtime_source = super::foundry_native::normalize_runtime_source_str(runtime_source); status @@ -616,7 +620,7 @@ impl FoundryLocalRuntime { alias: &str, _runtime_source: &str, ) -> anyhow::Result { - anyhow::bail!("Foundry Local Whisper is only available on Windows: {alias}"); + anyhow::bail!("Foundry Local Whisper is unavailable in this build: {alias}"); } pub async fn ensure_loaded_with_progress( @@ -628,10 +632,19 @@ impl FoundryLocalRuntime { where F: Fn(super::foundry::FoundryPrepareProgressPayload) + Send + Sync + 'static, { - anyhow::bail!("Foundry Local Whisper is only available on Windows: {alias}"); + anyhow::bail!("Foundry Local Whisper is unavailable in this build: {alias}"); + } + + pub fn request_cancel_prepare(&self) { + self.cancel_prepare + .store(true, std::sync::atomic::Ordering::SeqCst); } - pub fn request_cancel_prepare(&self) {} + #[cfg(test)] + pub(crate) fn cancel_prepare_requested_for_tests(&self) -> bool { + self.cancel_prepare + .load(std::sync::atomic::Ordering::SeqCst) + } pub async fn catalog_snapshot( &self, @@ -647,7 +660,7 @@ impl FoundryLocalRuntime { _audio_path: &std::path::Path, _audio_timeout: std::time::Duration, ) -> anyhow::Result { - anyhow::bail!("Foundry Local Whisper is only available on Windows: {alias}"); + anyhow::bail!("Foundry Local Whisper is unavailable in this build: {alias}"); } pub async fn release_now(&self) -> anyhow::Result<()> { From c25a1a0fe505fd0b793d6f175f19eb17a3e2bf9b Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Fri, 8 May 2026 23:08:49 +0800 Subject: [PATCH 02/15] Isolate Windows unit tests from native runtime artifacts Windows CI first checks the default Tauri backend build, then runs unit tests with Foundry disabled so the test harness can execute on the hosted runner. Keeping those builds in one target directory can leave unrelated native artifacts in the loader search area, so the unit-test lane now uses a dedicated target directory while the product default feature set remains covered by cargo check. Constraint: Windows hosted runner fails before Rust tests start with STATUS_ENTRYPOINT_NOT_FOUND after the default-feature check step.\nRejected: Remove the default-feature cargo check | that would stop covering the product Foundry-enabled build shape.\nConfidence: medium\nScope-risk: narrow\nDirective: Keep the Windows unit-test target directory separate from the default-feature check unless the native Foundry runner issue is proven gone.\nTested: git diff --check\nTested: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34726c2d..55db1873 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,14 +105,19 @@ jobs: # harness 启动前因 native entrypoint 退出;产品默认构建仍通过 # cargo check 覆盖该链接形态,单测门禁用无 native Foundry 链接的 # 构建来真正执行 Windows 后端测试。 + # 单测使用独立 target 目录,避免前面的默认特性 cargo check 产物 + # 影响无 Foundry native 链接的测试进程 DLL 搜索路径。 + $env:CARGO_TARGET_DIR = Join-Path (Get-Location) "src-tauri\target\windows-unit-tests" cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol $testExit = $LASTEXITCODE if ($testExit -ne 0) { - $testExe = Get-ChildItem -Path "src-tauri\target\debug\deps" -Filter "openless_lib-*.exe" | + $testExe = Get-ChildItem -Path (Join-Path $env:CARGO_TARGET_DIR "debug\deps") -Filter "openless_lib-*.exe" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if ($testExe) { Write-Host "[ci] failed test exe: $($testExe.FullName)" + Write-Host "[ci] dlls beside failed test exe" + Get-ChildItem -Path $testExe.DirectoryName -Filter "*.dll" | Select-Object Name,Length | Format-Table -AutoSize $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" if (Test-Path $vswhere) { $dumpbin = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -find "VC\Tools\MSVC\**\bin\Hostx64\x64\dumpbin.exe" 2>$null | Select-Object -First 1 From c10513941ec7acdadc35921b36318747278242a9 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Fri, 8 May 2026 23:20:33 +0800 Subject: [PATCH 03/15] Pin Windows Rust tests to lockfile MSRV The Windows runner still fails before executing the lib test binary when newest stable links the harness to WaitOnAddress through api-ms-win-core-synch-l1-2-0.dll. The lockfile already requires Rust 1.88, so the Windows unit-test lane now uses that minimum compatible toolchain while macOS and Linux continue tracking stable. Constraint: CI must run the Windows tests instead of returning to cargo test --no-run.\nConstraint: Current dependency graph requires Rust 1.88 or newer.\nRejected: Pin all platforms | the failure is Windows-runner-specific and broadens CI drift unnecessarily.\nConfidence: medium\nScope-risk: narrow\nDirective: Do not raise the Windows CI toolchain without checking that the test executable starts on the hosted Windows runner.\nTested: git diff --check\nTested: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55db1873..ae772f68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,15 +23,20 @@ jobs: - os: macos-latest label: macOS preflight: false + rust: stable # windows-latest 当前指向 Windows Server 2025;Rust lib test harness # 在该镜像启动时会遇到 STATUS_ENTRYPOINT_NOT_FOUND。CI 先固定到 # Windows Server 2022,确保真正执行后端单测,而不是退回 --no-run。 - os: windows-2022 label: Windows preflight: true + # 1.88 是当前锁文件依赖的最高 MSRV;避免 newest stable + # 生成的 test harness 在 Windows runner 上导入缺失 entrypoint。 + rust: 1.88.0 - os: ubuntu-latest label: Linux preflight: false + rust: stable runs-on: ${{ matrix.os }} defaults: run: @@ -50,6 +55,10 @@ jobs: cache-dependency-path: openless-all/app/package-lock.json - uses: dtolnay/rust-toolchain@stable + if: matrix.rust == 'stable' + + - uses: dtolnay/rust-toolchain@1.88.0 + if: matrix.rust == '1.88.0' - name: Install Linux check dependencies if: runner.os == 'Linux' From baf444ee413158b9ae936f3885b70cb34e7e1918 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Fri, 8 May 2026 23:32:52 +0800 Subject: [PATCH 04/15] Avoid Windows test loader imports from broad threading bindings Windows CI now runs Rust unit tests for real, so the test executable must not import APIs that are unavailable in the runner loader environment. The only local use of the broad Win32_System_Threading binding was GetCurrentThreadId for the hotkey hook shutdown path, while that feature also pulled WaitOnAddress through the generated windows crate bindings. Constraint: Windows CI failed before tests started with STATUS_ENTRYPOINT_NOT_FOUND on api-ms-win-core-synch-l1-2-0.dll / WaitOnAddress. Rejected: Return to cargo test --no-run | issue #295 requires actual backend test execution on Windows. Rejected: Keep Win32_System_Threading and rely on a newer runner | the failure persisted after pinning Rust to the lockfile MSRV. Confidence: medium Scope-risk: narrow Directive: Keep the Windows test binary free of broad generated bindings that import unavailable loader symbols unless CI proves they execute on GitHub runners. Tested: git diff --check Tested: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol Tested: cargo check --manifest-path src-tauri/Cargo.toml --- openless-all/app/src-tauri/Cargo.toml | 1 - openless-all/app/src-tauri/src/hotkey.rs | 9 +++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index 132ddecb..0ea48658 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -90,7 +90,6 @@ windows = { version = "0.58", features = [ "Win32_System_Com", "Win32_System_Ole", "Win32_System_Registry", - "Win32_System_Threading", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_TextServices", diff --git a/openless-all/app/src-tauri/src/hotkey.rs b/openless-all/app/src-tauri/src/hotkey.rs index 38bc516f..9888be9a 100644 --- a/openless-all/app/src-tauri/src/hotkey.rs +++ b/openless-all/app/src-tauri/src/hotkey.rs @@ -691,7 +691,6 @@ mod platform { use std::sync::Arc; use windows::Win32::Foundation::{LPARAM, LRESULT, WPARAM}; - use windows::Win32::System::Threading::GetCurrentThreadId; use windows::Win32::UI::WindowsAndMessaging::{ CallNextHookEx, DispatchMessageW, GetMessageW, PostThreadMessageW, SetWindowsHookExW, TranslateMessage, UnhookWindowsHookEx, HC_ACTION, HHOOK, KBDLLHOOKSTRUCT, MSG, @@ -723,6 +722,12 @@ mod platform { static HOOK_CONTEXT: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + #[link(name = "kernel32")] + unsafe extern "system" { + #[link_name = "GetCurrentThreadId"] + fn get_current_thread_id() -> u32; + } + pub fn start_adapter( binding: HotkeyBinding, tx: Sender, @@ -786,7 +791,7 @@ mod platform { unsafe impl Sync for CallbackContext {} fn run_listen_loop(shared: Arc, tx: Sender, status_tx: StartupTx) { - let thread_id = unsafe { GetCurrentThreadId() }; + let thread_id = unsafe { get_current_thread_id() }; let context = Box::into_raw(Box::new(CallbackContext { shared, tx, From c50fff63e04436a5b03dd4fa46d10ae464dd6217 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Fri, 8 May 2026 23:48:41 +0800 Subject: [PATCH 05/15] Provide Windows API-set stub before running Rust tests The previous fix removed a broad project-level Threading binding, but CI evidence showed the Rust test executable still imports WaitOnAddress before tests start. Keep the no-Foundry test build, compile the test binary first, place the official Windows Kits x64 API-set redist DLL beside it, and then run cargo test so the Windows backend tests still execute for real. Constraint: GitHub Windows Server 2022 runner exits the Rust test process with STATUS_ENTRYPOINT_NOT_FOUND for api-ms-win-core-synch-l1-2-0.dll / WaitOnAddress. Rejected: cargo test --no-run as the final gate | issue #295 requires actual test execution. Rejected: Disable Foundry Local product defaults | only the CI test harness should remove native Foundry startup. Confidence: medium Scope-risk: narrow Directive: The no-run phase is only preparation for DLL placement; do not remove the following cargo test execution. Tested: git diff --check Not-tested: Windows runner API-set redist path until PR CI reruns --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae772f68..ec9f2de9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,8 @@ jobs: - os: windows-2022 label: Windows preflight: true - # 1.88 是当前锁文件依赖的最高 MSRV;避免 newest stable - # 生成的 test harness 在 Windows runner 上导入缺失 entrypoint。 + # 1.88 是当前锁文件依赖的最高 MSRV;Windows 单测用这个版本 + # 固定住编译器变量,便于定位 runner / API-set 兼容问题。 rust: 1.88.0 - os: ubuntu-latest label: Linux @@ -110,14 +110,46 @@ jobs: - name: Run Rust backend unit tests (Windows) if: runner.os == 'Windows' run: | - # Foundry/WinML SDK 在 GitHub Windows Server runner 上会让 test - # harness 启动前因 native entrypoint 退出;产品默认构建仍通过 - # cargo check 覆盖该链接形态,单测门禁用无 native Foundry 链接的 - # 构建来真正执行 Windows 后端测试。 + # Foundry/WinML SDK 以及 Rust 1.78+ 的 Windows test harness 在 + # GitHub runner 上都可能让进程启动前因 native entrypoint 退出。 + # 产品默认构建仍通过 cargo check 覆盖 Foundry 链接形态,单测门禁 + # 用无 Foundry native 链接的构建来真正执行 Windows 后端测试。 # 单测使用独立 target 目录,避免前面的默认特性 cargo check 产物 # 影响无 Foundry native 链接的测试进程 DLL 搜索路径。 $env:CARGO_TARGET_DIR = Join-Path (Get-Location) "src-tauri\target\windows-unit-tests" - cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol + $testArgs = @( + "--manifest-path", "src-tauri/Cargo.toml", + "--lib", + "--no-default-features", + "--features", "custom-protocol" + ) + + # rustc 1.78+ 生成的 Windows 二进制会导入 WaitOnAddress 所在的 + # api-ms-win-core-synch-l1-2-0.dll。GitHub runner 上该 API-set + # 解析偶发缺 entrypoint;先 no-run 产出测试 exe,再把 Windows Kits + # 官方 x64 Redist stub 放到测试 exe 目录,最后仍然执行 cargo test。 + cargo test @testArgs --no-run + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + + $depsDir = Join-Path $env:CARGO_TARGET_DIR "debug\deps" + $windowsKitsRedist = Join-Path ${env:ProgramFiles(x86)} "Windows Kits\10\Redist" + $syncDll = $null + if (Test-Path $windowsKitsRedist) { + $syncDll = Get-ChildItem -Path $windowsKitsRedist -Recurse -Filter "api-ms-win-core-synch-l1-2-0.dll" -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -match "\\x64\\" } | + Sort-Object FullName -Descending | + Select-Object -First 1 + } + if ($syncDll) { + Write-Host "[ci] Copy Windows Kits API-set stub for Rust test harness: $($syncDll.FullName)" + Copy-Item -Path $syncDll.FullName -Destination $depsDir -Force + } else { + Write-Host "[ci] Windows Kits API-set stub not found; running tests without local stub" + } + + cargo test @testArgs $testExit = $LASTEXITCODE if ($testExit -ne 0) { $testExe = Get-ChildItem -Path (Join-Path $env:CARGO_TARGET_DIR "debug\deps") -Filter "openless_lib-*.exe" | From c8a54d9ddca85c4ebf12f6b37956f08ef9d39116 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Fri, 8 May 2026 23:59:38 +0800 Subject: [PATCH 06/15] Build a Windows WaitOnAddress shim for Rust tests Copying the Windows Kits API-set redist DLL did not resolve the runner loader failure because that stub still lacked the imported WaitOnAddress export. The CI harness now builds a tiny x64 DLL named api-ms-win-core-synch-l1-2-0.dll beside the Rust test executable; it forwards WaitOnAddress and WakeByAddress* to KernelBase/kernel32, then cargo test executes normally. Constraint: GitHub Windows Server 2022 still exited the test process with STATUS_ENTRYPOINT_NOT_FOUND after the Windows Kits redist DLL was copied beside the exe. Rejected: Keep the redist-copy workaround | dumpbin evidence showed the loader still could not resolve WaitOnAddress. Rejected: Return to --no-run-only | issue #295 requires actual Windows test execution. Confidence: medium Scope-risk: narrow Directive: The shim is CI-only and must stay in the test exe directory; do not ship it in product bundles. Tested: git diff --check Not-tested: Shim compilation and loader behavior until PR CI reruns --- .github/workflows/ci.yml | 87 ++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec9f2de9..390e67d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,27 +126,88 @@ jobs: # rustc 1.78+ 生成的 Windows 二进制会导入 WaitOnAddress 所在的 # api-ms-win-core-synch-l1-2-0.dll。GitHub runner 上该 API-set - # 解析偶发缺 entrypoint;先 no-run 产出测试 exe,再把 Windows Kits - # 官方 x64 Redist stub 放到测试 exe 目录,最后仍然执行 cargo test。 + # 解析偶发缺 entrypoint;先 no-run 产出测试 exe,再放置一个只转发 + # WaitOnAddress/WakeByAddress* 到 KernelBase/kernel32 的测试目录 shim, + # 最后仍然执行 cargo test。 cargo test @testArgs --no-run if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $depsDir = Join-Path $env:CARGO_TARGET_DIR "debug\deps" - $windowsKitsRedist = Join-Path ${env:ProgramFiles(x86)} "Windows Kits\10\Redist" - $syncDll = $null - if (Test-Path $windowsKitsRedist) { - $syncDll = Get-ChildItem -Path $windowsKitsRedist -Recurse -Filter "api-ms-win-core-synch-l1-2-0.dll" -ErrorAction SilentlyContinue | - Where-Object { $_.FullName -match "\\x64\\" } | - Sort-Object FullName -Descending | - Select-Object -First 1 + $shimSource = Join-Path $env:RUNNER_TEMP "openless-waitonaddress-shim.c" + @' + #define WIN32_LEAN_AND_MEAN + #include + + typedef BOOL (WINAPI *WaitOnAddressFn)(volatile VOID *, PVOID, SIZE_T, DWORD); + typedef VOID (WINAPI *WakeByAddressFn)(PVOID); + + static FARPROC resolve_export(const char *name) { + const wchar_t *modules[] = { L"KernelBase.dll", L"kernel32.dll" }; + for (int i = 0; i < 2; ++i) { + HMODULE module = GetModuleHandleW(modules[i]); + if (!module) { + module = LoadLibraryW(modules[i]); + } + if (module) { + FARPROC proc = GetProcAddress(module, name); + if (proc) { + return proc; + } + } + } + return NULL; + } + + __declspec(dllexport) BOOL WINAPI WaitOnAddress( + volatile VOID *Address, + PVOID CompareAddress, + SIZE_T AddressSize, + DWORD dwMilliseconds + ) { + WaitOnAddressFn fn = (WaitOnAddressFn)resolve_export("WaitOnAddress"); + if (!fn) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return fn(Address, CompareAddress, AddressSize, dwMilliseconds); + } + + __declspec(dllexport) VOID WINAPI WakeByAddressSingle(PVOID Address) { + WakeByAddressFn fn = (WakeByAddressFn)resolve_export("WakeByAddressSingle"); + if (fn) { + fn(Address); + } } - if ($syncDll) { - Write-Host "[ci] Copy Windows Kits API-set stub for Rust test harness: $($syncDll.FullName)" - Copy-Item -Path $syncDll.FullName -Destination $depsDir -Force + + __declspec(dllexport) VOID WINAPI WakeByAddressAll(PVOID Address) { + WakeByAddressFn fn = (WakeByAddressFn)resolve_export("WakeByAddressAll"); + if (fn) { + fn(Address); + } + } + '@ | Set-Content -Path $shimSource -Encoding ASCII + + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + $vsDevCmd = $null + if (Test-Path $vswhere) { + $installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null + if (-not [string]::IsNullOrWhiteSpace($installPath)) { + $vsDevCmd = Join-Path $installPath "Common7\Tools\VsDevCmd.bat" + } + } + if (-not $vsDevCmd -or -not (Test-Path $vsDevCmd)) { + throw "VsDevCmd.bat not found; cannot build Windows Rust test API-set shim." + } + + $shimDll = Join-Path $depsDir "api-ms-win-core-synch-l1-2-0.dll" + $shimBuild = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 >nul && cl /nologo /LD `"$shimSource`" /Fe:`"$shimDll`" /link /NOLOGO" + cmd.exe /d /s /c $shimBuild + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE } else { - Write-Host "[ci] Windows Kits API-set stub not found; running tests without local stub" + Write-Host "[ci] Built Windows Rust test API-set shim: $shimDll" } cargo test @testArgs From 948e981c98bea3cf11a55d73f407e3183d68c303 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sat, 9 May 2026 00:12:47 +0800 Subject: [PATCH 07/15] Try Windows Server 2022 for backend test execution Rust 1.78+ Windows test runners can trip over WaitOnAddress/API-set resolution in hosted runner environments. Replace the shim and Rust-version pin experiments with the documented runner-level mitigation: keep Windows CI on Server 2022 and run the backend tests for real. Constraint: Issue #295 requires actual Windows Rust unit test execution, not cargo test --no-run. Constraint: Product default Foundry Local runtime remains enabled; only the CI test build disables the native Foundry feature. Rejected: CI-local API-set shim | too invasive before exhausting the known runner-image workaround. Rejected: Pin Rust 1.88 | did not address the loader failure and drifted from normal stable CI. Confidence: medium Scope-risk: narrow Tested: git diff --check Tested: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol Tested: cargo check --manifest-path src-tauri/Cargo.toml --- .github/workflows/ci.yml | 140 +---------------------- openless-all/app/src-tauri/Cargo.toml | 1 + openless-all/app/src-tauri/src/hotkey.rs | 9 +- 3 files changed, 7 insertions(+), 143 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 390e67d3..952346a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,20 +23,15 @@ jobs: - os: macos-latest label: macOS preflight: false - rust: stable - # windows-latest 当前指向 Windows Server 2025;Rust lib test harness - # 在该镜像启动时会遇到 STATUS_ENTRYPOINT_NOT_FOUND。CI 先固定到 - # Windows Server 2022,确保真正执行后端单测,而不是退回 --no-run。 + # Rust 1.78+ 生成的 Windows 测试运行器会使用 WaitOnAddress。 + # 先避开 windows-latest/Server 2025 的 runner 变量,固定到 + # Server 2022,验证能否真正执行后端单测。 - os: windows-2022 label: Windows preflight: true - # 1.88 是当前锁文件依赖的最高 MSRV;Windows 单测用这个版本 - # 固定住编译器变量,便于定位 runner / API-set 兼容问题。 - rust: 1.88.0 - os: ubuntu-latest label: Linux preflight: false - rust: stable runs-on: ${{ matrix.os }} defaults: run: @@ -55,10 +50,6 @@ jobs: cache-dependency-path: openless-all/app/package-lock.json - uses: dtolnay/rust-toolchain@stable - if: matrix.rust == 'stable' - - - uses: dtolnay/rust-toolchain@1.88.0 - if: matrix.rust == '1.88.0' - name: Install Linux check dependencies if: runner.os == 'Linux' @@ -109,130 +100,7 @@ jobs: - name: Run Rust backend unit tests (Windows) if: runner.os == 'Windows' - run: | - # Foundry/WinML SDK 以及 Rust 1.78+ 的 Windows test harness 在 - # GitHub runner 上都可能让进程启动前因 native entrypoint 退出。 - # 产品默认构建仍通过 cargo check 覆盖 Foundry 链接形态,单测门禁 - # 用无 Foundry native 链接的构建来真正执行 Windows 后端测试。 - # 单测使用独立 target 目录,避免前面的默认特性 cargo check 产物 - # 影响无 Foundry native 链接的测试进程 DLL 搜索路径。 - $env:CARGO_TARGET_DIR = Join-Path (Get-Location) "src-tauri\target\windows-unit-tests" - $testArgs = @( - "--manifest-path", "src-tauri/Cargo.toml", - "--lib", - "--no-default-features", - "--features", "custom-protocol" - ) - - # rustc 1.78+ 生成的 Windows 二进制会导入 WaitOnAddress 所在的 - # api-ms-win-core-synch-l1-2-0.dll。GitHub runner 上该 API-set - # 解析偶发缺 entrypoint;先 no-run 产出测试 exe,再放置一个只转发 - # WaitOnAddress/WakeByAddress* 到 KernelBase/kernel32 的测试目录 shim, - # 最后仍然执行 cargo test。 - cargo test @testArgs --no-run - if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE - } - - $depsDir = Join-Path $env:CARGO_TARGET_DIR "debug\deps" - $shimSource = Join-Path $env:RUNNER_TEMP "openless-waitonaddress-shim.c" - @' - #define WIN32_LEAN_AND_MEAN - #include - - typedef BOOL (WINAPI *WaitOnAddressFn)(volatile VOID *, PVOID, SIZE_T, DWORD); - typedef VOID (WINAPI *WakeByAddressFn)(PVOID); - - static FARPROC resolve_export(const char *name) { - const wchar_t *modules[] = { L"KernelBase.dll", L"kernel32.dll" }; - for (int i = 0; i < 2; ++i) { - HMODULE module = GetModuleHandleW(modules[i]); - if (!module) { - module = LoadLibraryW(modules[i]); - } - if (module) { - FARPROC proc = GetProcAddress(module, name); - if (proc) { - return proc; - } - } - } - return NULL; - } - - __declspec(dllexport) BOOL WINAPI WaitOnAddress( - volatile VOID *Address, - PVOID CompareAddress, - SIZE_T AddressSize, - DWORD dwMilliseconds - ) { - WaitOnAddressFn fn = (WaitOnAddressFn)resolve_export("WaitOnAddress"); - if (!fn) { - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; - } - return fn(Address, CompareAddress, AddressSize, dwMilliseconds); - } - - __declspec(dllexport) VOID WINAPI WakeByAddressSingle(PVOID Address) { - WakeByAddressFn fn = (WakeByAddressFn)resolve_export("WakeByAddressSingle"); - if (fn) { - fn(Address); - } - } - - __declspec(dllexport) VOID WINAPI WakeByAddressAll(PVOID Address) { - WakeByAddressFn fn = (WakeByAddressFn)resolve_export("WakeByAddressAll"); - if (fn) { - fn(Address); - } - } - '@ | Set-Content -Path $shimSource -Encoding ASCII - - $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" - $vsDevCmd = $null - if (Test-Path $vswhere) { - $installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null - if (-not [string]::IsNullOrWhiteSpace($installPath)) { - $vsDevCmd = Join-Path $installPath "Common7\Tools\VsDevCmd.bat" - } - } - if (-not $vsDevCmd -or -not (Test-Path $vsDevCmd)) { - throw "VsDevCmd.bat not found; cannot build Windows Rust test API-set shim." - } - - $shimDll = Join-Path $depsDir "api-ms-win-core-synch-l1-2-0.dll" - $shimBuild = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 >nul && cl /nologo /LD `"$shimSource`" /Fe:`"$shimDll`" /link /NOLOGO" - cmd.exe /d /s /c $shimBuild - if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE - } else { - Write-Host "[ci] Built Windows Rust test API-set shim: $shimDll" - } - - cargo test @testArgs - $testExit = $LASTEXITCODE - if ($testExit -ne 0) { - $testExe = Get-ChildItem -Path (Join-Path $env:CARGO_TARGET_DIR "debug\deps") -Filter "openless_lib-*.exe" | - Sort-Object LastWriteTime -Descending | - Select-Object -First 1 - if ($testExe) { - Write-Host "[ci] failed test exe: $($testExe.FullName)" - Write-Host "[ci] dlls beside failed test exe" - Get-ChildItem -Path $testExe.DirectoryName -Filter "*.dll" | Select-Object Name,Length | Format-Table -AutoSize - $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" - if (Test-Path $vswhere) { - $dumpbin = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -find "VC\Tools\MSVC\**\bin\Hostx64\x64\dumpbin.exe" 2>$null | Select-Object -First 1 - if ($dumpbin -and (Test-Path $dumpbin)) { - Write-Host "[ci] dumpbin dependents" - & $dumpbin /dependents $testExe.FullName - Write-Host "[ci] dumpbin imports summary" - & $dumpbin /imports $testExe.FullName | Select-String -Pattern "DLL Name|api-ms|VCRUNTIME|WebView2|Foundry|onnx|Mdd|WaitOnAddress|GetThreadDescription|LocalFree" - } - } - } - exit $testExit - } + run: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol - name: Verify version sync across all 5 files # 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。 diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index 0ea48658..132ddecb 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -90,6 +90,7 @@ windows = { version = "0.58", features = [ "Win32_System_Com", "Win32_System_Ole", "Win32_System_Registry", + "Win32_System_Threading", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_TextServices", diff --git a/openless-all/app/src-tauri/src/hotkey.rs b/openless-all/app/src-tauri/src/hotkey.rs index 9888be9a..38bc516f 100644 --- a/openless-all/app/src-tauri/src/hotkey.rs +++ b/openless-all/app/src-tauri/src/hotkey.rs @@ -691,6 +691,7 @@ mod platform { use std::sync::Arc; use windows::Win32::Foundation::{LPARAM, LRESULT, WPARAM}; + use windows::Win32::System::Threading::GetCurrentThreadId; use windows::Win32::UI::WindowsAndMessaging::{ CallNextHookEx, DispatchMessageW, GetMessageW, PostThreadMessageW, SetWindowsHookExW, TranslateMessage, UnhookWindowsHookEx, HC_ACTION, HHOOK, KBDLLHOOKSTRUCT, MSG, @@ -722,12 +723,6 @@ mod platform { static HOOK_CONTEXT: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); - #[link(name = "kernel32")] - unsafe extern "system" { - #[link_name = "GetCurrentThreadId"] - fn get_current_thread_id() -> u32; - } - pub fn start_adapter( binding: HotkeyBinding, tx: Sender, @@ -791,7 +786,7 @@ mod platform { unsafe impl Sync for CallbackContext {} fn run_listen_loop(shared: Arc, tx: Sender, status_tx: StartupTx) { - let thread_id = unsafe { get_current_thread_id() }; + let thread_id = unsafe { GetCurrentThreadId() }; let context = Box::into_raw(Box::new(CallbackContext { shared, tx, From e26cd894da0733a44085827ad8efb519200b1681 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sat, 9 May 2026 00:29:53 +0800 Subject: [PATCH 08/15] Keep Windows Rust tests executable despite API-set loader gaps Server 2022 did not remove the hosted-runner loader failure: cargo built the lib test binary, then the process exited before any test with STATUS_ENTRYPOINT_NOT_FOUND while importing WaitOnAddress from api-ms-win-core-synch-l1-2-0.dll. Keep the Server 2022 pin, but make the Windows test step build the test exe first and place a CI-local API-set forwarding shim in that exe directory before running cargo test for real. The shim is scoped to the unit-test target directory and forwards only WaitOnAddress/WakeByAddress* to KernelBase/kernel32. Constraint: Issue #295 requires actual Windows Rust unit test execution rather than cargo test --no-run. Constraint: Product default Foundry Local runtime remains enabled; only the CI test build disables the native Foundry feature. Rejected: Runner-only mitigation | windows-2022 still failed with STATUS_ENTRYPOINT_NOT_FOUND on the test harness. Rejected: Ship or source-control a DLL | the workaround is CI-local and generated into the temporary test target directory only. Confidence: medium Scope-risk: narrow Directive: Do not remove the shim unless Windows CI proves cargo test executes the backend test binary without the api-ms WaitOnAddress loader failure. Tested: git diff --check Tested: YAML parsed with python yaml.safe_load --- .github/workflows/ci.yml | 113 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 952346a3..2f501936 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,8 +24,7 @@ jobs: label: macOS preflight: false # Rust 1.78+ 生成的 Windows 测试运行器会使用 WaitOnAddress。 - # 先避开 windows-latest/Server 2025 的 runner 变量,固定到 - # Server 2022,验证能否真正执行后端单测。 + # 固定 Server 2022,减少 windows-latest/Server 2025 的镜像变量。 - os: windows-2022 label: Windows preflight: true @@ -100,7 +99,115 @@ jobs: - name: Run Rust backend unit tests (Windows) if: runner.os == 'Windows' - run: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol + shell: pwsh + run: | + # Foundry/WinML SDK 以及 Rust 1.78+ 的 Windows test harness 在 + # GitHub runner 上都可能让进程启动前因 native entrypoint 退出。 + # 产品默认构建仍通过 cargo check 覆盖 Foundry 链接形态,单测门禁 + # 用无 Foundry native 链接的构建来真正执行 Windows 后端测试。 + # 单测使用独立 target 目录,避免前面的默认特性 cargo check 产物 + # 影响无 Foundry native 链接的测试进程 DLL 搜索路径。 + $env:CARGO_TARGET_DIR = Join-Path (Get-Location) "src-tauri\target\windows-unit-tests" + $testArgs = @( + "--manifest-path", "src-tauri/Cargo.toml", + "--lib", + "--no-default-features", + "--features", "custom-protocol" + ) + + # rustc 1.78+ 生成的 Windows 二进制会导入 WaitOnAddress 所在的 + # api-ms-win-core-synch-l1-2-0.dll。GitHub runner 上该 API-set + # 解析缺 entrypoint 时,测试进程会在任何单测运行前退出。这里先 + # no-run 产出测试 exe,再放置一个只转发 WaitOnAddress/WakeByAddress* + # 到 KernelBase/kernel32 的测试目录 shim,最后仍然执行 cargo test。 + cargo test @testArgs --no-run + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + + $depsDir = Join-Path $env:CARGO_TARGET_DIR "debug\deps" + $shimSource = Join-Path $env:RUNNER_TEMP "openless-waitonaddress-shim.c" + @' + #define WIN32_LEAN_AND_MEAN + #define WaitOnAddress OpenLessShimHeaderWaitOnAddress + #define WakeByAddressSingle OpenLessShimHeaderWakeByAddressSingle + #define WakeByAddressAll OpenLessShimHeaderWakeByAddressAll + #include + #undef WaitOnAddress + #undef WakeByAddressSingle + #undef WakeByAddressAll + + typedef BOOL (WINAPI *WaitOnAddressFn)(volatile VOID *, PVOID, SIZE_T, DWORD); + typedef VOID (WINAPI *WakeByAddressFn)(PVOID); + + static FARPROC resolve_export(const char *name) { + const wchar_t *modules[] = { L"KernelBase.dll", L"kernel32.dll" }; + for (int i = 0; i < 2; ++i) { + HMODULE module = GetModuleHandleW(modules[i]); + if (!module) { + module = LoadLibraryW(modules[i]); + } + if (module) { + FARPROC proc = GetProcAddress(module, name); + if (proc) { + return proc; + } + } + } + return NULL; + } + + __declspec(dllexport) BOOL WINAPI WaitOnAddress( + volatile VOID *Address, + PVOID CompareAddress, + SIZE_T AddressSize, + DWORD dwMilliseconds + ) { + WaitOnAddressFn fn = (WaitOnAddressFn)resolve_export("WaitOnAddress"); + if (!fn) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return fn(Address, CompareAddress, AddressSize, dwMilliseconds); + } + + __declspec(dllexport) VOID WINAPI WakeByAddressSingle(PVOID Address) { + WakeByAddressFn fn = (WakeByAddressFn)resolve_export("WakeByAddressSingle"); + if (fn) { + fn(Address); + } + } + + __declspec(dllexport) VOID WINAPI WakeByAddressAll(PVOID Address) { + WakeByAddressFn fn = (WakeByAddressFn)resolve_export("WakeByAddressAll"); + if (fn) { + fn(Address); + } + } + '@ | Set-Content -Path $shimSource -Encoding ASCII + + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + $vsDevCmd = $null + if (Test-Path $vswhere) { + $installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null + if (-not [string]::IsNullOrWhiteSpace($installPath)) { + $vsDevCmd = Join-Path $installPath "Common7\Tools\VsDevCmd.bat" + } + } + if (-not $vsDevCmd -or -not (Test-Path $vsDevCmd)) { + throw "VsDevCmd.bat not found; cannot build Windows Rust test API-set shim." + } + + $shimDll = Join-Path $depsDir "api-ms-win-core-synch-l1-2-0.dll" + $shimBuild = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 >nul && cl /nologo /LD `"$shimSource`" /Fe:`"$shimDll`" /link /NOLOGO" + cmd.exe /d /s /c $shimBuild + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } else { + Write-Host "[ci] Built Windows Rust test API-set shim: $shimDll" + } + + cargo test @testArgs - name: Verify version sync across all 5 files # 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。 From eb157ba7a895c33f4a60b87cf49a30f49dcbb5a6 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sat, 9 May 2026 00:42:25 +0800 Subject: [PATCH 09/15] Patch Windows test import to the real synchronization DLL The CI-local API-set shim built successfully, but the Windows loader still failed before running tests, which indicates the API-set contract import is resolved before app-local DLL search can help. After cargo test --no-run, patch the generated lib test executable import name from api-ms-win-core-synch-l1-2-0.dll to KernelBase.dll, where Server 2022 exports WaitOnAddress, then invoke cargo test normally so the harness still executes the Rust unit tests. Constraint: Issue #295 requires actual Windows Rust unit test execution rather than compile-only coverage. Rejected: App-local API-set shim | the loader still failed after the shim DLL was placed beside the test executable. Rejected: Returning to cargo test --no-run | that would not satisfy the ticket. Confidence: medium Scope-risk: narrow Directive: Keep this patch scoped to CI test binaries; do not apply binary import rewriting to shipped artifacts. Tested: git diff --check Tested: YAML parsed with python yaml.safe_load --- .github/workflows/ci.yml | 105 ++++++++++----------------------------- 1 file changed, 26 insertions(+), 79 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f501936..4127cc6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,93 +118,40 @@ jobs: # rustc 1.78+ 生成的 Windows 二进制会导入 WaitOnAddress 所在的 # api-ms-win-core-synch-l1-2-0.dll。GitHub runner 上该 API-set # 解析缺 entrypoint 时,测试进程会在任何单测运行前退出。这里先 - # no-run 产出测试 exe,再放置一个只转发 WaitOnAddress/WakeByAddress* - # 到 KernelBase/kernel32 的测试目录 shim,最后仍然执行 cargo test。 + # no-run 产出测试 exe,再把该 import 指向 Server 2022 自带且导出 + # WaitOnAddress 的 KernelBase.dll,最后仍然执行 cargo test。 cargo test @testArgs --no-run if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $depsDir = Join-Path $env:CARGO_TARGET_DIR "debug\deps" - $shimSource = Join-Path $env:RUNNER_TEMP "openless-waitonaddress-shim.c" - @' - #define WIN32_LEAN_AND_MEAN - #define WaitOnAddress OpenLessShimHeaderWaitOnAddress - #define WakeByAddressSingle OpenLessShimHeaderWakeByAddressSingle - #define WakeByAddressAll OpenLessShimHeaderWakeByAddressAll - #include - #undef WaitOnAddress - #undef WakeByAddressSingle - #undef WakeByAddressAll - - typedef BOOL (WINAPI *WaitOnAddressFn)(volatile VOID *, PVOID, SIZE_T, DWORD); - typedef VOID (WINAPI *WakeByAddressFn)(PVOID); - - static FARPROC resolve_export(const char *name) { - const wchar_t *modules[] = { L"KernelBase.dll", L"kernel32.dll" }; - for (int i = 0; i < 2; ++i) { - HMODULE module = GetModuleHandleW(modules[i]); - if (!module) { - module = LoadLibraryW(modules[i]); - } - if (module) { - FARPROC proc = GetProcAddress(module, name); - if (proc) { - return proc; - } - } - } - return NULL; - } - - __declspec(dllexport) BOOL WINAPI WaitOnAddress( - volatile VOID *Address, - PVOID CompareAddress, - SIZE_T AddressSize, - DWORD dwMilliseconds - ) { - WaitOnAddressFn fn = (WaitOnAddressFn)resolve_export("WaitOnAddress"); - if (!fn) { - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; - } - return fn(Address, CompareAddress, AddressSize, dwMilliseconds); - } - - __declspec(dllexport) VOID WINAPI WakeByAddressSingle(PVOID Address) { - WakeByAddressFn fn = (WakeByAddressFn)resolve_export("WakeByAddressSingle"); - if (fn) { - fn(Address); - } - } - - __declspec(dllexport) VOID WINAPI WakeByAddressAll(PVOID Address) { - WakeByAddressFn fn = (WakeByAddressFn)resolve_export("WakeByAddressAll"); - if (fn) { - fn(Address); - } - } - '@ | Set-Content -Path $shimSource -Encoding ASCII - - $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" - $vsDevCmd = $null - if (Test-Path $vswhere) { - $installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null - if (-not [string]::IsNullOrWhiteSpace($installPath)) { - $vsDevCmd = Join-Path $installPath "Common7\Tools\VsDevCmd.bat" + $needleText = "api-ms-win-core-synch-l1-2-0.dll" + [char]0 + $replacementText = "kernelbase.dll" + [char]0 + $encoding = [System.Text.Encoding]::GetEncoding("iso-8859-1") + $needle = $encoding.GetBytes($needleText) + $replacement = New-Object byte[] $needle.Length + $replacementBytes = $encoding.GetBytes($replacementText) + [Array]::Copy($replacementBytes, $replacement, $replacementBytes.Length) + $patchedAny = $false + Get-ChildItem -Path $depsDir -Filter "openless_lib-*.exe" | ForEach-Object { + $bytes = [System.IO.File]::ReadAllBytes($_.FullName) + $text = $encoding.GetString($bytes) + $offset = $text.IndexOf($needleText, [System.StringComparison]::Ordinal) + $patchedThis = $false + while ($offset -ge 0) { + [Array]::Copy($replacement, 0, $bytes, $offset, $replacement.Length) + $patchedThis = $true + $patchedAny = $true + $offset = $text.IndexOf($needleText, $offset + $needle.Length, [System.StringComparison]::Ordinal) + } + if ($patchedThis) { + [System.IO.File]::WriteAllBytes($_.FullName, $bytes) + Write-Host "[ci] Patched Windows Rust test import to KernelBase.dll: $($_.FullName)" } } - if (-not $vsDevCmd -or -not (Test-Path $vsDevCmd)) { - throw "VsDevCmd.bat not found; cannot build Windows Rust test API-set shim." - } - - $shimDll = Join-Path $depsDir "api-ms-win-core-synch-l1-2-0.dll" - $shimBuild = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 >nul && cl /nologo /LD `"$shimSource`" /Fe:`"$shimDll`" /link /NOLOGO" - cmd.exe /d /s /c $shimBuild - if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE - } else { - Write-Host "[ci] Built Windows Rust test API-set shim: $shimDll" + if (-not $patchedAny) { + throw "api-ms-win-core-synch-l1-2-0.dll import not found in Windows Rust test executable." } cargo test @testArgs From ca1a80e485c91655e9497709fa16924d7c92db40 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sat, 9 May 2026 00:55:42 +0800 Subject: [PATCH 10/15] Run patched Windows test harness without relinking The Windows runner can build the Rust lib test binary, but invoking cargo test a second time after the import-string patch relinks the executable and loses the patch before any unit test starts. The CI step now builds with --no-run, patches the generated harness, verifies the API-set import is gone, and executes that harness directly so the backend tests still run for real.\n\nConstraint: GitHub Windows runners fail the Rust-generated WaitOnAddress API-set import before test code starts.\nRejected: Re-run cargo test after patching | Cargo can rebuild/relink and replace the patched executable.\nConfidence: medium\nScope-risk: narrow\nDirective: Keep this workaround confined to Windows CI unit tests; product defaults must keep Foundry Local runtime enabled.\nTested: git diff --check; python yaml.safe_load(.github/workflows/ci.yml); cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol\nNot-tested: Windows GitHub runner after direct harness execution --- .github/workflows/ci.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4127cc6c..c1b20d32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,7 +119,8 @@ jobs: # api-ms-win-core-synch-l1-2-0.dll。GitHub runner 上该 API-set # 解析缺 entrypoint 时,测试进程会在任何单测运行前退出。这里先 # no-run 产出测试 exe,再把该 import 指向 Server 2022 自带且导出 - # WaitOnAddress 的 KernelBase.dll,最后仍然执行 cargo test。 + # WaitOnAddress 的 KernelBase.dll,然后直接执行已修补的 test + # harness;避免再次 cargo test 触发重新链接并覆盖补丁。 cargo test @testArgs --no-run if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE @@ -133,7 +134,7 @@ jobs: $replacement = New-Object byte[] $needle.Length $replacementBytes = $encoding.GetBytes($replacementText) [Array]::Copy($replacementBytes, $replacement, $replacementBytes.Length) - $patchedAny = $false + $patchedExes = @() Get-ChildItem -Path $depsDir -Filter "openless_lib-*.exe" | ForEach-Object { $bytes = [System.IO.File]::ReadAllBytes($_.FullName) $text = $encoding.GetString($bytes) @@ -142,19 +143,29 @@ jobs: while ($offset -ge 0) { [Array]::Copy($replacement, 0, $bytes, $offset, $replacement.Length) $patchedThis = $true - $patchedAny = $true $offset = $text.IndexOf($needleText, $offset + $needle.Length, [System.StringComparison]::Ordinal) } if ($patchedThis) { [System.IO.File]::WriteAllBytes($_.FullName, $bytes) + $patchedExes += $_.FullName Write-Host "[ci] Patched Windows Rust test import to KernelBase.dll: $($_.FullName)" } } - if (-not $patchedAny) { + if ($patchedExes.Count -eq 0) { throw "api-ms-win-core-synch-l1-2-0.dll import not found in Windows Rust test executable." } - cargo test @testArgs + foreach ($testExe in $patchedExes) { + $patchedText = $encoding.GetString([System.IO.File]::ReadAllBytes($testExe)) + if ($patchedText.Contains($needleText)) { + throw "Windows Rust test executable still imports api-ms-win-core-synch-l1-2-0.dll: $testExe" + } + Write-Host "[ci] Running patched Windows Rust test executable: $testExe" + & $testExe + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + } - name: Verify version sync across all 5 files # 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。 From 6e7e546bff37c3601d8b66f99a9ae592e9eef79e Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sat, 9 May 2026 01:11:44 +0800 Subject: [PATCH 11/15] Use the declared MSRV for Windows test execution The Server 2022 runner still fails Rust test startup with the WaitOnAddress API-set import, and binary patching only turns that loader abort into another pre-harness failure. OpenLess declares Rust 1.77 as its MSRV, so the Windows CI unit-test lane now installs 1.77.0 and executes the no-Foundry test build directly instead of editing generated executables.\n\nConstraint: Issue #295 requires Windows CI to run the Rust unit tests, not just compile them.\nRejected: Continue PE import patching | the patched harness still exits before tests print output on GitHub Windows.\nConfidence: medium\nScope-risk: narrow\nDirective: Keep product/default toolchain behavior covered by cargo check; this pin is only for the Windows unit-test execution lane.\nTested: git diff --check; python yaml.safe_load(.github/workflows/ci.yml); cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol\nNot-tested: Windows GitHub runner with Rust 1.77.0 until CI completes --- .github/workflows/ci.yml | 74 +++++++--------------------------------- 1 file changed, 12 insertions(+), 62 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1b20d32..d9a5f019 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,10 @@ jobs: cache-dependency-path: openless-all/app/package-lock.json - uses: dtolnay/rust-toolchain@stable + if: runner.os != 'Windows' + + - uses: dtolnay/rust-toolchain@1.77.0 + if: runner.os == 'Windows' - name: Install Linux check dependencies if: runner.os == 'Linux' @@ -101,71 +105,17 @@ jobs: if: runner.os == 'Windows' shell: pwsh run: | - # Foundry/WinML SDK 以及 Rust 1.78+ 的 Windows test harness 在 - # GitHub runner 上都可能让进程启动前因 native entrypoint 退出。 # 产品默认构建仍通过 cargo check 覆盖 Foundry 链接形态,单测门禁 # 用无 Foundry native 链接的构建来真正执行 Windows 后端测试。 - # 单测使用独立 target 目录,避免前面的默认特性 cargo check 产物 - # 影响无 Foundry native 链接的测试进程 DLL 搜索路径。 + # Windows 测试固定到 Cargo.toml 声明的 MSRV 1.77,避开 rustc + # 1.78+ test harness 在 GitHub runner 上触发的 WaitOnAddress + # API-set loader 问题。 $env:CARGO_TARGET_DIR = Join-Path (Get-Location) "src-tauri\target\windows-unit-tests" - $testArgs = @( - "--manifest-path", "src-tauri/Cargo.toml", - "--lib", - "--no-default-features", - "--features", "custom-protocol" - ) - - # rustc 1.78+ 生成的 Windows 二进制会导入 WaitOnAddress 所在的 - # api-ms-win-core-synch-l1-2-0.dll。GitHub runner 上该 API-set - # 解析缺 entrypoint 时,测试进程会在任何单测运行前退出。这里先 - # no-run 产出测试 exe,再把该 import 指向 Server 2022 自带且导出 - # WaitOnAddress 的 KernelBase.dll,然后直接执行已修补的 test - # harness;避免再次 cargo test 触发重新链接并覆盖补丁。 - cargo test @testArgs --no-run - if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE - } - - $depsDir = Join-Path $env:CARGO_TARGET_DIR "debug\deps" - $needleText = "api-ms-win-core-synch-l1-2-0.dll" + [char]0 - $replacementText = "kernelbase.dll" + [char]0 - $encoding = [System.Text.Encoding]::GetEncoding("iso-8859-1") - $needle = $encoding.GetBytes($needleText) - $replacement = New-Object byte[] $needle.Length - $replacementBytes = $encoding.GetBytes($replacementText) - [Array]::Copy($replacementBytes, $replacement, $replacementBytes.Length) - $patchedExes = @() - Get-ChildItem -Path $depsDir -Filter "openless_lib-*.exe" | ForEach-Object { - $bytes = [System.IO.File]::ReadAllBytes($_.FullName) - $text = $encoding.GetString($bytes) - $offset = $text.IndexOf($needleText, [System.StringComparison]::Ordinal) - $patchedThis = $false - while ($offset -ge 0) { - [Array]::Copy($replacement, 0, $bytes, $offset, $replacement.Length) - $patchedThis = $true - $offset = $text.IndexOf($needleText, $offset + $needle.Length, [System.StringComparison]::Ordinal) - } - if ($patchedThis) { - [System.IO.File]::WriteAllBytes($_.FullName, $bytes) - $patchedExes += $_.FullName - Write-Host "[ci] Patched Windows Rust test import to KernelBase.dll: $($_.FullName)" - } - } - if ($patchedExes.Count -eq 0) { - throw "api-ms-win-core-synch-l1-2-0.dll import not found in Windows Rust test executable." - } - - foreach ($testExe in $patchedExes) { - $patchedText = $encoding.GetString([System.IO.File]::ReadAllBytes($testExe)) - if ($patchedText.Contains($needleText)) { - throw "Windows Rust test executable still imports api-ms-win-core-synch-l1-2-0.dll: $testExe" - } - Write-Host "[ci] Running patched Windows Rust test executable: $testExe" - & $testExe - if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE - } - } + cargo test ` + --manifest-path src-tauri/Cargo.toml ` + --lib ` + --no-default-features ` + --features custom-protocol - name: Verify version sync across all 5 files # 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。 From 960cab102697ccf3dc86b07c8c245f39a1031104 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sat, 9 May 2026 01:17:41 +0800 Subject: [PATCH 12/15] Run Windows unit tests on the Win7 std target The MSRV pin cannot build the current dependency graph because newer transitive crates use edition2024 manifests. Keep the normal stable toolchain, but install Rust's x86_64-win7-windows-msvc target for the Windows unit-test lane so std uses the compatibility path instead of a direct WaitOnAddress API-set import. The default Windows target remains checked separately by cargo check.\n\nConstraint: Windows CI must execute backend tests while preserving the product's default Windows target coverage.\nRejected: Pin Rust 1.77.0 | current transitive dependency manifests require newer Cargo support.\nConfidence: medium\nScope-risk: narrow\nDirective: Do not replace the default Windows cargo check with the win7 target; it is only a test-execution workaround.\nTested: git diff --check; python yaml.safe_load(.github/workflows/ci.yml); cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol\nNot-tested: Windows GitHub runner with x86_64-win7-windows-msvc until CI completes --- .github/workflows/ci.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9a5f019..002146fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,10 +49,6 @@ jobs: cache-dependency-path: openless-all/app/package-lock.json - uses: dtolnay/rust-toolchain@stable - if: runner.os != 'Windows' - - - uses: dtolnay/rust-toolchain@1.77.0 - if: runner.os == 'Windows' - name: Install Linux check dependencies if: runner.os == 'Linux' @@ -78,6 +74,11 @@ jobs: shell: pwsh run: ./scripts/windows-preflight.ps1 -Toolchain msvc + - name: Install Windows unit-test target + if: runner.os == 'Windows' + shell: pwsh + run: rustup target add x86_64-win7-windows-msvc + - name: Check PowerShell scripts if: matrix.preflight shell: pwsh @@ -107,12 +108,13 @@ jobs: run: | # 产品默认构建仍通过 cargo check 覆盖 Foundry 链接形态,单测门禁 # 用无 Foundry native 链接的构建来真正执行 Windows 后端测试。 - # Windows 测试固定到 Cargo.toml 声明的 MSRV 1.77,避开 rustc - # 1.78+ test harness 在 GitHub runner 上触发的 WaitOnAddress - # API-set loader 问题。 + # Windows 单测使用 win7 target,让 std 走兼容路径,避开 + # GitHub runner 上 Rust test harness 直接导入 WaitOnAddress 的 + # API-set loader 问题;默认 Windows target 仍由 cargo check 覆盖。 $env:CARGO_TARGET_DIR = Join-Path (Get-Location) "src-tauri\target\windows-unit-tests" cargo test ` --manifest-path src-tauri/Cargo.toml ` + --target x86_64-win7-windows-msvc ` --lib ` --no-default-features ` --features custom-protocol From 7fa7c5cf1451ed23932c2e132504e1bab046fc8c Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sat, 9 May 2026 01:25:52 +0800 Subject: [PATCH 13/15] Run Windows checks inside the MSVC dev environment The Windows runner PATH reports Git's link.exe before the Visual Studio linker, which makes the previous loader-focused attempts too fragile. The CI now resolves VsDevCmd.bat explicitly and runs Windows cargo check plus the real unit-test command inside that MSVC environment, while keeping macOS/Linux unchanged.\n\nConstraint: Windows CI must execute the Rust backend unit tests without changing product code paths.\nRejected: x86_64-win7-windows-msvc target | stable toolchains do not ship prebuilt std artifacts for that target.\nConfidence: medium\nScope-risk: narrow\nDirective: Keep Windows cargo commands under VsDevCmd unless the runner PATH is proven to expose the real MSVC linker first.\nTested: git diff --check; python yaml.safe_load(.github/workflows/ci.yml)\nNot-tested: Windows GitHub runner after VsDevCmd wrapping until CI completes --- .github/workflows/ci.yml | 48 +++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 002146fe..cd881429 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,10 +74,20 @@ jobs: shell: pwsh run: ./scripts/windows-preflight.ps1 -Toolchain msvc - - name: Install Windows unit-test target + - name: Check MSVC developer command prompt if: runner.os == 'Windows' shell: pwsh - run: rustup target add x86_64-win7-windows-msvc + run: | + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + $vsInstall = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if (-not $vsInstall) { + throw "Visual Studio C++ toolchain not found" + } + $vsDevCmd = Join-Path $vsInstall "Common7\Tools\VsDevCmd.bat" + if (-not (Test-Path $vsDevCmd)) { + throw "VsDevCmd.bat not found: $vsDevCmd" + } + Write-Host "[ok] VsDevCmd.bat -> $vsDevCmd" - name: Check PowerShell scripts if: matrix.preflight @@ -96,8 +106,22 @@ jobs: run: npm run build - name: Check Tauri backend (cargo check) + if: runner.os != 'Windows' run: cargo check --manifest-path src-tauri/Cargo.toml + - name: Check Tauri backend (cargo check, Windows MSVC) + if: runner.os == 'Windows' + shell: pwsh + run: | + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + $vsInstall = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + $vsDevCmd = Join-Path $vsInstall "Common7\Tools\VsDevCmd.bat" + $command = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 && cargo check --manifest-path src-tauri/Cargo.toml" + & cmd.exe /d /s /c $command + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + - name: Run Rust backend unit tests if: runner.os != 'Windows' run: cargo test --manifest-path src-tauri/Cargo.toml --lib @@ -108,16 +132,18 @@ jobs: run: | # 产品默认构建仍通过 cargo check 覆盖 Foundry 链接形态,单测门禁 # 用无 Foundry native 链接的构建来真正执行 Windows 后端测试。 - # Windows 单测使用 win7 target,让 std 走兼容路径,避开 - # GitHub runner 上 Rust test harness 直接导入 WaitOnAddress 的 - # API-set loader 问题;默认 Windows target 仍由 cargo check 覆盖。 + # GitHub runner 的普通 PATH 会先命中 Git 自带的 link.exe; + # 单测链接与执行都显式进入 VS DevCmd,确保走 MSVC 链接环境。 $env:CARGO_TARGET_DIR = Join-Path (Get-Location) "src-tauri\target\windows-unit-tests" - cargo test ` - --manifest-path src-tauri/Cargo.toml ` - --target x86_64-win7-windows-msvc ` - --lib ` - --no-default-features ` - --features custom-protocol + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + $vsInstall = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + $vsDevCmd = Join-Path $vsInstall "Common7\Tools\VsDevCmd.bat" + $targetDir = $env:CARGO_TARGET_DIR + $command = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 && set `"CARGO_TARGET_DIR=$targetDir`" && cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol" + & cmd.exe /d /s /c $command + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } - name: Verify version sync across all 5 files # 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。 From 354a7159ce0b2334f2060c0d6f43d11e5551379b Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sat, 9 May 2026 01:36:30 +0800 Subject: [PATCH 14/15] Build Windows test std on the compatibility target Server 2022, MSRV pinning, direct harness execution, and explicit VsDevCmd still do not make the stable Windows test harness start on the runner. The remaining narrow CI-only path is to keep stable cargo check for the product Windows target, but execute unit tests with nightly build-std on x86_64-win7-windows-msvc so std avoids the direct WaitOnAddress API-set import.\n\nConstraint: Issue #295 requires actual Windows test execution; stable default-target harness exits before tests begin on GitHub runners.\nRejected: Use prebuilt win7 target std | stable does not ship artifacts for x86_64-win7-windows-msvc.\nConfidence: medium\nScope-risk: moderate\nDirective: This is a CI-only test harness workaround; do not infer product support for Windows 7 from it.\nTested: git diff --check; python yaml.safe_load(.github/workflows/ci.yml)\nNot-tested: Nightly build-std Windows CI until the workflow completes --- .github/workflows/ci.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd881429..1aff80de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,20 +126,27 @@ jobs: if: runner.os != 'Windows' run: cargo test --manifest-path src-tauri/Cargo.toml --lib + - name: Install nightly Rust source for Windows unit tests + if: runner.os == 'Windows' + shell: pwsh + run: rustup toolchain install nightly --profile minimal --component rust-src --no-self-update + - name: Run Rust backend unit tests (Windows) if: runner.os == 'Windows' shell: pwsh run: | # 产品默认构建仍通过 cargo check 覆盖 Foundry 链接形态,单测门禁 # 用无 Foundry native 链接的构建来真正执行 Windows 后端测试。 - # GitHub runner 的普通 PATH 会先命中 Git 自带的 link.exe; - # 单测链接与执行都显式进入 VS DevCmd,确保走 MSVC 链接环境。 + # 默认 Windows target 的 stable test harness 会在 runner 上因 + # WaitOnAddress API-set 入口缺失而进程启动失败。单测执行改用 + # nightly build-std 构建 win7 target 的 std 兼容路径;默认 Windows + # target 仍由上面的 stable cargo check 覆盖。 $env:CARGO_TARGET_DIR = Join-Path (Get-Location) "src-tauri\target\windows-unit-tests" $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" $vsInstall = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath $vsDevCmd = Join-Path $vsInstall "Common7\Tools\VsDevCmd.bat" $targetDir = $env:CARGO_TARGET_DIR - $command = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 && set `"CARGO_TARGET_DIR=$targetDir`" && cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol" + $command = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 && set `"CARGO_TARGET_DIR=$targetDir`" && cargo +nightly test -Z build-std=std,panic_unwind --target x86_64-win7-windows-msvc --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol" & cmd.exe /d /s /c $command if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE From 33f56632d661c0f952042328e5f8a4af3195e0b3 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Sat, 9 May 2026 01:49:32 +0800 Subject: [PATCH 15/15] Patch Windows test imports to Kernel32 The nightly build-std win7 target path cannot compile Tauri's ctor usage, while the previous KernelBase import rewrite still exited before harness output. Keep the no-run/direct-harness approach, but point the WaitOnAddress import at Kernel32.dll and print the native exit code when the patched harness fails.\n\nConstraint: Windows CI still needs real unit-test execution and GitHub runners reject the Rust API-set import before tests start.\nRejected: Nightly build-std win7 target | tauri-utils ctor is unsupported on that target.\nConfidence: low\nScope-risk: narrow\nDirective: If Kernel32 also fails, stop treating PE patching as proven and prefer a test-architecture split or upstream runner/toolchain fix.\nTested: git diff --check; python yaml.safe_load(.github/workflows/ci.yml)\nNot-tested: Kernel32 import patch on Windows CI until workflow completes --- .github/workflows/ci.yml | 60 ++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1aff80de..78069eab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,32 +126,70 @@ jobs: if: runner.os != 'Windows' run: cargo test --manifest-path src-tauri/Cargo.toml --lib - - name: Install nightly Rust source for Windows unit tests - if: runner.os == 'Windows' - shell: pwsh - run: rustup toolchain install nightly --profile minimal --component rust-src --no-self-update - - name: Run Rust backend unit tests (Windows) if: runner.os == 'Windows' shell: pwsh run: | # 产品默认构建仍通过 cargo check 覆盖 Foundry 链接形态,单测门禁 # 用无 Foundry native 链接的构建来真正执行 Windows 后端测试。 - # 默认 Windows target 的 stable test harness 会在 runner 上因 - # WaitOnAddress API-set 入口缺失而进程启动失败。单测执行改用 - # nightly build-std 构建 win7 target 的 std 兼容路径;默认 Windows - # target 仍由上面的 stable cargo check 覆盖。 + # stable test harness 在 runner 上会直接导入 + # api-ms-win-core-synch-l1-2-0.dll/WaitOnAddress 并在单测开始前 + # loader 失败。先 no-run 生成测试 exe,把该 import 指向更常规 + # 的 Kernel32.dll,再直接运行已修补的 harness,避免 cargo 重新链接。 $env:CARGO_TARGET_DIR = Join-Path (Get-Location) "src-tauri\target\windows-unit-tests" $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" $vsInstall = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath $vsDevCmd = Join-Path $vsInstall "Common7\Tools\VsDevCmd.bat" $targetDir = $env:CARGO_TARGET_DIR - $command = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 && set `"CARGO_TARGET_DIR=$targetDir`" && cargo +nightly test -Z build-std=std,panic_unwind --target x86_64-win7-windows-msvc --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol" - & cmd.exe /d /s /c $command + $buildCommand = "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 && set `"CARGO_TARGET_DIR=$targetDir`" && cargo test --manifest-path src-tauri/Cargo.toml --lib --no-default-features --features custom-protocol --no-run" + & cmd.exe /d /s /c $buildCommand if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $depsDir = Join-Path $env:CARGO_TARGET_DIR "debug\deps" + $needleText = "api-ms-win-core-synch-l1-2-0.dll" + [char]0 + $replacementText = "kernel32.dll" + [char]0 + $encoding = [System.Text.Encoding]::GetEncoding("iso-8859-1") + $needle = $encoding.GetBytes($needleText) + $replacement = New-Object byte[] $needle.Length + $replacementBytes = $encoding.GetBytes($replacementText) + [Array]::Copy($replacementBytes, $replacement, $replacementBytes.Length) + $patchedExes = @() + Get-ChildItem -Path $depsDir -Filter "openless_lib-*.exe" | ForEach-Object { + $bytes = [System.IO.File]::ReadAllBytes($_.FullName) + $text = $encoding.GetString($bytes) + $offset = $text.IndexOf($needleText, [System.StringComparison]::Ordinal) + $patchedThis = $false + while ($offset -ge 0) { + [Array]::Copy($replacement, 0, $bytes, $offset, $replacement.Length) + $patchedThis = $true + $offset = $text.IndexOf($needleText, $offset + $needle.Length, [System.StringComparison]::Ordinal) + } + if ($patchedThis) { + [System.IO.File]::WriteAllBytes($_.FullName, $bytes) + $patchedExes += $_.FullName + Write-Host "[ci] Patched Windows Rust test import to Kernel32.dll: $($_.FullName)" + } + } + if ($patchedExes.Count -eq 0) { + throw "api-ms-win-core-synch-l1-2-0.dll import not found in Windows Rust test executable." + } + + foreach ($testExe in $patchedExes) { + $patchedText = $encoding.GetString([System.IO.File]::ReadAllBytes($testExe)) + if ($patchedText.Contains($needleText)) { + throw "Windows Rust test executable still imports api-ms-win-core-synch-l1-2-0.dll: $testExe" + } + Write-Host "[ci] Running patched Windows Rust test executable: $testExe" + & $testExe + if ($LASTEXITCODE -ne 0) { + $unsignedExit = $LASTEXITCODE -band 0xffffffff + Write-Host ("[ci] Patched Windows Rust test executable failed with exit 0x{0:X8}" -f $unsignedExit) + exit $LASTEXITCODE + } + } + - name: Verify version sync across all 5 files # 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。 # 一旦版本号 drift 立刻 fail,避免发版时再发现漏改。