From d926c46c55e341494cd301d2b2684e109217695c Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 11 May 2026 16:59:33 -0400 Subject: [PATCH 1/2] Add minimal Windows port skeleton for compile + launch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three minimum-viable changes to unblock building Maple on Windows. Atomic migration, keyring storage, signing, TTS DLL shipping, and the CI matrix job are deferred to follow-up epics. tauri.conf.json (MPLR-uyqowcnn / 1a): - bundle.targets switched from "all" to an explicit list excluding "msi". Tauri's WiX template hard-codes HKLM + perMachine install, which would shadow the HKCU keys we rely on for cloud.opensecret.maple deep links. - bundle.windows.nsis.installMode = "currentUser" so the NSIS installer is per-user (no UAC) and writes deep-link registration under HKCU. - bundle.windows.webviewInstallMode.type = "downloadBootstrapper" set explicitly to avoid surprise behavior if the Tauri default changes. capabilities/default.json (MPLR-zivybmgl / 1b): - Dropped $HOME/.config/maple/** scope entries from every fs:allow-* permission. The path tokens don't resolve to anything meaningful on Windows, no JS code touches them via the Tauri fs plugin, and proxy.rs writes via raw std::fs which bypasses the capability system entirely. $APPCONFIG/** entries are kept. src/proxy.rs (MPLR-oshibkah / 1c): - get_config_path now takes &AppHandle and resolves to %APPDATA%\cloud.opensecret.maple\ on Windows via Tauri's path resolver. macOS and Linux behavior is byte-identical (still ~/.config/maple/). - AppHandle is plumbed through save_proxy_config, load_saved_proxy_config, start_proxy, load_proxy_config, save_proxy_settings, and the auto-start initializer. Frontend invoke() calls are unchanged — Tauri auto-injects AppHandle on commands. - USERPROFILE fallback removed; Windows now uses the proper Tauri path. cargo check + cargo clippy -D warnings + cargo fmt --check all pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src-tauri/capabilities/default.json | 25 ++------- frontend/src-tauri/src/proxy.rs | 55 +++++++++++--------- frontend/src-tauri/tauri.conf.json | 10 +++- 3 files changed, 43 insertions(+), 47 deletions(-) diff --git a/frontend/src-tauri/capabilities/default.json b/frontend/src-tauri/capabilities/default.json index 9e75622f..6263b21e 100644 --- a/frontend/src-tauri/capabilities/default.json +++ b/frontend/src-tauri/capabilities/default.json @@ -11,38 +11,23 @@ "fs:default", { "identifier": "fs:allow-read-file", - "allow": [ - { "path": "$APPCONFIG/**" }, - { "path": "$HOME/.config/maple/**" } - ] + "allow": [{ "path": "$APPCONFIG/**" }] }, { "identifier": "fs:allow-write-file", - "allow": [ - { "path": "$APPCONFIG/**" }, - { "path": "$HOME/.config/maple/**" } - ] + "allow": [{ "path": "$APPCONFIG/**" }] }, { "identifier": "fs:allow-create", - "allow": [ - { "path": "$APPCONFIG/**" }, - { "path": "$HOME/.config/maple/**" } - ] + "allow": [{ "path": "$APPCONFIG/**" }] }, { "identifier": "fs:allow-exists", - "allow": [ - { "path": "$APPCONFIG/**" }, - { "path": "$HOME/.config/maple/**" } - ] + "allow": [{ "path": "$APPCONFIG/**" }] }, { "identifier": "fs:allow-mkdir", - "allow": [ - { "path": "$APPCONFIG" }, - { "path": "$HOME/.config/maple" } - ] + "allow": [{ "path": "$APPCONFIG" }] }, { "identifier": "opener:allow-open-url", diff --git a/frontend/src-tauri/src/proxy.rs b/frontend/src-tauri/src/proxy.rs index 1b205357..d101e297 100644 --- a/frontend/src-tauri/src/proxy.rs +++ b/frontend/src-tauri/src/proxy.rs @@ -64,6 +64,7 @@ impl ProxyState { #[tauri::command] pub async fn start_proxy( + app_handle: AppHandle, state: State<'_, ProxyState>, config: ProxyConfig, ) -> Result { @@ -132,7 +133,7 @@ pub async fn start_proxy( *running = true; // Save config to disk - if let Err(e) = save_proxy_config(&config).await { + if let Err(e) = save_proxy_config(&app_handle, &config).await { log::error!("Failed to save proxy config: {e}"); } @@ -185,15 +186,15 @@ pub async fn get_proxy_status(state: State<'_, ProxyState>) -> Result Result { - load_saved_proxy_config() +pub async fn load_proxy_config(app_handle: AppHandle) -> Result { + load_saved_proxy_config(&app_handle) .await .map_err(|e| format!("Failed to load proxy config: {e}")) } #[tauri::command] -pub async fn save_proxy_settings(config: ProxyConfig) -> Result<(), String> { - save_proxy_config(&config) +pub async fn save_proxy_settings(app_handle: AppHandle, config: ProxyConfig) -> Result<(), String> { + save_proxy_config(&app_handle, &config) .await .map_err(|e| format!("Failed to save proxy config: {e}")) } @@ -214,20 +215,24 @@ pub async fn test_proxy_port(host: String, port: u16) -> Result { } } -// Helper functions for config persistence -async fn get_config_path() -> Result { - // Use a hardcoded app name for the data directory - let app_name = "maple"; - let home_dir = std::env::var("HOME") - .or_else(|_| std::env::var("USERPROFILE")) - .map_err(|_| anyhow!("Failed to get home directory"))?; - - // Note: We use ~/.config on all platforms for simplicity. - // This works well on Linux and macOS (our currently supported platforms). - // While macOS traditionally uses ~/Library/Application Support, many modern - // cross-platform tools use ~/.config on macOS as well. - // If Windows support is added in the future, consider using %APPDATA% instead. - let app_dir = PathBuf::from(home_dir).join(".config").join(app_name); +// Helper functions for config persistence. +// Epic 2 (PR 3) will unify all platforms onto app_config_dir() and add atomic +// migration + keyring-backed secret storage. This minimal arm just unblocks +// Windows compile + launch. +async fn get_config_path(app_handle: &AppHandle) -> Result { + let app_dir = if cfg!(target_os = "windows") { + // Resolves to %APPDATA%\cloud.opensecret.maple\ (Roaming). + app_handle + .path() + .app_config_dir() + .map_err(|e| anyhow!("Failed to resolve app config dir: {e}"))? + } else { + // macOS/Linux: ~/.config/maple/ — unchanged for byte-identical behavior. + let app_name = "maple"; + let home_dir = + std::env::var("HOME").map_err(|_| anyhow!("Failed to get home directory"))?; + PathBuf::from(home_dir).join(".config").join(app_name) + }; // Ensure directory exists tokio::fs::create_dir_all(&app_dir).await?; @@ -235,8 +240,8 @@ async fn get_config_path() -> Result { Ok(app_dir.join("proxy_config.json")) } -async fn save_proxy_config(config: &ProxyConfig) -> Result<()> { - let path = get_config_path().await?; +async fn save_proxy_config(app_handle: &AppHandle, config: &ProxyConfig) -> Result<()> { + let path = get_config_path(app_handle).await?; let json = serde_json::to_string_pretty(config)?; // Write the config file @@ -253,8 +258,8 @@ async fn save_proxy_config(config: &ProxyConfig) -> Result<()> { Ok(()) } -async fn load_saved_proxy_config() -> Result { - let path = get_config_path().await?; +async fn load_saved_proxy_config(app_handle: &AppHandle) -> Result { + let path = get_config_path(app_handle).await?; if !path.exists() { return Ok(ProxyConfig::default()); @@ -268,7 +273,7 @@ async fn load_saved_proxy_config() -> Result { // Initialize proxy on app startup if auto_start is enabled pub async fn init_proxy_on_startup_simple(app_handle: AppHandle) -> Result<()> { // Load saved config - let config = load_saved_proxy_config().await?; + let config = load_saved_proxy_config(&app_handle).await?; // Check if auto-start is enabled and we have an API key if config.auto_start && !config.api_key.is_empty() { @@ -278,7 +283,7 @@ pub async fn init_proxy_on_startup_simple(app_handle: AppHandle) -> Result<()> { let proxy_state: tauri::State = app_handle.state(); // Try to start the proxy - match start_proxy(proxy_state, config.clone()).await { + match start_proxy(app_handle.clone(), proxy_state, config.clone()).await { Ok(_) => { log::info!( "Proxy auto-started successfully on {}:{}", diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index c9448b41..97a7f6f9 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -63,7 +63,7 @@ }, "bundle": { "active": true, - "targets": "all", + "targets": ["nsis", "deb", "appimage", "rpm", "dmg", "app"], "publisher": "OpenSecret", "icon": [ "icons/32x32.png", @@ -102,7 +102,13 @@ "windows": { "certificateThumbprint": null, "digestAlgorithm": "sha256", - "timestampUrl": "" + "timestampUrl": "", + "webviewInstallMode": { + "type": "downloadBootstrapper" + }, + "nsis": { + "installMode": "currentUser" + } }, "createUpdaterArtifacts": true } From 4e074dc85e4af7d793daa26294ccea08b64f50c4 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 20 May 2026 20:40:32 -0400 Subject: [PATCH 2/2] Add windows-latest CI jobs for unsigned NSIS builds Introduces Windows as a third desktop build target across both desktop CI workflows. Both jobs are born conformant with the patterns shipped during PR review (pinned actions, Bun 1.3.5, Rust 1.95.0, sha256-verified binary fetches via onnxruntime-pins.sh + sibling helper script). desktop-build.yml (MPLR-hzehaxpp / 1e): - New build-windows job runs on push to master, mirrors build-macos / build-linux step-for-step, builds via tauri-action (signing env wired up so the action doesn't error mid-build, though Authenticode is deliberately out of scope here), uploads the .exe artifact. - All third-party actions SHA-pinned to the same commits used by the Linux/macOS jobs: actions/checkout, oven-sh/setup-bun, dtolnay/rust-toolchain, actions/cache, tauri-apps/tauri-action, actions/upload-artifact. - bundle.targets in tauri.conf.json already constrains the Windows build to NSIS (MPLR-zsvmhjjx / 1i), so the redundant `--bundles nsis` tauri-action arg is omitted. desktop-pr-build.yml (MPLR-hzehaxpp / 1e): - Parallel build-windows job runs on pull_request, modeled on the existing macOS/Linux PR jobs: no secrets, no codesigning, no updater artifacts (`bun tauri build --no-sign --config '{"bundle":{"createUpdaterArtifacts":false}}'`), dev-environment VITE URLs, artifact suffixed `-pr`. sccache install (MPLR-gelmlpfg / 1h): - Mirrors the Linux job's style (long-form curl, `sha256sum --check -`, current-dir extract). Windows twist: download/verify/extract run inside a subshell cd'd to RUNNER_TEMP so all paths stay relative. MSYS2 tar mangles absolute D:\ paths passed via -C/-f even with --force-local; relative paths sidestep it. Inline comment explains. ONNX Runtime install (MPLR-hzehaxpp / 1e): - New provide-windows-onnxruntime.sh helper mirrors provide-linux-onnxruntime.sh one-for-one: same sha256_file / verify_sha256 helpers, archive + DLL double SHA256 verify, emits ORT_LIB_LOCATION / ORT_SKIP_DOWNLOAD / ORT_DYLIB_PATH for the GITHUB_ENV file. Workflow step reduces to a single invocation piped to GITHUB_ENV. - onnxruntime-pins.sh extended with windows_x64_archive_sha256 and windows_x64_dll_sha256 lookups for 1.22.0 (matches the existing linux_x64 pattern). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/desktop-build.yml | 87 +++++++++++++++++++ .github/workflows/desktop-pr-build.yml | 84 ++++++++++++++++++ .../src-tauri/scripts/onnxruntime-pins.sh | 24 +++++ .../scripts/provide-windows-onnxruntime.sh | 80 +++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100755 frontend/src-tauri/scripts/provide-windows-onnxruntime.sh diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index f89ef8a9..466d615b 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -231,3 +231,90 @@ jobs: frontend/src-tauri/target/release/bundle/appimage/*.AppImage frontend/src-tauri/target/release/bundle/deb/*.deb retention-days: 5 + + build-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 + with: + persist-credentials: false + + - name: Setup Bun + uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7 # was v1 + with: + bun-version: 1.3.5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # was stable + with: + toolchain: 1.95.0 + + - name: Install sccache + shell: bash + run: | + SCCACHE_VERSION=0.8.2 + SCCACHE_SHA256="de5e9f66bb8a6bbdf0e28cb8a086a8d12699af796bf70bcd9dc40d80715bf9b8" + SCCACHE_ARCHIVE="sccache-v${SCCACHE_VERSION}-x86_64-pc-windows-msvc.tar.gz" + SCCACHE_URL="https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/${SCCACHE_ARCHIVE}" + # Run download/verify/extract inside a subshell cd'd to RUNNER_TEMP so + # the archive and target dir are referenced by relative names. MSYS2 tar + # mangles Windows paths like D:\a when given absolute -C/-f arguments + # (even with --force-local); relative paths sidestep that. + ( + cd "$RUNNER_TEMP" + curl --fail --location --show-error --silent "$SCCACHE_URL" --output "$SCCACHE_ARCHIVE" + echo "${SCCACHE_SHA256} ${SCCACHE_ARCHIVE}" | sha256sum --check - + tar xzf "$SCCACHE_ARCHIVE" + ) + SCCACHE_BIN_DIR="$RUNNER_TEMP/sccache-v${SCCACHE_VERSION}-x86_64-pc-windows-msvc" + echo "$SCCACHE_BIN_DIR" >> "$GITHUB_PATH" + "$SCCACHE_BIN_DIR/sccache.exe" --version + + - name: Cache sccache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # was v4 + with: + path: ~\AppData\Local\Mozilla\sccache + key: ${{ runner.os }}-sccache-windows-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-sccache-windows- + ${{ runner.os }}-sccache- + + - name: Provide ONNX Runtime (Windows) + shell: bash + run: | + ./frontend/src-tauri/scripts/provide-windows-onnxruntime.sh >> "$GITHUB_ENV" + + - name: Install frontend dependencies + working-directory: ./frontend + run: bun install --frozen-lockfile --ignore-scripts + + - name: Configure sccache + shell: bash + run: | + { + echo "RUSTC_WRAPPER=sccache" + echo "SCCACHE_CACHE_SIZE=2G" + } >> "$GITHUB_ENV" + + - name: Build Tauri App (Windows) + uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # was v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + VITE_OPEN_SECRET_API_URL: https://enclave.trymaple.ai + VITE_MAPLE_BILLING_API_URL: https://billing.opensecret.cloud + VITE_CLIENT_ID: ba5a14b5-d915-47b1-b7b1-afda52bc5fc6 + with: + projectPath: './frontend' + + - name: Show sccache stats + run: sccache --show-stats + + - name: Upload Windows Build + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # was v4 + with: + name: maple-windows-x64 + path: | + frontend/src-tauri/target/release/bundle/nsis/*.exe + retention-days: 5 diff --git a/.github/workflows/desktop-pr-build.yml b/.github/workflows/desktop-pr-build.yml index 291a81e6..bf7027c1 100644 --- a/.github/workflows/desktop-pr-build.yml +++ b/.github/workflows/desktop-pr-build.yml @@ -197,3 +197,87 @@ jobs: frontend/src-tauri/target/release/bundle/appimage/*.AppImage frontend/src-tauri/target/release/bundle/deb/*.deb retention-days: 5 + + build-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 + with: + persist-credentials: false + + - name: Setup Bun + uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7 # was v1 + with: + bun-version: 1.3.5 + + - name: Install Rust + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # was stable + with: + toolchain: 1.95.0 + + - name: Install sccache + shell: bash + run: | + SCCACHE_VERSION=0.8.2 + SCCACHE_SHA256="de5e9f66bb8a6bbdf0e28cb8a086a8d12699af796bf70bcd9dc40d80715bf9b8" + SCCACHE_ARCHIVE="sccache-v${SCCACHE_VERSION}-x86_64-pc-windows-msvc.tar.gz" + SCCACHE_URL="https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/${SCCACHE_ARCHIVE}" + # Run download/verify/extract inside a subshell cd'd to RUNNER_TEMP so + # the archive and target dir are referenced by relative names. MSYS2 tar + # mangles Windows paths like D:\a when given absolute -C/-f arguments + # (even with --force-local); relative paths sidestep that. + ( + cd "$RUNNER_TEMP" + curl --fail --location --show-error --silent "$SCCACHE_URL" --output "$SCCACHE_ARCHIVE" + echo "${SCCACHE_SHA256} ${SCCACHE_ARCHIVE}" | sha256sum --check - + tar xzf "$SCCACHE_ARCHIVE" + ) + SCCACHE_BIN_DIR="$RUNNER_TEMP/sccache-v${SCCACHE_VERSION}-x86_64-pc-windows-msvc" + echo "$SCCACHE_BIN_DIR" >> "$GITHUB_PATH" + "$SCCACHE_BIN_DIR/sccache.exe" --version + + - name: Cache sccache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # was v4 + with: + path: ~\AppData\Local\Mozilla\sccache + key: ${{ runner.os }}-sccache-windows-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-sccache-windows- + ${{ runner.os }}-sccache- + + - name: Provide ONNX Runtime (Windows) + shell: bash + run: | + ./frontend/src-tauri/scripts/provide-windows-onnxruntime.sh >> "$GITHUB_ENV" + + - name: Install frontend dependencies + working-directory: ./frontend + run: bun install --frozen-lockfile --ignore-scripts + + - name: Configure sccache + shell: bash + run: | + { + echo "RUSTC_WRAPPER=sccache" + echo "SCCACHE_CACHE_SIZE=2G" + } >> "$GITHUB_ENV" + + - name: Build Tauri App (Windows, unsigned) + working-directory: ./frontend + shell: bash + run: bun tauri build --no-sign --config '{"bundle":{"createUpdaterArtifacts":false}}' + env: + VITE_OPEN_SECRET_API_URL: https://enclave.secretgpt.ai + VITE_MAPLE_BILLING_API_URL: https://billing-dev.opensecret.cloud + VITE_CLIENT_ID: ba5a14b5-d915-47b1-b7b1-afda52bc5fc6 + + - name: Show sccache stats + run: sccache --show-stats + + - name: Upload Windows PR Build + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # was v4 + with: + name: maple-windows-x64-pr + path: | + frontend/src-tauri/target/release/bundle/nsis/*.exe + retention-days: 5 diff --git a/frontend/src-tauri/scripts/onnxruntime-pins.sh b/frontend/src-tauri/scripts/onnxruntime-pins.sh index e1fc595b..f9b3be27 100644 --- a/frontend/src-tauri/scripts/onnxruntime-pins.sh +++ b/frontend/src-tauri/scripts/onnxruntime-pins.sh @@ -24,6 +24,30 @@ onnxruntime_linux_x64_dylib_sha256_for_version() { esac } +onnxruntime_windows_x64_archive_sha256_for_version() { + case "$1" in + 1.22.0) + printf '%s\n' "174c616efc0271194488642a72f1a514e01487da4dfe84c49296d66e40ebe0da" + ;; + *) + echo "No pinned Windows x64 ONNX Runtime archive SHA-256 for version '$1'." >&2 + return 1 + ;; + esac +} + +onnxruntime_windows_x64_dll_sha256_for_version() { + case "$1" in + 1.22.0) + printf '%s\n' "579b636403983254346a5c1d80bd28f1519cd1e284cd204f8d4ff41f8d711559" + ;; + *) + echo "No pinned Windows x64 ONNX Runtime DLL SHA-256 for version '$1'." >&2 + return 1 + ;; + esac +} + onnxruntime_ios_commit_for_version() { case "$1" in 1.22.2) diff --git a/frontend/src-tauri/scripts/provide-windows-onnxruntime.sh b/frontend/src-tauri/scripts/provide-windows-onnxruntime.sh new file mode 100755 index 00000000..4fbf3142 --- /dev/null +++ b/frontend/src-tauri/scripts/provide-windows-onnxruntime.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -euo pipefail + +ORT_VERSION="${ORT_VERSION:-1.22.0}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/onnxruntime-pins.sh" + +TAURI_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +ORT_ROOT="${TAURI_DIR}/onnxruntime-windows" +ORT_DIR="${ORT_ROOT}/onnxruntime-win-x64-${ORT_VERSION}" +ORT_ARCHIVE="onnxruntime-win-x64-${ORT_VERSION}.zip" +ORT_URL="https://github.com/microsoft/onnxruntime/releases/download/v${ORT_VERSION}/${ORT_ARCHIVE}" +ORT_DLL="${ORT_DIR}/lib/onnxruntime.dll" +ORT_ARCHIVE_SHA256="$(onnxruntime_windows_x64_archive_sha256_for_version "${ORT_VERSION}")" +ORT_DLL_SHA256="$(onnxruntime_windows_x64_dll_sha256_for_version "${ORT_VERSION}")" + +sha256_file() { + local path="$1" + + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "${path}" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "${path}" | awk '{print $1}' + elif command -v openssl >/dev/null 2>&1; then + openssl dgst -sha256 -r "${path}" | awk '{print $1}' + else + echo "No SHA-256 tool found. Install sha256sum, shasum, or openssl." >&2 + return 1 + fi +} + +verify_sha256() { + local label="$1" + local path="$2" + local expected="$3" + local actual + + actual="$(sha256_file "${path}")" + if [ "${actual}" != "${expected}" ]; then + echo "${label} SHA-256 mismatch for ${path}" >&2 + echo "expected: ${expected}" >&2 + echo "actual: ${actual}" >&2 + return 1 + fi +} + +# Internal bash operations (curl, unzip, sha256sum, file checks) work fine with +# MSYS2-style /d/a/... paths. But ORT_LIB_LOCATION and ORT_DYLIB_PATH are +# consumed by the native Windows Rust toolchain (ort crate build script) in a +# later step, which interprets a leading /d/... as drive-relative and fails to +# resolve. Convert paths to native Windows form at the GITHUB_ENV boundary. +# Falls through unchanged on platforms without cygpath so the script stays +# runnable for local sanity checks. +to_native_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -w "$1" + else + printf '%s' "$1" + fi +} + +if [ ! -f "${ORT_DLL}" ]; then + rm -rf "${ORT_ROOT}" + mkdir -p "${ORT_ROOT}" + archive_path="${ORT_ROOT}/${ORT_ARCHIVE}" + + curl -fL --retry 5 --retry-delay 2 --retry-all-errors \ + "${ORT_URL}" \ + --output "${archive_path}" + + verify_sha256 "ONNX Runtime archive" "${archive_path}" "${ORT_ARCHIVE_SHA256}" + unzip -q "${archive_path}" -d "${ORT_ROOT}" + rm -f "${archive_path}" +fi + +verify_sha256 "ONNX Runtime DLL" "${ORT_DLL}" "${ORT_DLL_SHA256}" + +echo "ORT_LIB_LOCATION=$(to_native_path "${ORT_DIR}")" +echo "ORT_SKIP_DOWNLOAD=true" +echo "ORT_DYLIB_PATH=$(to_native_path "${ORT_DLL}")"