From cd24fe6233b253cb06214271e316674c1fd7009e Mon Sep 17 00:00:00 2001 From: philippe forest Date: Thu, 29 Oct 2026 15:33:41 +0100 Subject: [PATCH 1/2] Fixing path, platform name, and escape chars. --- .../java/com/wowool/sdk/WowoolPipeline.java | 11 +++- .../wowool/sdk/installer/WowoolInstaller.java | 19 ++++--- .../wowool/sdk/internal/ffi/WowoolNative.java | 51 ++++++++++++++++--- .../wowool/sdk/internal/ffi/WowoolPaths.java | 9 ++-- 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/wowool/sdk/WowoolPipeline.java b/src/main/java/com/wowool/sdk/WowoolPipeline.java index 32ce9a9..120c677 100644 --- a/src/main/java/com/wowool/sdk/WowoolPipeline.java +++ b/src/main/java/com/wowool/sdk/WowoolPipeline.java @@ -20,6 +20,7 @@ import com.wowool.sdk.internal.ffi.WowoolPaths; import com.wowool.sdk.internal.json.WowoolJsonParser; import com.wowool.sdk.model.WowoolDocument; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.lang.foreign.Arena; @@ -40,6 +41,8 @@ */ public final class WowoolPipeline implements AutoCloseable { + private static final ObjectMapper MAPPER = new ObjectMapper(); + private final WowoolEngine engine; private final Path languagePath; private final MemorySegment analyzerPtr; @@ -62,7 +65,13 @@ static WowoolPipeline create(WowoolEngine engine, Path languagePath, List stream = Files.list(libDir)) { hasBinary = stream.anyMatch(p -> { String name = p.getFileName().toString(); - return name.startsWith("libwowool.") && (name.endsWith(".dylib") || name.contains(".so")); + return name.startsWith("libwowool.") && (name.endsWith(".dylib") || name.contains(".so") || name.endsWith(".dll")); }); } catch (IOException ignored) { return false; @@ -321,11 +321,14 @@ private void installCppBundle(String platform, Path libDir, Path binDir) throws Predicate platformMatcher; if (platform.contains("darwin") || platform.contains("macosx") || platform.contains("mac")) { platformMatcher = fn -> fn.contains("macosx") && (fn.contains("arm64") || fn.contains("universal2")); - } else if (platform.contains("aarch64") || platform.contains("arm64")) { - platformMatcher = fn -> fn.contains("manylinux") && (fn.contains("aarch64") || fn.contains("arm64")); - } else { + } else if (platform.startsWith("windows") || platform.startsWith("win")) { + platformMatcher = fn -> fn.contains("win") && fn.contains("amd64"); + } else if (platform.startsWith("linux") && platform.contains("x86_64")) { platformMatcher = fn -> fn.contains("manylinux") && fn.contains("x86_64"); } + else{ + throw new WowoolException("Unsupported platform: " + platform); + } String wheelUrl = resolveWheelUrl(new PackageSpec("wowool-sdk-cpp", null), platformMatcher); log("Downloading native binary package from: " + wheelUrl); @@ -347,10 +350,10 @@ private void installCppBundle(String platform, Path libDir, Path binDir) throws String fn = fileName.toString(); // Shared libraries (*.dylib, *.so, *.so.*) - if (fn.contains(".dylib") || fn.contains(".so")) { + if (fn.contains(".dylib") || fn.contains(".so") || fn.endsWith(".dll")) { Path dest = libDir.resolve(fn); Files.copy(zis, dest, StandardCopyOption.REPLACE_EXISTING); - } else if (fn.equals("afst++") || fn.equals("woc++") || fn.equals("wow++")) { + } else if (fn.matches("(afst|woc|wow)\\+\\+(\\.exe)?")) { Path dest = binDir.resolve(fn); Files.copy(zis, dest, StandardCopyOption.REPLACE_EXISTING); makeExecutableSafe(dest); @@ -473,6 +476,7 @@ private void linkCanonicalLibrary(Path libDir) { return fn.startsWith("libwowool.so.") && !fn.equals("libwowool.so"); }).findFirst().ifPresent(target -> createSymlinkSafe(canonicalLinux, target.getFileName())); } + } catch (IOException ignored) { } } @@ -513,6 +517,9 @@ private static String resolvePlatform(String platform) { if (WowoolPaths.isLinux()) { return "linux-" + WowoolPaths.normalizedArch(); } + if (System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win")) { + return "windows-" + WowoolPaths.normalizedArch(); + } return "unknown-" + WowoolPaths.normalizedArch(); } diff --git a/src/main/java/com/wowool/sdk/internal/ffi/WowoolNative.java b/src/main/java/com/wowool/sdk/internal/ffi/WowoolNative.java index 01e805e..0c9ab44 100644 --- a/src/main/java/com/wowool/sdk/internal/ffi/WowoolNative.java +++ b/src/main/java/com/wowool/sdk/internal/ffi/WowoolNative.java @@ -70,13 +70,37 @@ public static synchronized void ensureLoaded(Path dylibDir) { if (!Files.exists(wowoolLib)) { wowoolLib = dylibDir.resolve("libwowool.so"); } + if (!Files.exists(wowoolLib)) { + wowoolLib = dylibDir.resolve("wowool.dll"); + } if (!Files.exists(wowoolLib)) { throw new WowoolException( - "Native library (libwowool.3.3.2.dylib / libwowool.dylib / libwowool.so) not found in " + "Native library (libwowool.3.3.2.dylib / libwowool.dylib / libwowool.so / wowool.dll) not found in " + dylibDir); } + if (wowoolLib.getFileName().toString().endsWith(".dll")) { + // Load dependencies first; Windows does not search the DLL's own directory + // for System.load'ed libraries. + for (String dep : new String[] { "vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll", + "msvcp140_1.dll", "msvcp140_2.dll", "msvcp140_atomic_wait.dll", "msvcp140_codecvt_ids.dll", + "concrt140.dll" }) { + Path p = dylibDir.resolve(dep); + if (Files.exists(p)) { + System.load(p.toAbsolutePath().toString()); + } + } + try (var dlls = Files.list(dylibDir)) { + for (Path p : (Iterable) dlls::iterator) { + String n = p.getFileName().toString(); + if (n.startsWith("icu") && n.endsWith(".dll")) { + System.load(p.toAbsolutePath().toString()); + } + } + } + } + if (Files.exists(icuData)) { System.load(icuData.toAbsolutePath().toString()); } @@ -88,17 +112,30 @@ public static synchronized void ensureLoaded(Path dylibDir) { Linker linker = Linker.nativeLinker(); // Ensure WOWOOL_ROOT and WOWOOL_SDK_KEY are exported into the native process - // environment via POSIX setenv - linker.defaultLookup().find("setenv").ifPresent(setenvSym -> { + // environment via POSIX setenv (or _putenv_s from the UCRT on Windows) + boolean windows = System.getProperty("os.name", "").toLowerCase().contains("win"); + linker.defaultLookup().find(windows ? "_putenv_s" : "setenv").ifPresent(setenvSym -> { try (Arena a = Arena.ofConfined()) { - MethodHandle setenv = linker.downcallHandle(setenvSym, FunctionDescriptor.of(ValueLayout.JAVA_INT, - ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)); + MethodHandle setenv = windows + ? linker.downcallHandle(setenvSym, FunctionDescriptor.of(ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, ValueLayout.ADDRESS)) + : linker.downcallHandle(setenvSym, FunctionDescriptor.of(ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)); Path wowoolRoot = WowoolPaths.resolveWowoolRoot(); - setenv.invoke(a.allocateFrom("WOWOOL_ROOT"), a.allocateFrom(wowoolRoot.toString()), 1); + String rootStr = wowoolRoot.toString(); + if (windows) { + setenv.invoke(a.allocateFrom("WOWOOL_ROOT"), a.allocateFrom(rootStr)); + } else { + setenv.invoke(a.allocateFrom("WOWOOL_ROOT"), a.allocateFrom(rootStr), 1); + } try { String key = WowoolPaths.resolveLicenseKey(); - setenv.invoke(a.allocateFrom("WOWOOL_SDK_KEY"), a.allocateFrom(key), 1); + if (windows) { + setenv.invoke(a.allocateFrom("WOWOOL_SDK_KEY"), a.allocateFrom(key)); + } else { + setenv.invoke(a.allocateFrom("WOWOOL_SDK_KEY"), a.allocateFrom(key), 1); + } } catch (Throwable ignored) { } } catch (Throwable ignored) { diff --git a/src/main/java/com/wowool/sdk/internal/ffi/WowoolPaths.java b/src/main/java/com/wowool/sdk/internal/ffi/WowoolPaths.java index d70eaca..7bebcbe 100644 --- a/src/main/java/com/wowool/sdk/internal/ffi/WowoolPaths.java +++ b/src/main/java/com/wowool/sdk/internal/ffi/WowoolPaths.java @@ -88,9 +88,10 @@ public static List expectedLibraryNames() { names.add("libwowool.so"); names.add("libwowool.dylib"); } else { - names.add("libwowool.3.4.1.dylib"); - names.add("libwowool.3.3.2.dylib"); - names.add("libwowool.dylib"); + names.add("wowool.dll"); + names.add("libwowool.3.4.1.dll"); + names.add("libwowool.3.3.2.dll"); + names.add("libwowool.dll"); names.add("libwowool.so"); } return Collections.unmodifiableList(names); @@ -123,7 +124,7 @@ public static String resolveLicenseKey() { } } - throw new WowoolException(buildMissingLicenseMessage(homeKey)); + return ""; } /** From 6c3fb3366ff4fe85487ac36e54e9b12a5eda7c94 Mon Sep 17 00:00:00 2001 From: Rik Doclo Date: Thu, 29 Oct 2026 19:27:03 +0100 Subject: [PATCH 2/2] fix(windows): resolve CI formatting, restore linux-aarch64 support, and fix windows dll discovery - Restore Linux aarch64/arm64 wheel detection in WowoolInstaller - Fix hasInstalledBinaries() to recognize wowool.dll on Windows - Add WowoolPaths.isWindows() and fix native/lib architecture folder resolution - Deterministically sort ICU DLL loading order (icudt -> icuuc -> other) on Windows - Only set WOWOOL_SDK_KEY when non-blank to prevent unexpected CRT env deletion - Remove unused buildMissingLicenseMessage dead code - Comply with Spotless formatting and Checkstyle standards - Add test coverage for Windows wowool.dll detection in WowoolInstallerTest --- .../java/com/wowool/sdk/WowoolPipeline.java | 4 +- .../wowool/sdk/installer/WowoolInstaller.java | 12 ++-- .../wowool/sdk/internal/ffi/WowoolNative.java | 63 +++++++++++-------- .../wowool/sdk/internal/ffi/WowoolPaths.java | 22 ++++--- .../wowool/sdk/PlatformDiagnosticsTest.java | 2 + .../sdk/installer/WowoolInstallerTest.java | 17 ++++- 6 files changed, 76 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/wowool/sdk/WowoolPipeline.java b/src/main/java/com/wowool/sdk/WowoolPipeline.java index 120c677..350ee3b 100644 --- a/src/main/java/com/wowool/sdk/WowoolPipeline.java +++ b/src/main/java/com/wowool/sdk/WowoolPipeline.java @@ -67,8 +67,8 @@ static WowoolPipeline create(WowoolEngine engine, Path languagePath, List stream = Files.list(libDir)) { hasBinary = stream.anyMatch(p -> { String name = p.getFileName().toString(); - return name.startsWith("libwowool.") && (name.endsWith(".dylib") || name.contains(".so") || name.endsWith(".dll")); + return (name.startsWith("libwowool.") || name.startsWith("wowool.")) + && (name.endsWith(".dylib") || name.contains(".so") || name.endsWith(".dll")); }); } catch (IOException ignored) { return false; @@ -323,10 +324,11 @@ private void installCppBundle(String platform, Path libDir, Path binDir) throws platformMatcher = fn -> fn.contains("macosx") && (fn.contains("arm64") || fn.contains("universal2")); } else if (platform.startsWith("windows") || platform.startsWith("win")) { platformMatcher = fn -> fn.contains("win") && fn.contains("amd64"); - } else if (platform.startsWith("linux") && platform.contains("x86_64")) { + } else if (platform.contains("aarch64") || platform.contains("arm64")) { + platformMatcher = fn -> fn.contains("manylinux") && (fn.contains("aarch64") || fn.contains("arm64")); + } else if (platform.contains("x86_64") || platform.contains("amd64") || platform.startsWith("linux")) { platformMatcher = fn -> fn.contains("manylinux") && fn.contains("x86_64"); - } - else{ + } else { throw new WowoolException("Unsupported platform: " + platform); } @@ -517,7 +519,7 @@ private static String resolvePlatform(String platform) { if (WowoolPaths.isLinux()) { return "linux-" + WowoolPaths.normalizedArch(); } - if (System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win")) { + if (WowoolPaths.isWindows()) { return "windows-" + WowoolPaths.normalizedArch(); } return "unknown-" + WowoolPaths.normalizedArch(); diff --git a/src/main/java/com/wowool/sdk/internal/ffi/WowoolNative.java b/src/main/java/com/wowool/sdk/internal/ffi/WowoolNative.java index 0c9ab44..4d0c9fe 100644 --- a/src/main/java/com/wowool/sdk/internal/ffi/WowoolNative.java +++ b/src/main/java/com/wowool/sdk/internal/ffi/WowoolNative.java @@ -27,6 +27,8 @@ import java.lang.invoke.MethodHandle; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; +import java.util.stream.Stream; /** * Low-level Project Panama (Foreign Function & Memory API - JEP 454) @@ -62,41 +64,45 @@ public static synchronized void ensureLoaded(Path dylibDir) { try { Path icuData = dylibDir.resolve("libicudata.dylib"); Path icuUc = dylibDir.resolve("libicuuc.dylib"); - Path wowoolLib = dylibDir.resolve("libwowool.3.3.2.dylib"); + Path wowoolLib = null; - if (!Files.exists(wowoolLib)) { - wowoolLib = dylibDir.resolve("libwowool.dylib"); - } - if (!Files.exists(wowoolLib)) { - wowoolLib = dylibDir.resolve("libwowool.so"); - } - if (!Files.exists(wowoolLib)) { - wowoolLib = dylibDir.resolve("wowool.dll"); + for (String candidate : WowoolPaths.expectedLibraryNames()) { + Path p = dylibDir.resolve(candidate); + if (Files.exists(p)) { + wowoolLib = p; + break; + } } - if (!Files.exists(wowoolLib)) { - throw new WowoolException( - "Native library (libwowool.3.3.2.dylib / libwowool.dylib / libwowool.so / wowool.dll) not found in " - + dylibDir); + if (wowoolLib == null) { + throw new WowoolException("Native library (" + String.join(" / ", WowoolPaths.expectedLibraryNames()) + + ") not found in " + dylibDir); } if (wowoolLib.getFileName().toString().endsWith(".dll")) { // Load dependencies first; Windows does not search the DLL's own directory // for System.load'ed libraries. - for (String dep : new String[] { "vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll", + for (String dep : new String[]{"vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll", "msvcp140_1.dll", "msvcp140_2.dll", "msvcp140_atomic_wait.dll", "msvcp140_codecvt_ids.dll", - "concrt140.dll" }) { + "concrt140.dll"}) { Path p = dylibDir.resolve(dep); if (Files.exists(p)) { System.load(p.toAbsolutePath().toString()); } } - try (var dlls = Files.list(dylibDir)) { - for (Path p : (Iterable) dlls::iterator) { + try (Stream dlls = Files.list(dylibDir)) { + List icuDlls = dlls.filter(p -> { String n = p.getFileName().toString(); - if (n.startsWith("icu") && n.endsWith(".dll")) { - System.load(p.toAbsolutePath().toString()); - } + return n.startsWith("icu") && n.endsWith(".dll"); + }).sorted((p1, p2) -> { + String n1 = p1.getFileName().toString(); + String n2 = p2.getFileName().toString(); + int r1 = n1.contains("dt") ? 0 : (n1.contains("uc") ? 1 : 2); + int r2 = n2.contains("dt") ? 0 : (n2.contains("uc") ? 1 : 2); + return Integer.compare(r1, r2); + }).toList(); + for (Path p : icuDlls) { + System.load(p.toAbsolutePath().toString()); } } } @@ -113,12 +119,13 @@ public static synchronized void ensureLoaded(Path dylibDir) { // Ensure WOWOOL_ROOT and WOWOOL_SDK_KEY are exported into the native process // environment via POSIX setenv (or _putenv_s from the UCRT on Windows) - boolean windows = System.getProperty("os.name", "").toLowerCase().contains("win"); + boolean windows = WowoolPaths.isWindows(); linker.defaultLookup().find(windows ? "_putenv_s" : "setenv").ifPresent(setenvSym -> { try (Arena a = Arena.ofConfined()) { MethodHandle setenv = windows - ? linker.downcallHandle(setenvSym, FunctionDescriptor.of(ValueLayout.JAVA_INT, - ValueLayout.ADDRESS, ValueLayout.ADDRESS)) + ? linker.downcallHandle(setenvSym, + FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, + ValueLayout.ADDRESS)) : linker.downcallHandle(setenvSym, FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)); Path wowoolRoot = WowoolPaths.resolveWowoolRoot(); @@ -131,10 +138,12 @@ public static synchronized void ensureLoaded(Path dylibDir) { try { String key = WowoolPaths.resolveLicenseKey(); - if (windows) { - setenv.invoke(a.allocateFrom("WOWOOL_SDK_KEY"), a.allocateFrom(key)); - } else { - setenv.invoke(a.allocateFrom("WOWOOL_SDK_KEY"), a.allocateFrom(key), 1); + if (key != null && !key.isBlank()) { + if (windows) { + setenv.invoke(a.allocateFrom("WOWOOL_SDK_KEY"), a.allocateFrom(key)); + } else { + setenv.invoke(a.allocateFrom("WOWOOL_SDK_KEY"), a.allocateFrom(key), 1); + } } } catch (Throwable ignored) { } diff --git a/src/main/java/com/wowool/sdk/internal/ffi/WowoolPaths.java b/src/main/java/com/wowool/sdk/internal/ffi/WowoolPaths.java index 7bebcbe..9b05a88 100644 --- a/src/main/java/com/wowool/sdk/internal/ffi/WowoolPaths.java +++ b/src/main/java/com/wowool/sdk/internal/ffi/WowoolPaths.java @@ -58,6 +58,13 @@ public static boolean isLinux() { return OS_NAME.contains("linux"); } + /** + * Checks if the host operating system is Windows. + */ + public static boolean isWindows() { + return OS_NAME.contains("win"); + } + /** * Returns a normalized architecture identifier (e.g., "aarch64", "x86_64"). */ @@ -87,6 +94,11 @@ public static List expectedLibraryNames() { names.add("libwowool.3.3.2.so"); names.add("libwowool.so"); names.add("libwowool.dylib"); + } else if (isWindows()) { + names.add("wowool.dll"); + names.add("libwowool.3.4.1.dll"); + names.add("libwowool.3.3.2.dll"); + names.add("libwowool.dll"); } else { names.add("wowool.dll"); names.add("libwowool.3.4.1.dll"); @@ -160,7 +172,8 @@ public static Path resolveDylibDir() { // 2. Project-local directory and arch subdirectories candidates.add(Path.of("native/dylibs")); candidates.add(Path.of("native/lib")); - candidates.add(Path.of("native/lib", isMac() ? "darwin-" + arch : "linux-" + arch)); + String osArchFolder = isMac() ? "darwin-" + arch : (isWindows() ? "windows-" + arch : "linux-" + arch); + candidates.add(Path.of("native/lib", osArchFolder)); candidates.add(Path.of("native/lib", arch)); // 3. Dynamic Python Virtualenv if active ($VIRTUAL_ENV or local .venv) @@ -544,11 +557,4 @@ private static String buildModelRootNotFoundMessage(List probed) { sb.append(" 3. Mount or unpack models into: ~/.wowool/models/ or /opt/wowool/models/\n"); return sb.toString(); } - - private static String buildMissingLicenseMessage(Path homeKey) { - return "Wowool license key not found.\n" + "Actionable Remediation:\n" - + " 1. Set environment variable: export WOWOOL_SDK_KEY=\"\"\n" - + " 2. Pass JVM property: -Dwowool.license.key=\"\"\n" - + " 3. Save key to file: mkdir -p ~/.config/wowool && echo \"\" > " + homeKey; - } } diff --git a/src/test/java/com/wowool/sdk/PlatformDiagnosticsTest.java b/src/test/java/com/wowool/sdk/PlatformDiagnosticsTest.java index 8649033..e060a60 100644 --- a/src/test/java/com/wowool/sdk/PlatformDiagnosticsTest.java +++ b/src/test/java/com/wowool/sdk/PlatformDiagnosticsTest.java @@ -45,6 +45,8 @@ void testPlatformArchitectureDetection() { assertTrue(names.contains("libwowool.3.3.2.dylib") || names.contains("libwowool.dylib")); } else if (WowoolPaths.isLinux()) { assertTrue(names.contains("libwowool.so.3.3.2") || names.contains("libwowool.so")); + } else if (WowoolPaths.isWindows()) { + assertTrue(names.contains("wowool.dll") || names.contains("libwowool.dll")); } } diff --git a/src/test/java/com/wowool/sdk/installer/WowoolInstallerTest.java b/src/test/java/com/wowool/sdk/installer/WowoolInstallerTest.java index cdbbafc..e87295a 100644 --- a/src/test/java/com/wowool/sdk/installer/WowoolInstallerTest.java +++ b/src/test/java/com/wowool/sdk/installer/WowoolInstallerTest.java @@ -61,7 +61,7 @@ void testIsInstalledWithInvalidPaths(@TempDir Path tempDir) { } @Test - @DisplayName("Verify isInstalled returns true when binaries and models are present") + @DisplayName("Verify isInstalled returns true when binaries and models are present across platforms") void testIsInstalledWithCompleteStructure(@TempDir Path tempDir) throws Exception { Path libDir = tempDir.resolve("lib"); Path modelsDir = tempDir.resolve("models/lxware/wowool"); @@ -70,7 +70,20 @@ void testIsInstalledWithCompleteStructure(@TempDir Path tempDir) throws Exceptio Files.createDirectories(modelsDir); Files.createFile(libDir.resolve("libwowool.dylib")); - assertTrue(WowoolInstaller.isInstalled(tempDir), "Properly structured directory must report as installed"); + assertTrue(WowoolInstaller.isInstalled(tempDir), "Darwin library structure must report as installed"); + } + + @Test + @DisplayName("Verify isInstalled recognizes Windows wowool.dll") + void testIsInstalledWithWindowsDll(@TempDir Path tempDir) throws Exception { + Path libDir = tempDir.resolve("lib"); + Path modelsDir = tempDir.resolve("models/lxware/wowool"); + + Files.createDirectories(libDir); + Files.createDirectories(modelsDir); + Files.createFile(libDir.resolve("wowool.dll")); + + assertTrue(WowoolInstaller.isInstalled(tempDir), "Windows wowool.dll structure must report as installed"); } @Test