diff --git a/sdk/runanywhere-commons/CMakeLists.txt b/sdk/runanywhere-commons/CMakeLists.txt index bb0ee984c..19f6af6ef 100644 --- a/sdk/runanywhere-commons/CMakeLists.txt +++ b/sdk/runanywhere-commons/CMakeLists.txt @@ -90,6 +90,9 @@ elseif(APPLE) elseif(UNIX) set(RAC_PLATFORM_LINUX TRUE) set(RAC_PLATFORM_NAME "Linux") +elseif(WIN32) + set(RAC_PLATFORM_WINDOWS TRUE) + set(RAC_PLATFORM_NAME "Windows") else() set(RAC_PLATFORM_NAME "Unknown") endif() @@ -125,6 +128,57 @@ if(NOT DEFINED LIBARCHIVE_VERSION) set(LIBARCHIVE_VERSION "3.8.1") endif() +# ----------------------------------------------------------------------------- +# Zlib: Bundle from source when system zlib is not available. +# Windows (MSVC) and some cross-compilation targets don't ship zlib. +# We try system first; if not found, build from source so libarchive gets it. +# ----------------------------------------------------------------------------- +find_package(ZLIB QUIET) +if(NOT ZLIB_FOUND) + message(STATUS "System zlib not found — bundling from source...") + FetchContent_Declare( + zlib_src + GIT_REPOSITORY https://github.com/madler/zlib.git + GIT_TAG v1.3.1 + GIT_SHALLOW TRUE + ) + # Prevent zlib from installing or building examples + set(ZLIB_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(SKIP_INSTALL_ALL ON CACHE BOOL "" FORCE) + set(SKIP_INSTALL_LIBRARIES ON CACHE BOOL "" FORCE) + set(SKIP_INSTALL_HEADERS ON CACHE BOOL "" FORCE) + set(SKIP_INSTALL_FILES ON CACHE BOOL "" FORCE) + # Save and restore BUILD_SHARED_LIBS + set(_SAVED_BSL_ZLIB ${BUILD_SHARED_LIBS}) + set(BUILD_SHARED_LIBS OFF) + FetchContent_MakeAvailable(zlib_src) + set(BUILD_SHARED_LIBS ${_SAVED_BSL_ZLIB}) + + # Set cache variables so libarchive's find_package(ZLIB) picks up our build. + # For multi-config generators (Visual Studio), we point to the static lib + # output. The actual path is resolved at build time via target_link_libraries. + set(ZLIB_INCLUDE_DIR "${zlib_src_SOURCE_DIR};${zlib_src_BINARY_DIR}" CACHE PATH "" FORCE) + set(ZLIB_INCLUDE_DIRS "${zlib_src_SOURCE_DIR};${zlib_src_BINARY_DIR}" CACHE PATH "" FORCE) + # Use the CMake target name directly - this works because we link manually below + set(ZLIB_LIBRARY zlibstatic CACHE STRING "" FORCE) + set(ZLIB_LIBRARIES zlibstatic CACHE STRING "" FORCE) + set(ZLIB_FOUND TRUE CACHE BOOL "" FORCE) + # Create an imported ZLIB::ZLIB target that points to our zlibstatic + if(NOT TARGET ZLIB::ZLIB) + add_library(ZLIB::ZLIB ALIAS zlibstatic) + endif() + # On MSVC, set zlibstatic output to a known location so the linker can find it + if(MSVC AND TARGET zlibstatic) + set_target_properties(zlibstatic PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib" + ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/lib" + ) + endif() + message(STATUS "Bundled zlib ready (v1.3.1)") +else() + message(STATUS "Using system zlib: ${ZLIB_LIBRARIES}") +endif() + # ----------------------------------------------------------------------------- # BZip2: Bundle from source for cross-compilation targets # Android NDK and Emscripten don't ship libbz2. macOS/iOS have it in the SDK. @@ -156,6 +210,13 @@ if(NOT BZIP2_FOUND) set(BZIP2_INCLUDE_DIR "${bzip2_src_SOURCE_DIR}" CACHE PATH "" FORCE) set(BZIP2_LIBRARIES bz2_bundled CACHE STRING "" FORCE) set(BZIP2_FOUND TRUE CACHE BOOL "" FORCE) + # On MSVC, set output to a known location so the linker can find it + if(MSVC AND TARGET bz2_bundled) + set_target_properties(bz2_bundled PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib" + ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/lib" + ) + endif() message(STATUS "Bundled BZip2 ready (v1.0.8)") else() message(STATUS "Using system BZip2: ${BZIP2_LIBRARIES}") @@ -182,7 +243,7 @@ set(ENABLE_LIBXML2 OFF CACHE BOOL "" FORCE) set(ENABLE_EXPAT OFF CACHE BOOL "" FORCE) set(ENABLE_PCREPOSIX OFF CACHE BOOL "" FORCE) set(ENABLE_PCRE2POSIX OFF CACHE BOOL "" FORCE) -set(ENABLE_LIBGCC OFF CACHE BOOL "" FORCE) +set(ENABLE_LIBGCC ON CACHE BOOL "" FORCE) set(ENABLE_CNG OFF CACHE BOOL "" FORCE) set(ENABLE_TAR OFF CACHE BOOL "" FORCE) # Don't build bsdtar binary set(ENABLE_CPIO OFF CACHE BOOL "" FORCE) # Don't build bsdcpio binary @@ -203,6 +264,32 @@ if(TARGET bz2_bundled AND TARGET archive_static) target_link_libraries(archive_static bz2_bundled) endif() +# Link bundled zlib to libarchive if we built it from source +if(TARGET zlibstatic AND TARGET archive_static) + target_link_libraries(archive_static zlibstatic) + target_include_directories(archive_static PRIVATE ${zlib_src_SOURCE_DIR} ${zlib_src_BINARY_DIR}) +endif() + +# On MSVC multi-config generators, libarchive references "zlibstatic.lib" and +# "bz2_bundled.lib" by bare filename. The linker can't find them because they +# are built into / subdirectories. Fix: redirect their output to a +# single known directory and add it to the global linker search path. +if(MSVC) + set(RAC_BUNDLED_LIB_DIR "${CMAKE_BINARY_DIR}/_bundled_libs") + foreach(_cfg Release Debug RelWithDebInfo MinSizeRel) + string(TOUPPER "${_cfg}" _CFG) + if(TARGET zlibstatic) + set_target_properties(zlibstatic PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY_${_CFG} "${RAC_BUNDLED_LIB_DIR}") + endif() + if(TARGET bz2_bundled) + set_target_properties(bz2_bundled PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY_${_CFG} "${RAC_BUNDLED_LIB_DIR}") + endif() + endforeach() + link_directories("${RAC_BUNDLED_LIB_DIR}") +endif() + message(STATUS "libarchive ready (v${LIBARCHIVE_VERSION})") # ============================================================================= @@ -234,6 +321,17 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") add_compile_options(-fvisibility=hidden) add_compile_options(-ffunction-sections -fdata-sections) endif() +elseif(MSVC) + # /W3 = reasonable warning level, /utf-8 = source/execution charset + # /Zc:__cplusplus = report correct __cplusplus value + # /EHsc = standard C++ exception handling + add_compile_options(/W3 /utf-8 /Zc:__cplusplus /EHsc) + # Suppress common harmless warnings in third-party code + add_compile_options(/wd4244 /wd4267 /wd4996) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS NOMINMAX WIN32_LEAN_AND_MEAN) + if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_compile_options(/O2 /DNDEBUG) + endif() endif() # ============================================================================= @@ -351,8 +449,11 @@ set(RAC_FEATURES_SOURCES src/features/embeddings/embeddings_component.cpp # Voice Agent src/features/voice_agent/voice_agent.cpp - # Result memory management - src/features/result_free.cpp + # Result memory management (weak symbol fallbacks) + # On MSVC, each service .cpp already provides strong definitions, + # so result_free.cpp (which uses __attribute__((weak)) on GCC/Clang) + # must be excluded to avoid LNK2005 duplicate symbol errors. + $<$>:src/features/result_free.cpp> ) # Platform services (Apple Foundation Models + System TTS + CoreML Diffusion) @@ -420,7 +521,20 @@ if(RAC_BUILD_SHARED) endif() # libarchive - native archive extraction -target_link_libraries(rac_commons PRIVATE archive_static) +# PUBLIC on Windows because MSVC static libs don't propagate transitive deps +if(WIN32) + target_link_libraries(rac_commons PUBLIC archive_static) + # Tell libarchive headers we're using the static library (not DLL) + target_compile_definitions(rac_commons PRIVATE LIBARCHIVE_STATIC) + if(TARGET zlibstatic) + target_link_libraries(rac_commons PUBLIC zlibstatic) + endif() + if(TARGET bz2_bundled) + target_link_libraries(rac_commons PUBLIC bz2_bundled) + endif() +else() + target_link_libraries(rac_commons PRIVATE archive_static) +endif() target_include_directories(rac_commons PRIVATE ${libarchive_SOURCE_DIR}/libarchive ${libarchive_BINARY_DIR}) # Platform-specific linking @@ -440,6 +554,12 @@ if(RAC_PLATFORM_ANDROID) target_link_libraries(rac_commons PUBLIC log) endif() +if(RAC_PLATFORM_WINDOWS) + target_compile_definitions(rac_commons PRIVATE RAC_PLATFORM_WINDOWS=1) + # ws2_32 = Winsock, shlwapi = path utilities + target_link_libraries(rac_commons PUBLIC ws2_32 shlwapi) +endif() + set_target_properties(rac_commons PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON @@ -587,6 +707,9 @@ message(STATUS " Services: LLM, STT, TTS, VAD interfaces") if(APPLE AND RAC_BUILD_PLATFORM) message(STATUS " Platform: Apple Foundation Models, System TTS") endif() +if(RAC_PLATFORM_WINDOWS) + message(STATUS " Platform: Windows (MSVC)") +endif() if(RAC_BUILD_BACKENDS) message(STATUS " Backends: LlamaCPP=${RAC_BACKEND_LLAMACPP}, ONNX=${RAC_BACKEND_ONNX}, WhisperCPP=${RAC_BACKEND_WHISPERCPP}, WhisperKitCoreML=${RAC_BACKEND_WHISPERKIT_COREML}") if(RAC_BACKEND_RAG) diff --git a/sdk/runanywhere-commons/CMakePresets.json b/sdk/runanywhere-commons/CMakePresets.json index 6d45784cb..16b931a37 100644 --- a/sdk/runanywhere-commons/CMakePresets.json +++ b/sdk/runanywhere-commons/CMakePresets.json @@ -27,6 +27,44 @@ "CMAKE_BUILD_TYPE": "Debug", "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" } + }, + { + "name": "windows-msvc", + "displayName": "Windows MSVC (all backends)", + "description": "Windows build with MSVC, all backends enabled", + "binaryDir": "${sourceDir}/build/windows", + "generator": "Visual Studio 17 2022", + "architecture": { + "value": "x64", + "strategy": "set" + }, + "cacheVariables": { + "RAC_BUILD_BACKENDS": "ON", + "RAC_BACKEND_LLAMACPP": "ON", + "RAC_BACKEND_ONNX": "ON", + "RAC_BACKEND_RAG": "ON", + "RAC_BACKEND_WHISPERCPP": "OFF", + "RAC_BUILD_PLATFORM": "OFF", + "RAC_BUILD_TESTS": "ON", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "windows-msvc-core", + "displayName": "Windows MSVC (core only)", + "description": "Windows build with MSVC, core library only (no backends)", + "binaryDir": "${sourceDir}/build/windows-core", + "generator": "Visual Studio 17 2022", + "architecture": { + "value": "x64", + "strategy": "set" + }, + "cacheVariables": { + "RAC_BUILD_BACKENDS": "OFF", + "RAC_BUILD_PLATFORM": "OFF", + "RAC_BUILD_TESTS": "ON", + "CMAKE_BUILD_TYPE": "Release" + } } ] } diff --git a/sdk/runanywhere-commons/VERSIONS b/sdk/runanywhere-commons/VERSIONS index 47c030882..7f6cf7dfc 100644 --- a/sdk/runanywhere-commons/VERSIONS +++ b/sdk/runanywhere-commons/VERSIONS @@ -49,6 +49,9 @@ ONNX_VERSION_MACOS=1.23.2 # Linux version (can use latest) ONNX_VERSION_LINUX=1.23.2 +# Windows version (can use latest) +ONNX_VERSION_WINDOWS=1.23.2 + # ============================================================================= # Sherpa-ONNX (via runanywhere-core) # ============================================================================= @@ -69,6 +72,9 @@ SHERPA_ONNX_VERSION_MACOS=1.12.18 # Linux version SHERPA_ONNX_VERSION_LINUX=1.12.23 +# Windows version +SHERPA_ONNX_VERSION_WINDOWS=1.12.23 + # ============================================================================= # llama.cpp (LLM inference) # ============================================================================= diff --git a/sdk/runanywhere-commons/cmake/FetchONNXRuntime.cmake b/sdk/runanywhere-commons/cmake/FetchONNXRuntime.cmake index f971d1aed..43a4b31e6 100644 --- a/sdk/runanywhere-commons/cmake/FetchONNXRuntime.cmake +++ b/sdk/runanywhere-commons/cmake/FetchONNXRuntime.cmake @@ -265,6 +265,39 @@ elseif(UNIX) message(STATUS "ONNX Runtime Linux library: ${onnxruntime_SOURCE_DIR}/lib/libonnxruntime.so") +elseif(WIN32) + # Windows: Download Windows binaries + if(NOT DEFINED ONNX_VERSION_WINDOWS OR "${ONNX_VERSION_WINDOWS}" STREQUAL "") + message(FATAL_ERROR "ONNX_VERSION_WINDOWS not defined in VERSIONS file") + endif() + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ONNX_URL "https://github.com/microsoft/onnxruntime/releases/download/v${ONNX_VERSION_WINDOWS}/onnxruntime-win-x64-${ONNX_VERSION_WINDOWS}.zip") + else() + set(ONNX_URL "https://github.com/microsoft/onnxruntime/releases/download/v${ONNX_VERSION_WINDOWS}/onnxruntime-win-x86-${ONNX_VERSION_WINDOWS}.zip") + endif() + + FetchContent_Declare( + onnxruntime + URL ${ONNX_URL} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + + FetchContent_MakeAvailable(onnxruntime) + + add_library(onnxruntime SHARED IMPORTED GLOBAL) + + set_target_properties(onnxruntime PROPERTIES + IMPORTED_IMPLIB "${onnxruntime_SOURCE_DIR}/lib/onnxruntime.lib" + IMPORTED_LOCATION "${onnxruntime_SOURCE_DIR}/lib/onnxruntime.dll" + ) + + target_include_directories(onnxruntime INTERFACE + "${onnxruntime_SOURCE_DIR}/include" + ) + + message(STATUS "ONNX Runtime Windows library: ${onnxruntime_SOURCE_DIR}/lib/onnxruntime.lib") + else() message(FATAL_ERROR "Unsupported platform for ONNX Runtime") endif() diff --git a/sdk/runanywhere-commons/cmake/LoadVersions.cmake b/sdk/runanywhere-commons/cmake/LoadVersions.cmake index 56bee13fa..b9d7a849f 100644 --- a/sdk/runanywhere-commons/cmake/LoadVersions.cmake +++ b/sdk/runanywhere-commons/cmake/LoadVersions.cmake @@ -56,10 +56,13 @@ message(STATUS " ONNX_VERSION_IOS: ${RAC_ONNX_VERSION_IOS}") message(STATUS " ONNX_VERSION_ANDROID: ${RAC_ONNX_VERSION_ANDROID}") message(STATUS " ONNX_VERSION_MACOS: ${RAC_ONNX_VERSION_MACOS}") message(STATUS " ONNX_VERSION_LINUX: ${RAC_ONNX_VERSION_LINUX}") +message(STATUS " ONNX_VERSION_WINDOWS: ${RAC_ONNX_VERSION_WINDOWS}") message(STATUS " Sherpa-ONNX:") message(STATUS " SHERPA_ONNX_VERSION_IOS: ${RAC_SHERPA_ONNX_VERSION_IOS}") message(STATUS " SHERPA_ONNX_VERSION_ANDROID: ${RAC_SHERPA_ONNX_VERSION_ANDROID}") message(STATUS " SHERPA_ONNX_VERSION_MACOS: ${RAC_SHERPA_ONNX_VERSION_MACOS}") +message(STATUS " SHERPA_ONNX_VERSION_LINUX: ${RAC_SHERPA_ONNX_VERSION_LINUX}") +message(STATUS " SHERPA_ONNX_VERSION_WINDOWS: ${RAC_SHERPA_ONNX_VERSION_WINDOWS}") message(STATUS " Other:") message(STATUS " LLAMACPP_VERSION: ${RAC_LLAMACPP_VERSION}") message(STATUS " NLOHMANN_JSON_VERSION: ${RAC_NLOHMANN_JSON_VERSION}") diff --git a/sdk/runanywhere-commons/include/rac/core/rac_platform_compat.h b/sdk/runanywhere-commons/include/rac/core/rac_platform_compat.h new file mode 100644 index 000000000..e92af6d64 --- /dev/null +++ b/sdk/runanywhere-commons/include/rac/core/rac_platform_compat.h @@ -0,0 +1,151 @@ +/** + * @file rac_platform_compat.h + * @brief RunAnywhere Commons - Platform Compatibility Layer + * + * Provides POSIX-like APIs on Windows (MSVC) so that the rest of the codebase + * can use dirent.h, S_ISDIR, S_ISREG, etc. without #ifdef clutter. + * + * On non-Windows platforms this header is a no-op passthrough. + */ + +#ifndef RAC_PLATFORM_COMPAT_H +#define RAC_PLATFORM_COMPAT_H + +#ifdef _WIN32 + +/* ---- POSIX string functions --------------------------------------------- */ +#include +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif +#ifndef strncasecmp +#define strncasecmp _strnicmp +#endif + +/* ---- stat / S_IS* macros ------------------------------------------------ */ +#include +#include + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +#endif + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + +#ifndef S_IFLNK +#define S_IFLNK 0120000 +#endif + +#ifndef S_ISLNK +#define S_ISLNK(m) (0) /* Windows does not have symlinks in the POSIX sense */ +#endif + +/* ---- dirent.h (minimal implementation) ---------------------------------- */ +/* Provides opendir / readdir / closedir using Win32 FindFirstFile API. */ + +#include +#include +#include +#include +#include + +#ifndef NAME_MAX +#define NAME_MAX 260 +#endif + +struct dirent { + char d_name[NAME_MAX + 1]; +}; + +typedef struct DIR { + HANDLE hFind; + WIN32_FIND_DATAA fdata; + struct dirent entry; + int first; /* 1 = first call to readdir */ +} DIR; + +static inline DIR* opendir(const char* path) { + if (!path || !*path) { errno = ENOENT; return NULL; } + + size_t len = strlen(path); + /* Build search pattern: path\* */ + char* pattern = (char*)malloc(len + 3); + if (!pattern) { errno = ENOMEM; return NULL; } + memcpy(pattern, path, len); + if (path[len - 1] != '\\' && path[len - 1] != '/') { + pattern[len++] = '\\'; + } + pattern[len++] = '*'; + pattern[len] = '\0'; + + DIR* dir = (DIR*)malloc(sizeof(DIR)); + if (!dir) { free(pattern); errno = ENOMEM; return NULL; } + + dir->hFind = FindFirstFileA(pattern, &dir->fdata); + free(pattern); + + if (dir->hFind == INVALID_HANDLE_VALUE) { + free(dir); + errno = ENOENT; + return NULL; + } + dir->first = 1; + return dir; +} + +static inline struct dirent* readdir(DIR* dir) { + if (!dir) return NULL; + + if (dir->first) { + dir->first = 0; + } else { + if (!FindNextFileA(dir->hFind, &dir->fdata)) return NULL; + } + strncpy(dir->entry.d_name, dir->fdata.cFileName, NAME_MAX); + dir->entry.d_name[NAME_MAX] = '\0'; + return &dir->entry; +} + +static inline int closedir(DIR* dir) { + if (!dir) return -1; + FindClose(dir->hFind); + free(dir); + return 0; +} + +#else /* !_WIN32 */ + +#include +#include +#include + +#endif /* _WIN32 */ + +/* ---- C++ helpers (available on all platforms) ---------------------------- */ +#ifdef __cplusplus +#include + +#ifdef _WIN32 +/** + * Convert a UTF-8 std::string to std::wstring for Windows wide-char APIs. + * Used by ONNX Runtime Session creation which requires wchar_t* on Windows. + */ +inline std::wstring rac_to_wstring(const std::string& s) { + return std::wstring(s.begin(), s.end()); +} +inline std::wstring rac_to_wstring(const char* s) { + if (!s) return {}; + return std::wstring(s, s + strlen(s)); +} +/** On Windows, ONNX Runtime expects wchar_t* paths */ +#define RAC_ORT_PATH(p) rac_to_wstring(p).c_str() +#else +/** On non-Windows, ONNX Runtime expects char* paths */ +#define RAC_ORT_PATH(p) (p) +#endif + +#endif /* __cplusplus */ + +#endif /* RAC_PLATFORM_COMPAT_H */ diff --git a/sdk/runanywhere-commons/scripts/build-windows.bat b/sdk/runanywhere-commons/scripts/build-windows.bat new file mode 100644 index 000000000..c5cc3b8dc --- /dev/null +++ b/sdk/runanywhere-commons/scripts/build-windows.bat @@ -0,0 +1,336 @@ +@echo off +setlocal enabledelayedexpansion + +:: ============================================================================= +:: build-windows.bat +:: Windows build script for runanywhere-commons (x64, MSVC) +:: +:: Usage: build-windows.bat [options] [backends] +:: backends: onnx | llamacpp | all (default: all) +:: - onnx: STT/TTS/VAD (ONNX Runtime) +:: - llamacpp: LLM text generation (GGUF models) +:: - all: onnx + llamacpp (default) +:: +:: Options: +:: --clean Clean build directory before building +:: --shared Build shared libraries (default: static) +:: --test Build and run tests +:: --help Show this help message +:: +:: Examples: +:: build-windows.bat Build all backends (static) +:: build-windows.bat --shared Build all backends (shared) +:: build-windows.bat llamacpp Build only LlamaCPP +:: build-windows.bat onnx Build only ONNX backend +:: build-windows.bat --clean all Clean build, all backends +:: build-windows.bat --test Build all + run tests +:: +:: Prerequisites: +:: - CMake 3.22+ +:: - Visual Studio 2022 (or Build Tools) with C++ workload +:: ============================================================================= + +set "SCRIPT_DIR=%~dp0" +set "ROOT_DIR=%SCRIPT_DIR%.." +set "BUILD_DIR=%ROOT_DIR%\build\windows-x64" +set "DIST_DIR=%ROOT_DIR%\dist\windows\x64" + +:: ============================================================================= +:: Load Versions +:: ============================================================================= +call :load_versions + +:: ============================================================================= +:: Defaults +:: ============================================================================= +set "CLEAN_BUILD=0" +set "BUILD_SHARED=OFF" +set "BUILD_TESTS=OFF" +set "RUN_TESTS=0" +set "BUILD_ONNX=OFF" +set "BUILD_LLAMACPP=OFF" +set "BACKENDS=" + +:: ============================================================================= +:: Parse Options +:: ============================================================================= +:parse_args +if "%~1"=="" goto :done_args +if "%~1"=="--clean" ( + set "CLEAN_BUILD=1" + shift + goto :parse_args +) +if "%~1"=="--shared" ( + set "BUILD_SHARED=ON" + shift + goto :parse_args +) +if "%~1"=="--test" ( + set "BUILD_TESTS=ON" + set "RUN_TESTS=1" + shift + goto :parse_args +) +if "%~1"=="--help" goto :show_help +if "%~1"=="-h" goto :show_help + +:: Must be a backend argument +set "BACKENDS=%~1" +shift +goto :parse_args + +:done_args + +:: Default backends = all +if "%BACKENDS%"=="" set "BACKENDS=all" + +if "%BACKENDS%"=="all" ( + set "BUILD_ONNX=ON" + set "BUILD_LLAMACPP=ON" +) else if "%BACKENDS%"=="onnx" ( + set "BUILD_ONNX=ON" +) else if "%BACKENDS%"=="llamacpp" ( + set "BUILD_LLAMACPP=ON" +) else ( + echo [ERROR] Unknown backend: %BACKENDS% + echo Usage: %~nx0 [options] [onnx ^| llamacpp ^| all] + exit /b 1 +) + +:: ============================================================================= +:: Print Header +:: ============================================================================= +echo. +echo ======================================== +echo RunAnywhere Windows Build +echo ======================================== +echo. +echo Architecture: x64 +echo Backends: ONNX=%BUILD_ONNX%, LlamaCPP=%BUILD_LLAMACPP% +if "%BUILD_SHARED%"=="ON" (echo Library type: Shared) else (echo Library type: Static) +echo Tests: %BUILD_TESTS% +echo Build dir: %BUILD_DIR% +echo Dist dir: %DIST_DIR% +echo. + +:: ============================================================================= +:: Prerequisites +:: ============================================================================= +echo [CHECK] Checking prerequisites... + +where cmake >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] cmake not found. Install CMake 3.22+ and add to PATH. + exit /b 1 +) +for /f "tokens=3" %%v in ('cmake --version 2^>^&1 ^| findstr /i "version"') do ( + echo [OK] Found cmake %%v +) + +:: Check for Visual Studio +where cl >nul 2>&1 +if errorlevel 1 ( + echo [WARN] cl.exe not in PATH. Attempting to find Visual Studio... + call :find_vs + if errorlevel 1 ( + echo [ERROR] Visual Studio 2022 with C++ workload not found. + echo Install from https://visualstudio.microsoft.com/ + exit /b 1 + ) +) +echo [OK] MSVC compiler available + +:: ============================================================================= +:: Clean Build +:: ============================================================================= +if "%CLEAN_BUILD%"=="1" ( + echo [CLEAN] Removing previous build... + if exist "%BUILD_DIR%" rmdir /s /q "%BUILD_DIR%" 2>nul + if exist "%DIST_DIR%" rmdir /s /q "%DIST_DIR%" 2>nul +) + +if not exist "%BUILD_DIR%" mkdir "%BUILD_DIR%" +if not exist "%DIST_DIR%" mkdir "%DIST_DIR%" + +:: ============================================================================= +:: Configure +:: ============================================================================= +echo. +echo ======================================== +echo Configuring CMake +echo ======================================== +echo. + +cmake -B "%BUILD_DIR%" ^ + -G "Visual Studio 17 2022" -A x64 ^ + -DRAC_BUILD_BACKENDS=ON ^ + -DRAC_BACKEND_ONNX=%BUILD_ONNX% ^ + -DRAC_BACKEND_LLAMACPP=%BUILD_LLAMACPP% ^ + -DRAC_BACKEND_WHISPERCPP=OFF ^ + -DRAC_BACKEND_RAG=ON ^ + -DRAC_BUILD_TESTS=%BUILD_TESTS% ^ + -DRAC_BUILD_SHARED=%BUILD_SHARED% ^ + -DRAC_BUILD_PLATFORM=OFF ^ + "%ROOT_DIR%" + +if errorlevel 1 ( + echo [ERROR] CMake configure failed. + exit /b 1 +) +echo [OK] CMake configure complete + +:: ============================================================================= +:: Build +:: ============================================================================= +echo. +echo ======================================== +echo Building +echo ======================================== +echo. + +cmake --build "%BUILD_DIR%" --config Release -- /m +if errorlevel 1 ( + echo [ERROR] Build failed. + exit /b 1 +) +echo [OK] Build complete + +:: ============================================================================= +:: Copy to Distribution Directory +:: ============================================================================= +echo. +echo [DIST] Copying libraries to distribution directory... + +if "%BUILD_SHARED%"=="ON" (set "LIB_EXT=dll") else (set "LIB_EXT=lib") + +:: Core library +if exist "%BUILD_DIR%\Release\rac_commons.lib" ( + copy /y "%BUILD_DIR%\Release\rac_commons.lib" "%DIST_DIR%\" >nul + echo [OK] Copied rac_commons.lib +) + +:: ONNX backend +if "%BUILD_ONNX%"=="ON" ( + if exist "%BUILD_DIR%\src\backends\onnx\Release\rac_backend_onnx.lib" ( + copy /y "%BUILD_DIR%\src\backends\onnx\Release\rac_backend_onnx.lib" "%DIST_DIR%\" >nul + echo [OK] Copied rac_backend_onnx.lib + ) +) + +:: LlamaCPP backend +if "%BUILD_LLAMACPP%"=="ON" ( + if exist "%BUILD_DIR%\src\backends\llamacpp\Release\rac_backend_llamacpp.lib" ( + copy /y "%BUILD_DIR%\src\backends\llamacpp\Release\rac_backend_llamacpp.lib" "%DIST_DIR%\" >nul + echo [OK] Copied rac_backend_llamacpp.lib + ) +) + +:: Headers +echo [DIST] Copying headers... +if not exist "%DIST_DIR%\include" mkdir "%DIST_DIR%\include" +xcopy /s /y /q "%ROOT_DIR%\include\rac" "%DIST_DIR%\include\rac\" >nul +echo [OK] Copied headers + +:: ============================================================================= +:: Run Tests +:: ============================================================================= +if "%RUN_TESTS%"=="1" ( + echo. + echo ======================================== + echo Running Tests + echo ======================================== + echo. + + set "TEST_DIR=%BUILD_DIR%\tests\Release" + set "TESTS_PASSED=0" + set "TESTS_FAILED=0" + + for %%t in (test_core test_extraction test_download_orchestrator) do ( + if exist "!TEST_DIR!\%%t.exe" ( + echo --- %%t --- + "!TEST_DIR!\%%t.exe" --run-all + if errorlevel 1 ( + set /a TESTS_FAILED+=1 + ) else ( + set /a TESTS_PASSED+=1 + ) + echo. + ) + ) + + echo ======================================== + echo Test Results: !TESTS_PASSED! passed, !TESTS_FAILED! failed + echo ======================================== +) + +:: ============================================================================= +:: Summary +:: ============================================================================= +echo. +echo ======================================== +echo Build Complete! +echo ======================================== +echo. +echo Distribution: %DIST_DIR% +echo. +dir /b "%DIST_DIR%\*.lib" 2>nul +echo. +echo To use in your project: +echo Include: /I"%DIST_DIR%\include" +echo Link: /LIBPATH:"%DIST_DIR%" rac_commons.lib +echo. + +exit /b 0 + +:: ============================================================================= +:: Subroutines +:: ============================================================================= + +:show_help +echo Usage: %~nx0 [options] [backends] +echo. +echo Backends: +echo onnx STT/TTS/VAD (ONNX Runtime + Sherpa-ONNX) +echo llamacpp LLM text generation (GGUF models via llama.cpp) +echo all onnx + llamacpp (default) +echo. +echo Options: +echo --clean Clean build directory before building +echo --shared Build shared libraries (default: static) +echo --test Build and run tests +echo --help Show this help message +echo. +echo Examples: +echo %~nx0 Build all backends (static) +echo %~nx0 --shared Build all backends (shared) +echo %~nx0 llamacpp Build only LlamaCPP +echo %~nx0 --clean --test all Clean build, all backends, run tests +exit /b 0 + +:load_versions +:: Read VERSIONS file and set variables +set "VERSIONS_FILE=%ROOT_DIR%\VERSIONS" +if not exist "%VERSIONS_FILE%" ( + echo [ERROR] VERSIONS file not found at %VERSIONS_FILE% + exit /b 1 +) +for /f "usebackq tokens=1,* delims==" %%a in ("%VERSIONS_FILE%") do ( + set "line=%%a" + if not "!line:~0,1!"=="#" if not "%%a"=="" ( + set "%%a=%%b" + ) +) +goto :eof + +:find_vs +:: Try to set up VS environment +set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" +if not exist "%VSWHERE%" exit /b 1 +for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -property installationPath`) do set "VS_PATH=%%i" +if not defined VS_PATH exit /b 1 +if exist "%VS_PATH%\VC\Auxiliary\Build\vcvars64.bat" ( + call "%VS_PATH%\VC\Auxiliary\Build\vcvars64.bat" >nul 2>&1 + exit /b 0 +) +exit /b 1 diff --git a/sdk/runanywhere-commons/scripts/windows/download-sherpa-onnx.bat b/sdk/runanywhere-commons/scripts/windows/download-sherpa-onnx.bat new file mode 100644 index 000000000..d9cf7323e --- /dev/null +++ b/sdk/runanywhere-commons/scripts/windows/download-sherpa-onnx.bat @@ -0,0 +1,146 @@ +@echo off +setlocal enabledelayedexpansion + +:: ============================================================================= +:: download-sherpa-onnx.bat +:: Download Sherpa-ONNX pre-built binaries for Windows x64 +:: +:: Usage: download-sherpa-onnx.bat [--force] +:: +:: Options: +:: --force Re-download even if already present +:: +:: Prerequisites: +:: - curl (included in Windows 10+) +:: - tar (included in Windows 10+) +:: ============================================================================= + +set "SCRIPT_DIR=%~dp0" +set "ROOT_DIR=%SCRIPT_DIR%..\.." +set "DEST_DIR=%ROOT_DIR%\third_party\sherpa-onnx-windows" + +:: Load versions +call :load_versions +if not defined SHERPA_ONNX_VERSION_WINDOWS set "SHERPA_ONNX_VERSION_WINDOWS=1.12.23" +set "VERSION=%SHERPA_ONNX_VERSION_WINDOWS%" + +:: Parse options +set "FORCE=0" +if "%~1"=="--force" set "FORCE=1" +if "%~1"=="--help" goto :show_help +if "%~1"=="-h" goto :show_help + +:: Check if already downloaded +if exist "%DEST_DIR%\lib" if "%FORCE%"=="0" ( + echo [OK] Sherpa-ONNX already downloaded at %DEST_DIR% + echo Use --force to re-download. + exit /b 0 +) + +:: Determine URL +set "URL=https://github.com/k2-fsa/sherpa-onnx/releases/download/v%VERSION%/sherpa-onnx-v%VERSION%-win-x64-shared.tar.bz2" +set "ARCHIVE_NAME=sherpa-onnx-v%VERSION%-win-x64-shared" + +echo. +echo ======================================== +echo Downloading Sherpa-ONNX for Windows +echo ======================================== +echo. +echo Version: %VERSION% +echo URL: %URL% +echo Destination: %DEST_DIR% +echo. + +:: Clean existing +if exist "%DEST_DIR%" ( + echo [CLEAN] Removing existing directory... + rmdir /s /q "%DEST_DIR%" 2>nul +) + +:: Create temp dir +set "TEMP_DL=%TEMP%\sherpa_onnx_dl_%RANDOM%" +mkdir "%TEMP_DL%" 2>nul + +:: Download +echo [DOWNLOAD] Downloading Sherpa-ONNX v%VERSION%... +curl -L -o "%TEMP_DL%\sherpa-onnx.tar.bz2" "%URL%" +if errorlevel 1 ( + echo [ERROR] Download failed. + rmdir /s /q "%TEMP_DL%" 2>nul + exit /b 1 +) + +:: Extract +echo [EXTRACT] Extracting archive... +mkdir "%DEST_DIR%" 2>nul +tar -xjf "%TEMP_DL%\sherpa-onnx.tar.bz2" -C "%TEMP_DL%" +if errorlevel 1 ( + echo [ERROR] Extraction failed. + rmdir /s /q "%TEMP_DL%" 2>nul + exit /b 1 +) + +:: Move contents (strip top-level directory) +for /d %%d in ("%TEMP_DL%\sherpa-onnx-*") do ( + xcopy /s /y /q "%%d\*" "%DEST_DIR%\" >nul +) + +:: Download C API headers if missing +if not exist "%DEST_DIR%\include\sherpa-onnx\c-api\c-api.h" ( + echo [DOWNLOAD] Downloading C API headers... + mkdir "%DEST_DIR%\include\sherpa-onnx\c-api" 2>nul + curl -sL "https://raw.githubusercontent.com/k2-fsa/sherpa-onnx/v%VERSION%/sherpa-onnx/c-api/c-api.h" ^ + -o "%DEST_DIR%\include\sherpa-onnx\c-api\c-api.h" +) + +:: Cleanup temp +rmdir /s /q "%TEMP_DL%" 2>nul + +:: Verify +echo [VERIFY] Checking installation... +set "VERIFY_OK=1" + +if not exist "%DEST_DIR%\lib" ( + echo [ERROR] lib directory not found + set "VERIFY_OK=0" +) +if not exist "%DEST_DIR%\include\sherpa-onnx\c-api\c-api.h" ( + echo [ERROR] C API header not found + set "VERIFY_OK=0" +) + +if "%VERIFY_OK%"=="0" ( + echo [ERROR] Verification failed. + exit /b 1 +) + +:: Summary +echo. +echo [OK] Sherpa-ONNX v%VERSION% downloaded successfully! +echo. +echo Libraries: %DEST_DIR%\lib\ +dir /b "%DEST_DIR%\lib\*.lib" 2>nul +dir /b "%DEST_DIR%\lib\*.dll" 2>nul +echo. +echo Headers: %DEST_DIR%\include\ +echo. + +exit /b 0 + +:: ============================================================================= +:: Subroutines +:: ============================================================================= + +:show_help +echo Usage: %~nx0 [--force] +echo --force Re-download even if already present +exit /b 0 + +:load_versions +set "VERSIONS_FILE=%ROOT_DIR%\VERSIONS" +if not exist "%VERSIONS_FILE%" exit /b 1 +for /f "usebackq tokens=1,* delims==" %%a in ("%VERSIONS_FILE%") do ( + set "line=%%a" + if not "!line:~0,1!"=="#" if not "%%a"=="" set "%%a=%%b" +) +goto :eof diff --git a/sdk/runanywhere-commons/src/backends/llamacpp/CMakeLists.txt b/sdk/runanywhere-commons/src/backends/llamacpp/CMakeLists.txt index 92f934451..91852006b 100644 --- a/sdk/runanywhere-commons/src/backends/llamacpp/CMakeLists.txt +++ b/sdk/runanywhere-commons/src/backends/llamacpp/CMakeLists.txt @@ -87,6 +87,19 @@ elseif(RAC_PLATFORM_LINUX) set(GGML_NEON ON CACHE BOOL "" FORCE) message(STATUS "Enabling NEON for Linux aarch64") endif() +elseif(RAC_PLATFORM_WINDOWS) + # Windows: CPU-only by default, no Metal/CUDA/Vulkan + set(GGML_METAL OFF CACHE BOOL "" FORCE) + set(GGML_CUDA OFF CACHE BOOL "" FORCE) + set(GGML_VULKAN OFF CACHE BOOL "" FORCE) + set(GGML_OPENCL OFF CACHE BOOL "" FORCE) + set(GGML_HIPBLAS OFF CACHE BOOL "" FORCE) + set(GGML_SYCL OFF CACHE BOOL "" FORCE) + set(GGML_KOMPUTE OFF CACHE BOOL "" FORCE) + set(GGML_RPC OFF CACHE BOOL "" FORCE) + set(GGML_OPENMP OFF CACHE BOOL "" FORCE) + set(GGML_NATIVE OFF CACHE BOOL "" FORCE) + message(STATUS "Configuring llama.cpp for Windows (CPU-only)") endif() set(BUILD_SHARED_LIBS OFF CACHE BOOL "Force static libraries for llama.cpp" FORCE) @@ -245,6 +258,10 @@ elseif(RAC_PLATFORM_LINUX) message(STATUS "Configuring LlamaCPP backend for Linux") # Linux-specific link libraries target_link_libraries(rac_backend_llamacpp PUBLIC pthread dl) + +elseif(RAC_PLATFORM_WINDOWS) + message(STATUS "Configuring LlamaCPP backend for Windows") + # No extra link libraries needed on Windows (threading is built-in) endif() # ============================================================================= diff --git a/sdk/runanywhere-commons/src/backends/onnx/CMakeLists.txt b/sdk/runanywhere-commons/src/backends/onnx/CMakeLists.txt index d5aeca964..1646c9d9e 100644 --- a/sdk/runanywhere-commons/src/backends/onnx/CMakeLists.txt +++ b/sdk/runanywhere-commons/src/backends/onnx/CMakeLists.txt @@ -166,6 +166,42 @@ elseif(RAC_PLATFORM_LINUX) else() message(STATUS "Sherpa-ONNX not found. Run: ./scripts/linux/download-sherpa-onnx.sh") endif() + +elseif(RAC_PLATFORM_WINDOWS) + set(SHERPA_ONNX_ROOT "${RUNANYWHERE_COMMONS_ROOT}/third_party/sherpa-onnx-windows") + + if(EXISTS "${SHERPA_ONNX_ROOT}/lib/sherpa-onnx-c-api.lib") + set(SHERPA_ONNX_AVAILABLE ON) + set(SHERPA_LIB_PATH "${SHERPA_ONNX_ROOT}/lib/sherpa-onnx-c-api.lib") + set(SHERPA_DLL_PATH "${SHERPA_ONNX_ROOT}/bin/sherpa-onnx-c-api.dll") + set(SHERPA_HEADER_PATH "${SHERPA_ONNX_ROOT}/include") + + add_library(sherpa_onnx SHARED IMPORTED GLOBAL) + set_target_properties(sherpa_onnx PROPERTIES + IMPORTED_IMPLIB "${SHERPA_LIB_PATH}" + IMPORTED_LOCATION "${SHERPA_DLL_PATH}" + INTERFACE_INCLUDE_DIRECTORIES "${SHERPA_HEADER_PATH}" + ) + + # Link supporting libraries if present + set(SHERPA_ONNX_DEPS + "sherpa-onnx-core" "sherpa-onnx-fst" "sherpa-onnx-fstfar" + "sherpa-onnx-kaldifst-core" "kaldi-decoder-core" "kaldi-native-fbank-core" + "piper_phonemize" "espeak-ng" "ucd" + ) + + foreach(dep ${SHERPA_ONNX_DEPS}) + if(EXISTS "${SHERPA_ONNX_ROOT}/lib/${dep}.lib") + add_library(sherpa_${dep} STATIC IMPORTED GLOBAL) + set_target_properties(sherpa_${dep} PROPERTIES IMPORTED_LOCATION "${SHERPA_ONNX_ROOT}/lib/${dep}.lib") + target_link_libraries(sherpa_onnx INTERFACE sherpa_${dep}) + endif() + endforeach() + + message(STATUS "Found Sherpa-ONNX Windows: ${SHERPA_LIB_PATH}") + else() + message(STATUS "Sherpa-ONNX not found for Windows at ${SHERPA_ONNX_ROOT}") + endif() endif() # ============================================================================= @@ -282,6 +318,10 @@ elseif(RAC_PLATFORM_MACOS) elseif(RAC_PLATFORM_LINUX) target_link_libraries(rac_backend_onnx PUBLIC pthread dl) + +elseif(RAC_PLATFORM_WINDOWS) + # No extra link libraries needed on Windows + message(STATUS "ONNX Backend: CPU EP for Windows") endif() # ============================================================================= diff --git a/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.cpp b/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.cpp index c8f3310a1..2332c6d93 100644 --- a/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.cpp +++ b/sdk/runanywhere-commons/src/backends/onnx/onnx_backend.cpp @@ -14,8 +14,11 @@ #include "onnx_backend.h" -#include -#include +#include "rac/core/rac_platform_compat.h" + +#ifdef _WIN32 +#include // for _mkdir +#endif #include #include @@ -673,7 +676,11 @@ static void ensure_espeak_voice_files(const std::string& espeak_data_dir) { } if (stat(voices_dir.c_str(), &st) != 0) { +#ifdef _WIN32 + int mk = _mkdir(voices_dir.c_str()); +#else int mk = mkdir(voices_dir.c_str(), 0755); +#endif RAC_LOG_INFO("ONNX.TTS", "[ensure_voices] Created voices/ dir: result=%d errno=%d", mk, errno); } else { RAC_LOG_INFO("ONNX.TTS", "[ensure_voices] voices/ dir already exists"); diff --git a/sdk/runanywhere-commons/src/backends/onnx/wakeword_onnx.cpp b/sdk/runanywhere-commons/src/backends/onnx/wakeword_onnx.cpp index f0350b434..a62ebca32 100644 --- a/sdk/runanywhere-commons/src/backends/onnx/wakeword_onnx.cpp +++ b/sdk/runanywhere-commons/src/backends/onnx/wakeword_onnx.cpp @@ -19,6 +19,7 @@ #include "rac/backends/rac_wakeword_onnx.h" #include "rac/backends/rac_vad_onnx.h" #include "rac/core/rac_logger.h" +#include "rac/core/rac_platform_compat.h" #ifdef RAC_HAS_ONNX #include @@ -567,7 +568,7 @@ RAC_ONNX_API rac_result_t rac_wakeword_onnx_init_shared_models( // Load melspectrogram model (required for proper pipeline) if (melspec_model_path) { backend->melspec_session = std::make_unique( - *backend->env, melspec_model_path, *backend->session_options); + *backend->env, RAC_ORT_PATH(melspec_model_path), *backend->session_options); // Get input/output names auto input_name = backend->melspec_session->GetInputNameAllocated(0, backend->allocator); @@ -584,7 +585,7 @@ RAC_ONNX_API rac_result_t rac_wakeword_onnx_init_shared_models( // Load embedding model (required) if (embedding_model_path) { backend->embedding_session = std::make_unique( - *backend->env, embedding_model_path, *backend->session_options); + *backend->env, RAC_ORT_PATH(embedding_model_path), *backend->session_options); // Get input/output names auto input_name = backend->embedding_session->GetInputNameAllocated(0, backend->allocator); @@ -651,7 +652,7 @@ RAC_ONNX_API rac_result_t rac_wakeword_onnx_load_model( model.threshold = backend->global_threshold; model.session = std::make_unique( - *backend->env, model_path, *backend->session_options); + *backend->env, RAC_ORT_PATH(model_path), *backend->session_options); // Get input/output names auto input_name = model.session->GetInputNameAllocated(0, backend->allocator); diff --git a/sdk/runanywhere-commons/src/backends/whispercpp/CMakeLists.txt b/sdk/runanywhere-commons/src/backends/whispercpp/CMakeLists.txt index 84a5dcee3..dad3aad0b 100644 --- a/sdk/runanywhere-commons/src/backends/whispercpp/CMakeLists.txt +++ b/sdk/runanywhere-commons/src/backends/whispercpp/CMakeLists.txt @@ -70,6 +70,14 @@ elseif(RAC_PLATFORM_MACOS) set(GGML_METAL ON CACHE BOOL "" FORCE) set(GGML_ACCELERATE ON CACHE BOOL "" FORCE) set(GGML_METAL_EMBED_LIBRARY ON CACHE BOOL "" FORCE) +elseif(RAC_PLATFORM_WINDOWS) + set(GGML_METAL OFF CACHE BOOL "" FORCE) + set(GGML_CUDA OFF CACHE BOOL "" FORCE) + set(GGML_VULKAN OFF CACHE BOOL "" FORCE) + set(GGML_OPENCL OFF CACHE BOOL "" FORCE) + set(GGML_OPENMP OFF CACHE BOOL "" FORCE) + set(GGML_NATIVE OFF CACHE BOOL "" FORCE) + message(STATUS "Configuring whisper.cpp for Windows (CPU-only)") endif() set(BUILD_SHARED_LIBS OFF CACHE BOOL "Force static libraries for whisper.cpp" FORCE) @@ -154,6 +162,10 @@ elseif(RAC_PLATFORM_MACOS) "-framework Metal" "-framework MetalKit" ) + +elseif(RAC_PLATFORM_WINDOWS) + message(STATUS "Configuring WhisperCPP backend for Windows") + # No extra link libraries needed on Windows endif() # ============================================================================= diff --git a/sdk/runanywhere-commons/src/core/rac_structured_error.cpp b/sdk/runanywhere-commons/src/core/rac_structured_error.cpp index 67d34c510..07ebcd88d 100644 --- a/sdk/runanywhere-commons/src/core/rac_structured_error.cpp +++ b/sdk/runanywhere-commons/src/core/rac_structured_error.cpp @@ -18,6 +18,9 @@ #if defined(__APPLE__) || defined(__linux__) #include #endif +#if defined(_WIN32) +#include +#endif // ============================================================================= // THREAD-LOCAL STORAGE @@ -226,6 +229,21 @@ int32_t rac_error_capture_stack_trace(rac_error_t* error) { free(symbols); } + return captured; +#elif defined(_WIN32) + void* buffer[RAC_MAX_STACK_FRAMES]; + USHORT frame_count = CaptureStackBackTrace(2, RAC_MAX_STACK_FRAMES, buffer, NULL); + + int captured = 0; + for (USHORT i = 0; i < frame_count && captured < RAC_MAX_STACK_FRAMES; i++) { + error->stack_frames[captured].address = buffer[i]; + error->stack_frames[captured].function = nullptr; + error->stack_frames[captured].file = nullptr; + error->stack_frames[captured].line = 0; + captured++; + } + + error->stack_frame_count = captured; return captured; #else // Platform doesn't support backtrace (Android, Windows, etc.) diff --git a/sdk/runanywhere-commons/src/features/rag/CMakeLists.txt b/sdk/runanywhere-commons/src/features/rag/CMakeLists.txt index 1e943c26a..defc1bd85 100644 --- a/sdk/runanywhere-commons/src/features/rag/CMakeLists.txt +++ b/sdk/runanywhere-commons/src/features/rag/CMakeLists.txt @@ -103,6 +103,7 @@ set_target_properties(rac_backend_rag PROPERTIES target_compile_options(rac_backend_rag PRIVATE $<$:-Wno-unused-parameter> $<$:-Wno-missing-field-initializers> + $<$:/wd4244 /wd4267 /wd4996> ) target_compile_definitions(rac_backend_rag PRIVATE diff --git a/sdk/runanywhere-commons/src/features/rag/onnx_embedding_provider.cpp b/sdk/runanywhere-commons/src/features/rag/onnx_embedding_provider.cpp index 85ac2475c..79ff80904 100644 --- a/sdk/runanywhere-commons/src/features/rag/onnx_embedding_provider.cpp +++ b/sdk/runanywhere-commons/src/features/rag/onnx_embedding_provider.cpp @@ -1007,12 +1007,23 @@ class ONNXEmbeddingProvider::Impl { } // Load model with session options +#ifdef _WIN32 + // ONNX Runtime on Windows requires wchar_t* paths + std::wstring wpath(model_path.begin(), model_path.end()); + status_guard.reset(ort_api_->CreateSession( + ort_env_, + wpath.c_str(), + options_guard.get(), + &session_ + )); +#else status_guard.reset(ort_api_->CreateSession( ort_env_, model_path.c_str(), options_guard.get(), &session_ )); +#endif // options_guard automatically releases session options on scope exit if (status_guard.is_error()) { diff --git a/sdk/runanywhere-commons/src/features/rag/rag_backend.h b/sdk/runanywhere-commons/src/features/rag/rag_backend.h index 05775c25d..6d74962e1 100644 --- a/sdk/runanywhere-commons/src/features/rag/rag_backend.h +++ b/sdk/runanywhere-commons/src/features/rag/rag_backend.h @@ -44,7 +44,11 @@ struct RAGBackendConfig { * Coordinates vector store, embeddings service, and LLM service for * retrieval-augmented generation. Thread-safe for all operations. */ +#if defined(_MSC_VER) +class RAGBackend { +#else class __attribute__((visibility("default"))) RAGBackend { +#endif public: /** * @brief Construct RAG pipeline with service handles diff --git a/sdk/runanywhere-commons/src/features/result_free.cpp b/sdk/runanywhere-commons/src/features/result_free.cpp index 241ddffe3..f47f55da9 100644 --- a/sdk/runanywhere-commons/src/features/result_free.cpp +++ b/sdk/runanywhere-commons/src/features/result_free.cpp @@ -13,9 +13,17 @@ #include "rac/features/tts/rac_tts_types.h" #include "rac/features/embeddings/rac_embeddings_types.h" +// MSVC does not support __attribute__((weak)). +// Use /alternatename linker directive for weak symbol emulation on MSVC. +#ifdef _MSC_VER +#define RAC_WEAK_SYMBOL +#else +#define RAC_WEAK_SYMBOL __attribute__((weak)) +#endif + extern "C" { -__attribute__((weak)) void rac_llm_result_free(rac_llm_result_t* result) { +RAC_WEAK_SYMBOL void rac_llm_result_free(rac_llm_result_t* result) { if (result) { if (result->text) { free(const_cast(result->text)); @@ -24,7 +32,7 @@ __attribute__((weak)) void rac_llm_result_free(rac_llm_result_t* result) { } } -__attribute__((weak)) void rac_stt_result_free(rac_stt_result_t* result) { +RAC_WEAK_SYMBOL void rac_stt_result_free(rac_stt_result_t* result) { if (result) { if (result->text) { free(const_cast(result->text)); @@ -48,7 +56,7 @@ __attribute__((weak)) void rac_stt_result_free(rac_stt_result_t* result) { } } -__attribute__((weak)) void rac_tts_result_free(rac_tts_result_t* result) { +RAC_WEAK_SYMBOL void rac_tts_result_free(rac_tts_result_t* result) { if (result) { if (result->audio_data) { free(result->audio_data); @@ -58,7 +66,7 @@ __attribute__((weak)) void rac_tts_result_free(rac_tts_result_t* result) { } } -__attribute__((weak)) void rac_embeddings_result_free(rac_embeddings_result_t* result) { +RAC_WEAK_SYMBOL void rac_embeddings_result_free(rac_embeddings_result_t* result) { if (result) { if (result->embeddings) { for (size_t i = 0; i < result->num_embeddings; i++) { diff --git a/sdk/runanywhere-commons/src/features/vlm/vlm_component.cpp b/sdk/runanywhere-commons/src/features/vlm/vlm_component.cpp index 2bc804233..04084adec 100644 --- a/sdk/runanywhere-commons/src/features/vlm/vlm_component.cpp +++ b/sdk/runanywhere-commons/src/features/vlm/vlm_component.cpp @@ -9,11 +9,10 @@ #include #include #include -#include #include #include #include -#include +#include "rac/core/rac_platform_compat.h" #include "rac/core/capabilities/rac_lifecycle.h" #include "rac/core/rac_core.h" diff --git a/sdk/runanywhere-commons/src/infrastructure/download/download_orchestrator.cpp b/sdk/runanywhere-commons/src/infrastructure/download/download_orchestrator.cpp index b8cbebb63..f52b0646f 100644 --- a/sdk/runanywhere-commons/src/infrastructure/download/download_orchestrator.cpp +++ b/sdk/runanywhere-commons/src/infrastructure/download/download_orchestrator.cpp @@ -18,12 +18,15 @@ #include #include #include -#include #include #include #include -#include #include +#include "rac/core/rac_platform_compat.h" + +#ifdef _WIN32 +#include // for _mkdir +#endif #include "rac/core/rac_logger.h" #include "rac/core/rac_platform_adapter.h" @@ -147,10 +150,18 @@ static bool mkdir_p(const char* path) { while ((pos = s.find('/', pos + 1)) != std::string::npos) { std::string sub = s.substr(0, pos); if (!sub.empty()) { +#ifdef _WIN32 + _mkdir(sub.c_str()); +#else mkdir(sub.c_str(), 0755); +#endif } } +#ifdef _WIN32 + return _mkdir(s.c_str()) == 0 || dir_exists(path); +#else return mkdir(s.c_str(), 0755) == 0 || dir_exists(path); +#endif } /** diff --git a/sdk/runanywhere-commons/src/infrastructure/extraction/rac_extraction.cpp b/sdk/runanywhere-commons/src/infrastructure/extraction/rac_extraction.cpp index daed9a0dd..cb6e5fea9 100644 --- a/sdk/runanywhere-commons/src/infrastructure/extraction/rac_extraction.cpp +++ b/sdk/runanywhere-commons/src/infrastructure/extraction/rac_extraction.cpp @@ -15,8 +15,7 @@ #include #include #include -#include -#include +#include "rac/core/rac_platform_compat.h" #ifdef _WIN32 #include // for _mkdir diff --git a/sdk/runanywhere-commons/src/infrastructure/telemetry/telemetry_json.cpp b/sdk/runanywhere-commons/src/infrastructure/telemetry/telemetry_json.cpp index fab9ec8d6..65d34cd3b 100644 --- a/sdk/runanywhere-commons/src/infrastructure/telemetry/telemetry_json.cpp +++ b/sdk/runanywhere-commons/src/infrastructure/telemetry/telemetry_json.cpp @@ -124,7 +124,11 @@ class JsonBuilder { time_t secs = ms / 1000; int millis = ms % 1000; struct tm tm_info; +#ifdef _WIN32 + gmtime_s(&tm_info, &secs); +#else gmtime_r(&secs, &tm_info); +#endif char buf[32]; strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &tm_info); diff --git a/sdk/runanywhere-commons/tests/CMakeLists.txt b/sdk/runanywhere-commons/tests/CMakeLists.txt index 6ddd981ba..77e49b104 100644 --- a/sdk/runanywhere-commons/tests/CMakeLists.txt +++ b/sdk/runanywhere-commons/tests/CMakeLists.txt @@ -4,6 +4,20 @@ cmake_minimum_required(VERSION 3.22) # Integration Test Suite for runanywhere-commons # ============================================================================= +# Helper: link bundled archive dependencies to test targets on MSVC. +# On MSVC, transitive static lib deps from archive_static don't propagate fully. +function(rac_link_archive_deps target) + if(TARGET archive_static) + target_link_libraries(${target} PRIVATE archive_static) + endif() + if(TARGET zlibstatic) + target_link_libraries(${target} PRIVATE zlibstatic) + endif() + if(TARGET bz2_bundled) + target_link_libraries(${target} PRIVATE bz2_bundled) + endif() +endfunction() + # --- test_core: Always built (no backend dependency) --- add_executable(test_core test_core.cpp) target_include_directories(test_core PRIVATE @@ -11,29 +25,29 @@ target_include_directories(test_core PRIVATE ${CMAKE_SOURCE_DIR}/include ) target_link_libraries(test_core PRIVATE rac_commons) +rac_link_archive_deps(test_core) target_compile_features(test_core PRIVATE cxx_std_20) add_test(NAME core_tests COMMAND test_core --run-all) # --- test_extraction: Always built (no backend dependency) --- -# Tests rac_extract_archive_native() and rac_detect_archive_type() from libarchive. add_executable(test_extraction test_extraction.cpp) target_include_directories(test_extraction PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/include ) target_link_libraries(test_extraction PRIVATE rac_commons) +rac_link_archive_deps(test_extraction) target_compile_features(test_extraction PRIVATE cxx_std_17) add_test(NAME extraction_tests COMMAND test_extraction --run-all) # --- test_download_orchestrator: Always built (no backend dependency) --- -# Tests rac_find_model_path_after_extraction(), rac_download_compute_destination(), -# and rac_download_requires_extraction() from rac_download_orchestrator.h. add_executable(test_download_orchestrator test_download_orchestrator.cpp) target_include_directories(test_download_orchestrator PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/include ) target_link_libraries(test_download_orchestrator PRIVATE rac_commons) +rac_link_archive_deps(test_download_orchestrator) target_compile_features(test_download_orchestrator PRIVATE cxx_std_17) add_test(NAME download_orchestrator_tests COMMAND test_download_orchestrator --run-all) @@ -132,11 +146,29 @@ if(UNIX AND NOT APPLE) endforeach() endif() +# --- Windows: Copy DLLs to test output directory --- +if(WIN32) + set(ONNX_WIN_BIN_DIR "${CMAKE_SOURCE_DIR}/third_party/onnxruntime-windows/bin") + set(SHERPA_WIN_BIN_DIR "${CMAKE_SOURCE_DIR}/third_party/sherpa-onnx-windows/bin") + foreach(test_target test_vad test_stt test_tts test_wakeword test_voice_agent) + if(TARGET ${test_target}) + # Copy ONNX Runtime DLL if present + if(EXISTS "${onnxruntime_SOURCE_DIR}/lib/onnxruntime.dll") + add_custom_command(TARGET ${test_target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${onnxruntime_SOURCE_DIR}/lib/onnxruntime.dll" + $ + ) + endif() + endif() + endforeach() +endif() + # ============================================================================= # RAG Pipeline Tests (GoogleTest) # ============================================================================= -if(RAC_BACKEND_RAG) +if(RAC_BUILD_BACKENDS AND RAC_BACKEND_RAG AND NOT (WIN32 AND RAC_BUILD_SHARED)) include(FetchContent) find_package(Threads REQUIRED) @@ -149,11 +181,16 @@ if(RAC_BACKEND_RAG) ) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) + include(GoogleTest) - # RAG pipeline thread safety test + # RAG pipeline thread safety test (disabled on Windows — needs IEmbeddingProvider internal header) + if(NOT WIN32) add_executable(rac_rag_backend_thread_safety_test rag_backend_thread_safety_test.cpp ) + target_include_directories(rac_rag_backend_thread_safety_test PRIVATE + ${CMAKE_SOURCE_DIR}/src/features/rag + ) target_link_libraries(rac_rag_backend_thread_safety_test PRIVATE rac_commons @@ -161,7 +198,6 @@ if(RAC_BACKEND_RAG) GTest::gtest_main ) target_compile_features(rac_rag_backend_thread_safety_test PRIVATE cxx_std_20) - include(GoogleTest) gtest_discover_tests(rac_rag_backend_thread_safety_test DISCOVERY_MODE PRE_TEST ) @@ -169,11 +205,15 @@ if(RAC_BACKEND_RAG) NAME rac_rag_backend_thread_safety_test COMMAND rac_rag_backend_thread_safety_test ) + endif() # Chunker Unit Tests add_executable(rac_chunker_test chunker_test.cpp ) + target_include_directories(rac_chunker_test PRIVATE + ${CMAKE_SOURCE_DIR}/src/features/rag + ) target_link_libraries(rac_chunker_test PRIVATE rac_commons diff --git a/sdk/runanywhere-commons/tests/test_common.h b/sdk/runanywhere-commons/tests/test_common.h index 748ef9513..d37fac566 100644 --- a/sdk/runanywhere-commons/tests/test_common.h +++ b/sdk/runanywhere-commons/tests/test_common.h @@ -1,6 +1,13 @@ #ifndef TEST_COMMON_H #define TEST_COMMON_H +// Ensure M_PI is defined on MSVC (requires _USE_MATH_DEFINES before ) +#ifdef _MSC_VER +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif +#endif + #include #include #include diff --git a/sdk/runanywhere-commons/tests/test_config.h b/sdk/runanywhere-commons/tests/test_config.h index 5dab8ca11..296c00ff4 100644 --- a/sdk/runanywhere-commons/tests/test_config.h +++ b/sdk/runanywhere-commons/tests/test_config.h @@ -4,7 +4,20 @@ #include #include #include -#include +#include "rac/core/rac_platform_compat.h" + +#ifdef _WIN32 +#include +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif +// realpath equivalent on Windows +static inline char* realpath(const char* path, char* resolved) { + return _fullpath(resolved, path, PATH_MAX); +} +#else +#include +#endif #include "test_common.h" diff --git a/sdk/runanywhere-commons/tests/test_download_orchestrator.cpp b/sdk/runanywhere-commons/tests/test_download_orchestrator.cpp index 614802595..3a6dc026d 100644 --- a/sdk/runanywhere-commons/tests/test_download_orchestrator.cpp +++ b/sdk/runanywhere-commons/tests/test_download_orchestrator.cpp @@ -19,8 +19,17 @@ #include #include #include -#include +#include "rac/core/rac_platform_compat.h" + +#ifdef _WIN32 +#include +#include +#include +#include +#define getpid _getpid +#else #include +#endif // ============================================================================= // Test helpers @@ -28,6 +37,14 @@ /** Create a unique temporary directory for test artifacts. */ static std::string create_temp_dir(const std::string& suffix) { +#ifdef _WIN32 + char tmp_path[MAX_PATH]; + GetTempPathA(MAX_PATH, tmp_path); + char tmp_dir[MAX_PATH]; + snprintf(tmp_dir, sizeof(tmp_dir), "%srac_dl_test_%s_%d", tmp_path, suffix.c_str(), getpid()); + _mkdir(tmp_dir); + return std::string(tmp_dir); +#else char tmpl[256]; snprintf(tmpl, sizeof(tmpl), "/tmp/rac_dl_test_%s_XXXXXX", suffix.c_str()); char* result = mkdtemp(tmpl); @@ -36,11 +53,16 @@ static std::string create_temp_dir(const std::string& suffix) { return ""; } return std::string(result); +#endif } /** Recursively remove a directory. */ static void remove_dir(const std::string& path) { +#ifdef _WIN32 + std::string cmd = "rmdir /s /q \"" + path + "\" 2>nul"; +#else std::string cmd = "rm -rf \"" + path + "\""; +#endif system(cmd.c_str()); } diff --git a/sdk/runanywhere-commons/tests/test_extraction.cpp b/sdk/runanywhere-commons/tests/test_extraction.cpp index bc62fc87a..0b2c2f370 100644 --- a/sdk/runanywhere-commons/tests/test_extraction.cpp +++ b/sdk/runanywhere-commons/tests/test_extraction.cpp @@ -19,12 +19,30 @@ #include #include #include -#include -#include +#include "rac/core/rac_platform_compat.h" #include +#ifdef _WIN32 +#include +#include +#include +#include +#define getpid _getpid +#else +#include +#endif + // No platform adapter or rac_init() needed — extraction APIs are standalone. +// Portable mkdir wrapper +static inline int compat_mkdir(const char* path) { +#ifdef _WIN32 + return _mkdir(path); +#else + return compat_mkdir(path); +#endif +} + // ============================================================================= // Test helpers // ============================================================================= @@ -33,6 +51,14 @@ static std::string g_test_dir; /** Create a unique temporary directory for test artifacts. */ static std::string create_temp_dir(const std::string& suffix) { +#ifdef _WIN32 + char tmp_path[MAX_PATH]; + GetTempPathA(MAX_PATH, tmp_path); + char tmp_dir[MAX_PATH]; + snprintf(tmp_dir, sizeof(tmp_dir), "%srac_test_%s_%d", tmp_path, suffix.c_str(), _getpid()); + _mkdir(tmp_dir); + return std::string(tmp_dir); +#else char tmpl[256]; snprintf(tmpl, sizeof(tmpl), "/tmp/rac_test_%s_XXXXXX", suffix.c_str()); char* result = mkdtemp(tmpl); @@ -41,6 +67,7 @@ static std::string create_temp_dir(const std::string& suffix) { return ""; } return std::string(result); +#endif } /** Recursively remove a directory. */ @@ -93,8 +120,8 @@ static bool has_zip() { static std::string create_test_tar_gz(const std::string& base_dir) { std::string content_dir = base_dir + "/content"; std::string sub_dir = content_dir + "/subdir"; - mkdir(content_dir.c_str(), 0755); - mkdir(sub_dir.c_str(), 0755); + compat_mkdir(content_dir.c_str()); + compat_mkdir(sub_dir.c_str()); write_file(content_dir + "/hello.txt", "Hello, World!\n"); write_file(content_dir + "/data.bin", std::string(256, '\x42')); @@ -113,8 +140,8 @@ static std::string create_test_tar_gz(const std::string& base_dir) { static std::string create_test_zip(const std::string& base_dir) { std::string content_dir = base_dir + "/zipcontent"; std::string sub_dir = content_dir + "/subdir"; - mkdir(content_dir.c_str(), 0755); - mkdir(sub_dir.c_str(), 0755); + compat_mkdir(content_dir.c_str()); + compat_mkdir(sub_dir.c_str()); write_file(content_dir + "/readme.txt", "ZIP test file\n"); write_file(content_dir + "/binary.dat", std::string(128, '\xAB')); @@ -565,8 +592,8 @@ static TestResult test_default_options_skip_macos() { std::string archive_dir = create_temp_dir("macos_src"); std::string content_dir = archive_dir + "/macos_content"; std::string macosx_dir = content_dir + "/__MACOSX"; - mkdir(content_dir.c_str(), 0755); - mkdir(macosx_dir.c_str(), 0755); + compat_mkdir(content_dir.c_str()); + compat_mkdir(macosx_dir.c_str()); write_file(content_dir + "/real_file.txt", "real content\n"); write_file(content_dir + "/._resource_fork", "resource fork\n"); @@ -618,8 +645,8 @@ static TestResult test_custom_options_keep_macos() { std::string archive_dir = create_temp_dir("keepmac_src"); std::string content_dir = archive_dir + "/keep_content"; std::string macosx_dir = content_dir + "/__MACOSX"; - mkdir(content_dir.c_str(), 0755); - mkdir(macosx_dir.c_str(), 0755); + compat_mkdir(content_dir.c_str()); + compat_mkdir(macosx_dir.c_str()); write_file(content_dir + "/file.txt", "content\n"); write_file(macosx_dir + "/meta.plist", "metadata\n");