diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml old mode 100644 new mode 100755 index 1b4dd3a8..fc35ef5e --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -35,10 +35,47 @@ jobs: */target/native/macos-*/ if-no-files-found: error + build-windows-natives: + name: "Build: Windows Natives" + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Cache M2 local repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: m2 + - name: Set up JDK 25 + uses: actions/setup-java@v4 + with: + java-version: 25 + distribution: temurin + - name: Set up MSYS2 (UCRT64) + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: false + install: >- + mingw-w64-ucrt-x86_64-gcc + mingw-w64-ucrt-x86_64-dlfcn + make + - name: Add MSYS2 UCRT64 to PATH + shell: msys2 {0} + run: echo "$(cygpath -w /ucrt64/bin)" >> $GITHUB_PATH + - name: Build + run: mvn --batch-mode --no-transfer-progress compile + - name: Upload Windows natives + uses: actions/upload-artifact@v4 + with: + name: windows-natives + path: | + */target/native/windows-*/ + if-no-files-found: error + build-jar: name: "Build: Packaging JARs" runs-on: ubuntu-latest - needs: [build-macos-natives] + needs: [build-macos-natives, build-windows-natives] steps: - uses: actions/checkout@v4 - name: Cache M2 local repository @@ -60,6 +97,11 @@ jobs: with: name: macos-natives path: . + - name: Download Windows natives + uses: actions/download-artifact@v4 + with: + name: windows-natives + path: . - name: Build run: mvn --batch-mode --no-transfer-progress package -DskipTests - name: Log content of jar files @@ -137,3 +179,39 @@ jobs: distribution: temurin - name: Test run: mvn --batch-mode --no-transfer-progress --projects test-utils,blas,lapack,arpack test --fail-at-end -Dmaven.main.skip=true -Dmaven.antrun.skip=true + + test-windows: + name: "Test: runs-on: windows-latest, jdk: ${{ matrix.jdk }}" + runs-on: windows-latest + needs: [build-jar] + strategy: + matrix: + jdk: [11, 17, 21, 25] + steps: + - uses: actions/checkout@v4 + - name: Cache M2 local repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: m2 + - name: Set up MSYS2 (UCRT64) with OpenBLAS + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: false + install: mingw-w64-ucrt-x86_64-openblas + - name: Add MSYS2 ucrt64 bin to PATH + shell: msys2 {0} + run: echo "$(cygpath -w /ucrt64/bin)" >> $GITHUB_PATH + - name: Download target folder + uses: actions/download-artifact@v4 + with: + name: target-dir + path: . + - name: Set up JDK ${{ matrix.jdk }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.jdk }} + distribution: temurin + - name: Test + run: mvn --batch-mode --no-transfer-progress --projects test-utils,blas test --fail-at-end "-Dmaven.main.skip=true" "-Dmaven.antrun.skip=true" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml old mode 100644 new mode 100755 index 89f013c9..f8fd7373 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,10 +35,47 @@ jobs: */target/native/macos-*/ if-no-files-found: error + build-windows-natives: + name: "Build: Windows Natives" + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Cache M2 local repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: m2 + - name: Set up JDK 25 + uses: actions/setup-java@v4 + with: + java-version: 25 + distribution: temurin + - name: Set up MSYS2 (UCRT64) + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: false + install: >- + mingw-w64-ucrt-x86_64-gcc + mingw-w64-ucrt-x86_64-dlfcn + make + - name: Add MSYS2 UCRT64 to PATH + shell: msys2 {0} + run: echo "$(cygpath -w /ucrt64/bin)" >> $GITHUB_PATH + - name: Build + run: mvn --batch-mode --no-transfer-progress compile + - name: Upload Windows natives + uses: actions/upload-artifact@v4 + with: + name: windows-natives + path: | + */target/native/windows-*/ + if-no-files-found: error + build-jar: name: "Build: Packaging JARs" runs-on: ubuntu-latest - needs: [build-macos-natives] + needs: [build-macos-natives, build-windows-natives] steps: - uses: actions/checkout@v4 - name: Cache M2 local repository @@ -60,6 +97,11 @@ jobs: with: name: macos-natives path: . + - name: Download Windows natives + uses: actions/download-artifact@v4 + with: + name: windows-natives + path: . - name: Build run: mvn --batch-mode --no-transfer-progress package -DskipTests - name: Log content of jar files @@ -138,10 +180,46 @@ jobs: - name: Test run: mvn --batch-mode --no-transfer-progress --projects test-utils,blas,lapack,arpack test --fail-at-end -Dmaven.main.skip=true -Dmaven.antrun.skip=true + test-windows: + name: "Test: runs-on: windows-latest, jdk: ${{ matrix.jdk }}" + runs-on: windows-latest + needs: [build-jar] + strategy: + matrix: + jdk: [11, 17, 21, 25] + steps: + - uses: actions/checkout@v4 + - name: Cache M2 local repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: m2 + - name: Set up MSYS2 (UCRT64) with OpenBLAS + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: false + install: mingw-w64-ucrt-x86_64-openblas + - name: Add MSYS2 ucrt64 bin to PATH + shell: msys2 {0} + run: echo "$(cygpath -w /ucrt64/bin)" >> $GITHUB_PATH + - name: Download target folder + uses: actions/download-artifact@v4 + with: + name: target-dir + path: . + - name: Set up JDK ${{ matrix.jdk }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.jdk }} + distribution: temurin + - name: Test + run: mvn --batch-mode --no-transfer-progress --projects test-utils,blas test --fail-at-end "-Dmaven.main.skip=true" "-Dmaven.antrun.skip=true" + release: name: Release runs-on: ubuntu-latest - needs: [build-jar, test-linux, test-macos] + needs: [build-jar, test-linux, test-macos, test-windows] # Map step output to job output outputs: release_upload_url: ${{ steps.create_release.outputs.upload_url }} diff --git a/arpack/src/main/java/dev/ludovic/netlib/arpack/JNIARPACK.java b/arpack/src/main/java/dev/ludovic/netlib/arpack/JNIARPACK.java index fa84cdf6..9e206285 100644 --- a/arpack/src/main/java/dev/ludovic/netlib/arpack/JNIARPACK.java +++ b/arpack/src/main/java/dev/ludovic/netlib/arpack/JNIARPACK.java @@ -28,9 +28,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermissions; final class JNIARPACK extends AbstractARPACK implements NativeARPACK { @@ -42,8 +44,11 @@ protected JNIARPACK() { if (osName == null || osName.isEmpty()) { throw new RuntimeException("Unable to load native implementation"); } + boolean isWindows = osName.startsWith("Windows"); if (osName.equals("Mac OS X")) { osName = "macos"; + } else if (isWindows) { + osName = "windows"; } String osArch = System.getProperty("os.arch"); if (osArch == null || osArch.isEmpty()) { @@ -51,15 +56,19 @@ protected JNIARPACK() { } String libPrefix = "libnetlibarpackjni"; - String libExtension = osName.equals("macos") ? ".dylib" : ".so"; + String libExtension = osName.equals("macos") ? ".dylib" : isWindows ? ".dll" : ".so"; String libName = libPrefix + libExtension; + FileAttribute[] attrs = FileSystems.getDefault() + .supportedFileAttributeViews().contains("posix") + ? new FileAttribute[]{ PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-x---")) } + : new FileAttribute[0]; + Path temp; try (InputStream resource = this.getClass().getClassLoader().getResourceAsStream( String.format("resources/native/%s-%s/%s", osName, osArch, libName))) { assert resource != null; - Files.copy(resource, temp = Files.createTempFile(libPrefix, libExtension, - PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-x---"))), + Files.copy(resource, temp = Files.createTempFile(libPrefix, libExtension, attrs), StandardCopyOption.REPLACE_EXISTING); temp.toFile().deleteOnExit(); } catch (IOException e) { diff --git a/arpack/src/main/native/jni.c b/arpack/src/main/native/jni.c old mode 100644 new mode 100755 index b21e3953..ec3c109c --- a/arpack/src/main/native/jni.c +++ b/arpack/src/main/native/jni.c @@ -26,7 +26,17 @@ #include #include #include -#include +#ifdef _WIN32 +# include +# define dlopen(name, flags) ((void*)LoadLibraryA(name)) +# define dlsym(h, name) ((void*)GetProcAddress((HMODULE)(h), name)) +# define dlclose(h) FreeLibrary((HMODULE)(h)) +# define dlerror() "LoadLibrary failed" +# define RTLD_LAZY 0 +# define RTLD_LOCAL 0 +#else +# include +#endif #include "dev_ludovic_netlib_arpack_JNIARPACK.h" @@ -2326,6 +2336,8 @@ jint JNI_OnLoad(JavaVM *vm, UNUSED void *reserved) { #else #error Unsupported darwin architecture #endif +#elif defined(_WIN32) + static const char *default_native_lib = "libarpack.dll"; #else static const char *default_native_lib = "libarpack.so.2"; #endif diff --git a/blas/src/main/java/dev/ludovic/netlib/blas/JNIBLAS.java b/blas/src/main/java/dev/ludovic/netlib/blas/JNIBLAS.java index f0793c42..8c60c8eb 100644 --- a/blas/src/main/java/dev/ludovic/netlib/blas/JNIBLAS.java +++ b/blas/src/main/java/dev/ludovic/netlib/blas/JNIBLAS.java @@ -28,9 +28,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermissions; import java.util.logging.Logger; @@ -45,8 +47,11 @@ protected JNIBLAS() { if (osName == null || osName.isEmpty()) { throw new RuntimeException("Unable to load native implementation"); } + boolean isWindows = osName.startsWith("Windows"); if (osName.equals("Mac OS X")) { osName = "macos"; + } else if (isWindows) { + osName = "windows"; } String osArch = System.getProperty("os.arch"); if (osArch == null || osArch.isEmpty()) { @@ -54,18 +59,20 @@ protected JNIBLAS() { } String libPrefix = "libnetlibblasjni"; - String libExtension = osName.equals("macos") ? ".dylib" : ".so"; + String libExtension = osName.equals("macos") ? ".dylib" : isWindows ? ".dll" : ".so"; String libName = libPrefix + libExtension; log.fine(String.format("Trying to load native implementation from resource: resources/native/%s-%s/%s", osName, osArch, libName)); + FileAttribute[] attrs = FileSystems.getDefault() + .supportedFileAttributeViews().contains("posix") + ? new FileAttribute[]{ PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-x---")) } + : new FileAttribute[0]; Path temp; try (InputStream resource = this.getClass().getClassLoader().getResourceAsStream( String.format("resources/native/%s-%s/%s", osName, osArch, libName))) { assert resource != null; - Files.copy(resource, temp = Files.createTempFile(libPrefix, libExtension, - PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-x---"))), - StandardCopyOption.REPLACE_EXISTING); + Files.copy(resource, temp = Files.createTempFile(libPrefix, libExtension, attrs), StandardCopyOption.REPLACE_EXISTING); temp.toFile().deleteOnExit(); } catch (IOException e) { throw new UncheckedIOException("Unable to load native implementation", e); diff --git a/blas/src/main/native/jni.c b/blas/src/main/native/jni.c old mode 100644 new mode 100755 index 9fdfac10..c17d0c40 --- a/blas/src/main/native/jni.c +++ b/blas/src/main/native/jni.c @@ -26,7 +26,17 @@ #include #include #include -#include +#ifdef _WIN32 +# include +# define dlopen(name, flags) ((void*)LoadLibraryA(name)) +# define dlsym(h, name) ((void*)GetProcAddress((HMODULE)(h), name)) +# define dlclose(h) FreeLibrary((HMODULE)(h)) +# define dlerror() "LoadLibrary failed" +# define RTLD_LAZY 0 +# define RTLD_LOCAL 0 +#else +# include +#endif #include "dev_ludovic_netlib_blas_JNIBLAS.h" @@ -2206,6 +2216,8 @@ jint JNI_OnLoad(JavaVM *vm, UNUSED void *reserved) { jstring property_nativeLib; #ifdef __APPLE__ static const char *default_native_lib = "/System/Library/Frameworks/Accelerate.framework/Accelerate"; +#elif defined(_WIN32) + static const char *default_native_lib = "libopenblas.dll"; #else static const char *default_native_lib = "libblas.so.3"; #endif diff --git a/generator.py b/generator.py old mode 100644 new mode 100755 index f032330b..13164e6d --- a/generator.py +++ b/generator.py @@ -345,7 +345,7 @@ def render_load_symbol(self): print(" // LOAD_SYMBOL({name}_);".format(name=self.name)) class Library: - def __init__(self, pkg, linux_libname, darwin_libname, routines): + def __init__(self, pkg, linux_libname, darwin_libname, win32_libname, routines): # Print copyright header print("/*") print(" * Copyright 2020, 2021, Ludovic Henry") @@ -376,7 +376,17 @@ def __init__(self, pkg, linux_libname, darwin_libname, routines): print("#include ") print("#include ") print("#include ") - print("#include ") + print("#ifdef _WIN32") + print("# include ") + print("# define dlopen(name, flags) ((void*)LoadLibraryA(name))") + print("# define dlsym(h, name) ((void*)GetProcAddress((HMODULE)(h), name))") + print("# define dlclose(h) FreeLibrary((HMODULE)(h))") + print("# define dlerror() \"LoadLibrary failed\"") + print("# define RTLD_LAZY 0") + print("# define RTLD_LOCAL 0") + print("#else") + print("# include ") + print("#endif") print() print("#include \"dev_ludovic_netlib_{pkg}_JNI{pkgupper}.h\"".format(pkg=pkg, pkgupper=pkg.upper())) print() @@ -504,6 +514,8 @@ def __init__(self, pkg, linux_libname, darwin_libname, routines): print("#endif") else: print(" static const char *default_native_lib = \"{libname}\";".format(libname=darwin_libname)) + print("#elif defined(_WIN32)") + print(" static const char *default_native_lib = \"{libname}\";".format(libname=win32_libname)) print("#else") print(" static const char *default_native_lib = \"{libname}\";".format(libname=linux_libname)) print("#endif") @@ -561,6 +573,7 @@ def __init__(self, pkg, linux_libname, darwin_libname, routines): Library("blas", linux_libname="libblas.so.3", darwin_libname="/System/Library/Frameworks/Accelerate.framework/Accelerate", + win32_libname="libopenblas.dll", routines=( RoutineR (JDoubleR(), "dasum", JInt("n"), JDoubleArray("x", "JNI_ABORT"), JInt("incx")), RoutineR (JFloatR(), "sasum", JInt("n"), JFloatArray("x", "JNI_ABORT"), JInt("incx")), @@ -636,6 +649,7 @@ def __init__(self, pkg, linux_libname, darwin_libname, routines): Library("lapack", linux_libname="liblapack.so.3", darwin_libname="/System/Library/Frameworks/Accelerate.framework/Accelerate", + win32_libname="libopenblas.dll", routines=( Routine ( "dbdsdc", JString("uplo"), JString("compq"), JInt("n"), JDoubleArray("d"), JDoubleArray("e"), JDoubleArray("u"), JInt("ldu"), JDoubleArray("vt"), JInt("ldvt"), JDoubleArray("q"), JIntArray("iq"), JDoubleArray("work"), JIntArray("iwork"), JIntW("info")), Routine ( "dbdsqr", JString("uplo"), JInt("n"), JInt("ncvt"), JInt("nru"), JInt("ncc"), JDoubleArray("d"), JDoubleArray("e"), JDoubleArray("vt"), JInt("ldvt"), JDoubleArray("u"), JInt("ldu"), JDoubleArray("c"), JInt("Ldc"), JDoubleArray("work"), JIntW("info")), @@ -1367,6 +1381,7 @@ def __init__(self, pkg, linux_libname, darwin_libname, routines): Library("arpack", linux_libname="libarpack.so.2", darwin_libname={"aarch64":"/opt/homebrew/lib/libarpack.dylib", "x86_64":"/usr/local/lib/libarpack.dylib"}, + win32_libname="libarpack.dll", routines=( Routine ( "dmout", JInt("lout"), JInt("m"), JInt("n"), JDoubleArray("a"), JInt("lda"), JInt("idigit"), JString("ifmt")), Routine ( "smout", JInt("lout"), JInt("m"), JInt("n"), JFloatArray("a"), JInt("lda"), JInt("idigit"), JString("ifmt")), diff --git a/lapack/src/main/java/dev/ludovic/netlib/lapack/JNILAPACK.java b/lapack/src/main/java/dev/ludovic/netlib/lapack/JNILAPACK.java index 1d6718ec..130a730f 100644 --- a/lapack/src/main/java/dev/ludovic/netlib/lapack/JNILAPACK.java +++ b/lapack/src/main/java/dev/ludovic/netlib/lapack/JNILAPACK.java @@ -28,9 +28,11 @@ import java.io.InputStream; import java.io.UncheckedIOException; import java.io.IOException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermissions; final class JNILAPACK extends AbstractLAPACK implements NativeLAPACK { @@ -42,8 +44,11 @@ protected JNILAPACK() { if (osName == null || osName.isEmpty()) { throw new RuntimeException("Unable to load native implementation"); } + boolean isWindows = osName.startsWith("Windows"); if (osName.equals("Mac OS X")) { osName = "macos"; + } else if (isWindows) { + osName = "windows"; } String osArch = System.getProperty("os.arch"); if (osArch == null || osArch.isEmpty()) { @@ -51,15 +56,19 @@ protected JNILAPACK() { } String libPrefix = "libnetliblapackjni"; - String libExtension = osName.equals("macos") ? ".dylib" : ".so"; + String libExtension = osName.equals("macos") ? ".dylib" : isWindows ? ".dll" : ".so"; String libName = libPrefix + libExtension; + FileAttribute[] attrs = FileSystems.getDefault() + .supportedFileAttributeViews().contains("posix") + ? new FileAttribute[]{ PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-x---")) } + : new FileAttribute[0]; + Path temp; try (InputStream resource = this.getClass().getClassLoader().getResourceAsStream( String.format("resources/native/%s-%s/%s", osName, osArch, libName))) { assert resource != null; - Files.copy(resource, temp = Files.createTempFile(libPrefix, libExtension, - PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-x---"))), + Files.copy(resource, temp = Files.createTempFile(libPrefix, libExtension, attrs), StandardCopyOption.REPLACE_EXISTING); temp.toFile().deleteOnExit(); } catch (IOException e) { diff --git a/lapack/src/main/native/jni.c b/lapack/src/main/native/jni.c old mode 100644 new mode 100755 index 97df3110..a3cfcf66 --- a/lapack/src/main/native/jni.c +++ b/lapack/src/main/native/jni.c @@ -26,7 +26,17 @@ #include #include #include -#include +#ifdef _WIN32 +# include +# define dlopen(name, flags) ((void*)LoadLibraryA(name)) +# define dlsym(h, name) ((void*)GetProcAddress((HMODULE)(h), name)) +# define dlclose(h) FreeLibrary((HMODULE)(h)) +# define dlerror() "LoadLibrary failed" +# define RTLD_LAZY 0 +# define RTLD_LOCAL 0 +#else +# include +#endif #include "dev_ludovic_netlib_lapack_JNILAPACK.h" @@ -29141,6 +29151,8 @@ jint JNI_OnLoad(JavaVM *vm, UNUSED void *reserved) { jstring property_nativeLib; #ifdef __APPLE__ static const char *default_native_lib = "/System/Library/Frameworks/Accelerate.framework/Accelerate"; +#elif defined(_WIN32) + static const char *default_native_lib = "libopenblas.dll"; #else static const char *default_native_lib = "liblapack.so.3"; #endif diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755 index 45943ea8..d3c66cce --- a/pom.xml +++ b/pom.xml @@ -193,6 +193,45 @@ information or have any questions. + + windows-native + + + windows + + + + + + + maven-antrun-plugin + + + compile + + run + + + + + + + + + + + + + + + + + + + + + +