From 95dad1429023946d49ed786d987c866bd73ff19f Mon Sep 17 00:00:00 2001 From: philwalk Date: Sun, 15 Mar 2026 10:54:28 -0600 Subject: [PATCH 01/12] Fix UnsupportedOperationException on Windows in JNI* loaders On Windows (NTFS), Files.createTempFile with POSIX file attributes throws: UnsupportedOperationException: 'posix:permissions' not supported as initial attribute This crash prevents JNIBLAS, JNILAPACK, and JNIARPACK from loading on any Windows JVM, silently falling back to the pure-Java implementation. Fix: guard the POSIX attribute with a FileSystems.getDefault() check, which returns an empty attribute array on non-POSIX filesystems (Windows/NTFS). Also add Windows os.name normalisation ("windows") and .dll extension to match the existing macOS pattern. Affected files: JNIBLAS.java, JNILAPACK.java, JNIARPACK.java --- .../java/dev/ludovic/netlib/arpack/JNIARPACK.java | 15 ++++++++++++--- .../java/dev/ludovic/netlib/blas/JNIBLAS.java | 15 +++++++++++---- .../java/dev/ludovic/netlib/lapack/JNILAPACK.java | 15 ++++++++++++--- 3 files changed, 35 insertions(+), 10 deletions(-) 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/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/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) { From 1a76ac60f919778784d3d9254e306265e0eed176 Mon Sep 17 00:00:00 2001 From: philwalk Date: Fri, 20 Mar 2026 06:44:55 -0600 Subject: [PATCH 02/12] Add Windows native build support (UCRT64 JNI DLL + CI workflow) --- .github/workflows/build-and-test.yml | 87 ++++++++++++++++++++++++++- .github/workflows/release.yml | 89 +++++++++++++++++++++++++++- blas/src/main/native/jni.c | 12 +++- mybuild.sh | 32 ++++++++++ pom.xml | 39 ++++++++++++ 5 files changed, 255 insertions(+), 4 deletions(-) mode change 100644 => 100755 .github/workflows/build-and-test.yml mode change 100644 => 100755 .github/workflows/release.yml mode change 100644 => 100755 blas/src/main/native/jni.c create mode 100755 mybuild.sh mode change 100644 => 100755 pom.xml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml old mode 100644 new mode 100755 index 1b4dd3a8..20d2b237 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -35,10 +35,57 @@ 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 + make + - name: Generate JNI header + run: mvn --batch-mode --no-transfer-progress -pl blas -am compile -Dmaven.antrun.skip=true + - name: Build Windows DLL + shell: msys2 {0} + run: | + JAVA_INC="$(cygpath -u "$JAVA_HOME")/include" + mkdir -p blas/target/native/windows-amd64 blas/target/objs/windows-amd64 + gcc -c -Werror -Wall -Wextra -Wno-unused-label -O2 \ + -I blas/src/main/native \ + -I blas/target/include \ + -I "$JAVA_INC" -I "$JAVA_INC/win32" \ + -o blas/target/objs/windows-amd64/jni.o \ + blas/src/main/native/jni.c + gcc -shared -Wl,-Bstatic,-ldl,-Bdynamic \ + -o blas/target/native/windows-amd64/libnetlibblasjni.dll \ + blas/target/objs/windows-amd64/jni.o + - 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 +107,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 +189,36 @@ 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: 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 -Ddev.ludovic.netlib.blas.nativeLib=libopenblas.dll diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml old mode 100644 new mode 100755 index 89f013c9..88a7f9c6 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,10 +35,57 @@ 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 + make + - name: Generate JNI header + run: mvn --batch-mode --no-transfer-progress -pl blas -am compile -Dmaven.antrun.skip=true + - name: Build Windows DLL + shell: msys2 {0} + run: | + JAVA_INC="$(cygpath -u "$JAVA_HOME")/include" + mkdir -p blas/target/native/windows-amd64 blas/target/objs/windows-amd64 + gcc -c -Werror -Wall -Wextra -Wno-unused-label -O2 \ + -I blas/src/main/native \ + -I blas/target/include \ + -I "$JAVA_INC" -I "$JAVA_INC/win32" \ + -o blas/target/objs/windows-amd64/jni.o \ + blas/src/main/native/jni.c + gcc -shared -Wl,-Bstatic,-ldl,-Bdynamic \ + -o blas/target/native/windows-amd64/libnetlibblasjni.dll \ + blas/target/objs/windows-amd64/jni.o + - 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 +107,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 +190,43 @@ 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: 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 -Ddev.ludovic.netlib.blas.nativeLib=libopenblas.dll + 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/blas/src/main/native/jni.c b/blas/src/main/native/jni.c old mode 100644 new mode 100755 index 9fdfac10..073c092f --- 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" diff --git a/mybuild.sh b/mybuild.sh new file mode 100755 index 00000000..70749189 --- /dev/null +++ b/mybuild.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Replicates the CI "build-windows-natives" job locally. +# Run from the repo root in an MSYS2 UCRT64 shell. +set -euo pipefail + +PATH="/ucrt64/bin:$PATH" + +# Step 1: compile Java + generate JNI header into blas/target/include/ +echo "=== Generating JNI header ===" +mvn --batch-mode --no-transfer-progress -pl blas -am compile -Dmaven.antrun.skip=true + +# Step 2: build the DLL (mirrors CI "Build Windows DLL" step) +echo "=== Building Windows DLL ===" +JAVA_INC="$(cygpath -u "$JAVA_HOME")/include" +mkdir -p blas/target/native/windows-amd64 blas/target/objs/windows-amd64 + +gcc -c -Werror -Wall -Wextra -Wno-unused-label -O2 \ + -I blas/src/main/native \ + -I blas/target/include \ + -I "$JAVA_INC" -I "$JAVA_INC/win32" \ + -o blas/target/objs/windows-amd64/jni.o \ + blas/src/main/native/jni.c + +gcc -shared -Wl,-Bstatic,-ldl,-Bdynamic \ + -o blas/target/native/windows-amd64/libnetlibblasjni.dll \ + blas/target/objs/windows-amd64/jni.o + +echo "" +echo "Done: blas/target/native/windows-amd64/libnetlibblasjni.dll" +echo "" +echo "DLL imports:" +objdump -p blas/target/native/windows-amd64/libnetlibblasjni.dll | grep "DLL Name" 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 + + + + + + + + + + + + + + + + + + + + + + From 170a1d87da9e9db691ecb938c30ee71cf8450458 Mon Sep 17 00:00:00 2001 From: philwalk Date: Fri, 20 Mar 2026 07:37:45 -0600 Subject: [PATCH 03/12] fix for powershell mvn errors --- .github/workflows/build-and-test.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 20d2b237..c56f0793 100755 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -59,7 +59,7 @@ jobs: mingw-w64-ucrt-x86_64-gcc make - name: Generate JNI header - run: mvn --batch-mode --no-transfer-progress -pl blas -am compile -Dmaven.antrun.skip=true + run: mvn --batch-mode --no-transfer-progress -pl blas -am compile "-Dmaven.antrun.skip=true" - name: Build Windows DLL shell: msys2 {0} run: | @@ -221,4 +221,4 @@ jobs: 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 -Ddev.ludovic.netlib.blas.nativeLib=libopenblas.dll + run: mvn --batch-mode --no-transfer-progress --projects test-utils,blas test --fail-at-end "-Dmaven.main.skip=true" "-Dmaven.antrun.skip=true" "-Ddev.ludovic.netlib.blas.nativeLib=libopenblas.dll" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88a7f9c6..1bc32d8f 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,7 +59,7 @@ jobs: mingw-w64-ucrt-x86_64-gcc make - name: Generate JNI header - run: mvn --batch-mode --no-transfer-progress -pl blas -am compile -Dmaven.antrun.skip=true + run: mvn --batch-mode --no-transfer-progress -pl blas -am compile "-Dmaven.antrun.skip=true" - name: Build Windows DLL shell: msys2 {0} run: | @@ -221,7 +221,7 @@ jobs: 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 -Ddev.ludovic.netlib.blas.nativeLib=libopenblas.dll + run: mvn --batch-mode --no-transfer-progress --projects test-utils,blas test --fail-at-end "-Dmaven.main.skip=true" "-Dmaven.antrun.skip=true" "-Ddev.ludovic.netlib.blas.nativeLib=libopenblas.dll" release: name: Release From 48bc52bee5b689170b72856c5cb50d6c83611ff0 Mon Sep 17 00:00:00 2001 From: philwalk Date: Fri, 20 Mar 2026 07:41:35 -0600 Subject: [PATCH 04/12] added required ucr64 lib --- .github/workflows/build-and-test.yml | 1 + .github/workflows/release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index c56f0793..ec2519fa 100755 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -57,6 +57,7 @@ jobs: update: false install: >- mingw-w64-ucrt-x86_64-gcc + mingw-w64-ucrt-x86_64-dlfcn make - name: Generate JNI header run: mvn --batch-mode --no-transfer-progress -pl blas -am compile "-Dmaven.antrun.skip=true" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1bc32d8f..0ea09a3f 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,6 +57,7 @@ jobs: update: false install: >- mingw-w64-ucrt-x86_64-gcc + mingw-w64-ucrt-x86_64-dlfcn make - name: Generate JNI header run: mvn --batch-mode --no-transfer-progress -pl blas -am compile "-Dmaven.antrun.skip=true" From eab9b049a139162235691d7ef2ae0f80d2bb8e75 Mon Sep 17 00:00:00 2001 From: philwalk Date: Fri, 20 Mar 2026 08:15:01 -0600 Subject: [PATCH 05/12] resolve ucrt64 path for powershell --- .github/workflows/build-and-test.yml | 3 +++ .github/workflows/release.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ec2519fa..88196dab 100755 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -211,6 +211,9 @@ jobs: 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: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ea09a3f..b8d175d6 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -211,6 +211,9 @@ jobs: 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: From 986554700219d6a490e2e44937b081fbb5debaae Mon Sep 17 00:00:00 2001 From: philwalk Date: Sun, 29 Mar 2026 09:29:02 -0600 Subject: [PATCH 06/12] moved build steps to Makefile --- .github/workflows/build-and-test.yml | 18 ++-------------- .github/workflows/release.yml | 18 ++-------------- mybuild.sh | 32 ---------------------------- 3 files changed, 4 insertions(+), 64 deletions(-) delete mode 100755 mybuild.sh diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 88196dab..48b19ba8 100755 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -59,22 +59,8 @@ jobs: mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-dlfcn make - - name: Generate JNI header - run: mvn --batch-mode --no-transfer-progress -pl blas -am compile "-Dmaven.antrun.skip=true" - - name: Build Windows DLL - shell: msys2 {0} - run: | - JAVA_INC="$(cygpath -u "$JAVA_HOME")/include" - mkdir -p blas/target/native/windows-amd64 blas/target/objs/windows-amd64 - gcc -c -Werror -Wall -Wextra -Wno-unused-label -O2 \ - -I blas/src/main/native \ - -I blas/target/include \ - -I "$JAVA_INC" -I "$JAVA_INC/win32" \ - -o blas/target/objs/windows-amd64/jni.o \ - blas/src/main/native/jni.c - gcc -shared -Wl,-Bstatic,-ldl,-Bdynamic \ - -o blas/target/native/windows-amd64/libnetlibblasjni.dll \ - blas/target/objs/windows-amd64/jni.o + - name: Build + run: mvn --batch-mode --no-transfer-progress compile - name: Upload Windows natives uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8d175d6..929bebb4 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,22 +59,8 @@ jobs: mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-dlfcn make - - name: Generate JNI header - run: mvn --batch-mode --no-transfer-progress -pl blas -am compile "-Dmaven.antrun.skip=true" - - name: Build Windows DLL - shell: msys2 {0} - run: | - JAVA_INC="$(cygpath -u "$JAVA_HOME")/include" - mkdir -p blas/target/native/windows-amd64 blas/target/objs/windows-amd64 - gcc -c -Werror -Wall -Wextra -Wno-unused-label -O2 \ - -I blas/src/main/native \ - -I blas/target/include \ - -I "$JAVA_INC" -I "$JAVA_INC/win32" \ - -o blas/target/objs/windows-amd64/jni.o \ - blas/src/main/native/jni.c - gcc -shared -Wl,-Bstatic,-ldl,-Bdynamic \ - -o blas/target/native/windows-amd64/libnetlibblasjni.dll \ - blas/target/objs/windows-amd64/jni.o + - name: Build + run: mvn --batch-mode --no-transfer-progress compile - name: Upload Windows natives uses: actions/upload-artifact@v4 with: diff --git a/mybuild.sh b/mybuild.sh deleted file mode 100755 index 70749189..00000000 --- a/mybuild.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -# Replicates the CI "build-windows-natives" job locally. -# Run from the repo root in an MSYS2 UCRT64 shell. -set -euo pipefail - -PATH="/ucrt64/bin:$PATH" - -# Step 1: compile Java + generate JNI header into blas/target/include/ -echo "=== Generating JNI header ===" -mvn --batch-mode --no-transfer-progress -pl blas -am compile -Dmaven.antrun.skip=true - -# Step 2: build the DLL (mirrors CI "Build Windows DLL" step) -echo "=== Building Windows DLL ===" -JAVA_INC="$(cygpath -u "$JAVA_HOME")/include" -mkdir -p blas/target/native/windows-amd64 blas/target/objs/windows-amd64 - -gcc -c -Werror -Wall -Wextra -Wno-unused-label -O2 \ - -I blas/src/main/native \ - -I blas/target/include \ - -I "$JAVA_INC" -I "$JAVA_INC/win32" \ - -o blas/target/objs/windows-amd64/jni.o \ - blas/src/main/native/jni.c - -gcc -shared -Wl,-Bstatic,-ldl,-Bdynamic \ - -o blas/target/native/windows-amd64/libnetlibblasjni.dll \ - blas/target/objs/windows-amd64/jni.o - -echo "" -echo "Done: blas/target/native/windows-amd64/libnetlibblasjni.dll" -echo "" -echo "DLL imports:" -objdump -p blas/target/native/windows-amd64/libnetlibblasjni.dll | grep "DLL Name" From f4ac5737d3489ade6d20c299264a6dc54a956418 Mon Sep 17 00:00:00 2001 From: philwalk Date: Sun, 29 Mar 2026 09:44:14 -0600 Subject: [PATCH 07/12] path to ucrt64 gcc in pom --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d3c66cce..62b79ad2 100755 --- a/pom.xml +++ b/pom.xml @@ -217,9 +217,9 @@ information or have any questions. - + - + From 4be07f333e771d5cf7f232648b682061de168a3a Mon Sep 17 00:00:00 2001 From: philwalk Date: Sun, 29 Mar 2026 09:58:38 -0600 Subject: [PATCH 08/12] modify how gcc is called in pom --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 62b79ad2..2422021b 100755 --- a/pom.xml +++ b/pom.xml @@ -214,12 +214,13 @@ information or have any questions. + - + - + From b8a9403b9ffd16f4c3d7dc8ea97b0f4493b10fc5 Mon Sep 17 00:00:00 2001 From: philwalk Date: Sun, 29 Mar 2026 10:16:06 -0600 Subject: [PATCH 09/12] adjust compiler paths --- .github/workflows/build-and-test.yml | 3 +++ .github/workflows/release.yml | 3 +++ pom.xml | 1 - 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 48b19ba8..9476a3c8 100755 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -59,6 +59,9 @@ jobs: 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 929bebb4..f407cf75 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,6 +59,9 @@ jobs: 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 diff --git a/pom.xml b/pom.xml index 2422021b..d3c66cce 100755 --- a/pom.xml +++ b/pom.xml @@ -214,7 +214,6 @@ information or have any questions. - From 7d1dcef737b901ddf444b5f9d139a4ab486c552b Mon Sep 17 00:00:00 2001 From: philwalk Date: Sun, 29 Mar 2026 10:25:33 -0600 Subject: [PATCH 10/12] update arpack/lapack --- arpack/src/main/native/jni.c | 12 +++++++++++- lapack/src/main/native/jni.c | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) mode change 100644 => 100755 arpack/src/main/native/jni.c mode change 100644 => 100755 lapack/src/main/native/jni.c diff --git a/arpack/src/main/native/jni.c b/arpack/src/main/native/jni.c old mode 100644 new mode 100755 index b21e3953..54878f8a --- 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" diff --git a/lapack/src/main/native/jni.c b/lapack/src/main/native/jni.c old mode 100644 new mode 100755 index 97df3110..d0cecf91 --- 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" From 278d0e1a4ab8006c618ec44f7b2160ffd970bcbd Mon Sep 17 00:00:00 2001 From: philwalk Date: Tue, 31 Mar 2026 08:30:41 -0600 Subject: [PATCH 11/12] update generator.py for windows support --- arpack/src/main/native/jni.c | 2 ++ blas/src/main/native/jni.c | 2 ++ generator.py | 19 +++++++++++++++++-- lapack/src/main/native/jni.c | 2 ++ 4 files changed, 23 insertions(+), 2 deletions(-) mode change 100644 => 100755 generator.py diff --git a/arpack/src/main/native/jni.c b/arpack/src/main/native/jni.c index 54878f8a..ec3c109c 100755 --- a/arpack/src/main/native/jni.c +++ b/arpack/src/main/native/jni.c @@ -2336,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/native/jni.c b/blas/src/main/native/jni.c index 073c092f..c17d0c40 100755 --- a/blas/src/main/native/jni.c +++ b/blas/src/main/native/jni.c @@ -2216,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/native/jni.c b/lapack/src/main/native/jni.c index d0cecf91..a3cfcf66 100755 --- a/lapack/src/main/native/jni.c +++ b/lapack/src/main/native/jni.c @@ -29151,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 From 269685026c9e0e9a5b7bc63d5dedb59eacc053c5 Mon Sep 17 00:00:00 2001 From: philwalk Date: Tue, 31 Mar 2026 09:09:15 -0600 Subject: [PATCH 12/12] removed redundant mvn args --- .github/workflows/build-and-test.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 9476a3c8..fc35ef5e 100755 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -214,4 +214,4 @@ jobs: 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" "-Ddev.ludovic.netlib.blas.nativeLib=libopenblas.dll" + 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 index f407cf75..f8fd7373 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -214,7 +214,7 @@ jobs: 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" "-Ddev.ludovic.netlib.blas.nativeLib=libopenblas.dll" + 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