Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/main/java/com/wowool/sdk/WowoolPipeline.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -62,7 +65,13 @@ static WowoolPipeline create(WowoolEngine engine, Path languagePath, List<String
MemorySegment exInfo = WowoolNative.allocateExceptionInfo(arena);

// 1. Create Analyzer
String analyzerOpts = "{\"language\":\"" + languagePath.toAbsolutePath() + "\"}";
String analyzerOpts;
try {
analyzerOpts = MAPPER
.writeValueAsString(Collections.singletonMap("language", languagePath.toAbsolutePath().toString()));
} catch (IOException e) {
throw new WowoolException("Failed to serialize analyzer options: " + e.getMessage(), e);
}
MemorySegment analyzerPtr = WowoolNative.createAnalyzer(engine.nativePointer(), analyzerOpts, arena, exInfo);

// 2. Load Domains with exception safety
Expand Down
17 changes: 13 additions & 4 deletions src/main/java/com/wowool/sdk/installer/WowoolInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ public static boolean isInstalled(Path targetDir) {
try (Stream<Path> 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.startsWith("wowool."))
&& (name.endsWith(".dylib") || name.contains(".so") || name.endsWith(".dll"));
});
} catch (IOException ignored) {
return false;
Expand Down Expand Up @@ -321,10 +322,14 @@ private void installCppBundle(String platform, Path libDir, Path binDir) throws
Predicate<String> 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.startsWith("windows") || platform.startsWith("win")) {
platformMatcher = fn -> fn.contains("win") && fn.contains("amd64");
} else if (platform.contains("aarch64") || platform.contains("arm64")) {
platformMatcher = fn -> fn.contains("manylinux") && (fn.contains("aarch64") || fn.contains("arm64"));
} else {
} else if (platform.contains("x86_64") || platform.contains("amd64") || platform.startsWith("linux")) {
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);
Expand All @@ -347,10 +352,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);
Expand Down Expand Up @@ -473,6 +478,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) {
}
}
Expand Down Expand Up @@ -513,6 +519,9 @@ private static String resolvePlatform(String platform) {
if (WowoolPaths.isLinux()) {
return "linux-" + WowoolPaths.normalizedArch();
}
if (WowoolPaths.isWindows()) {
return "windows-" + WowoolPaths.normalizedArch();
}
return "unknown-" + WowoolPaths.normalizedArch();
}

Expand Down
76 changes: 61 additions & 15 deletions src/main/java/com/wowool/sdk/internal/ffi/WowoolNative.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 &amp; Memory API - JEP 454)
Expand Down Expand Up @@ -62,19 +64,47 @@ 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");
for (String candidate : WowoolPaths.expectedLibraryNames()) {
Path p = dylibDir.resolve(candidate);
if (Files.exists(p)) {
wowoolLib = p;
break;
}
}
if (!Files.exists(wowoolLib)) {
wowoolLib = dylibDir.resolve("libwowool.so");

if (wowoolLib == null) {
throw new WowoolException("Native library (" + String.join(" / ", WowoolPaths.expectedLibraryNames())
+ ") not found in " + dylibDir);
}

if (!Files.exists(wowoolLib)) {
throw new WowoolException(
"Native library (libwowool.3.3.2.dylib / libwowool.dylib / libwowool.so) 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 (Stream<Path> dlls = Files.list(dylibDir)) {
List<Path> icuDlls = dlls.filter(p -> {
String n = p.getFileName().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());
}
}
}

if (Files.exists(icuData)) {
Expand All @@ -88,17 +118,33 @@ 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 = WowoolPaths.isWindows();
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 (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) {
}
} catch (Throwable ignored) {
Expand Down
31 changes: 19 additions & 12 deletions src/main/java/com/wowool/sdk/internal/ffi/WowoolPaths.java
Original file line number Diff line number Diff line change
Expand Up @@ -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").
*/
Expand Down Expand Up @@ -87,10 +94,16 @@ public static List<String> 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("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);
Expand Down Expand Up @@ -123,7 +136,7 @@ public static String resolveLicenseKey() {
}
}

throw new WowoolException(buildMissingLicenseMessage(homeKey));
return "";
}

/**
Expand Down Expand Up @@ -159,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)
Expand Down Expand Up @@ -543,11 +557,4 @@ private static String buildModelRootNotFoundMessage(List<String> 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=\"<your-license-key>\"\n"
+ " 2. Pass JVM property: -Dwowool.license.key=\"<your-license-key>\"\n"
+ " 3. Save key to file: mkdir -p ~/.config/wowool && echo \"<key>\" > " + homeKey;
}
}
2 changes: 2 additions & 0 deletions src/test/java/com/wowool/sdk/PlatformDiagnosticsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}

Expand Down
17 changes: 15 additions & 2 deletions src/test/java/com/wowool/sdk/installer/WowoolInstallerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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
Expand Down
Loading