From be53b22a23d61b7a1f158f9ecae2b21f7114026c Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 13:08:37 +0200 Subject: [PATCH 01/16] ci: pin jextract 25 for Linux and macOS in setup-jextract Download only the jextract 25 Unix binaries with correct platform detection and verify the installed version for consistent FFM CI output. Co-authored-by: Cursor --- .github/actions/setup-jextract/action.yml | 56 ++++++++++++----------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/.github/actions/setup-jextract/action.yml b/.github/actions/setup-jextract/action.yml index c3dba35d19d7..f54b246d4b8d 100644 --- a/.github/actions/setup-jextract/action.yml +++ b/.github/actions/setup-jextract/action.yml @@ -2,7 +2,7 @@ name: 'Setup jextract' description: 'Install jextract for FFM binding generation across all platforms' inputs: java-version: - description: 'Java version for jextract (24, 25, latest)' + description: 'Java version for jextract (25)' required: false default: '25' @@ -21,47 +21,45 @@ runs: id: setup-jextract-unix if: runner.os != 'Windows' shell: bash + env: + INPUT_JAVA_VERSION: ${{ inputs.java-version }} run: | - echo "Installing jextract for $RUNNER_OS..." + echo "Installing jextract 25 for $RUNNER_OS..." + + if [[ "$INPUT_JAVA_VERSION" != "25" ]]; then + echo "ERROR: Only jextract 25 is supported (requested: $INPUT_JAVA_VERSION)" + exit 1 + fi # Determine platform if [[ "$RUNNER_OS" == "macOS" ]]; then - PLATFORM="macos-x64" + if [[ "$(uname -m)" == "arm64" ]]; then + PLATFORM="macos-aarch64" + else + PLATFORM="macos-x64" + fi + elif [[ "$(uname -m)" == "aarch64" ]]; then + PLATFORM="linux-aarch64" else PLATFORM="linux-x64" fi - # Try different jextract versions (from latest to older) - # Check https://jdk.java.net/jextract/ for available builds - JEXTRACT_URLS=( - "https://download.java.net/java/early_access/jextract/22/6/openjdk-22-jextract+6-47_${PLATFORM}_bin.tar.gz" - "https://download.java.net/java/early_access/jextract/21/5/openjdk-21-jextract+5-31_${PLATFORM}_bin.tar.gz" - "https://download.java.net/java/early_access/jextract/20/1/openjdk-20-jextract+1-2_${PLATFORM}_bin.tar.gz" - ) + # Use fixed jextract 25 version + JEXTRACT_URL="https://download.java.net/java/early_access/jextract/25/2/openjdk-25-jextract+2-4_${PLATFORM}_bin.tar.gz" + echo "Downloading jextract 25 from: $JEXTRACT_URL" mkdir -p $HOME/jextract cd $HOME/jextract - SUCCESS=false - for URL in "${JEXTRACT_URLS[@]}"; do - echo "Trying to download from: $URL" - if curl -L -f -o jextract.tar.gz "$URL" 2>/dev/null; then - echo "✓ Download successful from $URL" - tar -xzf jextract.tar.gz --strip-components=1 - rm jextract.tar.gz - SUCCESS=true - break - else - echo "✗ Failed to download from $URL, trying next..." - fi - done - - if [ "$SUCCESS" = false ]; then - echo "ERROR: Failed to download jextract from any known source" - echo "Please check https://jdk.java.net/jextract/ for available builds" + if ! curl -L -f -o jextract.tar.gz "$JEXTRACT_URL"; then + echo "ERROR: Failed to download jextract 25 from $JEXTRACT_URL" + echo "See https://jdk.java.net/jextract/25/ for available builds" exit 1 fi + tar -xzf jextract.tar.gz --strip-components=1 + rm jextract.tar.gz + # Set outputs echo "jextract-home=$HOME/jextract" >> $GITHUB_OUTPUT @@ -70,6 +68,10 @@ runs: VERSION=$($HOME/jextract/bin/jextract --version 2>&1 | head -1 || echo "unknown") echo "jextract-version=$VERSION" >> $GITHUB_OUTPUT echo "✓ jextract installed successfully: $VERSION" + if [[ "$VERSION" != jextract\ 25* ]]; then + echo "ERROR: Expected jextract 25, got: $VERSION" + exit 1 + fi else echo "jextract-version=unknown" >> $GITHUB_OUTPUT echo "✓ jextract installed (version check not supported)" From 077d5796203ceedfe89c415f8aa96e65bef67e3c Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 13:10:56 +0200 Subject: [PATCH 02/16] ci: pin jextract 25 for Windows in setup-jextract Download only the jextract 25 Windows binary, verify the installed version, and use a forward-slash PATH entry on GitHub Windows runners. Co-authored-by: Cursor --- .github/actions/setup-jextract/action.yml | 113 ++++++++++------------ 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/.github/actions/setup-jextract/action.yml b/.github/actions/setup-jextract/action.yml index f54b246d4b8d..360d69862b4c 100644 --- a/.github/actions/setup-jextract/action.yml +++ b/.github/actions/setup-jextract/action.yml @@ -81,70 +81,58 @@ runs: id: setup-jextract-windows if: runner.os == 'Windows' shell: pwsh + env: + INPUT_JAVA_VERSION: ${{ inputs.java-version }} run: | - Write-Host "Installing jextract for Windows..." + Write-Host "Installing jextract 25 for Windows..." - # Try multiple jextract versions (latest to older) - # Windows now uses .tar.gz format - $JextractUrls = @( - "https://download.java.net/java/early_access/jextract/22/6/openjdk-22-jextract+6-47_windows-x64_bin.tar.gz", - "https://download.java.net/java/early_access/jextract/21/5/openjdk-21-jextract+5-31_windows-x64_bin.tar.gz", - "https://download.java.net/java/early_access/jextract/20/1/openjdk-20-jextract+1-2_windows-x64_bin.tar.gz" - ) + if ($env:INPUT_JAVA_VERSION -ne "25") { + Write-Host "ERROR: Only jextract 25 is supported (requested: $env:INPUT_JAVA_VERSION)" + exit 1 + } + + # Use fixed jextract 25 version + $JextractUrl = "https://download.java.net/java/early_access/jextract/25/2/openjdk-25-jextract+2-4_windows-x64_bin.tar.gz" + Write-Host "Downloading jextract 25 from: $JextractUrl" $JextractHome = "$env:USERPROFILE\jextract" New-Item -ItemType Directory -Force -Path $JextractHome | Out-Null + $TarPath = "$JextractHome\jextract.tar.gz" + + try { + Invoke-WebRequest -Uri $JextractUrl -OutFile $TarPath -ErrorAction Stop + } + catch { + Write-Host "ERROR: Failed to download jextract 25 from $JextractUrl" + Write-Host "See https://jdk.java.net/jextract/25/ for available builds" + Write-Host "Error: $_" + exit 1 + } + + # Extract using tar (available in Windows 10+) + $TempExtract = "$JextractHome\temp" + New-Item -ItemType Directory -Force -Path $TempExtract | Out-Null + tar -xzf "$TarPath" -C "$TempExtract" 2>&1 | Out-Null - $Success = $false - foreach ($Url in $JextractUrls) { - Write-Host "Trying to download from: $Url" - $TarPath = "$JextractHome\jextract.tar.gz" - - try { - Invoke-WebRequest -Uri $Url -OutFile $TarPath -ErrorAction Stop - Write-Host "✓ Download successful from $Url" - - # Extract using tar (available in Windows 10+) - $TempExtract = "$JextractHome\temp" - New-Item -ItemType Directory -Force -Path $TempExtract | Out-Null - tar -xzf "$TarPath" -C "$TempExtract" 2>&1 | Out-Null - - # Find jextract.bat - $JextractBat = Get-ChildItem -Path $TempExtract -Filter "jextract.bat" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 - - if ($JextractBat) { - Write-Host "Found jextract.bat at: $($JextractBat.FullName)" - - # Move contents to JextractHome - $JextractRoot = $JextractBat.Directory.Parent.FullName - Get-ChildItem -Path $JextractRoot | ForEach-Object { - Move-Item -Path $_.FullName -Destination $JextractHome -Force - } - - # Clean up - Remove-Item -Path $TempExtract -Recurse -Force - Remove-Item -Path $TarPath -Force - - # Verify - if (Test-Path "$JextractHome\bin\jextract.bat") { - Write-Host "✓ jextract extracted successfully to $JextractHome" - $Success = $true - break - } - } else { - Write-Host "✗ Could not find jextract.bat in extracted files" - Remove-Item -Path $TempExtract -Recurse -Force -ErrorAction SilentlyContinue - } - } - catch { - Write-Host "✗ Failed to download or extract from $Url" - Write-Host "Error: $_" - } + # Find jextract.bat + $JextractBat = Get-ChildItem -Path $TempExtract -Filter "jextract.bat" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 + if (-not $JextractBat) { + Write-Host "ERROR: jextract.bat not found in downloaded jextract 25 archive" + exit 1 } - if (-not $Success) { - Write-Host "ERROR: Failed to download jextract from any known source" - Write-Host "Please check https://jdk.java.net/jextract/ for available builds" + $JextractRoot = $JextractBat.Directory.Parent.FullName + Get-ChildItem -Path $JextractRoot | ForEach-Object { + Move-Item -Path $_.FullName -Destination $JextractHome -Force + } + + # Clean up + Remove-Item -Path $TempExtract -Recurse -Force + Remove-Item -Path $TarPath -Force + + # Verify + if (-not (Test-Path "$JextractHome\bin\jextract.bat")) { + Write-Host "ERROR: jextract 25 install incomplete (missing bin\jextract.bat)" exit 1 } @@ -152,13 +140,12 @@ runs: echo "jextract-home=$JextractHome" >> $env:GITHUB_OUTPUT # Verify installation - try { - $Version = & "$JextractHome\bin\jextract.bat" --version 2>&1 | Select-Object -First 1 - echo "jextract-version=$Version" >> $env:GITHUB_OUTPUT - Write-Host "✓ jextract installed successfully: $Version" - } catch { - echo "jextract-version=unknown" >> $env:GITHUB_OUTPUT - Write-Host "✓ jextract installed (version check not supported)" + $Version = & "$JextractHome\bin\jextract.bat" --version 2>&1 | Select-Object -First 1 + echo "jextract-version=$Version" >> $env:GITHUB_OUTPUT + Write-Host "✓ jextract installed successfully: $Version" + if ($Version -notlike "jextract 25*") { + Write-Host "ERROR: Expected jextract 25, got: $Version" + exit 1 } - name: Set environment variables From a89ffe8b3b3001d794e2b105cabbe8b9c73d380d Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:05:18 +0200 Subject: [PATCH 03/16] build: verify vendored java/lib JARs offline Co-authored-by: Cursor --- .../scripts/verify-vendored-java-lib-jars.sh | 30 ++++++++++++ .../workflows/verify-vendored-java-lib.yml | 32 +++++++++++++ java/lib/NOTICES.txt | 47 +++++++++++++++++++ java/lib/vendored-jars.sha256 | 4 ++ 4 files changed, 113 insertions(+) create mode 100755 .github/scripts/verify-vendored-java-lib-jars.sh create mode 100644 .github/workflows/verify-vendored-java-lib.yml create mode 100644 java/lib/NOTICES.txt create mode 100644 java/lib/vendored-jars.sha256 diff --git a/.github/scripts/verify-vendored-java-lib-jars.sh b/.github/scripts/verify-vendored-java-lib-jars.sh new file mode 100755 index 000000000000..16b669e6e062 --- /dev/null +++ b/.github/scripts/verify-vendored-java-lib-jars.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Verify vendored JARs under java/lib/ against committed SHA-256 checksums (offline). +# See java/lib/NOTICES.txt for details. +# +# Manifest: java/lib/vendored-jars.sha256 (GNU sha256sum format, paths relative to repo root). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ROOT="${1:-$PROJECT_ROOT}" + +MANIFEST_REL="java/lib/vendored-jars.sha256" +MANIFEST="${ROOT}/${MANIFEST_REL}" + +if [[ ! -f "$MANIFEST" ]]; then + echo "ERROR: checksum manifest missing: ${MANIFEST_REL}" >&2 + exit 1 +fi + +cd "$ROOT" +if command -v sha256sum >/dev/null 2>&1; then + sha256sum -c "$MANIFEST_REL" +elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 -c "$MANIFEST_REL" +else + echo "ERROR: need sha256sum or shasum for offline verification" >&2 + exit 1 +fi + +echo "All vendored java/lib JARs match committed SHA-256 checksums." diff --git a/.github/workflows/verify-vendored-java-lib.yml b/.github/workflows/verify-vendored-java-lib.yml new file mode 100644 index 000000000000..a6108de4c3ec --- /dev/null +++ b/.github/workflows/verify-vendored-java-lib.yml @@ -0,0 +1,32 @@ +name: Verify vendored java/lib JARs + +# Ensures vendored libraries copies match committed SHA-256 +# digests in java/lib/vendored-jars.sha256 (offline; no network). +on: + workflow_dispatch: + push: + paths: + - 'java/lib/**' + - '.github/scripts/verify-vendored-java-lib-jars.sh' + - '.github/workflows/verify-vendored-java-lib.yml' + pull_request: + paths: + - 'java/lib/**' + - '.github/scripts/verify-vendored-java-lib-jars.sh' + - '.github/workflows/verify-vendored-java-lib.yml' + +permissions: + contents: read + +jobs: + verify-offline-checksums: + name: SHA-256 vs committed manifest + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Verify vendored JARs + run: ./.github/scripts/verify-vendored-java-lib-jars.sh diff --git a/java/lib/NOTICES.txt b/java/lib/NOTICES.txt new file mode 100644 index 000000000000..4d3fe51e7c67 --- /dev/null +++ b/java/lib/NOTICES.txt @@ -0,0 +1,47 @@ +Vendored third-party JARs in this directory +=========================================== + +The following binary artifacts are copied from Maven Central for HDF5 Java +builds and tests. CI verifies them offline against SHA-256 digests in +vendored-jars.sha256 (see .github/scripts/verify-vendored-java-lib-jars.sh). + +When upgrading a vendored version, replace the JARs, confirm artifact integrity +(for example against published checksums on repo1.maven.org), then refresh +vendored-jars.sha256 from the repository root, for example: + + sha256sum \ + java/lib/slf4j-api-2.0.16.jar \ + java/lib/ext/slf4j-nop-2.0.16.jar \ + java/lib/ext/slf4j-simple-2.0.16.jar \ + java/lib/native-lib-loader-2.5.0.jar \ + > java/lib/vendored-jars.sha256 + +1) SLF4J 2.0.16 (org.slf4j) + - slf4j-api-2.0.16.jar + - ext/slf4j-nop-2.0.16.jar + - ext/slf4j-simple-2.0.16.jar + License: MIT License + Project: https://www.slf4j.org/ + Source repository: https://github.com/qos-ch/slf4j + +2) SciJava Native Library Loader 2.5.0 (org.scijava) + - native-lib-loader-2.5.0.jar + License: BSD 2-Clause "Simplified" License + Project: https://github.com/scijava/native-lib-loader + +Native Maven bundles produced by the HDF5 build may also redistribute zlib +shared libraries in org.hdfgroup:hdf5-zlib-native when Maven deployment is +configured with shared zlib support, and libaec shared libraries in +org.hdfgroup:hdf5-szip-native when Maven deployment is configured with shared +libaec (SZIP) support. + +3) zlib + License: zlib License + Project: https://zlib.net/ + +4) libaec (Adaptive Entropy Coding library, providing the SZIP-compatible API) + License: 2-clause BSD License + Project: https://gitlab.dkrz.de/k202009/libaec + +Other JARs in this tree (for example test-only dependencies) are not covered +by the checksum manifest unless listed here. diff --git a/java/lib/vendored-jars.sha256 b/java/lib/vendored-jars.sha256 new file mode 100644 index 000000000000..db7bf852d63c --- /dev/null +++ b/java/lib/vendored-jars.sha256 @@ -0,0 +1,4 @@ +a12578dde1ba00bd9b816d388a0b879928d00bab3c83c240f7013bf4196c579a java/lib/slf4j-api-2.0.16.jar +deca6c04ed35515a0a911fa44c0e836bee92c0c59d2e8fa9bab8ffbc464a9ba7 java/lib/ext/slf4j-nop-2.0.16.jar +effc32018658bea09d1e08c7d1060ccad46c086960f583d07dd7ffe9c1172a47 java/lib/ext/slf4j-simple-2.0.16.jar +7ec0dcf847cabdd6487c4724c3cede107a46bdb6585a9dcb63f54c0276b04b40 java/lib/native-lib-loader-2.5.0.jar From e269c2394b9c422705410c7cd20f0fccdb443769 Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:05:37 +0200 Subject: [PATCH 04/16] build: vendor SciJava native-lib-loader and expose CMake path Co-authored-by: Cursor --- CMakeLists.txt | 1 + config/cmake/HDF5ExampleCache.cmake | 2 +- java/lib/native-lib-loader-2.5.0.jar | Bin 0 -> 20860 bytes java/src-jni/test/CMakeLists.txt | 2 +- java/test/CMakeLists.txt | 2 +- 5 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 java/lib/native-lib-loader-2.5.0.jar diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cb582502156..ff16291b2f90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -328,6 +328,7 @@ set (HDF5_JAVA_JSRC_DIR ${HDF5_SOURCE_DIR}/java/jsrc) set (HDF5_JAVA_LOGGING_JAR ${HDF5_SOURCE_DIR}/java/lib/slf4j-api-2.0.16.jar CACHE FILEPATH "Path to SLF4J API jar") set (HDF5_JAVA_LOGGING_NOP_JAR ${HDF5_SOURCE_DIR}/java/lib/ext/slf4j-nop-2.0.16.jar CACHE FILEPATH "Path to SLF4J NOP binding jar") set (HDF5_JAVA_LOGGING_SIMPLE_JAR ${HDF5_SOURCE_DIR}/java/lib/ext/slf4j-simple-2.0.16.jar CACHE FILEPATH "Path to SLF4J simple binding jar") +set (HDF5_JAVA_NATIVE_LIB_LOADER_JAR ${HDF5_SOURCE_DIR}/java/lib/native-lib-loader-2.5.0.jar CACHE FILEPATH "Path to SciJava native library loader jar") set (HDF5_JAVA_JUNIT_JAR ${HDF5_JAVA_LIB_DIR}/org.junit.jar CACHE FILEPATH "Path to JUnit jar") set (HDF5_JAVA_HAMCREST_JAR ${HDF5_JAVA_LIB_DIR}/org.hamcrest.jar CACHE FILEPATH "Path to Hamcrest jar") set (HDF5_DOCS_DIR ${HDF5_SOURCE_DIR}/docs) diff --git a/config/cmake/HDF5ExampleCache.cmake b/config/cmake/HDF5ExampleCache.cmake index 3129cd5dd156..8384d1f40f15 100644 --- a/config/cmake/HDF5ExampleCache.cmake +++ b/config/cmake/HDF5ExampleCache.cmake @@ -139,7 +139,7 @@ else () message (STATUS "HDF5 Provides JNI: ${HDF5_PROVIDES_JNI}, Compat: ${HDF5_PROVIDES_JAVA_COMPAT} (Implementation: ${HDF5_JAVA_IMPLEMENTATION})") # Set up Java library and include variables for examples message (STATUS "HDF5 java cache jar: ${HDF5_JAVA_JARS}") - set (HDF5_JAVA_INCLUDE_DIRS ${HDF5_JAVA_JARS} ${HDF5_JAVA_LOGGING_JAR}) + set (HDF5_JAVA_INCLUDE_DIRS ${HDF5_JAVA_JARS} ${HDF5_JAVA_LOGGING_JAR} ${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}) set (HDF5_Java_FOUND 1) if (Java_VERSION_STRING VERSION_LESS "25.0.0" OR HDF5_ENABLE_JNI) set (H5EXAMPLE_JAVA_LIBRARY ${HDF5_JAVA_JNI_LIB_TARGET}) diff --git a/java/lib/native-lib-loader-2.5.0.jar b/java/lib/native-lib-loader-2.5.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..e368e627d6bd6d070f99f62dd0bb02c89adc9da1 GIT binary patch literal 20860 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 NGy(tq3*ca|{{x8Vy3YUr literal 0 HcmV?d00001 diff --git a/java/src-jni/test/CMakeLists.txt b/java/src-jni/test/CMakeLists.txt index d23894831836..ebbae507ac89 100644 --- a/java/src-jni/test/CMakeLists.txt +++ b/java/src-jni/test/CMakeLists.txt @@ -75,7 +75,7 @@ if (HDF5_ENABLE_HDFS) ) endif () -set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVA_JUNIT_JAR};${HDF5_JAVA_HAMCREST_JAR};${HDF5_JAVA_JARS};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_LOGGING_SIMPLE_JAR}") +set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVA_JUNIT_JAR};${HDF5_JAVA_HAMCREST_JAR};${HDF5_JAVA_JARS};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_LOGGING_SIMPLE_JAR};${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}") foreach (test_file ${HDF5_JAVA_TEST_SOURCES}) diff --git a/java/test/CMakeLists.txt b/java/test/CMakeLists.txt index 318200c62eaf..17aed018ca6d 100644 --- a/java/test/CMakeLists.txt +++ b/java/test/CMakeLists.txt @@ -75,7 +75,7 @@ if (HDF5_ENABLE_HDFS) ) endif () -set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVA_JARS};${HDF5_JAVAHDF5_JARS};${HDF5_JAVA_JUNIT_JAR};${HDF5_JAVA_HAMCREST_JAR};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_LOGGING_SIMPLE_JAR}") +set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVA_JARS};${HDF5_JAVAHDF5_JARS};${HDF5_JAVA_JUNIT_JAR};${HDF5_JAVA_HAMCREST_JAR};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_LOGGING_SIMPLE_JAR};${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}") foreach (test_file ${HDF5_JAVA_TEST_SOURCES}) From b727b0624a8dcab57b466912f63829e8f762ceb4 Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:05:40 +0200 Subject: [PATCH 05/16] build: add Maven presets for shared zlib and libaec Co-authored-by: Cursor --- CMakePresets.json | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/CMakePresets.json b/CMakePresets.json index fb4609cf44ad..0a3037a7438c 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -283,6 +283,38 @@ "HDF5_MAVEN_SNAPSHOT": "ON" } }, + { + "name": "ci-Maven-Zlib", + "hidden": true, + "inherits": [ + "ci-base-tgz", + "ci-CompressionVars" + ], + "cacheVariables": { + "HDF5_PACKAGE_EXTLIBS": "ON", + "HDF5_USE_ZLIB_NG": "OFF", + "ZLIB_USE_LOCALCONTENT": "OFF", + "HDF5_USE_ZLIB_STATIC": "OFF", + "HDF5_ENABLE_ZLIB_SUPPORT": "ON", + "ZLIB_USE_EXTERNAL": "ON" + } + }, + { + "name": "ci-Maven-Libaec", + "hidden": true, + "inherits": [ + "ci-base-tgz", + "ci-CompressionVars" + ], + "cacheVariables": { + "HDF5_PACKAGE_EXTLIBS": "ON", + "LIBAEC_USE_LOCALCONTENT": "OFF", + "HDF5_USE_LIBAEC_STATIC": "OFF", + "HDF5_ENABLE_SZIP_SUPPORT": "ON", + "HDF5_ENABLE_SZIP_ENCODING": "ON", + "SZIP_USE_EXTERNAL": "ON" + } + }, { "name": "ci-Maven-Minimal", "hidden": true, @@ -513,6 +545,8 @@ "inherits": [ "ci-x64-Release-GNUC", "ci-Java", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal" ] }, @@ -522,6 +556,8 @@ "inherits": [ "ci-x64-Release-GNUC", "ci-Java", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal-Snapshot" ] }, @@ -531,6 +567,8 @@ "inherits": [ "ci-x64-Release-MSVC", "ci-Java", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal" ] }, @@ -540,6 +578,8 @@ "inherits": [ "ci-x64-Release-MSVC", "ci-Java", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal-Snapshot" ] }, @@ -549,6 +589,8 @@ "inherits": [ "ci-x64-Release-Clang", "ci-Java", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal" ] }, @@ -558,6 +600,8 @@ "inherits": [ "ci-x64-Release-Clang", "ci-Java", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal-Snapshot" ] }, @@ -567,6 +611,8 @@ "inherits": [ "ci-x64-Release-GNUC", "ci-Java-FFM", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal" ] }, @@ -576,6 +622,8 @@ "inherits": [ "ci-x64-Release-GNUC", "ci-Java-FFM", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal-Snapshot" ] }, @@ -585,6 +633,8 @@ "inherits": [ "ci-x64-Release-MSVC", "ci-Java-FFM", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal" ] }, @@ -594,6 +644,8 @@ "inherits": [ "ci-x64-Release-MSVC", "ci-Java-FFM", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal-Snapshot" ] }, @@ -603,6 +655,8 @@ "inherits": [ "ci-x64-Release-Clang", "ci-Java-FFM", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal" ] }, @@ -612,6 +666,8 @@ "inherits": [ "ci-x64-Release-Clang", "ci-Java-FFM", + "ci-Maven-Zlib", + "ci-Maven-Libaec", "ci-Maven-Minimal-Snapshot" ] }, From 51625ed9433c7f5d24b53835117b73e165e58533 Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:06:35 +0200 Subject: [PATCH 06/16] build/java: add native Maven bundle CMake infrastructure Extract shared helpers for SciJava native JAR staging, manifest generation, and OS-activated Maven profile snippets. Wire HDF5JavaNativeBundles.cmake from FFM and JNI Java binding CMakeLists and move add_subdirectory(java) before the Maven deploy status message. Co-authored-by: Cursor --- CMakeLists.txt | 2 +- java/cmake/HDF5JavaNativeBundles.cmake | 166 ++++++++++++++++++++++++ java/cmake/pom-jni-native.xml.in | 44 +++++++ java/cmake/pom-native.xml.in | 44 +++++++ java/cmake/pom-szip-native.xml.in | 49 +++++++ java/cmake/pom-zlib-native.xml.in | 49 +++++++ java/hdf/hdf5lib/CMakeLists.txt | 7 +- java/src-jni/hdf/hdf5lib/CMakeLists.txt | 7 +- 8 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 java/cmake/HDF5JavaNativeBundles.cmake create mode 100644 java/cmake/pom-jni-native.xml.in create mode 100644 java/cmake/pom-native.xml.in create mode 100644 java/cmake/pom-szip-native.xml.in create mode 100644 java/cmake/pom-zlib-native.xml.in diff --git a/CMakeLists.txt b/CMakeLists.txt index ff16291b2f90..22338eb467d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1380,10 +1380,10 @@ if (EXISTS "${HDF5_SOURCE_DIR}/java" AND IS_DIRECTORY "${HDF5_SOURCE_DIR}/java") if (NOT BUILD_SHARED_LIBS) message (FATAL_ERROR "\nJava requires shared libraries!\n") else () + add_subdirectory (java) if (HDF5_ENABLE_MAVEN_DEPLOY) message (STATUS "Maven deployment enabled for ${HDF5_JAVA_ARTIFACT_ID}") endif () - add_subdirectory (java) endif () endif () endif () diff --git a/java/cmake/HDF5JavaNativeBundles.cmake b/java/cmake/HDF5JavaNativeBundles.cmake new file mode 100644 index 000000000000..c000b4ab8551 --- /dev/null +++ b/java/cmake/HDF5JavaNativeBundles.cmake @@ -0,0 +1,166 @@ +#----------------------------------------------------------------------------- +# HDF5JavaNativeBundles.cmake - Package native shared libraries as Maven JARs +# +# Include when HDF5_ENABLE_MAVEN_DEPLOY is ON. Before include, set: +# HDF5JAVA_MAVEN_NATIVE_JNI - TRUE to add hdf5-jni-native jar (JNI builds only) +# +# Requires: HDF5_MAVEN_PLATFORM, HDF5_MAVEN_ARCHITECTURE, HDF5_JAR_CLASSIFIER, +# HDF5_PACKAGE_VERSION, HDF5_MAVEN_VERSION_SUFFIX, HDF5_LIBSH_TARGET, +# HDF5_INSTALL_JAR_DIR, Java_JAR_EXECUTABLE (from FindJava) +# Natives are staged under natives// per SciJava native-lib-loader +# (org.scijava.nativelib.NativeLibraryUtil.Architecture names, lowercased). +#----------------------------------------------------------------------------- + +if (NOT DEFINED HDF5JAVA_MAVEN_NATIVE_JNI) + set (HDF5JAVA_MAVEN_NATIVE_JNI FALSE) +endif () + +if (NOT DEFINED HDF5_MAVEN_PLATFORM OR NOT DEFINED HDF5_MAVEN_ARCHITECTURE) + message (FATAL_ERROR "HDF5JavaNativeBundles.cmake requires HDF5_MAVEN_PLATFORM and HDF5_MAVEN_ARCHITECTURE") +endif () + +# Map HDF5 Maven classifiers to native-lib-loader platform directory names +if (HDF5_MAVEN_PLATFORM STREQUAL "linux") + if (HDF5_MAVEN_ARCHITECTURE STREQUAL "aarch64") + set (HDF5_NATIVE_LOADER_PLATFORM "linux_arm64") + elseif (HDF5_MAVEN_ARCHITECTURE STREQUAL "x86_64") + set (HDF5_NATIVE_LOADER_PLATFORM "linux_64") + elseif (HDF5_MAVEN_ARCHITECTURE STREQUAL "x86") + set (HDF5_NATIVE_LOADER_PLATFORM "linux_32") + else () + set (HDF5_NATIVE_LOADER_PLATFORM "linux_64") + endif () +elseif (HDF5_MAVEN_PLATFORM STREQUAL "windows") + if (HDF5_MAVEN_ARCHITECTURE STREQUAL "aarch64") + set (HDF5_NATIVE_LOADER_PLATFORM "windows_arm64") + else () + set (HDF5_NATIVE_LOADER_PLATFORM "windows_64") + endif () +elseif (HDF5_MAVEN_PLATFORM STREQUAL "macos") + if (HDF5_MAVEN_ARCHITECTURE STREQUAL "aarch64") + set (HDF5_NATIVE_LOADER_PLATFORM "osx_arm64") + else () + set (HDF5_NATIVE_LOADER_PLATFORM "osx_64") + endif () +else () + message (FATAL_ERROR "HDF5JavaNativeBundles.cmake: unknown HDF5_MAVEN_PLATFORM '${HDF5_MAVEN_PLATFORM}'") +endif () + +set (_HDF5_NATIVE_IMPL_VERSION "${HDF5_PACKAGE_VERSION}${HDF5_MAVEN_VERSION_SUFFIX}") +set (HDF5_MAVEN_ZLIB_NATIVE_PROFILES "") +set (HDF5_MAVEN_SZIP_NATIVE_PROFILES "") + +if (NOT Java_JAR_EXECUTABLE AND Java_JAVA_EXECUTABLE) + get_filename_component (_jbin "${Java_JAVA_EXECUTABLE}" DIRECTORY) + if (EXISTS "${_jbin}/jar${CMAKE_EXECUTABLE_SUFFIX}") + set (Java_JAR_EXECUTABLE "${_jbin}/jar${CMAKE_EXECUTABLE_SUFFIX}") + elseif (EXISTS "${_jbin}/jar.exe") + set (Java_JAR_EXECUTABLE "${_jbin}/jar.exe") + endif () +endif () + +if (NOT Java_JAR_EXECUTABLE) + message (FATAL_ERROR "Java jar tool not found; cannot build hdf5-native Maven bundles.") +endif () + +# Write a SciJava native-bundle manifest (Implementation-Version drives extraction cache keys). +function (hdf5java_write_native_manifest _manifest_path _bundle_name) + file (WRITE "${_manifest_path}" +"Manifest-Version: 1.0\nImplementation-Version: ${_HDF5_NATIVE_IMPL_VERSION}\nHDF5-Native-Bundle: ${_bundle_name}\nHDF5-Classifier: ${HDF5_JAR_CLASSIFIER}\nHDF5-Native-Loader-Platform: ${HDF5_NATIVE_LOADER_PLATFORM}\n" + ) +endfunction () + +# Append one OS-activated Maven profile dependency block for a native artifact. +function (hdf5java_append_maven_native_profile _profiles_var _artifact_id _profile_id _family _os_name _arch _classifier _optional) + if (_optional) + set (_optional_xml " true\n") + else () + set (_optional_xml "") + endif () + if (_os_name) + set (_os_name_xml "${_os_name}") + else () + set (_os_name_xml "") + endif () + set (_chunk " + + ${_profile_id} + + ${_family}${_os_name_xml}${_arch} + + + + org.hdfgroup + ${_artifact_id} + \${project.version} + ${_classifier} +${_optional_xml} + + ") + set (${_profiles_var} "${${_profiles_var}}${_chunk}" PARENT_SCOPE) +endfunction () + +# Generate five platform OS-activated profiles for a classified native artifact. +function (hdf5java_generate_maven_native_profiles _out_var _artifact_id _profile_prefix _comment _optional) + set (_profiles "${_comment}") + hdf5java_append_maven_native_profile (_profiles "${_artifact_id}" "${_profile_prefix}-linux-x86_64" "unix" "Linux" "amd64" "linux-x86_64" "${_optional}") + hdf5java_append_maven_native_profile (_profiles "${_artifact_id}" "${_profile_prefix}-linux-aarch64" "unix" "Linux" "aarch64" "linux-aarch64" "${_optional}") + hdf5java_append_maven_native_profile (_profiles "${_artifact_id}" "${_profile_prefix}-windows-amd64" "windows" "" "amd64" "windows-x86_64" "${_optional}") + hdf5java_append_maven_native_profile (_profiles "${_artifact_id}" "${_profile_prefix}-macos-x86_64" "mac" "" "x86_64" "macos-x86_64" "${_optional}") + hdf5java_append_maven_native_profile (_profiles "${_artifact_id}" "${_profile_prefix}-macos-aarch64" "mac" "" "aarch64" "macos-aarch64" "${_optional}") + set (${_out_var} "${_profiles}" PARENT_SCOPE) +endfunction () + +# Stage shared library(ies) under natives//, jar, install JAR + POM. +function (hdf5java_add_native_maven_jar) + set (_options "") + set (_oneValueArgs + ARTIFACT_ID + BUNDLE_NAME + JAR_OUT + STAGE_DIR + NATIVES_PREFIX + MANIFEST_PATH + POM_TEMPLATE + POM_OUT + TARGET_NAME + COMMENT + ) + set (_multiValueArgs COPY_COMMANDS DEPENDS) + cmake_parse_arguments (_args "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + + hdf5java_write_native_manifest ("${_args_MANIFEST_PATH}" "${_args_BUNDLE_NAME}") + + add_custom_command ( + OUTPUT "${_args_JAR_OUT}" + COMMAND ${CMAKE_COMMAND} -E rm -rf "${_args_STAGE_DIR}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${_args_NATIVES_PREFIX}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${_args_STAGE_DIR}/META-INF" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_args_MANIFEST_PATH}" "${_args_STAGE_DIR}/META-INF/MANIFEST.MF" + ${_args_COPY_COMMANDS} + COMMAND ${Java_JAR_EXECUTABLE} cfm "${_args_JAR_OUT}" "${_args_STAGE_DIR}/META-INF/MANIFEST.MF" -C "${_args_STAGE_DIR}" . + DEPENDS ${_args_DEPENDS} + COMMENT "${_args_COMMENT}" + VERBATIM + ) + + add_custom_target (${_args_TARGET_NAME} ALL DEPENDS "${_args_JAR_OUT}") + set_target_properties (${_args_TARGET_NAME} PROPERTIES FOLDER libraries/java) + + install ( + FILES "${_args_JAR_OUT}" + DESTINATION ${HDF5_INSTALL_JAR_DIR} + COMPONENT maven + ) + + configure_file ( + ${HDF5_SOURCE_DIR}/java/cmake/${_args_POM_TEMPLATE} + ${CMAKE_CURRENT_BINARY_DIR}/${_args_POM_OUT} + @ONLY + ) + install ( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${_args_POM_OUT} + DESTINATION ${HDF5_INSTALL_JAR_DIR} + COMPONENT maven + ) +endfunction () diff --git a/java/cmake/pom-jni-native.xml.in b/java/cmake/pom-jni-native.xml.in new file mode 100644 index 000000000000..f57aaa5c086a --- /dev/null +++ b/java/cmake/pom-jni-native.xml.in @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.hdfgroup + hdf5-jni-native + @HDF5_PACKAGE_VERSION@@HDF5_MAVEN_VERSION_SUFFIX@ + jar + + HDF5 JNI bridge native library + Bundled hdf5_java JNI bridge shared library under natives/ (SciJava native-lib-loader layout). Use with org.hdfgroup:hdf5-native and org.hdfgroup:hdf5-java-jni. + https://github.com/HDFGroup/hdf5 + + + + BSD-style License + https://github.com/HDFGroup/hdf5/blob/develop/LICENSE + repo + + + + + + The HDF Group + https://www.hdfgroup.org + + + + + scm:git:https://github.com/HDFGroup/hdf5.git + scm:git:git@github.com:HDFGroup/hdf5.git + https://github.com/HDFGroup/hdf5 + @HDF5_PACKAGE_VERSION@ + + + + GitHub + https://github.com/HDFGroup/hdf5/issues + + + \ No newline at end of file diff --git a/java/cmake/pom-native.xml.in b/java/cmake/pom-native.xml.in new file mode 100644 index 000000000000..de991f1f6c06 --- /dev/null +++ b/java/cmake/pom-native.xml.in @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.hdfgroup + hdf5-native + @HDF5_PACKAGE_VERSION@@HDF5_MAVEN_VERSION_SUFFIX@ + jar + + HDF5 native shared libraries + Bundled libhdf5 shared library for HDF5 Java (FFM/JNI) under natives/ (SciJava native-lib-loader layout). Loaded from the classpath by hdf5-java-ffm / hdf5-java-jni when present. + https://github.com/HDFGroup/hdf5 + + + + BSD-style License + https://github.com/HDFGroup/hdf5/blob/develop/LICENSE + repo + + + + + + The HDF Group + https://www.hdfgroup.org + + + + + scm:git:https://github.com/HDFGroup/hdf5.git + scm:git:git@github.com:HDFGroup/hdf5.git + https://github.com/HDFGroup/hdf5 + @HDF5_PACKAGE_VERSION@ + + + + GitHub + https://github.com/HDFGroup/hdf5/issues + + + \ No newline at end of file diff --git a/java/cmake/pom-szip-native.xml.in b/java/cmake/pom-szip-native.xml.in new file mode 100644 index 000000000000..fd7cd14917e6 --- /dev/null +++ b/java/cmake/pom-szip-native.xml.in @@ -0,0 +1,49 @@ + + + 4.0.0 + + org.hdfgroup + hdf5-szip-native + @HDF5_PACKAGE_VERSION@@HDF5_MAVEN_VERSION_SUFFIX@ + jar + + HDF5 szip (libaec) native shared libraries + Bundled libaec/libsz shared libraries under natives/ (SciJava native-lib-loader layout). Use with org.hdfgroup:hdf5-native when libhdf5 is built with dynamic SZIP support (libaec szip-compatible API). + https://github.com/HDFGroup/hdf5 + + + + BSD-style License + https://github.com/HDFGroup/hdf5/blob/develop/LICENSE + repo + + + libaec License (2-clause BSD) + https://gitlab.dkrz.de/k202009/libaec/-/blob/master/LICENSE.txt + repo + + + + + + The HDF Group + https://www.hdfgroup.org + + + + + scm:git:https://github.com/HDFGroup/hdf5.git + scm:git:git@github.com:HDFGroup/hdf5.git + https://github.com/HDFGroup/hdf5 + @HDF5_PACKAGE_VERSION@ + + + + GitHub + https://github.com/HDFGroup/hdf5/issues + + + diff --git a/java/cmake/pom-zlib-native.xml.in b/java/cmake/pom-zlib-native.xml.in new file mode 100644 index 000000000000..3acb808f27d0 --- /dev/null +++ b/java/cmake/pom-zlib-native.xml.in @@ -0,0 +1,49 @@ + + + 4.0.0 + + org.hdfgroup + hdf5-zlib-native + @HDF5_PACKAGE_VERSION@@HDF5_MAVEN_VERSION_SUFFIX@ + jar + + HDF5 zlib native shared libraries + Bundled zlib shared library under natives/ (SciJava native-lib-loader layout). Use with org.hdfgroup:hdf5-native when libhdf5 is built with dynamic zlib support for deflate/GZIP filters. + https://github.com/HDFGroup/hdf5 + + + + BSD-style License + https://github.com/HDFGroup/hdf5/blob/develop/LICENSE + repo + + + zlib License + https://zlib.net/zlib_license.html + repo + + + + + + The HDF Group + https://www.hdfgroup.org + + + + + scm:git:https://github.com/HDFGroup/hdf5.git + scm:git:git@github.com:HDFGroup/hdf5.git + https://github.com/HDFGroup/hdf5 + @HDF5_PACKAGE_VERSION@ + + + + GitHub + https://github.com/HDFGroup/hdf5/issues + + + diff --git a/java/hdf/hdf5lib/CMakeLists.txt b/java/hdf/hdf5lib/CMakeLists.txt index ac7d34cb7ef3..c6295657204d 100644 --- a/java/hdf/hdf5lib/CMakeLists.txt +++ b/java/hdf/hdf5lib/CMakeLists.txt @@ -119,6 +119,7 @@ set (HDF5_JAVA_HDF_HDF5_SOURCES HDF5Constants.java HDFNativeData.java H5.java + Hdf5NativeLoader.java ${CMAKE_CURRENT_BINARY_DIR}/H5Version.java VLDataConverter.java ) @@ -133,7 +134,7 @@ file (WRITE ${PROJECT_BINARY_DIR}/Manifest.txt " ) -set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVAHDF5_JARS};${HDF5_JAVA_LOGGING_JAR}") +set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVAHDF5_JARS};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}") # Create main JAR with platform classifier if Maven deployment is enabled if (HDF5_ENABLE_MAVEN_DEPLOY) @@ -236,6 +237,10 @@ if (HDF5_ENABLE_MAVEN_DEPLOY) # Set HDF5_JAVA_LIBRARY for examples to depend on SET_GLOBAL_VARIABLE (HDF5_JAVA_LIBRARY ${HDF5_JAVA_UNIVERSAL_TARGET}) + + # Native shared libraries as Maven JARs (FFM: libhdf5 only) + set (HDF5JAVA_MAVEN_NATIVE_JNI FALSE) + include (${HDF5_SOURCE_DIR}/java/cmake/HDF5JavaNativeBundles.cmake) else () # Standard JAR creation without Maven classifiers # Include FFM bindings if this is an FFM build diff --git a/java/src-jni/hdf/hdf5lib/CMakeLists.txt b/java/src-jni/hdf/hdf5lib/CMakeLists.txt index 9db942db4ad5..adab44c99f0a 100644 --- a/java/src-jni/hdf/hdf5lib/CMakeLists.txt +++ b/java/src-jni/hdf/hdf5lib/CMakeLists.txt @@ -120,6 +120,7 @@ set (HDF5_JAVA_HDF_HDF5_SOURCES HDF5Constants.java HDFNativeData.java H5.java + ${HDF5_SOURCE_DIR}/java/hdf/hdf5lib/Hdf5NativeLoader.java ${CMAKE_CURRENT_BINARY_DIR}/H5Version.java ) @@ -135,7 +136,7 @@ file (WRITE ${PROJECT_BINARY_DIR}/Manifest.txt " ) -set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVA_LOGGING_JAR}") +set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}") # Create main JAR with platform classifier if Maven deployment is enabled if (HDF5_ENABLE_MAVEN_DEPLOY) @@ -211,6 +212,10 @@ if (HDF5_ENABLE_MAVEN_DEPLOY) # Set HDF5_JAVA_LIBRARY for examples to depend on SET_GLOBAL_VARIABLE (HDF5_JAVA_LIBRARY ${HDF5_JAVA_UNIVERSAL_TARGET}) + + # Native shared libraries as Maven JARs (JNI: libhdf5 + hdf5_java bridge) + set (HDF5JAVA_MAVEN_NATIVE_JNI TRUE) + include (${HDF5_SOURCE_DIR}/java/cmake/HDF5JavaNativeBundles.cmake) else () # Standard JAR creation without Maven classifiers add_jar (${HDF5_JAVA_HDF5_LIB_TARGET} OUTPUT_NAME "${HDF5_JAVA_HDF5_LIB_TARGET}-${HDF5_PACKAGE_VERSION}" MANIFEST ${PROJECT_BINARY_DIR}/Manifest.txt ${HDF5_JAVA_HDF_HDF5_CALLBACKS_SOURCES} ${HDF5_JAVA_HDF_HDF5_EXCEPTIONS_SOURCES} ${HDF5_JAVA_HDF_HDF5_STRUCTS_SOURCES} ${HDF5_JAVA_HDF_HDF5_SOURCES}) From 7a72632b3f60acaa6fa776a5a39651a0f3cdc7b6 Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:06:53 +0200 Subject: [PATCH 07/16] build/java: produce hdf5-native Maven artifact Bundle libhdf5 under natives// with SciJava-mapped aliases and install hdf5-native-*-{classifier}.jar plus pom-hdf5-native.xml. Co-authored-by: Cursor --- java/cmake/HDF5JavaNativeBundles.cmake | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/java/cmake/HDF5JavaNativeBundles.cmake b/java/cmake/HDF5JavaNativeBundles.cmake index c000b4ab8551..16d64b77ea4f 100644 --- a/java/cmake/HDF5JavaNativeBundles.cmake +++ b/java/cmake/HDF5JavaNativeBundles.cmake @@ -164,3 +164,41 @@ function (hdf5java_add_native_maven_jar) COMPONENT maven ) endfunction () + +# SciJava BaseJniExtractor looks for System.mapLibraryName("hdf5") under natives//. +if (HDF5_MAVEN_PLATFORM STREQUAL "linux") + set (_HDF5_NATIVE_SCIJAVA_MAPPED_NAME "libhdf5.so") +elseif (HDF5_MAVEN_PLATFORM STREQUAL "macos") + set (_HDF5_NATIVE_SCIJAVA_MAPPED_NAME "libhdf5.dylib") +elseif (HDF5_MAVEN_PLATFORM STREQUAL "windows") + set (_HDF5_NATIVE_SCIJAVA_MAPPED_NAME "hdf5.dll") +else () + set (_HDF5_NATIVE_SCIJAVA_MAPPED_NAME "") +endif () +if (_HDF5_NATIVE_SCIJAVA_MAPPED_NAME STREQUAL "") + message (FATAL_ERROR "HDF5JavaNativeBundles.cmake: add SciJava mapped library name for platform '${HDF5_MAVEN_PLATFORM}'") +endif () + +set (_HDF5_NATIVE_MAVEN_JAR + "${CMAKE_CURRENT_BINARY_DIR}/hdf5-native-${HDF5_PACKAGE_VERSION}${HDF5_MAVEN_VERSION_SUFFIX}-${HDF5_JAR_CLASSIFIER}.jar" +) +set (_HDF5_NATIVE_STAGE "${CMAKE_CURRENT_BINARY_DIR}/native-bundle/hdf5-native") +set (_HDF5_NATIVE_PREFIX "${_HDF5_NATIVE_STAGE}/natives/${HDF5_NATIVE_LOADER_PLATFORM}") +set (_HDF5_NATIVE_MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/native-bundle/META-INF_MANIFEST_NATIVE.mf") + +hdf5java_add_native_maven_jar ( + ARTIFACT_ID hdf5-native + BUNDLE_NAME hdf5-native + JAR_OUT "${_HDF5_NATIVE_MAVEN_JAR}" + STAGE_DIR "${_HDF5_NATIVE_STAGE}" + NATIVES_PREFIX "${_HDF5_NATIVE_PREFIX}" + MANIFEST_PATH "${_HDF5_NATIVE_MANIFEST}" + POM_TEMPLATE pom-native.xml.in + POM_OUT pom-hdf5-native.xml + TARGET_NAME hdf5_native_maven_jar + COMMENT "Creating Maven native bundle hdf5-native (${HDF5_JAR_CLASSIFIER}, ${HDF5_NATIVE_LOADER_PLATFORM})" + COPY_COMMANDS + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_NATIVE_PREFIX}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_NATIVE_PREFIX}/${_HDF5_NATIVE_SCIJAVA_MAPPED_NAME} + DEPENDS ${HDF5_LIBSH_TARGET} +) From 8694a9ec79e7b588e9c02637dcdf4bbf950150ad Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:07:09 +0200 Subject: [PATCH 08/16] build/java: produce hdf5-zlib-native and hdf5-szip-native artifacts Add optional Maven bundles for shared zlib and libaec/szip with platform aliases and optional OS-activated binding-POM profile snippets. Co-authored-by: Cursor --- java/cmake/HDF5JavaNativeBundles.cmake | 166 +++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/java/cmake/HDF5JavaNativeBundles.cmake b/java/cmake/HDF5JavaNativeBundles.cmake index 16d64b77ea4f..f7ef674c0723 100644 --- a/java/cmake/HDF5JavaNativeBundles.cmake +++ b/java/cmake/HDF5JavaNativeBundles.cmake @@ -202,3 +202,169 @@ hdf5java_add_native_maven_jar ( COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_NATIVE_PREFIX}/${_HDF5_NATIVE_SCIJAVA_MAPPED_NAME} DEPENDS ${HDF5_LIBSH_TARGET} ) + +# Optional zlib JAR: libhdf5's built-in deflate filter needs libz when linked dynamically. +set (_HDF5_ZLIB_NATIVE_TARGET "") +if (H5_ZLIB_FOUND AND NOT HDF5_USE_ZLIB_STATIC) + foreach (_zlib_candidate IN ITEMS ${H5_ZLIB_LIBRARY} ZLIB::ZLIB zlib zlib-ng) + if (TARGET ${_zlib_candidate} AND _HDF5_ZLIB_NATIVE_TARGET STREQUAL "") + set (_HDF5_ZLIB_NATIVE_TARGET "${_zlib_candidate}") + endif () + endforeach () +endif () + +if (NOT _HDF5_ZLIB_NATIVE_TARGET STREQUAL "") + get_target_property (_HDF5_ZLIB_ALIASED_TARGET ${_HDF5_ZLIB_NATIVE_TARGET} ALIASED_TARGET) + if (_HDF5_ZLIB_ALIASED_TARGET) + set (_HDF5_ZLIB_NATIVE_TARGET "${_HDF5_ZLIB_ALIASED_TARGET}") + endif () + + get_target_property (_HDF5_ZLIB_TARGET_TYPE ${_HDF5_ZLIB_NATIVE_TARGET} TYPE) + if (NOT _HDF5_ZLIB_TARGET_TYPE STREQUAL "STATIC_LIBRARY") + if (HDF5_MAVEN_PLATFORM STREQUAL "linux") + set (_HDF5_ZLIB_NATIVE_SCIJAVA_MAPPED_NAME "libz.so") + elseif (HDF5_MAVEN_PLATFORM STREQUAL "macos") + set (_HDF5_ZLIB_NATIVE_SCIJAVA_MAPPED_NAME "libz.dylib") + elseif (HDF5_MAVEN_PLATFORM STREQUAL "windows") + set (_HDF5_ZLIB_NATIVE_SCIJAVA_MAPPED_NAME "z.dll") + else () + set (_HDF5_ZLIB_NATIVE_SCIJAVA_MAPPED_NAME "") + endif () + + if (_HDF5_ZLIB_NATIVE_SCIJAVA_MAPPED_NAME STREQUAL "") + message (FATAL_ERROR "HDF5JavaNativeBundles.cmake: add SciJava mapped zlib name for platform '${HDF5_MAVEN_PLATFORM}'") + endif () + + set (_HDF5_ZLIB_NATIVE_STAGE "${CMAKE_CURRENT_BINARY_DIR}/native-bundle/hdf5-zlib-native") + set (_HDF5_ZLIB_NATIVE_PREFIX "${_HDF5_ZLIB_NATIVE_STAGE}/natives/${HDF5_NATIVE_LOADER_PLATFORM}") + + set (_HDF5_ZLIB_NATIVE_EXTRA_ALIASES "") + if (HDF5_MAVEN_PLATFORM STREQUAL "windows") + list (APPEND _HDF5_ZLIB_NATIVE_EXTRA_ALIASES "zlib.dll" "zlib1.dll") + elseif (HDF5_MAVEN_PLATFORM STREQUAL "linux") + list (APPEND _HDF5_ZLIB_NATIVE_EXTRA_ALIASES "libz.so.1") + elseif (HDF5_MAVEN_PLATFORM STREQUAL "macos") + list (APPEND _HDF5_ZLIB_NATIVE_EXTRA_ALIASES "libz.1.dylib") + endif () + + set (_HDF5_ZLIB_NATIVE_ALIAS_COMMANDS "") + foreach (_zlib_alias IN LISTS _HDF5_ZLIB_NATIVE_EXTRA_ALIASES) + if (NOT _zlib_alias STREQUAL _HDF5_ZLIB_NATIVE_SCIJAVA_MAPPED_NAME) + list (APPEND _HDF5_ZLIB_NATIVE_ALIAS_COMMANDS + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + ${_HDF5_ZLIB_NATIVE_PREFIX}/${_zlib_alias} + ) + endif () + endforeach () + + hdf5java_generate_maven_native_profiles (HDF5_MAVEN_ZLIB_NATIVE_PROFILES "hdf5-zlib-native" + "hdf5-bundled-zlib-native" + " " TRUE) + + set (_HDF5_ZLIB_NATIVE_MAVEN_JAR + "${CMAKE_CURRENT_BINARY_DIR}/hdf5-zlib-native-${HDF5_PACKAGE_VERSION}${HDF5_MAVEN_VERSION_SUFFIX}-${HDF5_JAR_CLASSIFIER}.jar" + ) + set (_HDF5_ZLIB_NATIVE_MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/native-bundle/META-INF_MANIFEST_ZLIB_NATIVE.mf") + + hdf5java_add_native_maven_jar ( + ARTIFACT_ID hdf5-zlib-native + BUNDLE_NAME hdf5-zlib-native + JAR_OUT "${_HDF5_ZLIB_NATIVE_MAVEN_JAR}" + STAGE_DIR "${_HDF5_ZLIB_NATIVE_STAGE}" + NATIVES_PREFIX "${_HDF5_ZLIB_NATIVE_PREFIX}" + MANIFEST_PATH "${_HDF5_ZLIB_NATIVE_MANIFEST}" + POM_TEMPLATE pom-zlib-native.xml.in + POM_OUT pom-hdf5-zlib-native.xml + TARGET_NAME hdf5_zlib_native_maven_jar + COMMENT "Creating Maven native bundle hdf5-zlib-native (${HDF5_JAR_CLASSIFIER}, ${HDF5_NATIVE_LOADER_PLATFORM})" + COPY_COMMANDS + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_ZLIB_NATIVE_PREFIX}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_ZLIB_NATIVE_PREFIX}/${_HDF5_ZLIB_NATIVE_SCIJAVA_MAPPED_NAME} + ${_HDF5_ZLIB_NATIVE_ALIAS_COMMANDS} + DEPENDS ${_HDF5_ZLIB_NATIVE_TARGET} + ) + endif () +endif () + +# Optional szip JAR: bundle libaec and libsz when libhdf5 uses dynamic SZIP support. +set (_HDF5_SZIP_LIBSZ_TARGET "") +set (_HDF5_SZIP_LIBAEC_TARGET "") +if (H5_SZIP_FOUND AND NOT HDF5_USE_LIBAEC_STATIC) + foreach (_libsz_candidate IN ITEMS + ${HDF_PACKAGE_NAMESPACE}sz-shared sz-shared libaec::sz) + if (TARGET ${_libsz_candidate} AND _HDF5_SZIP_LIBSZ_TARGET STREQUAL "") + set (_HDF5_SZIP_LIBSZ_TARGET "${_libsz_candidate}") + endif () + endforeach () + foreach (_libaec_candidate IN ITEMS + ${HDF_PACKAGE_NAMESPACE}aec-shared aec-shared libaec::aec) + if (TARGET ${_libaec_candidate} AND _HDF5_SZIP_LIBAEC_TARGET STREQUAL "") + set (_HDF5_SZIP_LIBAEC_TARGET "${_libaec_candidate}") + endif () + endforeach () +endif () + +if (NOT _HDF5_SZIP_LIBSZ_TARGET STREQUAL "" AND NOT _HDF5_SZIP_LIBAEC_TARGET STREQUAL "") + get_target_property (_HDF5_SZIP_LIBSZ_ALIASED ${_HDF5_SZIP_LIBSZ_TARGET} ALIASED_TARGET) + if (_HDF5_SZIP_LIBSZ_ALIASED) + set (_HDF5_SZIP_LIBSZ_TARGET "${_HDF5_SZIP_LIBSZ_ALIASED}") + endif () + get_target_property (_HDF5_SZIP_LIBAEC_ALIASED ${_HDF5_SZIP_LIBAEC_TARGET} ALIASED_TARGET) + if (_HDF5_SZIP_LIBAEC_ALIASED) + set (_HDF5_SZIP_LIBAEC_TARGET "${_HDF5_SZIP_LIBAEC_ALIASED}") + endif () + + get_target_property (_HDF5_SZIP_LIBSZ_TYPE ${_HDF5_SZIP_LIBSZ_TARGET} TYPE) + get_target_property (_HDF5_SZIP_LIBAEC_TYPE ${_HDF5_SZIP_LIBAEC_TARGET} TYPE) + if (NOT _HDF5_SZIP_LIBSZ_TYPE STREQUAL "STATIC_LIBRARY" + AND NOT _HDF5_SZIP_LIBAEC_TYPE STREQUAL "STATIC_LIBRARY") + if (HDF5_MAVEN_PLATFORM STREQUAL "linux") + set (_HDF5_SZIP_NATIVE_SCIJAVA_LIBSZ "libsz.so") + set (_HDF5_SZIP_NATIVE_SCIJAVA_LIBAEC "libaec.so") + elseif (HDF5_MAVEN_PLATFORM STREQUAL "macos") + set (_HDF5_SZIP_NATIVE_SCIJAVA_LIBSZ "libsz.dylib") + set (_HDF5_SZIP_NATIVE_SCIJAVA_LIBAEC "libaec.dylib") + elseif (HDF5_MAVEN_PLATFORM STREQUAL "windows") + set (_HDF5_SZIP_NATIVE_SCIJAVA_LIBSZ "sz.dll") + set (_HDF5_SZIP_NATIVE_SCIJAVA_LIBAEC "aec.dll") + else () + set (_HDF5_SZIP_NATIVE_SCIJAVA_LIBSZ "") + set (_HDF5_SZIP_NATIVE_SCIJAVA_LIBAEC "") + endif () + + if (_HDF5_SZIP_NATIVE_SCIJAVA_LIBSZ STREQUAL "" OR _HDF5_SZIP_NATIVE_SCIJAVA_LIBAEC STREQUAL "") + message (FATAL_ERROR "HDF5JavaNativeBundles.cmake: add SciJava mapped libaec/libsz names for platform '${HDF5_MAVEN_PLATFORM}'") + endif () + + hdf5java_generate_maven_native_profiles (HDF5_MAVEN_SZIP_NATIVE_PROFILES "hdf5-szip-native" + "hdf5-bundled-szip-native" + " " TRUE) + + set (_HDF5_SZIP_NATIVE_MAVEN_JAR + "${CMAKE_CURRENT_BINARY_DIR}/hdf5-szip-native-${HDF5_PACKAGE_VERSION}${HDF5_MAVEN_VERSION_SUFFIX}-${HDF5_JAR_CLASSIFIER}.jar" + ) + set (_HDF5_SZIP_NATIVE_STAGE "${CMAKE_CURRENT_BINARY_DIR}/native-bundle/hdf5-szip-native") + set (_HDF5_SZIP_NATIVE_PREFIX "${_HDF5_SZIP_NATIVE_STAGE}/natives/${HDF5_NATIVE_LOADER_PLATFORM}") + set (_HDF5_SZIP_NATIVE_MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/native-bundle/META-INF_MANIFEST_SZIP_NATIVE.mf") + + hdf5java_add_native_maven_jar ( + ARTIFACT_ID hdf5-szip-native + BUNDLE_NAME hdf5-szip-native + JAR_OUT "${_HDF5_SZIP_NATIVE_MAVEN_JAR}" + STAGE_DIR "${_HDF5_SZIP_NATIVE_STAGE}" + NATIVES_PREFIX "${_HDF5_SZIP_NATIVE_PREFIX}" + MANIFEST_PATH "${_HDF5_SZIP_NATIVE_MANIFEST}" + POM_TEMPLATE pom-szip-native.xml.in + POM_OUT pom-hdf5-szip-native.xml + TARGET_NAME hdf5_szip_native_maven_jar + COMMENT "Creating Maven native bundle hdf5-szip-native (${HDF5_JAR_CLASSIFIER}, ${HDF5_NATIVE_LOADER_PLATFORM})" + COPY_COMMANDS + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_SZIP_NATIVE_PREFIX}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_SZIP_NATIVE_PREFIX}/${_HDF5_SZIP_NATIVE_SCIJAVA_LIBAEC} + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_SZIP_NATIVE_PREFIX}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_SZIP_NATIVE_PREFIX}/${_HDF5_SZIP_NATIVE_SCIJAVA_LIBSZ} + DEPENDS ${_HDF5_SZIP_LIBAEC_TARGET} ${_HDF5_SZIP_LIBSZ_TARGET} + ) + endif () +endif () From fd11ed44e15d701c196a5c4b2a5e2a308cb0c524 Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:07:27 +0200 Subject: [PATCH 09/16] build/java: produce hdf5-jni-native Maven artifact Add JNI bridge native bundle when HDF5JAVA_MAVEN_NATIVE_JNI is enabled. The standalone pom-jni-native template carries no Maven deps on libhdf5. Co-authored-by: Cursor --- java/cmake/HDF5JavaNativeBundles.cmake | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/java/cmake/HDF5JavaNativeBundles.cmake b/java/cmake/HDF5JavaNativeBundles.cmake index f7ef674c0723..cb09b02ec1ce 100644 --- a/java/cmake/HDF5JavaNativeBundles.cmake +++ b/java/cmake/HDF5JavaNativeBundles.cmake @@ -368,3 +368,29 @@ if (NOT _HDF5_SZIP_LIBSZ_TARGET STREQUAL "" AND NOT _HDF5_SZIP_LIBAEC_TARGET STR ) endif () endif () + +# JNI bridge JAR (JNI Maven builds only) +if (HDF5JAVA_MAVEN_NATIVE_JNI) + set (_HDF5_JNI_NATIVE_MAVEN_JAR + "${CMAKE_CURRENT_BINARY_DIR}/hdf5-jni-native-${HDF5_PACKAGE_VERSION}${HDF5_MAVEN_VERSION_SUFFIX}-${HDF5_JAR_CLASSIFIER}.jar" + ) + set (_HDF5_JNI_NATIVE_STAGE "${CMAKE_CURRENT_BINARY_DIR}/native-bundle/hdf5-jni-native") + set (_HDF5_JNI_NATIVE_PREFIX "${_HDF5_JNI_NATIVE_STAGE}/natives/${HDF5_NATIVE_LOADER_PLATFORM}") + set (_HDF5_JNI_NATIVE_MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/native-bundle/META-INF_MANIFEST_JNI_NATIVE.mf") + + hdf5java_add_native_maven_jar ( + ARTIFACT_ID hdf5-jni-native + BUNDLE_NAME hdf5-jni-native + JAR_OUT "${_HDF5_JNI_NATIVE_MAVEN_JAR}" + STAGE_DIR "${_HDF5_JNI_NATIVE_STAGE}" + NATIVES_PREFIX "${_HDF5_JNI_NATIVE_PREFIX}" + MANIFEST_PATH "${_HDF5_JNI_NATIVE_MANIFEST}" + POM_TEMPLATE pom-jni-native.xml.in + POM_OUT pom-hdf5-jni-native.xml + TARGET_NAME hdf5_jni_native_maven_jar + COMMENT "Creating Maven native bundle hdf5-jni-native (${HDF5_JAR_CLASSIFIER}, ${HDF5_NATIVE_LOADER_PLATFORM})" + COPY_COMMANDS + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_HDF5_JNI_NATIVE_PREFIX}/ + DEPENDS ${HDF5_JAVA_JNI_LIB_TARGET} + ) +endif () From 742a3f27758a485188c305ba7143339c5308b945 Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:08:31 +0200 Subject: [PATCH 10/16] build/java: add Hdf5NativeLoader and wire classpath bootstrap Consolidate bundled zlib/szip/libhdf5 loading in loadBundledDependenciesBeforeHdf5(), use it from FFM/JNI H5.loadH5Lib and the slimmed hdf5_h_2 jextract patch, and layer binding POM dependencies (required hdf5-jni-native on java-jni; optional libhdf5 stack). Co-authored-by: Cursor --- java/CMakeLists.txt | 75 ++++++++ java/cmake/ffm-jextract-hdf5_h_2/README.txt | 10 + java/cmake/ffm-jextract-hdf5_h_2/needle.txt | 3 + .../ffm-jextract-hdf5_h_2/replacement.txt | 66 +++++++ java/hdf/hdf5lib/H5.java | 7 + java/hdf/hdf5lib/HDF5Constants.java | 2 +- java/hdf/hdf5lib/Hdf5NativeLoader.java | 172 ++++++++++++++++++ java/hdf/hdf5lib/pom.xml.in | 92 +++++++++- java/jsrc/CMakeLists.txt | 7 +- java/src-jni/hdf/hdf5lib/H5.java | 77 ++++++-- java/src-jni/hdf/hdf5lib/pom.xml.in | 120 +++++++++++- 11 files changed, 608 insertions(+), 23 deletions(-) create mode 100644 java/cmake/ffm-jextract-hdf5_h_2/README.txt create mode 100644 java/cmake/ffm-jextract-hdf5_h_2/needle.txt create mode 100644 java/cmake/ffm-jextract-hdf5_h_2/replacement.txt create mode 100644 java/hdf/hdf5lib/Hdf5NativeLoader.java diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt index 615914b19a6b..dc69282a97db 100644 --- a/java/CMakeLists.txt +++ b/java/CMakeLists.txt @@ -8,6 +8,11 @@ include (UseJava) mark_as_advanced (CMAKE_Java_ARCHIVE) mark_as_advanced (CMAKE_Java_RUNTIME) +# Development component provides Java_JAR_EXECUTABLE for native Maven bundles +if (HDF5_ENABLE_MAVEN_DEPLOY) + find_package (Java COMPONENTS Development) +endif () + if (WIN32) set (HDF_JRE_DIRECTORY "C:/Program Files/Java/jre") else () @@ -163,6 +168,75 @@ if (Java_VERSION_STRING VERSION_GREATER_EQUAL "25.0.0") message (FATAL_ERROR "jextract did not generate expected file: ${EXPECTED_FFM_FILE}") endif () + # Post-process jextract output: vanilla SYMBOL_LOOKUP breaks bundled Maven natives (see + # java/cmake/ffm-jextract-hdf5_h_2/README.txt). Needle and replacement live there. + set (_HDF5_FFM_JEXTRACT_PATCH_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake/ffm-jextract-hdf5_h_2") + set (_HDF5_FFM_JEXTRACT_NEEDLE "${_HDF5_FFM_JEXTRACT_PATCH_DIR}/needle.txt") + set (_HDF5_FFM_JEXTRACT_REPLACEMENT "${_HDF5_FFM_JEXTRACT_PATCH_DIR}/replacement.txt") + if (NOT EXISTS "${_HDF5_FFM_JEXTRACT_NEEDLE}") + message ( + FATAL_ERROR + "FFM jextract post-process needle missing: ${_HDF5_FFM_JEXTRACT_NEEDLE}" + ) + endif () + if (NOT EXISTS "${_HDF5_FFM_JEXTRACT_REPLACEMENT}") + message ( + FATAL_ERROR + "FFM jextract post-process replacement missing: ${_HDF5_FFM_JEXTRACT_REPLACEMENT}" + ) + endif () + file (READ "${_HDF5_FFM_JEXTRACT_NEEDLE}" _HDF5_h2_old) + string (REPLACE "\r\n" "\n" _HDF5_h2_old "${_HDF5_h2_old}") + file (READ "${_HDF5_FFM_JEXTRACT_REPLACEMENT}" _HDF5_h2_new) + string (REPLACE "\r\n" "\n" _HDF5_h2_new "${_HDF5_h2_new}") + + set (_HDF5_HDF5_H_2 "") + file ( + GLOB _HDF5_FFM_H2_CANDIDATES + "${JEXTRACT_OUTPUT_DIR}/org/hdfgroup/javahdf5/hdf5_h*.java" + ) + foreach (_HDF5_h2_candidate IN LISTS _HDF5_FFM_H2_CANDIDATES) + if (NOT EXISTS "${_HDF5_h2_candidate}") + continue () + endif () + file (READ "${_HDF5_h2_candidate}" _HDF5_h2_probe) + string (REPLACE "\r\n" "\n" _HDF5_h2_probe "${_HDF5_h2_probe}") + string ( + FIND "${_HDF5_h2_probe}" + "static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup" + _HDF5_h2_has_lookup + ) + if (NOT _HDF5_h2_has_lookup EQUAL -1) + set (_HDF5_HDF5_H_2 "${_HDF5_h2_candidate}") + break () + endif () + endforeach () + if ("${_HDF5_HDF5_H_2}" STREQUAL "") + message ( + FATAL_ERROR + "jextract did not generate a header class with SYMBOL_LOOKUP under " + "${JEXTRACT_OUTPUT_DIR}/org/hdfgroup/javahdf5/" + ) + endif () + + file (READ "${_HDF5_HDF5_H_2}" _HDF5_h2_src) + string (FIND "${_HDF5_h2_src}" "HDF5_FFM_NATIVE_LOOKUP" _HDF5_h2_already_patched) + if (_HDF5_h2_already_patched EQUAL -1) + string (REPLACE "\r\n" "\n" _HDF5_h2_src "${_HDF5_h2_src}") + set (_HDF5_h2_out "${_HDF5_h2_src}") + string (REPLACE "${_HDF5_h2_old}" "${_HDF5_h2_new}" _HDF5_h2_out "${_HDF5_h2_out}") + if ("${_HDF5_h2_out}" STREQUAL "${_HDF5_h2_src}") + message ( + FATAL_ERROR + "Post-jextract patch of ${_HDF5_HDF5_H_2} failed (needle not found). " + "jextract output may have changed; update needle.txt under " + "${_HDF5_FFM_JEXTRACT_PATCH_DIR}." + ) + endif () + message (STATUS "Patched ${_HDF5_HDF5_H_2} using ${_HDF5_FFM_JEXTRACT_NEEDLE}") + file (WRITE "${_HDF5_HDF5_H_2}" "${_HDF5_h2_out}") + endif () + message (STATUS "FFM bindings generated successfully at ${JEXTRACT_OUTPUT_DIR}") endif () else () @@ -218,6 +292,7 @@ install ( ${HDF5_JAVA_LOGGING_JAR} ${HDF5_JAVA_LOGGING_NOP_JAR} ${HDF5_JAVA_LOGGING_SIMPLE_JAR} + ${HDF5_JAVA_NATIVE_LIB_LOADER_JAR} DESTINATION ${HDF5_INSTALL_JAR_DIR} COMPONENT libraries ) diff --git a/java/cmake/ffm-jextract-hdf5_h_2/README.txt b/java/cmake/ffm-jextract-hdf5_h_2/README.txt new file mode 100644 index 000000000000..0f10b9fe436a --- /dev/null +++ b/java/cmake/ffm-jextract-hdf5_h_2/README.txt @@ -0,0 +1,10 @@ +Post-jextract adjustment for org.hdfgroup.javahdf5.hdf5_h_* (FFM). + +needle.txt — exact jextract SYMBOL_LOOKUP snippet (LF only). +replacement.txt — shared patched block (createHdf5SymbolLookup + helpers). + +CMake picks the first hdf5_h*.java that contains SYMBOL_LOOKUP (jextract may +place it in hdf5_h_2, hdf5_h_3, etc. depending on declaration split count), +then replaces needle.txt with replacement.txt. +If jextract changes its emitted SYMBOL_LOOKUP block, update needle.txt and +keep replacement.txt semantically in sync. diff --git a/java/cmake/ffm-jextract-hdf5_h_2/needle.txt b/java/cmake/ffm-jextract-hdf5_h_2/needle.txt new file mode 100644 index 000000000000..777a740cdbfe --- /dev/null +++ b/java/cmake/ffm-jextract-hdf5_h_2/needle.txt @@ -0,0 +1,3 @@ + static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("hdf5"), LIBRARY_ARENA) + .or(SymbolLookup.loaderLookup()) + .or(Linker.nativeLinker().defaultLookup()); diff --git a/java/cmake/ffm-jextract-hdf5_h_2/replacement.txt b/java/cmake/ffm-jextract-hdf5_h_2/replacement.txt new file mode 100644 index 000000000000..0fa931ab1c66 --- /dev/null +++ b/java/cmake/ffm-jextract-hdf5_h_2/replacement.txt @@ -0,0 +1,66 @@ + static final SymbolLookup SYMBOL_LOOKUP = createHdf5SymbolLookup(); + + /* + * Replaces jextract's flat chain: + * libraryLookup(...).or(loaderLookup()).or(defaultLookup()) + * with the same three lookups grouped as: + * libraryLookup(...).or(loaderLookup().or(defaultLookup())) + * + * jextract still evaluates libraryLookup(...) first; if it throws, would fail. + * So the non-bundled path keeps libraryLookup inside try/catch. The bundled Maven path + * extracts and loads natives via NativeLoader, then uses libraryLookup on the extracted + * libhdf5 path so symbols resolve before H5.loadH5Lib() runs. + */ + private static void loadBundledNativeDependencies() { + try { + hdf.hdf5lib.Hdf5NativeLoader.loadBundledDependenciesBeforeHdf5(); + } + catch (Throwable ignored) { + } + } + + private static java.nio.file.Path bundledHdf5LibraryPath() { + try { + Object extractor = + Class.forName("org.scijava.nativelib.NativeLoader") + .getMethod("getJniExtractor") + .invoke(null); + if (extractor == null) { + return null; + } + java.lang.reflect.Method getJniDir = + Class.forName("org.scijava.nativelib.BaseJniExtractor") + .getMethod("getJniDir"); + java.io.File jniDir = (java.io.File) getJniDir.invoke(extractor); + java.io.File lib = new java.io.File(jniDir, System.mapLibraryName("hdf5")); + return lib.isFile() ? lib.toPath() : null; + } + catch (Throwable ignored) { + return null; + } + } + + private static SymbolLookup createHdf5SymbolLookup() { + // HDF5_FFM_NATIVE_LOOKUP — marker for idempotent CMake post-process + loadBundledNativeDependencies(); + java.nio.file.Path bundledHdf5 = bundledHdf5LibraryPath(); + if (bundledHdf5 != null) { + try { + return SymbolLookup.libraryLookup(bundledHdf5, LIBRARY_ARENA) + .or(SymbolLookup.loaderLookup() + .or(Linker.nativeLinker().defaultLookup())); + } + catch (IllegalArgumentException e) { + return SymbolLookup.loaderLookup() + .or(Linker.nativeLinker().defaultLookup()); + } + } + try { + return SymbolLookup.libraryLookup(System.mapLibraryName("hdf5"), LIBRARY_ARENA) + .or(SymbolLookup.loaderLookup() + .or(Linker.nativeLinker().defaultLookup())); + } catch (IllegalArgumentException e) { + return SymbolLookup.loaderLookup() + .or(Linker.nativeLinker().defaultLookup()); + } + } diff --git a/java/hdf/hdf5lib/H5.java b/java/hdf/hdf5lib/H5.java index 628a543711c4..ac5703efc2e8 100644 --- a/java/hdf/hdf5lib/H5.java +++ b/java/hdf/hdf5lib/H5.java @@ -328,6 +328,13 @@ public static void loadH5Lib() if (isLibraryLoaded) return; + try { + Hdf5NativeLoader.loadBundledDependenciesBeforeHdf5(); + } + catch (Throwable err) { + log.debug("Bundled HDF5 native dependencies not loaded: " + err.getMessage()); + } + try { H5.H5open(); isLibraryLoaded = true; diff --git a/java/hdf/hdf5lib/HDF5Constants.java b/java/hdf/hdf5lib/HDF5Constants.java index 6c444399fedc..bce4e5778a56 100644 --- a/java/hdf/hdf5lib/HDF5Constants.java +++ b/java/hdf/hdf5lib/HDF5Constants.java @@ -37,7 +37,7 @@ */ public class HDF5Constants { - static { System.err.println("OpenIDs = " + H5.getOpenIDCount()); } + static { Hdf5NativeLoader.loadBundledDependenciesBeforeHdf5(); } /** Special parameters for szip compression */ public static final int H5_SZIP_MAX_PIXELS_PER_BLOCK = H5_SZIP_MAX_PIXELS_PER_BLOCK(); diff --git a/java/hdf/hdf5lib/Hdf5NativeLoader.java b/java/hdf/hdf5lib/Hdf5NativeLoader.java new file mode 100644 index 000000000000..7abefa47be90 --- /dev/null +++ b/java/hdf/hdf5lib/Hdf5NativeLoader.java @@ -0,0 +1,172 @@ +package hdf.hdf5lib; + +import org.scijava.nativelib.NativeLibraryUtil; +import org.scijava.nativelib.NativeLoader; + +/** + * Loads bundled HDF5 shared libraries from {@code hdf5-native}, {@code hdf5-zlib-native}, + * {@code hdf5-szip-native}, and {@code hdf5-jni-native} Maven JARs using SciJava native-lib-loader + * ({@code natives/<platform>/} layout). Extraction uses {@link NativeLibraryUtil#loadNativeLibrary} + * so only classpath native JARs are consulted — not {@code java.library.path} (see + * {@link NativeLoader#loadLibrary(String, String...)}). No Panama/JNI types are referenced here so this class + * can initialize before jextract-generated classes (FFM) or {@code System.loadLibrary("hdf5_java")} (JNI). + */ +public final class Hdf5NativeLoader { + /** + * Skip loading from bundled JARs (use a system install / {@code java.library.path} only). Kept identical + * to the former {@code NativeLibraryBootstrap} property name for backward compatibility. + */ + public static final String SKIP_PROPERTY = "hdf.hdf5lib.NativeLibraryBootstrap.skip"; + + private static volatile boolean attemptedHdf5 = false; + private static volatile boolean attemptedHdf5Java = false; + private static volatile boolean attemptedZlib = false; + private static volatile boolean attemptedSzip = false; + private static volatile boolean loadedHdf5 = false; + private static volatile boolean loadedHdf5Java = false; + private static volatile boolean loadedZlib = false; + private static volatile boolean loadedSzip = false; + + private Hdf5NativeLoader() {} + + private static boolean skipBundledLoad() + { + return "true".equalsIgnoreCase(System.getProperty(SKIP_PROPERTY, "")); + } + + /** + * Load {@code libName} only from bundled native Maven JARs ({@code natives/<platform>/} on the + * classpath). Unlike {@link NativeLoader#loadLibrary(String, String...)}, does not call + * {@code System.loadLibrary} first, so in-tree CI with {@code java.library.path} does not preload + * build-tree libs before {@code hdf5_java}. + */ + private static boolean loadBundledFromClasspathJars(String libName) + { + return NativeLibraryUtil.loadNativeLibrary(NativeLoader.getJniExtractor(), libName); + } + + /** + * Loads bundled zlib when present. Safe to call multiple times. + * This should be called before loading libhdf5 when libhdf5 was built with dynamic zlib support. + * + * @return true if bundled zlib was loaded successfully + */ + public static synchronized boolean loadBundledZlibIfPresent() + { + if (attemptedZlib) + return loadedZlib; + attemptedZlib = true; + + if (skipBundledLoad()) + return false; + + // SciJava maps "z" -> libz.so / z.dll. Some Windows zlib builds export zlib.dll or + // zlib1.dll instead; try those names so libhdf5's PE import table resolves before load. + final String[] zlibCandidates; + if (System.getProperty("os.name", "").toLowerCase().contains("win")) { + zlibCandidates = new String[] {"zlib1", "zlib", "z"}; + } + else { + zlibCandidates = new String[] {"z"}; + } + + boolean any = false; + for (String zlibName : zlibCandidates) { + if (loadBundledFromClasspathJars(zlibName)) { + any = true; + } + } + loadedZlib = any; + return any; + } + + /** @return true if bundled zlib was loaded successfully this session */ + public static boolean bundledZlibLoadSucceeded() { return loadedZlib; } + + /** + * Loads the bundled libaec/szip pair when present. Safe to call multiple times. + * This should be called before loading libhdf5 when libhdf5 was built with dynamic + * SZIP (libaec) support. libaec is loaded first because libsz depends on it; libsz + * exposes the szip-compatible API used by libhdf5's SZIP filter. + * + * @return true if at least the szip-compatible library was loaded successfully + */ + public static synchronized boolean loadBundledSzipIfPresent() + { + if (attemptedSzip) + return loadedSzip; + attemptedSzip = true; + + if (skipBundledLoad()) + return false; + + boolean any = false; + for (String name : new String[] {"aec", "sz", "szip"}) { + if (loadBundledFromClasspathJars(name)) { + any = true; + } + } + + loadedSzip = any; + return any; + } + + /** @return true if bundled szip (libaec) was loaded successfully this session */ + public static boolean bundledSzipLoadSucceeded() { return loadedSzip; } + + /** + * Loads the bundled HDF5 library when present. Safe to call multiple times. + * This should be called early before attempting to load hdf5_java. + * + * @return true if bundled hdf5 was loaded successfully + */ + public static synchronized boolean loadBundledHdf5IfPresent() + { + if (attemptedHdf5) + return loadedHdf5; + attemptedHdf5 = true; + + if (skipBundledLoad()) + return false; + + loadedHdf5 = loadBundledFromClasspathJars("hdf5"); + return loadedHdf5; + } + + /** @return true if bundled hdf5 library was loaded successfully this session */ + public static boolean bundledHdf5LoadSucceeded() { return loadedHdf5; } + + /** + * Loads the bundled hdf5_java JNI library when present. Safe to call multiple times. + * This should be called as a last resort fallback after attempting to load hdf5_java + * from system paths. + * + * @return true if bundled hdf5_java was loaded successfully + */ + public static synchronized boolean loadBundledHdf5JavaIfPresent() + { + if (attemptedHdf5Java) + return loadedHdf5Java; + attemptedHdf5Java = true; + + if (skipBundledLoad()) + return false; + + loadedHdf5Java = loadBundledFromClasspathJars("hdf5_java"); + return loadedHdf5Java; + } + + /** @return true if bundled hdf5_java library was loaded successfully this session */ + public static boolean bundledHdf5JavaLoadSucceeded() { return loadedHdf5Java; } + + /** + * Loads bundled filter dependencies and libhdf5 when present (zlib, then libaec/szip, then hdf5). + * Safe to call multiple times. + */ + public static void loadBundledDependenciesBeforeHdf5() + { + loadBundledZlibIfPresent(); + loadBundledSzipIfPresent(); + loadBundledHdf5IfPresent(); + } +} diff --git a/java/hdf/hdf5lib/pom.xml.in b/java/hdf/hdf5lib/pom.xml.in index a8bfbe0a6a5a..eaf220e01e1a 100644 --- a/java/hdf/hdf5lib/pom.xml.in +++ b/java/hdf/hdf5lib/pom.xml.in @@ -11,7 +11,7 @@ jar HDF5 Java Bindings - Java bindings for the HDF5 scientific data format library + Java bindings for the HDF5 scientific data format library. Depends on org.hdfgroup:hdf5-native (optional Maven profile) for bundled libhdf5 via SciJava native-lib-loader, or use a system HDF5 install with java.library.path / LD_LIBRARY_PATH. https://github.com/HDFGroup/hdf5 @@ -48,6 +48,7 @@ @HDF5_PACKAGE_VERSION@ @HDF5_MAVEN_PLATFORM@ @HDF5_MAVEN_ARCHITECTURE@ + 2.5.0 @@ -57,6 +58,12 @@ slf4j-api 2.0.16 + + + org.scijava + native-lib-loader + ${native-lib-loader.version} + @@ -184,6 +191,87 @@ + + + hdf5-bundled-native-linux-x86_64 + + unixLinuxamd64 + + + + org.hdfgroup + hdf5-native + ${project.version} + linux-x86_64 + true + + + + + hdf5-bundled-native-linux-aarch64 + + unixLinuxaarch64 + + + + org.hdfgroup + hdf5-native + ${project.version} + linux-aarch64 + true + + + + + hdf5-bundled-native-windows-amd64 + + windowsamd64 + + + + org.hdfgroup + hdf5-native + ${project.version} + windows-x86_64 + true + + + + + hdf5-bundled-native-macos-x86_64 + + macx86_64 + + + + org.hdfgroup + hdf5-native + ${project.version} + macos-x86_64 + true + + + + + hdf5-bundled-native-macos-aarch64 + + macaarch64 + + + + org.hdfgroup + hdf5-native + ${project.version} + macos-aarch64 + true + + + + +@HDF5_MAVEN_ZLIB_NATIVE_PROFILES@ + +@HDF5_MAVEN_SZIP_NATIVE_PROFILES@ + snapshot @@ -198,4 +286,4 @@ - \ No newline at end of file + diff --git a/java/jsrc/CMakeLists.txt b/java/jsrc/CMakeLists.txt index fbed495c656c..7019883145fc 100644 --- a/java/jsrc/CMakeLists.txt +++ b/java/jsrc/CMakeLists.txt @@ -15,7 +15,12 @@ file (WRITE ${PROJECT_BINARY_DIR}/Manifest.txt " ) -set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVA_LOGGING_JAR}") +set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}") + +# jextract-generated hdf5_h references Hdf5NativeLoader in a static initializer +if (HDF5_JAVA_USE_FFM) + list (APPEND HDF5_JAVA_JSRC_SOURCES ${HDF5_SOURCE_DIR}/java/hdf/hdf5lib/Hdf5NativeLoader.java) +endif () # Set version suffix for snapshots vs releases if (HDF5_ENABLE_MAVEN_DEPLOY AND HDF5_MAVEN_SNAPSHOT) diff --git a/java/src-jni/hdf/hdf5lib/H5.java b/java/src-jni/hdf/hdf5lib/H5.java index 069e80a0b94b..f708341c8f07 100644 --- a/java/src-jni/hdf/hdf5lib/H5.java +++ b/java/src-jni/hdf/hdf5lib/H5.java @@ -291,7 +291,9 @@ public class H5 implements java.io.Serializable { */ public final static String H5_LIBRARY_NAME_PROPERTY_KEY = "hdf.hdf5lib.H5.loadLibraryName"; private static String s_libraryName; - private static boolean isLibraryLoaded = false; + private static boolean isHdf5LibraryLoaded = false; // bundled hdf5 load succeeded + private static boolean isHdf5JavaLibraryLoaded = false; // hdf5_java load succeeded + private static boolean isInitialized = false; // full loadH5Lib() completed private final static boolean IS_CRITICAL_PINNING = true; private final static LinkedHashSet OPEN_IDS = new LinkedHashSet(); @@ -306,78 +308,119 @@ public class H5 implements java.io.Serializable { public static void loadH5Lib() { // Make sure that the library is loaded only once - if (isLibraryLoaded) + if (isInitialized) return; - // first try loading library by name from user supplied library path + // first try loading bundled dependencies, then hdf5 (dependencies for hdf5_java) + try { + Hdf5NativeLoader.loadBundledDependenciesBeforeHdf5(); + if (Hdf5NativeLoader.bundledZlibLoadSucceeded()) { + log.info("HDF5 library dependency: zlib (bundled)"); + log.info("Successfully loaded bundled zlib"); + } + if (Hdf5NativeLoader.bundledSzipLoadSucceeded()) { + log.info("HDF5 library dependency: szip/libaec (bundled)"); + log.info("Successfully loaded bundled szip (libaec)"); + } + if (Hdf5NativeLoader.bundledHdf5LoadSucceeded()) { + log.info("HDF5 library: hdf5 (bundled)"); + log.info("Successfully loaded bundled hdf5"); + isHdf5LibraryLoaded = true; + } + } + catch (Throwable err) { + log.debug("Bundled HDF5 native dependencies not loaded: " + err.getMessage()); + isHdf5LibraryLoaded = false; + } + + // first try loading hdf5_java library by name from user supplied library path s_libraryName = System.getProperty(H5_LIBRARY_NAME_PROPERTY_KEY, null); String mappedName = null; if ((s_libraryName != null) && (s_libraryName.length() > 0)) { try { mappedName = System.mapLibraryName(s_libraryName); System.loadLibrary(s_libraryName); - isLibraryLoaded = true; + isHdf5JavaLibraryLoaded = true; } catch (Throwable err) { err.printStackTrace(); - isLibraryLoaded = false; + isHdf5JavaLibraryLoaded = false; } finally { log.info("HDF5 library: " + s_libraryName); log.debug(" resolved to: " + mappedName + "; "); - log.info((isLibraryLoaded ? "" : " NOT") + " successfully loaded from system property"); + log.info((isHdf5JavaLibraryLoaded ? "" : " NOT") + + " successfully loaded from system property"); } } - if (!isLibraryLoaded) { - // else try loading library via full path + if (!isHdf5JavaLibraryLoaded) { + // else try loading hdf5_java library via full path String filename = System.getProperty(H5PATH_PROPERTY_KEY, null); if ((filename != null) && (filename.length() > 0)) { File h5dll = new File(filename); if (h5dll.exists() && h5dll.canRead() && h5dll.isFile()) { try { System.load(filename); - isLibraryLoaded = true; + isHdf5JavaLibraryLoaded = true; } catch (Throwable err) { err.printStackTrace(); - isLibraryLoaded = false; + isHdf5JavaLibraryLoaded = false; } finally { log.info("HDF5 library: "); log.debug(filename); - log.info((isLibraryLoaded ? "" : " NOT") + " successfully loaded."); + log.info((isHdf5JavaLibraryLoaded ? "" : " NOT") + " successfully loaded."); } } else { - isLibraryLoaded = false; + isHdf5JavaLibraryLoaded = false; throw(new UnsatisfiedLinkError("Invalid HDF5 library, " + filename)); } } } - // else load standard library - if (!isLibraryLoaded) { + // Bundled hdf5_java (e.g. org.hdfgroup:hdf5-jni-native) before java.library.path — avoids a + // failed System.loadLibrary and noisy stack trace when only the Maven JAR is present. + if (!isHdf5JavaLibraryLoaded) { + try { + if (Hdf5NativeLoader.loadBundledHdf5JavaIfPresent()) { + log.info("HDF5 library: hdf5_java (bundled)"); + log.info("Successfully loaded bundled hdf5_java"); + isHdf5JavaLibraryLoaded = true; + } + } + catch (Throwable err) { + log.debug("Bundled hdf5_java not loaded: " + err.getMessage()); + isHdf5JavaLibraryLoaded = false; + } + } + + // else load standard hdf5_java library from java.library.path + if (!isHdf5JavaLibraryLoaded) { try { s_libraryName = "hdf5_java"; mappedName = System.mapLibraryName(s_libraryName); System.loadLibrary("hdf5_java"); - isLibraryLoaded = true; + isHdf5JavaLibraryLoaded = true; } catch (Throwable err) { err.printStackTrace(); - isLibraryLoaded = false; + isHdf5JavaLibraryLoaded = false; } finally { log.info("HDF5 library: " + s_libraryName); log.debug(" resolved to: " + mappedName + "; "); - log.info((isLibraryLoaded ? "" : " NOT") + " successfully loaded from java.library.path"); + log.info((isHdf5JavaLibraryLoaded ? "" : " NOT") + + " successfully loaded from java.library.path"); } } /* Important! Exit quietly */ try { H5.H5dont_atexit(); + isInitialized = true; } catch (HDF5LibraryException e) { System.exit(1); diff --git a/java/src-jni/hdf/hdf5lib/pom.xml.in b/java/src-jni/hdf/hdf5lib/pom.xml.in index 551c2a711914..5ce88626e600 100644 --- a/java/src-jni/hdf/hdf5lib/pom.xml.in +++ b/java/src-jni/hdf/hdf5lib/pom.xml.in @@ -11,7 +11,7 @@ jar HDF5 Java JNI Bindings - Java Native Interface bindings for the HDF5 scientific data format library + Java Native Interface bindings for the HDF5 scientific data format library. Requires org.hdfgroup:hdf5-jni-native (transitive Maven dependency). Optional org.hdfgroup:hdf5-native and filter native JARs load via SciJava native-lib-loader, or use a system HDF5 install. https://github.com/HDFGroup/hdf5 @@ -50,6 +50,7 @@ @HDF5_MAVEN_ARCHITECTURE@ JNI 11 + 2.5.0 @@ -59,6 +60,11 @@ slf4j-api 2.0.16 + + org.scijava + native-lib-loader + ${native-lib-loader.version} + @@ -188,6 +194,116 @@ + + hdf5-bundled-native-linux-x86_64 + + unixLinuxamd64 + + + + org.hdfgroup + hdf5-native + ${project.version} + linux-x86_64 + true + + + org.hdfgroup + hdf5-jni-native + ${project.version} + linux-x86_64 + + + + + hdf5-bundled-native-linux-aarch64 + + unixLinuxaarch64 + + + + org.hdfgroup + hdf5-native + ${project.version} + linux-aarch64 + true + + + org.hdfgroup + hdf5-jni-native + ${project.version} + linux-aarch64 + + + + + hdf5-bundled-native-windows-amd64 + + windowsamd64 + + + + org.hdfgroup + hdf5-native + ${project.version} + windows-x86_64 + true + + + org.hdfgroup + hdf5-jni-native + ${project.version} + windows-x86_64 + + + + + hdf5-bundled-native-macos-x86_64 + + macx86_64 + + + + org.hdfgroup + hdf5-native + ${project.version} + macos-x86_64 + true + + + org.hdfgroup + hdf5-jni-native + ${project.version} + macos-x86_64 + + + + + hdf5-bundled-native-macos-aarch64 + + macaarch64 + + + + org.hdfgroup + hdf5-native + ${project.version} + macos-aarch64 + true + + + org.hdfgroup + hdf5-jni-native + ${project.version} + macos-aarch64 + + + + +@HDF5_MAVEN_ZLIB_NATIVE_PROFILES@ + +@HDF5_MAVEN_SZIP_NATIVE_PROFILES@ + snapshot @@ -202,4 +318,4 @@ - \ No newline at end of file + From 029ca538d7e7ccee70d9879146ceb1e8d9ddcf54 Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:08:48 +0200 Subject: [PATCH 11/16] test/ci: validate and smoke-test native Maven artifacts Add artifact validation, Maven consumer smoke test with explicit native deps, FFM deflate smoke test, and CI workflow updates for native bundle artifacts. Co-authored-by: Cursor --- .github/scripts/install-hdf5-maven-local.sh | 190 ++++++++++++++++++++ .github/scripts/test-maven-consumer.sh | 54 +++++- .github/scripts/validate-maven-artifacts.sh | 118 ++++++++++-- .github/workflows/ctest.yml | 48 +++-- .github/workflows/maven-deploy.yml | 44 ++++- .github/workflows/maven-staging.yml | 96 ++++++++-- .gitignore | 1 + java/jtest/CMakeLists.txt | 5 +- java/jtest/TestH5DeflateNativeffm.java | 155 ++++++++++++++++ 9 files changed, 664 insertions(+), 47 deletions(-) create mode 100755 .github/scripts/install-hdf5-maven-local.sh create mode 100644 java/jtest/TestH5DeflateNativeffm.java diff --git a/.github/scripts/install-hdf5-maven-local.sh b/.github/scripts/install-hdf5-maven-local.sh new file mode 100755 index 000000000000..7f9fef31c3c2 --- /dev/null +++ b/.github/scripts/install-hdf5-maven-local.sh @@ -0,0 +1,190 @@ +#!/usr/bin/env bash +# Install HDF5 Java/Maven build outputs into the local repository (~/.m2). +# Mirrors coordinate rules from .github/workflows/maven-deploy.yml. +# +# Usage: +# ./install-hdf5-maven-local.sh [--jni-dir DIR] [--ffm-dir DIR] [--ffm-jsrc DIR] +# +# Defaults (repo root = parent of .github): +# JNI: build-maven-jni/java/src-jni/hdf/hdf5lib +# FFM: build-maven-ffm/java/hdf/hdf5lib +# jsrc: build-maven-ffm/java/jsrc +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +JNI_LIB_DIR="${REPO_ROOT}/build-maven-jni/java/src-jni/hdf/hdf5lib" +FFM_LIB_DIR="${REPO_ROOT}/build-maven-ffm/java/hdf/hdf5lib" +FFM_JSRC_DIR="${REPO_ROOT}/build-maven-ffm/java/jsrc" +G="org.hdfgroup" + +while [[ $# -gt 0 ]]; do + case "$1" in + --jni-dir) JNI_LIB_DIR="$2"; shift 2 ;; + --ffm-dir) FFM_LIB_DIR="$2"; shift 2 ;; + --ffm-jsrc) FFM_JSRC_DIR="$2"; shift 2 ;; + -h|--help) + sed -n '1,20p' "$0" + exit 0 + ;; + *) echo "Unknown option: $1" >&2; exit 2 ;; + esac +done + +pom_version() { + local pom="$1" + if [[ ! -f "$pom" ]]; then + echo "ERROR: missing POM: $pom" >&2 + exit 1 + fi + grep -o '[^<]*' "$pom" | head -1 | sed 's/<[^>]*>//g' +} + +classifier_from_native_jar() { + local ver="$1" + local jar="$2" + # hdf5-native-${ver}-linux-x86_64.jar -> linux-x86_64 + local base + base=$(basename "$jar" .jar) + local prefix="hdf5-native-${ver}-" + if [[ "$base" != "$prefix"* ]]; then + echo "ERROR: unexpected native JAR name: $jar" >&2 + exit 1 + fi + echo "${base#"$prefix"}" +} + +install_file() { + echo "[install] $*" + mvn -q install:install-file "$@" +} + +VER="" +CLASS="" + +if [[ -f "${JNI_LIB_DIR}/pom.xml" ]]; then + VER="$(pom_version "${JNI_LIB_DIR}/pom.xml")" +elif [[ -f "${FFM_LIB_DIR}/pom.xml" ]]; then + VER="$(pom_version "${FFM_LIB_DIR}/pom.xml")" +else + echo "ERROR: need at least one of:" >&2 + echo " ${JNI_LIB_DIR}/pom.xml" >&2 + echo " ${FFM_LIB_DIR}/pom.xml" >&2 + exit 1 +fi + +# Discover platform classifier from an existing native bundle JAR +NATIVE_JAR="" +if ls "${JNI_LIB_DIR}"/hdf5-native-"${VER}"-*.jar >/dev/null 2>&1; then + NATIVE_JAR="$(ls "${JNI_LIB_DIR}"/hdf5-native-"${VER}"-*.jar | head -1)" +elif ls "${FFM_LIB_DIR}"/hdf5-native-"${VER}"-*.jar >/dev/null 2>&1; then + NATIVE_JAR="$(ls "${FFM_LIB_DIR}"/hdf5-native-"${VER}"-*.jar | head -1)" +else + echo "ERROR: no hdf5-native-${VER}-*.jar under JNI or FFM lib dirs" >&2 + exit 1 +fi +CLASS="$(classifier_from_native_jar "$VER" "$NATIVE_JAR")" +echo "HDF5 Maven local install: version=${VER} classifier=${CLASS}" + +# 1) hdf5-native (JNI tree, then FFM if present) +if [[ -f "${JNI_LIB_DIR}/pom-hdf5-native.xml" && -f "${JNI_LIB_DIR}/hdf5-native-${VER}-${CLASS}.jar" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-native -Dversion="$VER" \ + -Dpackaging=jar -Dclassifier="$CLASS" \ + -Dfile="${JNI_LIB_DIR}/hdf5-native-${VER}-${CLASS}.jar" \ + -DpomFile="${JNI_LIB_DIR}/pom-hdf5-native.xml" +fi +if [[ -f "${FFM_LIB_DIR}/hdf5-native-${VER}-${CLASS}.jar" && -f "${FFM_LIB_DIR}/pom-hdf5-native.xml" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-native -Dversion="$VER" \ + -Dpackaging=jar -Dclassifier="$CLASS" \ + -Dfile="${FFM_LIB_DIR}/hdf5-native-${VER}-${CLASS}.jar" \ + -DpomFile="${FFM_LIB_DIR}/pom-hdf5-native.xml" +fi + +# 2) hdf5-zlib-native (JNI tree, then FFM if present) +if [[ -f "${JNI_LIB_DIR}/pom-hdf5-zlib-native.xml" && -f "${JNI_LIB_DIR}/hdf5-zlib-native-${VER}-${CLASS}.jar" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-zlib-native -Dversion="$VER" \ + -Dpackaging=jar -Dclassifier="$CLASS" \ + -Dfile="${JNI_LIB_DIR}/hdf5-zlib-native-${VER}-${CLASS}.jar" \ + -DpomFile="${JNI_LIB_DIR}/pom-hdf5-zlib-native.xml" +fi +if [[ -f "${FFM_LIB_DIR}/hdf5-zlib-native-${VER}-${CLASS}.jar" && -f "${FFM_LIB_DIR}/pom-hdf5-zlib-native.xml" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-zlib-native -Dversion="$VER" \ + -Dpackaging=jar -Dclassifier="$CLASS" \ + -Dfile="${FFM_LIB_DIR}/hdf5-zlib-native-${VER}-${CLASS}.jar" \ + -DpomFile="${FFM_LIB_DIR}/pom-hdf5-zlib-native.xml" +fi + +# 3) hdf5-szip-native (JNI tree, then FFM if present) +if [[ -f "${JNI_LIB_DIR}/pom-hdf5-szip-native.xml" && -f "${JNI_LIB_DIR}/hdf5-szip-native-${VER}-${CLASS}.jar" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-szip-native -Dversion="$VER" \ + -Dpackaging=jar -Dclassifier="$CLASS" \ + -Dfile="${JNI_LIB_DIR}/hdf5-szip-native-${VER}-${CLASS}.jar" \ + -DpomFile="${JNI_LIB_DIR}/pom-hdf5-szip-native.xml" +fi +if [[ -f "${FFM_LIB_DIR}/hdf5-szip-native-${VER}-${CLASS}.jar" && -f "${FFM_LIB_DIR}/pom-hdf5-szip-native.xml" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-szip-native -Dversion="$VER" \ + -Dpackaging=jar -Dclassifier="$CLASS" \ + -Dfile="${FFM_LIB_DIR}/hdf5-szip-native-${VER}-${CLASS}.jar" \ + -DpomFile="${FFM_LIB_DIR}/pom-hdf5-szip-native.xml" +fi + +# 4) hdf5-jni-native (JNI only) +if [[ -f "${JNI_LIB_DIR}/pom-hdf5-jni-native.xml" && -f "${JNI_LIB_DIR}/hdf5-jni-native-${VER}-${CLASS}.jar" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-jni-native -Dversion="$VER" \ + -Dpackaging=jar -Dclassifier="$CLASS" \ + -Dfile="${JNI_LIB_DIR}/hdf5-jni-native-${VER}-${CLASS}.jar" \ + -DpomFile="${JNI_LIB_DIR}/pom-hdf5-jni-native.xml" +fi + +# 5) javahdf5 (FFM — generated POM) +if [[ -f "${FFM_JSRC_DIR}/javahdf5-${VER}.jar" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=javahdf5 -Dversion="$VER" \ + -Dpackaging=jar -DgeneratePom=true \ + -Dfile="${FFM_JSRC_DIR}/javahdf5-${VER}.jar" +else + echo "[skip] FFM bindings JAR not found: ${FFM_JSRC_DIR}/javahdf5-${VER}.jar" +fi + +# 6) Main Java API JARs (Maven artifactId differs from file name jarhdf5) +if [[ -f "${JNI_LIB_DIR}/pom.xml" && -f "${JNI_LIB_DIR}/jarhdf5-${VER}-${CLASS}.jar" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-java-jni -Dversion="$VER" \ + -Dpackaging=jar -Dclassifier="$CLASS" \ + -Dfile="${JNI_LIB_DIR}/jarhdf5-${VER}-${CLASS}.jar" \ + -DpomFile="${JNI_LIB_DIR}/pom.xml" +fi + +if [[ -f "${FFM_LIB_DIR}/pom.xml" && -f "${FFM_LIB_DIR}/jarhdf5-${VER}-${CLASS}.jar" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-java-ffm -Dversion="$VER" \ + -Dpackaging=jar -Dclassifier="$CLASS" \ + -Dfile="${FFM_LIB_DIR}/jarhdf5-${VER}-${CLASS}.jar" \ + -DpomFile="${FFM_LIB_DIR}/pom.xml" +fi + +# 7) Universal jarhdf5 (no classifier) — copy of platform JAR for examples/tests +if [[ -f "${JNI_LIB_DIR}/jarhdf5-${VER}.jar" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-java-jni -Dversion="$VER" \ + -Dpackaging=jar \ + -Dfile="${JNI_LIB_DIR}/jarhdf5-${VER}.jar" \ + -DpomFile="${JNI_LIB_DIR}/pom.xml" +fi +if [[ -f "${FFM_LIB_DIR}/jarhdf5-${VER}.jar" ]]; then + install_file \ + -DgroupId="$G" -DartifactId=hdf5-java-ffm -Dversion="$VER" \ + -Dpackaging=jar \ + -Dfile="${FFM_LIB_DIR}/jarhdf5-${VER}.jar" \ + -DpomFile="${FFM_LIB_DIR}/pom.xml" +fi + +echo "Done. Installed under ~/.m2/repository/org/hdfgroup/" diff --git a/.github/scripts/test-maven-consumer.sh b/.github/scripts/test-maven-consumer.sh index 0117c6445ac6..aa25ca5932df 100755 --- a/.github/scripts/test-maven-consumer.sh +++ b/.github/scripts/test-maven-consumer.sh @@ -44,19 +44,30 @@ cat > pom.xml << EOF - + org.hdfgroup - hdf5-java + hdf5-java-jni \${hdf5.version} linux-x86_64 - - org.hdfgroup - hdf5-java-examples + hdf5-native \${hdf5.version} + linux-x86_64 + + + org.hdfgroup + hdf5-zlib-native + \${hdf5.version} + linux-x86_64 + + + org.hdfgroup + hdf5-szip-native + \${hdf5.version} + linux-x86_64 @@ -67,8 +78,11 @@ mkdir -p src/main/java/org/hdfgroup/test cat > src/main/java/org/hdfgroup/test/TestConsumer.java << 'EOF' package org.hdfgroup.test; +import hdf.hdf5lib.H5; +import hdf.hdf5lib.HDF5Constants; + public class TestConsumer { - public static void main(String[] args) { + public static void main(String[] args) throws Exception { System.out.println("Testing HDF5 Maven artifact consumption..."); try { @@ -79,6 +93,25 @@ public class TestConsumer { System.out.println("⚠ HDF5 Java library classes not found: " + e.getMessage()); } + H5.loadH5Lib(); + if (H5.H5Zfilter_avail(HDF5Constants.H5Z_FILTER_DEFLATE) <= 0) { + throw new IllegalStateException("H5Z_FILTER_DEFLATE is not available"); + } + System.out.println("✓ H5Z_FILTER_DEFLATE is available after H5.loadH5Lib()"); + + long dcpl = H5.H5Pcreate(HDF5Constants.H5P_DATASET_CREATE); + long[] chunk = {10, 20}; + H5.H5Pset_chunk(dcpl, 2, chunk); + H5.H5Pset_deflate(dcpl, 6); + H5.H5Pclose(dcpl); + System.out.println("✓ HDF5 deflate/GZIP filter is available"); + + long szDcpl = H5.H5Pcreate(HDF5Constants.H5P_DATASET_CREATE); + H5.H5Pset_chunk(szDcpl, 2, chunk); + H5.H5Pset_szip(szDcpl, HDF5Constants.H5_SZIP_NN_OPTION_MASK, 8); + H5.H5Pclose(szDcpl); + System.out.println("✓ HDF5 SZIP (libaec) filter is available"); + System.out.println("✓ Maven artifact consumption test completed"); } } @@ -103,6 +136,15 @@ else exit 1 fi +# Test runtime loading and deflate availability +echo "=== Testing Runtime Loading and Deflate ===" +if mvn exec:java -Dexec.mainClass=org.hdfgroup.test.TestConsumer -Dexec.jvmArgs=--enable-native-access=ALL-UNNAMED -q; then + echo "✓ Runtime loading and deflate test successful" +else + echo "❌ Runtime loading or deflate test failed" + exit 1 +fi + # List resolved dependencies echo "=== Resolved Dependencies ===" mvn dependency:list | grep org.hdfgroup || echo "No org.hdfgroup dependencies found" diff --git a/.github/scripts/validate-maven-artifacts.sh b/.github/scripts/validate-maven-artifacts.sh index 76ed526df6ed..f071939ffe02 100755 --- a/.github/scripts/validate-maven-artifacts.sh +++ b/.github/scripts/validate-maven-artifacts.sh @@ -133,6 +133,44 @@ validate_jar_file() { # - jarhdf5-*.jar: Wrapper classes (hdf/hdf5lib/*) # JNI builds have single JAR with hdf/hdf5lib/* classes + if [[ "${jar_basename}" == hdf5-native-* ]] || [[ "${jar_basename}" == hdf5-zlib-native-* ]] || [[ "${jar_basename}" == hdf5-szip-native-* ]] || [[ "${jar_basename}" == hdf5-jni-native-* ]]; then + local native_lib + # Match unversioned (.so) and versioned sonames (e.g. libhdf5.so.1.2.3) + native_lib=$(find "${temp_dir}/natives" -type f \( -name "*.so" -o -name "*.so.*" -o -name "*.dll" -o -name "*.dylib" \) 2>/dev/null | head -1) + if [[ -z "${native_lib}" ]]; then + add_error "Native bundle JAR missing shared libraries under natives// (SciJava layout): ${jar_basename}" + else + log_info "Found native library in bundle: $(basename "${native_lib}")" + fi + if [[ "${jar_basename}" == hdf5-zlib-native-* ]]; then + local mapped_zlib + mapped_zlib=$(find "${temp_dir}/natives" -type f \( -name "libz.so" -o -name "libz.so.*" -o -name "libz.dylib" -o -name "libz.*.dylib" -o -name "z.dll" -o -name "zlib.dll" -o -name "zlib1.dll" \) 2>/dev/null | head -1) + if [[ -z "${mapped_zlib}" ]]; then + add_error "hdf5-zlib-native JAR missing mapped zlib name under natives//" + else + log_info "Found mapped zlib library in bundle: $(basename "${mapped_zlib}")" + fi + fi + if [[ "${jar_basename}" == hdf5-szip-native-* ]]; then + local mapped_libsz mapped_libaec + mapped_libsz=$(find "${temp_dir}/natives" -type f \( -name "libsz.so" -o -name "libsz.dylib" -o -name "sz.dll" \) 2>/dev/null | head -1) + mapped_libaec=$(find "${temp_dir}/natives" -type f \( -name "libaec.so" -o -name "libaec.dylib" -o -name "aec.dll" \) 2>/dev/null | head -1) + if [[ -z "${mapped_libsz}" ]]; then + add_error "hdf5-szip-native JAR missing mapped libsz name under natives//" + else + log_info "Found mapped libsz library in bundle: $(basename "${mapped_libsz}")" + fi + if [[ -z "${mapped_libaec}" ]]; then + add_error "hdf5-szip-native JAR missing mapped libaec name under natives//" + else + log_info "Found mapped libaec library in bundle: $(basename "${mapped_libaec}")" + fi + fi + rm -rf "${temp_dir}" + log_success "JAR validation completed: ${jar_basename}" + return 0 + fi + if [[ "${jar_basename}" == *"javahdf5"* ]]; then # This is the FFM bindings JAR - check for FFM classes local ffm_classes=( @@ -210,8 +248,25 @@ validate_pom_file() { add_error "Invalid or missing groupId in POM" fi - if ! grep -qE "hdf5-java(-ffm|-jni)?" "${pom_file}"; then - add_error "Invalid or missing artifactId in POM (expected hdf5-java, hdf5-java-ffm, or hdf5-java-jni)" + local allowed_hdf5_artifact_ids=( + hdf5-native + hdf5-zlib-native + hdf5-szip-native + hdf5-jni-native + hdf5-java + hdf5-java-ffm + hdf5-java-jni + hdf5-java-examples + ) + local artifact_id_ok=false + for aid in "${allowed_hdf5_artifact_ids[@]}"; do + if grep -Fq "${aid}" "${pom_file}"; then + artifact_id_ok=true + break + fi + done + if [[ "${artifact_id_ok}" != "true" ]]; then + add_error "Invalid or missing artifactId in POM (expected one of: hdf5-native, hdf5-zlib-native, hdf5-szip-native, hdf5-jni-native, hdf5-java, hdf5-java-ffm, hdf5-java-jni, hdf5-java-examples)" fi # Extract version @@ -303,6 +358,7 @@ validate_platform_classifiers() { local valid_classifiers=( "linux-x86_64" + "linux-aarch64" "windows-x86_64" "macos-x86_64" "macos-aarch64" @@ -368,9 +424,9 @@ simulate_maven_dependency() { artifact_id=$(grep -o '[^<]*' "${pom_file}" | head -1 | sed 's/<[^>]*>//g' || echo "") version=$(grep -o '[^<]*' "${pom_file}" | head -1 | sed 's/<[^>]*>//g' || echo "") - # Find the JAR file in the artifacts directory + # Find the JAR for this POM's artifactId (parenthesize -o so -maxdepth applies to both) local jar_file - jar_file=$(find "$(dirname "${pom_file}")" -maxdepth 2 -name "${artifact_id}-${version}.jar" -o -name "${artifact_id}-*.jar" | head -1) + jar_file=$(find "$(dirname "${pom_file}")" -maxdepth 2 \( -name "${artifact_id}-${version}.jar" -o -name "${artifact_id}-${version}-*.jar" -o -name "${artifact_id}-*.jar" \) 2>/dev/null | head -1) if [ -z "${jar_file}" ]; then add_warning "Could not find JAR file for ${artifact_id}:${version} - skipping dependency simulation" @@ -433,7 +489,7 @@ check_deployment_readiness() { local jar_files pom_files # Only count HDF5 JAR files, exclude dependencies like slf4j jar_files=($(find "${artifacts_dir}" -name "*hdf5*.jar" -not -name "*test*" 2>/dev/null || true)) - pom_files=($(find "${artifacts_dir}" -name "pom.xml" 2>/dev/null || true)) + pom_files=($(find "${artifacts_dir}" \( -name "pom.xml" -o -name "pom-hdf5-native.xml" -o -name "pom-hdf5-zlib-native.xml" -o -name "pom-hdf5-szip-native.xml" -o -name "pom-hdf5-jni-native.xml" \) 2>/dev/null || true)) if [[ ${#jar_files[@]} -eq 0 ]]; then add_error "No JAR files found in artifacts directory" @@ -507,7 +563,7 @@ main() { local jar_files pom_files all_jars # Only validate HDF5 JAR files, exclude dependencies like slf4j jar_files=($(find "${artifacts_dir}" -name "*hdf5*.jar" -not -name "*test*" 2>/dev/null || true)) - pom_files=($(find "${artifacts_dir}" -name "pom.xml" 2>/dev/null || true)) + pom_files=($(find "${artifacts_dir}" \( -name "pom.xml" -o -name "pom-hdf5-native.xml" -o -name "pom-hdf5-zlib-native.xml" -o -name "pom-hdf5-szip-native.xml" -o -name "pom-hdf5-jni-native.xml" \) 2>/dev/null || true)) all_jars=($(find "${artifacts_dir}" -name "*.jar" 2>/dev/null || true)) # Log what we found @@ -534,9 +590,49 @@ main() { validate_pom_file "${pom_file}" done - # Version consistency check + # Version consistency: pair each POM with JARs that share its artifactId (not only pom_files[0]) if [[ ${#pom_files[@]} -gt 0 && ${#jar_files[@]} -gt 0 ]]; then - validate_version_consistency "${pom_files[0]}" "${jar_files[@]}" + for pom_file in "${pom_files[@]}"; do + local pname aid + pname=$(basename "${pom_file}") + # Main module POM is handled below (JAR names may not equal Maven artifactId) + if [[ "${pname}" == "pom.xml" ]]; then + continue + fi + if [[ "${pname}" =~ ^pom-(.+)\.xml$ ]]; then + aid="${BASH_REMATCH[1]}" + else + aid=$(grep -o '[^<]*' "${pom_file}" | head -1 | sed 's/<[^>]*>//g' || true) + fi + if [[ -z "${aid}" ]]; then + continue + fi + local matching=() + for jf in "${jar_files[@]}"; do + if [[ "$(basename "${jf}")" == "${aid}-"* ]]; then + matching+=("${jf}") + fi + done + if [[ ${#matching[@]} -gt 0 ]]; then + validate_version_consistency "${pom_file}" "${matching[@]}" + fi + done + for pom_file in "${pom_files[@]}"; do + if [[ "$(basename "${pom_file}")" != "pom.xml" ]]; then + continue + fi + local rest=() + for jf in "${jar_files[@]}"; do + local jb + jb=$(basename "${jf}") + if [[ "${jb}" != hdf5-native-* && "${jb}" != hdf5-zlib-native-* && "${jb}" != hdf5-szip-native-* && "${jb}" != hdf5-jni-native-* ]]; then + rest+=("${jf}") + fi + done + if [[ ${#rest[@]} -gt 0 ]]; then + validate_version_consistency "${pom_file}" "${rest[@]}" + fi + done fi # Platform classifier validation @@ -544,9 +640,11 @@ main() { validate_platform_classifiers "${jar_files[@]}" fi - # Maven dependency simulation + # Maven dependency simulation (once per POM that has a matching JAR in the same directory tree) if [[ ${#pom_files[@]} -gt 0 ]]; then - simulate_maven_dependency "${pom_files[0]}" + for pom_file in "${pom_files[@]}"; do + simulate_maven_dependency "${pom_file}" + done fi # Generate final report diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 04b6e67517bb..f76fbec6d902 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -551,25 +551,53 @@ jobs: # Upload Maven artifacts when Maven deployment is enabled - name: Collect Maven artifacts (Linux) if: ${{ inputs.maven_enabled == true }} + shell: bash + env: + ARTIFACTS_DIR: ${{ runner.workspace }}/maven-artifacts + ACTIVE_PRESET: ${{ steps.run-ctest.outputs.ACTIVE_PRESET }} + BUILD_DIR: ${{ runner.workspace }}/hdf5/build/${{ steps.run-ctest.outputs.ACTIVE_PRESET }} run: | echo "Collecting Maven artifacts for deployment..." - mkdir -p ${{ runner.workspace }}/maven-artifacts - - # Determine the build directory based on Maven preset used - BUILD_DIR="${{ runner.workspace }}/hdf5/build/${{ steps.run-ctest.outputs.ACTIVE_PRESET }}" + mkdir -p "$ARTIFACTS_DIR" echo "Looking for artifacts in: ${BUILD_DIR}" # Copy JAR files - find "${BUILD_DIR}" -name "*.jar" -not -name "*test*" -not -name "*H5Ex_*" -exec cp {} ${{ runner.workspace }}/maven-artifacts/ \; - - # Copy POM files - find "${BUILD_DIR}" -name "pom.xml" -exec cp {} ${{ runner.workspace }}/maven-artifacts/ \; + find "${BUILD_DIR}" -name "*.jar" -not -name "*test*" -not -name "*H5Ex_*" -exec cp {} "$ARTIFACTS_DIR/" \; + + # Copy POM files from the active Java tree only (flat cp clobber if both FFM and JNI + # pom.xml exist). Preset naming matches CMakePresets *Maven-FFM* vs *Maven* (JNI). + JNI_POM_COUNT=$(find "${BUILD_DIR}" -path '*/java/src-jni/hdf/hdf5lib/pom.xml' 2>/dev/null | wc -l | awk '{print $1}') + FFM_POM_COUNT=$(find "${BUILD_DIR}" -path '*/java/hdf/hdf5lib/pom.xml' 2>/dev/null | wc -l | awk '{print $1}') + if [ "${JNI_POM_COUNT:-0}" -gt 0 ] && [ "${FFM_POM_COUNT:-0}" -gt 0 ]; then + echo "::error::Both JNI and FFM generated pom.xml trees found under ${BUILD_DIR}; refusing ambiguous flat copy" + find "${BUILD_DIR}" \( -path '*/java/src-jni/hdf/hdf5lib/pom.xml' -o -path '*/java/hdf/hdf5lib/pom.xml' \) 2>/dev/null | head -20 + exit 1 + fi + case "$ACTIVE_PRESET" in + *Maven-FFM*) + find "${BUILD_DIR}" \( \ + -path '*/java/hdf/hdf5lib/pom.xml' -o \ + -path '*/java/hdf/hdf5lib/pom-hdf5-native.xml' -o \ + -path '*/java/hdf/hdf5lib/pom-hdf5-zlib-native.xml' -o \ + -path '*/java/hdf/hdf5lib/pom-hdf5-szip-native.xml' -o \ + -path '*/java/hdf/hdf5lib/pom-hdf5-jni-native.xml' \ + \) -exec cp {} "$ARTIFACTS_DIR/" \; + ;; + *) + find "${BUILD_DIR}" \( \ + -path '*/java/src-jni/hdf/hdf5lib/pom.xml' -o \ + -path '*/java/src-jni/hdf/hdf5lib/pom-hdf5-native.xml' -o \ + -path '*/java/src-jni/hdf/hdf5lib/pom-hdf5-zlib-native.xml' -o \ + -path '*/java/src-jni/hdf/hdf5lib/pom-hdf5-szip-native.xml' -o \ + -path '*/java/src-jni/hdf/hdf5lib/pom-hdf5-jni-native.xml' \ + \) -exec cp {} "$ARTIFACTS_DIR/" \; + ;; + esac # List collected artifacts echo "Collected Maven artifacts:" - ls -la ${{ runner.workspace }}/maven-artifacts/ - shell: bash + ls -la "$ARTIFACTS_DIR/" - name: Upload Maven artifacts (Linux) if: ${{ inputs.maven_enabled == true }} diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml index 3f23c5194f8c..d5d41dc2899e 100644 --- a/.github/workflows/maven-deploy.yml +++ b/.github/workflows/maven-deploy.yml @@ -136,8 +136,8 @@ jobs: echo " Platform: $PLATFORM, Implementation: auto" fi - # Find main HDF5 JAR files (jarhdf5-*.jar) and examples JAR files (hdf5-java-examples-*.jar) - artifact_jars=$(find "$artifact_dir" \( -name "jarhdf5-*.jar" -o -name "hdf5-java-examples-*.jar" \) ! -name "*sources*" ! -name "*javadoc*" 2>/dev/null || true) + # Main Java JAR, examples, and native bundle JARs (libhdf5 / zlib / szip / JNI bridge) + artifact_jars=$(find "$artifact_dir" \( -name "jarhdf5-*.jar" -o -name "hdf5-java-examples-*.jar" -o -name "hdf5-native-*.jar" -o -name "hdf5-zlib-native-*.jar" -o -name "hdf5-szip-native-*.jar" -o -name "hdf5-jni-native-*.jar" \) ! -name "*sources*" ! -name "*javadoc*" 2>/dev/null || true) if [ -n "$artifact_jars" ]; then echo " Found HDF5 JARs:" @@ -303,6 +303,7 @@ jobs: env: HDF5_VERSION: ${{ needs.validate-artifacts.outputs.hdf5-version }} PLATFORM_CLASSIFIER: ${{ needs.validate-artifacts.outputs.platform-classifier }} + DEFAULT_POM_FILE: ${{ needs.validate-artifacts.outputs.pom-file }} run: | echo "=== Maven Deployment Debug Info ===" echo "Version: ${HDF5_VERSION}" @@ -364,6 +365,26 @@ jobs: CURRENT_CLASSIFIER="" # Examples JAR has no platform classifier classifier_opts="" echo "Artifact type: Java Examples (platform-independent)" + elif [[ "${jar_basename}" == hdf5-native-*.jar ]]; then + ARTIFACT_ID="hdf5-native" + CURRENT_CLASSIFIER="$platform" + classifier_opts="-Dclassifier=${CURRENT_CLASSIFIER}" + echo "Artifact type: HDF5 native shared library bundle" + elif [[ "${jar_basename}" == hdf5-jni-native-*.jar ]]; then + ARTIFACT_ID="hdf5-jni-native" + CURRENT_CLASSIFIER="$platform" + classifier_opts="-Dclassifier=${CURRENT_CLASSIFIER}" + echo "Artifact type: HDF5 JNI bridge native bundle" + elif [[ "${jar_basename}" == hdf5-zlib-native-*.jar ]]; then + ARTIFACT_ID="hdf5-zlib-native" + CURRENT_CLASSIFIER="$platform" + classifier_opts="-Dclassifier=${CURRENT_CLASSIFIER}" + echo "Artifact type: HDF5 zlib native shared library bundle" + elif [[ "${jar_basename}" == hdf5-szip-native-*.jar ]]; then + ARTIFACT_ID="hdf5-szip-native" + CURRENT_CLASSIFIER="$platform" + classifier_opts="-Dclassifier=${CURRENT_CLASSIFIER}" + echo "Artifact type: HDF5 szip (libaec) native shared library bundle" else # Main HDF5 Java library - determine implementation # Check JAR contents to detect FFM vs JNI @@ -400,10 +421,25 @@ jobs: fi fi + POM_FOR_DEPLOY="$DEFAULT_POM_FILE" + if [[ "${jar_basename}" == hdf5-native-*.jar ]]; then + NATIVE_POM="$(dirname "$jar_file")/pom-hdf5-native.xml" + [[ -f "$NATIVE_POM" ]] && POM_FOR_DEPLOY="$NATIVE_POM" + elif [[ "${jar_basename}" == hdf5-jni-native-*.jar ]]; then + JNI_NATIVE_POM="$(dirname "$jar_file")/pom-hdf5-jni-native.xml" + [[ -f "$JNI_NATIVE_POM" ]] && POM_FOR_DEPLOY="$JNI_NATIVE_POM" + elif [[ "${jar_basename}" == hdf5-zlib-native-*.jar ]]; then + ZLIB_NATIVE_POM="$(dirname "$jar_file")/pom-hdf5-zlib-native.xml" + [[ -f "$ZLIB_NATIVE_POM" ]] && POM_FOR_DEPLOY="$ZLIB_NATIVE_POM" + elif [[ "${jar_basename}" == hdf5-szip-native-*.jar ]]; then + SZIP_NATIVE_POM="$(dirname "$jar_file")/pom-hdf5-szip-native.xml" + [[ -f "$SZIP_NATIVE_POM" ]] && POM_FOR_DEPLOY="$SZIP_NATIVE_POM" + fi + # Check if this is a dry run if [ "${{ inputs.dry_run }}" == "true" ]; then echo "🧪 DRY RUN: Would deploy ${jar_basename} as ${ARTIFACT_ID} with classifier ${CURRENT_CLASSIFIER:-none}" - echo "Command would be: mvn deploy:deploy-file -DgroupId=org.hdfgroup -DartifactId=${ARTIFACT_ID} -Dversion=${HDF5_VERSION} -Dfile=${jar_file} ${classifier_opts}" + echo "Command would be: mvn deploy:deploy-file -DgroupId=org.hdfgroup -DartifactId=${ARTIFACT_ID} -Dversion=${HDF5_VERSION} -Dfile=${jar_file} -DpomFile=${POM_FOR_DEPLOY} ${classifier_opts}" success_count=$((success_count + 1)) else echo "🚀 Deploying ${jar_basename}..." @@ -414,7 +450,7 @@ jobs: -DartifactId=\"${ARTIFACT_ID}\" \ -Dversion=\"${HDF5_VERSION}\" \ -Dfile=\"${jar_file}\" \ - -DpomFile=\"${{ needs.validate-artifacts.outputs.pom-file }}\" \ + -DpomFile=\"${POM_FOR_DEPLOY}\" \ -DrepositoryId=\"${{ inputs.repository_id }}\" \ -Durl=\"${{ inputs.repository_url }}\" \ ${classifier_opts} \ diff --git a/.github/workflows/maven-staging.yml b/.github/workflows/maven-staging.yml index 9aa6ff2dc18e..4f9219aace43 100644 --- a/.github/workflows/maven-staging.yml +++ b/.github/workflows/maven-staging.yml @@ -6,12 +6,18 @@ on: branches: [ develop, main ] paths: - 'java/src/hdf/hdf5lib/**' + - 'java/hdf/hdf5lib/**' + - 'java/src-jni/**' + - 'java/cmake/**' + - 'java/lib/**' - 'HDF5Examples/JAVA/**' - '.github/workflows/maven-*.yml' - '.github/workflows/java-examples-*.yml' - 'CMakePresets.json' - '**/CMakeLists.txt' - 'java/src/hdf/hdf5lib/pom.xml.in' + - 'java/hdf/hdf5lib/pom.xml.in' + - 'java/src-jni/hdf/hdf5lib/pom.xml.in' - 'HDF5Examples/JAVA/pom-examples.xml.in' workflow_call: inputs: @@ -110,7 +116,7 @@ jobs: # For pull requests, check changed files if [ -n "${{ github.base_ref }}" ]; then git fetch origin ${{ github.base_ref }} - MAVEN_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E "(java/src/hdf/hdf5lib/|HDF5Examples/JAVA/|maven|pom\.xml|CMakePresets\.json)" || true) + MAVEN_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E "(java/src/hdf/hdf5lib/|java/hdf/hdf5lib/|java/src-jni/|java/cmake/|java/lib/|HDF5Examples/JAVA/|maven|pom\.xml|CMakePresets\.json)" || true) else # Fallback: assume changes if base_ref not available MAVEN_FILES="true" @@ -381,9 +387,12 @@ jobs: - name: Collect Maven artifacts shell: bash + env: + ARTIFACTS_DIR: ${{ runner.workspace }}/maven-artifacts + JAVA_IMPL: ${{ matrix.implementation }} run: | echo "Collecting Maven artifacts for testing..." - mkdir -p "${{ runner.workspace }}/maven-artifacts" + mkdir -p "$ARTIFACTS_DIR" BUILD_ROOT="${{ runner.workspace }}/build/${{ steps.set-preset.outputs.preset }}" echo "Looking for Maven artifacts in build root: $BUILD_ROOT" @@ -443,14 +452,14 @@ jobs: find "$BUILD_DIR" -name "*slf4j*.jar" -type f # Copy JAR files (excluding test and H5Ex_ example JARs) - find "$BUILD_DIR" -name "*.jar" -not -name "*test*" -not -name "*H5Ex_*" -exec cp {} "${{ runner.workspace }}/maven-artifacts/" \; + find "$BUILD_DIR" -name "*.jar" -not -name "*test*" -not -name "*H5Ex_*" -exec cp {} "$ARTIFACTS_DIR/" \; # Also look for Maven dependencies in common locations echo "Looking for Maven dependencies in additional locations..." # Check if there's a Maven repository in the build area if [ -d "$BUILD_ROOT" ]; then - find "$BUILD_ROOT" -name "*slf4j*.jar" -type f -exec cp {} "${{ runner.workspace }}/maven-artifacts/" \; + find "$BUILD_ROOT" -name "*slf4j*.jar" -type f -exec cp {} "$ARTIFACTS_DIR/" \; fi # Check common Maven local repository locations @@ -466,27 +475,57 @@ jobs: find "$repo_path" -name "slf4j-api*.jar" -o -name "slf4j-simple*.jar" 2>/dev/null | head -2 | while read jar_file; do if [ -f "$jar_file" ]; then echo "Found dependency: $jar_file" - cp "$jar_file" "${{ runner.workspace }}/maven-artifacts/" + cp "$jar_file" "$ARTIFACTS_DIR/" fi done fi done - # Copy POM files - find "$BUILD_DIR" -name "pom.xml" -exec cp {} "${{ runner.workspace }}/maven-artifacts/" \; + # Copy POM files from the Java tree for this matrix implementation only. A broad + # -name find plus flat cp would clobber: CMake emits pom.xml under either + # java/hdf/hdf5lib (FFM) or java/src-jni/hdf/hdf5lib (JNI), same basenames. + case "$JAVA_IMPL" in + ffm) + find "$BUILD_ROOT" \( \ + -path '*/java/hdf/hdf5lib/pom.xml' -o \ + -path '*/java/hdf/hdf5lib/pom-hdf5-native.xml' -o \ + -path '*/java/hdf/hdf5lib/pom-hdf5-zlib-native.xml' -o \ + -path '*/java/hdf/hdf5lib/pom-hdf5-szip-native.xml' -o \ + -path '*/java/hdf/hdf5lib/pom-hdf5-jni-native.xml' \ + \) -exec cp {} "$ARTIFACTS_DIR/" \; + ;; + jni|auto|*) + find "$BUILD_ROOT" \( \ + -path '*/java/src-jni/hdf/hdf5lib/pom.xml' -o \ + -path '*/java/src-jni/hdf/hdf5lib/pom-hdf5-native.xml' -o \ + -path '*/java/src-jni/hdf/hdf5lib/pom-hdf5-zlib-native.xml' -o \ + -path '*/java/src-jni/hdf/hdf5lib/pom-hdf5-szip-native.xml' -o \ + -path '*/java/src-jni/hdf/hdf5lib/pom-hdf5-jni-native.xml' \ + \) -exec cp {} "$ARTIFACTS_DIR/" \; + ;; + esac # List collected artifacts echo "Collected Maven artifacts:" - ls -la "${{ runner.workspace }}/maven-artifacts/" + ls -la "$ARTIFACTS_DIR/" - name: Validate artifacts id: artifacts-check shell: bash + env: + ARTIFACT_DIR: ${{ runner.workspace }}/maven-artifacts run: | - ARTIFACT_COUNT=$(find "${{ runner.workspace }}/maven-artifacts" -name "*.jar" | wc -l) - POM_COUNT=$(find "${{ runner.workspace }}/maven-artifacts" -name "pom.xml" | wc -l) + ARTIFACT_COUNT=$(find "$ARTIFACT_DIR" -name "*.jar" | wc -l | awk '{print $1}') + POM_COUNT=$(find "$ARTIFACT_DIR" \( -name "pom.xml" -o -name "pom-hdf5-native.xml" -o -name "pom-hdf5-zlib-native.xml" -o -name "pom-hdf5-szip-native.xml" -o -name "pom-hdf5-jni-native.xml" \) | wc -l | awk '{print $1}') + MAIN_POM_COUNT=$(find "$ARTIFACT_DIR" -name "pom.xml" | wc -l | awk '{print $1}') + + echo "Found $ARTIFACT_COUNT JAR files and $POM_COUNT POM files (pom.xml count: $MAIN_POM_COUNT)" - echo "Found $ARTIFACT_COUNT JAR files and $POM_COUNT POM files" + if [ "$MAIN_POM_COUNT" -ne 1 ]; then + echo "❌ Expected exactly one pom.xml in maven-artifacts (flat layout); found $MAIN_POM_COUNT" + find "$ARTIFACT_DIR" -name "pom.xml" -print || true + exit 1 + fi if [ $ARTIFACT_COUNT -gt 0 ] && [ $POM_COUNT -gt 0 ]; then echo "created=true" >> $GITHUB_OUTPUT @@ -509,12 +548,11 @@ jobs: VERIFICATION_FAILED=false IMPL="${{ matrix.implementation }}" - # Find the main HDF5 JAR (not slf4j dependencies) - HDF5_JAR=$(find "$ARTIFACT_DIR" -name "*hdf5*.jar" ! -name "*sources*" ! -name "*javadoc*" ! -name "*slf4j*" | head -1) + # Main Java API JAR (exclude native bundles hdf5-native-*, hdf5-zlib-native-*, hdf5-jni-native-*) + HDF5_JAR=$(find "$ARTIFACT_DIR" -name "jarhdf5*.jar" ! -name "*sources*" ! -name "*javadoc*" | head -1) if [ -z "$HDF5_JAR" ]; then - # Try alternative naming (jarhdf5) - HDF5_JAR=$(find "$ARTIFACT_DIR" -name "jarhdf5*.jar" ! -name "*sources*" ! -name "*javadoc*" | head -1) + HDF5_JAR=$(find "$ARTIFACT_DIR" -name "*hdf5*.jar" ! -name "*sources*" ! -name "*javadoc*" ! -name "*slf4j*" ! -name "hdf5-native-*" ! -name "hdf5-zlib-native-*" ! -name "hdf5-szip-native-*" ! -name "hdf5-jni-native-*" | head -1) fi if [ -z "$HDF5_JAR" ]; then @@ -600,6 +638,34 @@ jobs: TOTAL_FILES=$(jar tf "$HDF5_JAR" | wc -l) echo "✅ Total files in JAR: $TOTAL_FILES" + ZLIB_NATIVE_JAR=$(find "$ARTIFACT_DIR" -name "hdf5-zlib-native-*.jar" | head -1) + if [ -z "$ZLIB_NATIVE_JAR" ]; then + echo "::error::Could not find hdf5-zlib-native bundle" + VERIFICATION_FAILED=true + elif jar tf "$ZLIB_NATIVE_JAR" | grep -Eq 'natives/.*/(libz\.so|libz\.so\.[0-9]+|libz\.dylib|z\.dll|zlib\.dll|zlib1\.dll)'; then + echo "✅ hdf5-zlib-native contains SciJava zlib library layout" + else + echo "::error::hdf5-zlib-native is missing mapped zlib library under natives//" + jar tf "$ZLIB_NATIVE_JAR" | head -30 2>/dev/null || true + VERIFICATION_FAILED=true + fi + + SZIP_NATIVE_JAR=$(find "$ARTIFACT_DIR" -name "hdf5-szip-native-*.jar" | head -1) + if [ -z "$SZIP_NATIVE_JAR" ]; then + echo "::error::Could not find hdf5-szip-native bundle" + VERIFICATION_FAILED=true + else + HAS_LIBSZ=$(jar tf "$SZIP_NATIVE_JAR" | grep -E 'natives/.*/(libsz\.so|libsz\.dylib|sz\.dll)' || true) + HAS_LIBAEC=$(jar tf "$SZIP_NATIVE_JAR" | grep -E 'natives/.*/(libaec\.so|libaec\.dylib|aec\.dll)' || true) + if [ -n "$HAS_LIBSZ" ] && [ -n "$HAS_LIBAEC" ]; then + echo "✅ hdf5-szip-native contains SciJava libaec/libsz library layout" + else + echo "::error::hdf5-szip-native is missing mapped libaec/libsz under natives//" + jar tf "$SZIP_NATIVE_JAR" | head -30 2>/dev/null || true + VERIFICATION_FAILED=true + fi + fi + echo "::endgroup::" if [ "$VERIFICATION_FAILED" = true ]; then diff --git a/.gitignore b/.gitignore index facf067ae733..0df67e0ce997 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # Other files, most of which are created by scripts in bin/ /build* src/H5config.h.in +target/ /.classpath .claude/ diff --git a/java/jtest/CMakeLists.txt b/java/jtest/CMakeLists.txt index ca3b0a16be31..8e494e744ab1 100644 --- a/java/jtest/CMakeLists.txt +++ b/java/jtest/CMakeLists.txt @@ -58,11 +58,12 @@ set (HDF5_JAVA_JTEST_FFM_TEST_SOURCES TestH5VLffm TestH5PLffm TestH5Zffm + TestH5DeflateNativeffm TestH5FDffm ) # Update classpath to include FfmTestSupport JAR for test compilation -set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVAHDF5_JARS};${${HDF5_JAVA_JTEST_LIB_TARGET}_FfmTestSupport_JAR_FILE};${HDF5_JAVA_JUNIT_JAR};${HDF5_JAVA_HAMCREST_JAR};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_LOGGING_SIMPLE_JAR}") +set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVAHDF5_JARS};${${HDF5_JAVA_JTEST_LIB_TARGET}_FfmTestSupport_JAR_FILE};${HDF5_JAVA_JUNIT_JAR};${HDF5_JAVA_HAMCREST_JAR};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_LOGGING_SIMPLE_JAR};${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}") foreach (ffm_test_file ${HDF5_JAVA_JTEST_FFM_TEST_SOURCES}) @@ -90,7 +91,7 @@ Enable-Native-Access: ALL-UNNAMED endforeach () # Restore classpath for other tests -set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVAHDF5_JARS};${HDF5_JAVA_JUNIT_JAR};${HDF5_JAVA_HAMCREST_JAR};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_LOGGING_SIMPLE_JAR}") +set (CMAKE_JAVA_INCLUDE_PATH "${HDF5_JAVAHDF5_JARS};${HDF5_JAVA_JUNIT_JAR};${HDF5_JAVA_HAMCREST_JAR};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_LOGGING_SIMPLE_JAR};${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}") HDFTEST_COPY_FILE("${PROJECT_SOURCE_DIR}/h5ex_g_iterate.orig" "${PROJECT_BINARY_DIR}/h5ex_g_iterate.hdf" "${HDF5_JAVA_JTEST_LIB_TARGET}_files") HDFTEST_COPY_FILE("${PROJECT_SOURCE_DIR}/h5ex_g_iterate.orig" "${PROJECT_BINARY_DIR}/h5ex_g_iterateL1.hdf" "${HDF5_JAVA_JTEST_LIB_TARGET}_files") diff --git a/java/jtest/TestH5DeflateNativeffm.java b/java/jtest/TestH5DeflateNativeffm.java new file mode 100644 index 000000000000..0c20072d37ee --- /dev/null +++ b/java/jtest/TestH5DeflateNativeffm.java @@ -0,0 +1,155 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright by The HDF Group. * + * All rights reserved. * + * * + * This file is part of HDF5. The full HDF5 copyright notice, including * + * terms governing use, modification, and redistribution, is contained in * + * the LICENSE file, which can be found at the root of the source code * + * distribution tree, or in https://www.hdfgroup.org/licenses. * + * If you do not have access to either file, you may request a copy from * + * help@hdfgroup.org. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +package jtest; + +import static org.junit.Assert.*; + +import static jtest.FfmTestSupport.*; + +import java.io.File; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +import hdf.hdf5lib.Hdf5NativeLoader; + +import org.hdfgroup.javahdf5.hdf5_h; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +/** + * FFM smoke tests for Maven-shipped native stacks: {@link Hdf5NativeLoader} must load bundled zlib + * before libhdf5 so the built-in deflate (gzip) filter is registered, and chunked+gzip datasets + * must round-trip through the FFM API. + */ +public class TestH5DeflateNativeffm { + @Rule + public TestName testname = new TestName(); + + private static final String H5_FILE = "testDeflateNativeffm.h5"; + private static final String DATASET_NAME = "gzip_chunked_dset"; + + @Before + public void setup() + { + System.out.print(testname.getMethodName()); + + // Same native bootstrap path Maven consumers use (bundled zlib/szip then libhdf5). + Hdf5NativeLoader.loadBundledDependenciesBeforeHdf5(); + hdf5_h.H5open(); + } + + @After + public void cleanup() + { + deleteFile(H5_FILE); + System.out.println(); + } + + private static void deleteFile(String filename) + { + File file = new File(filename); + if (file.exists()) { + try { + file.delete(); + } + catch (SecurityException e) { + // ignore + } + } + } + + @Test + public void testBundledLoaderAndDeflateFilterAvail() + { + int deflateAvail = hdf5_h.H5Zfilter_avail(hdf5_h.H5Z_FILTER_DEFLATE()); + assertTrue("DEFLATE filter must be available when libhdf5 is built with zlib support " + + "(bundledZlib=" + Hdf5NativeLoader.bundledZlibLoadSucceeded() + ")", + deflateAvail > 0); + } + + @Test + public void testChunkedGzipDatasetRoundTrip() + { + assertTrue("DEFLATE filter required for this test", + hdf5_h.H5Zfilter_avail(hdf5_h.H5Z_FILTER_DEFLATE()) > 0); + + final int dimX = 8; + final int dimY = 12; + final int rank = 2; + final long[] chunkDims = {4, 6}; + + try (Arena arena = Arena.ofConfined()) { + MemorySegment fileNameSegment = stringToSegment(arena, H5_FILE); + long fid = hdf5_h.H5Fcreate(fileNameSegment, hdf5_h.H5F_ACC_TRUNC(), hdf5_h.H5P_DEFAULT(), + hdf5_h.H5P_DEFAULT()); + assertTrue("H5Fcreate failed", isValidId(fid)); + + long dcpl = hdf5_h.H5Pcreate(hdf5_h.H5P_CLS_DATASET_CREATE_ID_g()); + assertTrue("H5Pcreate dcpl failed", isValidId(dcpl)); + + MemorySegment chunkDimsSegment = allocateLongArray(arena, rank); + copyToSegment(chunkDimsSegment, chunkDims); + assertTrue("H5Pset_chunk failed", isSuccess(hdf5_h.H5Pset_chunk(dcpl, rank, chunkDimsSegment))); + assertTrue("H5Pset_deflate failed", isSuccess(hdf5_h.H5Pset_deflate(dcpl, 6))); + + long[] dims = {dimX, dimY}; + MemorySegment dimsSegment = allocateLongArray(arena, rank); + copyToSegment(dimsSegment, dims); + long sid = hdf5_h.H5Screate_simple(rank, dimsSegment, MemorySegment.NULL); + assertTrue("H5Screate_simple failed", isValidId(sid)); + + MemorySegment dsetNameSegment = stringToSegment(arena, DATASET_NAME); + long did = hdf5_h.H5Dcreate2(fid, dsetNameSegment, hdf5_h.H5T_NATIVE_INT_g(), sid, + hdf5_h.H5P_DEFAULT(), dcpl, hdf5_h.H5P_DEFAULT()); + assertTrue("H5Dcreate2 failed", isValidId(did)); + + int[] writeData = new int[dimX * dimY]; + for (int i = 0; i < writeData.length; i++) { + writeData[i] = i; + } + MemorySegment writeSegment = allocateIntArray(arena, writeData.length); + copyToSegment(writeSegment, writeData); + assertTrue("H5Dwrite failed", + isSuccess(hdf5_h.H5Dwrite(did, hdf5_h.H5T_NATIVE_INT_g(), hdf5_h.H5S_ALL(), + hdf5_h.H5S_ALL(), hdf5_h.H5P_DEFAULT(), writeSegment))); + + hdf5_h.H5Fflush(fid, hdf5_h.H5F_SCOPE_LOCAL()); + closeQuietly(did, hdf5_h::H5Dclose); + closeQuietly(sid, hdf5_h::H5Sclose); + closeQuietly(dcpl, hdf5_h::H5Pclose); + closeQuietly(fid, hdf5_h::H5Fclose); + + // Reopen and read through the gzip chunk pipeline (decode path). + fid = hdf5_h.H5Fopen(fileNameSegment, hdf5_h.H5F_ACC_RDONLY(), hdf5_h.H5P_DEFAULT()); + assertTrue("H5Fopen failed", isValidId(fid)); + + did = hdf5_h.H5Dopen2(fid, dsetNameSegment, hdf5_h.H5P_DEFAULT()); + assertTrue("H5Dopen2 failed", isValidId(did)); + + int[] readData = new int[writeData.length]; + MemorySegment readSegment = allocateIntArray(arena, readData.length); + assertTrue("H5Dread failed", + isSuccess(hdf5_h.H5Dread(did, hdf5_h.H5T_NATIVE_INT_g(), hdf5_h.H5S_ALL(), + hdf5_h.H5S_ALL(), hdf5_h.H5P_DEFAULT(), readSegment))); + copyFromSegment(readSegment, readData); + assertArrayEquals("gzip chunked dataset round-trip", writeData, readData); + + closeQuietly(did, hdf5_h::H5Dclose); + closeQuietly(fid, hdf5_h::H5Fclose); + } + } +} From 3a5f7af02d42688099bdad03f440bafd7fbbbb8d Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Wed, 10 Jun 2026 00:09:09 +0200 Subject: [PATCH 12/16] docs: document native Maven artifacts for Java consumers Describe optional libhdf5-stack artifacts, required hdf5-jni-native on java-jni, classpath-only vs system-native consumer paths, and runtime init contract. Co-authored-by: Cursor --- docs/INSTALL_CMake.md | 18 ++++++++++-- docs/doxygen/dox/cookbook/MavenArtifacts.dox | 30 +++++++++++++++++++- release_docs/CHANGELOG.md | 1 + release_docs/RELEASE_PROCESS.md | 4 ++- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/docs/INSTALL_CMake.md b/docs/INSTALL_CMake.md index 0fd7b1f777a7..90785a7dbddf 100644 --- a/docs/INSTALL_CMake.md +++ b/docs/INSTALL_CMake.md @@ -686,10 +686,24 @@ HDF5 Java bindings support two native interface implementations: Maven Artifacts: -- `org.hdfgroup:hdf5-java-ffm` is FFM implementation +- `org.hdfgroup:hdf5-native` (classified by platform, e.g. `linux-x86_64`) bundles the `libhdf5` shared library built by CMake +- `org.hdfgroup:hdf5-zlib-native` (same classifiers) bundles zlib when `libhdf5` is built with dynamic zlib support for the built-in deflate/GZIP filter +- `org.hdfgroup:hdf5-szip-native` (same classifiers) bundles libaec (`libaec` + `libsz`) when `libhdf5` is built with dynamic SZIP support for the built-in SZIP filter +- `org.hdfgroup:hdf5-jni-native` (same classifiers) bundles only the JNI bridge (`hdf5_java`); use with `hdf5-java-jni` - `org.hdfgroup:hdf5-java-jni` is JNI implementation +- `org.hdfgroup:hdf5-java-ffm` is FFM implementation + +When `HDF5_ENABLE_MAVEN_DEPLOY` is on, CMake produces `hdf5-native-*.jar`, `hdf5-zlib-native-*.jar` when shared zlib is enabled, `hdf5-szip-native-*.jar` when shared libaec (szip) is enabled, and (JNI only) `hdf5-jni-native-*.jar` with `COMPONENT maven`. Native artifacts place shared libraries under `natives//` for [SciJava native-lib-loader](https://github.com/scijava/native-lib-loader) (BSD 2-clause), which is a declared dependency of `hdf5-java-ffm` / `hdf5-java-jni` alongside SLF4J. + +**Maven dependency layering:** `hdf5-java-jni` declares a **required** (transitive) dependency on `hdf5-jni-native`. The libhdf5 stack (`hdf5-native`, `hdf5-zlib-native`, `hdf5-szip-native`) is **optional** on the binding POMs — Maven consumers get Java bindings only by default and must add native artifacts explicitly for classpath-only deployments, or use a system HDF5 install (`LD_LIBRARY_PATH` / `java.library.path`, or `-Dhdf.hdf5lib.NativeLibraryBootstrap.skip=true`). At runtime, when native JARs are present, `hdf.hdf5lib.Hdf5NativeLoader` loads bundled zlib first, then bundled libaec/szip, then HDF5, then the JNI bridge when needed. + +Bundled native JARs contain HDF Group–built binaries only. If the C library links other vendors’ DLLs or `.so` files dynamically (for example zlib on some Windows builds), redistribution of those binaries may require separate license compliance. The MSVC Universal C Runtime is **not** bundled; Windows consumers may need the Visual C++ Redistributable installed. + +Transitive native dependencies that are **not** built-in filters (for example **dynamically loaded H5PL filter plugins** the HDF5 build links against or loads at runtime) are **not** packaged inside `hdf5-native` or `hdf5-jni-native` JARs. The built-in deflate/GZIP and SZIP filters are special-cased: when the corresponding shared libraries are part of the build they are published as `hdf5-zlib-native` / `hdf5-szip-native` so those filters work in classpath-only deployments. Other native dependencies still need to be resolved by the OS (system install, `LD_LIBRARY_PATH` / `PATH`, or an HDF5 build that links them statically). + +**Runtime init contract (Maven / classpath consumers):** call `hdf.hdf5lib.H5.loadH5Lib()` (or use any API that triggers it, such as the static initializer on `hdf.hdf5lib.H5`). When bundled native JARs are on the classpath, that loads zlib, then libaec/szip when present, then libhdf5. Add `hdf5-native` (+ `hdf5-zlib-native` / `hdf5-szip-native` when needed) explicitly to your Maven POM for classpath-only deployments; `hdf5-jni-native` is pulled in transitively via `hdf5-java-jni`. You do **not** need `HDF5_PLUGIN_PATH`, `HDF5_PLUGIN_PRELOAD`, or a system HDF5 install for the built-in deflate/GZIP filter when using artifacts built with the `ci-Maven-Zlib` preset (zlib enabled, shared). After `H5.loadH5Lib()`, `H5.H5Zfilter_avail(HDF5Constants.H5Z_FILTER_DEFLATE)` must be non-zero and chunked+gzip datasets are readable. Set `-Dhdf.hdf5lib.NativeLibraryBootstrap.skip=true` only when supplying your own native libraries. -Both implementations use the same `hdf.hdf5lib.*` package structure for seamless migration. +Both Java implementations use the same `hdf.hdf5lib.*` package structure for seamless migration. To build HDF5 with Maven deployment support: diff --git a/docs/doxygen/dox/cookbook/MavenArtifacts.dox b/docs/doxygen/dox/cookbook/MavenArtifacts.dox index 9445b517a092..f518fc29cad2 100644 --- a/docs/doxygen/dox/cookbook/MavenArtifacts.dox +++ b/docs/doxygen/dox/cookbook/MavenArtifacts.dox @@ -15,7 +15,7 @@ platform and Java version. \subsection subsec_maven_artifacts Maven Artifact Selection -HDF5 provides three main Maven artifacts: +HDF5 provides Java API artifacts and platform-specific native bundles: @@ -31,6 +31,30 @@ HDF5 provides three main Maven artifacts: + + + + + + + + + + + + + + + + + + + + + + + + @@ -39,6 +63,10 @@ HDF5 provides three main Maven artifacts:
Artifact IDJava VersionDescriptionStatus
FFM implementation (future default) Production-ready
hdf5-nativeJava 8+Platform native libhdf5 bundle loaded via SciJava native-lib-loaderOptional; add explicitly for bundled libhdf5
hdf5-zlib-nativeJava 8+Platform zlib bundle used by libhdf5's built-in deflate/GZIP filterOptional; add explicitly when shared zlib is enabled
hdf5-szip-nativeJava 8+Platform libaec (libsz + libaec) bundle used by libhdf5's built-in SZIP filterOptional; add explicitly when shared libaec (szip) is enabled
hdf5-jni-nativeJava 8+Platform JNI bridge bundle for hdf5-java-jniRequired transitive dependency of hdf5-java-jni
hdf5-java-examples Java 8+
+The HDF5 deflate filter is exposed through APIs such as H5Pset_deflate and through tools as GZIP=level. It is built into libhdf5 when zlib support is enabled; hdf5-zlib-native ships the runtime zlib library needed by that built-in filter when libhdf5 links zlib dynamically. Call H5.loadH5Lib() once at startup; bundled zlib is loaded automatically before libhdf5. No HDF5_PLUGIN_PATH or external HDF5 installation is required for gzip/chunked datasets when using these Maven artifacts. + +The HDF5 SZIP filter is exposed through H5Pset_szip and uses the libaec szip-compatible API. It is built into libhdf5 when SZIP support is enabled; hdf5-szip-native ships the runtime libaec/libsz shared libraries needed by that built-in filter when libhdf5 links libaec dynamically. + Note: FFM will become the default implementation in the next major HDF5 release. \subsection subsec_maven_jni Using JNI Implementation (Java 8+) diff --git a/release_docs/CHANGELOG.md b/release_docs/CHANGELOG.md index c9d5ac47ad65..271cc9b2c854 100644 --- a/release_docs/CHANGELOG.md +++ b/release_docs/CHANGELOG.md @@ -43,6 +43,7 @@ For releases prior to version 2.0.0, please see the release.txt file and for mor ## Java Enhancements: - Java dependency JAR paths are now configurable CMake cache variables, allowing system-provided JARs to be used in place of the bundled copies. +- Maven native artifacts (`hdf5-native`, `hdf5-zlib-native`, `hdf5-szip-native`) built with the Maven CI presets now ship shared zlib and libaec so the built-in deflate/GZIP and SZIP filters work from the classpath via `H5.loadH5Lib()` without `HDF5_PLUGIN_PATH` or a system HDF5 install. `hdf5-zlib-native` includes common zlib library name aliases on each platform (for example `z.dll`, `zlib.dll`, and `zlib1.dll` on Windows). ## Acknowledgements: diff --git a/release_docs/RELEASE_PROCESS.md b/release_docs/RELEASE_PROCESS.md index 132e2a78f5f5..f00fec171ab8 100644 --- a/release_docs/RELEASE_PROCESS.md +++ b/release_docs/RELEASE_PROCESS.md @@ -194,7 +194,9 @@ For more information on the HDF5 versioning and backward and forward compatibili - **Java Examples Testing:** Comprehensive validation of Java examples (org.hdfgroup:hdf5-java-examples) across all platforms with Maven artifacts - **Deployment Process:** - `maven-staging.yml` workflow generates artifacts for all platforms - - `maven-deploy.yml` workflow deploys filtered main HDF5 JARs (jarhdf5-*.jar) only + - `maven-deploy.yml` workflow deploys `jarhdf5-*.jar`, native bundles (`hdf5-native-*.jar`, `hdf5-jni-native-*.jar` when applicable), and `hdf5-java-examples-*.jar`. + - Pair each artifact with its POM: `jarhdf5-*.jar` and `hdf5-java-examples-*.jar` use `pom.xml` from the artifact directory; `hdf5-native-*.jar` uses `pom-hdf5-native.xml` beside the JAR; `hdf5-jni-native-*.jar` (when present) uses `pom-hdf5-jni-native.xml` beside the JAR. + - In release notes, mention third-party shared-library redistribution when the build ships dynamic zlib (or similar) alongside `libhdf5`. - Monitor both workflows for successful completion - **Troubleshooting:** Check debug output in workflow logs for permission or authentication issues - **Go-Live:** After successful dry run testing, set `dry_run: false` in `.github/workflows/release.yml` From a830fa3f0fc3fa87d868c5d89ba0e73d12404e1d Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Tue, 23 Jun 2026 14:40:47 +0200 Subject: [PATCH 13/16] build/java: add shared CMake module for Java javadoc generation Centralize SciJava -link URL, native-lib-loader classpath handling, and hdf5_java_doc target creation for FFM and JNI binding trees. Co-authored-by: Cursor --- java/cmake/HDF5JavaJavadoc.cmake | 88 ++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 java/cmake/HDF5JavaJavadoc.cmake diff --git a/java/cmake/HDF5JavaJavadoc.cmake b/java/cmake/HDF5JavaJavadoc.cmake new file mode 100644 index 000000000000..6d2b5dd3e394 --- /dev/null +++ b/java/cmake/HDF5JavaJavadoc.cmake @@ -0,0 +1,88 @@ +#----------------------------------------------------------------------------- +# Shared Java javadoc generation for hdf.hdf5lib bindings. +# Used by FFM and JNI CMakeLists.txt when HDF5_BUILD_DOC is enabled. +#----------------------------------------------------------------------------- + +# Version aligned with java/lib/native-lib-loader-2.5.0.jar and pom.xml.in +set (HDF5_JAVA_NATIVE_LIB_LOADER_JAVADOC_VERSION "2.5.0") +set (HDF5_JAVA_NATIVE_LIB_LOADER_JAVADOC_LINK + "https://javadoc.io/doc/org.scijava/native-lib-loader/${HDF5_JAVA_NATIVE_LIB_LOADER_JAVADOC_VERSION}/" +) + +function (hdf5_java_add_javadoc_target CLASSPATH_JARS) + if (NOT HDF5_BUILD_DOC) + return () + endif () + if (TARGET hdf5_java_doc) + return () + endif () + + if (NOT Java_JAVADOC_EXECUTABLE) + find_package (Java COMPONENTS Development) + if (NOT Java_JAVADOC_EXECUTABLE) + message (WARNING "Java javadoc executable not found; skipping hdf5_java_doc target") + return () + endif () + endif () + + set (_javadoc_sources + ${HDF5_JAVADOC_HDF_HDF5_CALLBACKS_SOURCES} + ${HDF5_JAVADOC_HDF_HDF5_EXCEPTIONS_SOURCES} + ${HDF5_JAVADOC_HDF_HDF5_STRUCTS_SOURCES} + ${HDF5_JAVADOC_HDF_HDF5_SOURCES} + ) + + # Callers pass the classpath jars as a CMake list (";"-separated). javadoc + # expects the platform-native path separator, which is ":" on Unix-like hosts + # and ";" on Windows. Normalize so the jars resolve on every platform. + if (CMAKE_HOST_UNIX) + string (REPLACE ";" ":" _javadoc_classpath "${CLASSPATH_JARS}") + else () + set (_javadoc_classpath "${CLASSPATH_JARS}") + endif () + + set (_javadoc_builddir "${CMAKE_CURRENT_BINARY_DIR}/javadoc/hdf5_java_doc") + set (_javadoc_stamp "${_javadoc_builddir}/.javadoc.stamp") + + set (_javadoc_depends + ${_javadoc_sources} + ${HDF5_JAVA_LOGGING_JAR} + ${HDF5_JAVA_NATIVE_LIB_LOADER_JAR} + ) + if (HDF5_JAVAHDF5_JARS) + list (APPEND _javadoc_depends ${HDF5_JAVAHDF5_JARS}) + endif () + + add_custom_command ( + OUTPUT "${_javadoc_stamp}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${_javadoc_builddir}" + COMMAND ${Java_JAVADOC_EXECUTABLE} + -d "${_javadoc_builddir}" + -classpath "${_javadoc_classpath}" + -link "${HDF5_JAVA_NATIVE_LIB_LOADER_JAVADOC_LINK}" + -tag "defgroup:a:Group:" + -tag "ingroup:a:Group:" + -tag "ref:a:See:" + -overview "${HDF5_SOURCE_DIR}/java/src-jni/hdf/overview.html" + -windowtitle "HDF5 Java" + -doctitle "

HDF5 Java Wrapper

" + -author + -use + -version + ${_javadoc_sources} + COMMAND ${CMAKE_COMMAND} -E touch "${_javadoc_stamp}" + DEPENDS ${_javadoc_depends} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Generating HDF5 Java API documentation with javadoc" + VERBATIM + ) + + add_custom_target (hdf5_java_doc DEPENDS "${_javadoc_stamp}") + set_target_properties (hdf5_java_doc PROPERTIES FOLDER documentation) + + install ( + DIRECTORY "${_javadoc_builddir}/" + DESTINATION "${HDF5_INSTALL_DOC_DIR}/java" + COMPONENT hdfdocuments + ) +endfunction () From cc89375ac801132ae610f45906af4c387678d995 Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Tue, 23 Jun 2026 14:41:09 +0200 Subject: [PATCH 14/16] build/java: enable javadoc target for FFM hdf.hdf5lib bindings Wire HDF5JavaJavadoc.cmake with the FFM compile classpath so javadoc can resolve SciJava {@link} references in Hdf5NativeLoader. Co-authored-by: Cursor --- java/hdf/hdf5lib/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/java/hdf/hdf5lib/CMakeLists.txt b/java/hdf/hdf5lib/CMakeLists.txt index c6295657204d..382c95088765 100644 --- a/java/hdf/hdf5lib/CMakeLists.txt +++ b/java/hdf/hdf5lib/CMakeLists.txt @@ -44,7 +44,7 @@ set (HDF5_JAVA_HDF_HDF5_CALLBACKS_SOURCES callbacks/H5P_iterate_t.java ) -set (HDF5_JAVADOC_HDFH5I_INVALID_HID_HDF5_CALLBACKS_SOURCES +set (HDF5_JAVADOC_HDF_HDF5_CALLBACKS_SOURCES ${HDF5_JAVA_HDF_HDF5_CALLBACKS_SOURCES} callbacks/package-info.java ) @@ -318,5 +318,10 @@ if (HDF5_ENABLE_FORMATTERS) clang_format (HDF5_JAVA_SRC_FORMAT ${HDF5_JAVA_HDF_HDF5_CALLBACKS_SOURCES} ${HDF5_JAVA_HDF_HDF5_EXCEPTIONS_SOURCES} ${HDF5_JAVA_HDF_HDF5_STRUCTS_SOURCES} ${HDF5_JAVA_HDF_HDF5_SOURCES}) endif () +include (${HDF5_SOURCE_DIR}/java/cmake/HDF5JavaJavadoc.cmake) +hdf5_java_add_javadoc_target ( + "${HDF5_JAVAHDF5_JARS};${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}" +) + set (CMAKE_JAVA_INCLUDE_PATH "") From 8c824904dbdc2d4bd7dc39c3a28064ca4b88c18b Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Tue, 23 Jun 2026 14:41:32 +0200 Subject: [PATCH 15/16] build/java: enable javadoc target for JNI hdf.hdf5lib bindings Wire HDF5JavaJavadoc.cmake with the JNI classpath so javadoc resolves SciJava cross-references when building the JNI binding tree. Co-authored-by: Cursor --- java/src-jni/hdf/hdf5lib/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/java/src-jni/hdf/hdf5lib/CMakeLists.txt b/java/src-jni/hdf/hdf5lib/CMakeLists.txt index adab44c99f0a..5b34378f3d7f 100644 --- a/java/src-jni/hdf/hdf5lib/CMakeLists.txt +++ b/java/src-jni/hdf/hdf5lib/CMakeLists.txt @@ -268,5 +268,10 @@ if (HDF5_ENABLE_FORMATTERS) clang_format (HDF5_JAVA_SRC_FORMAT ${HDF5_JAVA_HDF_HDF5_CALLBACKS_SOURCES} ${HDF5_JAVA_HDF_HDF5_EXCEPTIONS_SOURCES} ${HDF5_JAVA_HDF_HDF5_STRUCTS_SOURCES} ${HDF5_JAVA_HDF_HDF5_SOURCES}) endif () +include (${HDF5_SOURCE_DIR}/java/cmake/HDF5JavaJavadoc.cmake) +hdf5_java_add_javadoc_target ( + "${HDF5_JAVA_LOGGING_JAR};${HDF5_JAVA_NATIVE_LIB_LOADER_JAR}" +) + set (CMAKE_JAVA_INCLUDE_PATH "") From 7815c86ea339cd2536a899c4cd5a305d79119cee Mon Sep 17 00:00:00 2001 From: Matteo Di Giovinazzo Date: Tue, 23 Jun 2026 14:41:34 +0200 Subject: [PATCH 16/16] build/doc: depend hdf5lib_doc on Java javadoc when Java is enabled Ensure doc builds with HDF5_BUILD_JAVA and HDF5_BUILD_DOC also generate hdf5_java_doc before the Doxygen hdf5lib_doc stamp target. Co-authored-by: Cursor --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22338eb467d5..6654b5515e97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1491,6 +1491,10 @@ if (HDF5_BUILD_DOC AND DOXYGEN_FOUND) ) add_dependencies (doxygen hdf5lib_doc) + if (HDF5_BUILD_JAVA AND TARGET hdf5_java_doc) + add_dependencies (hdf5lib_doc hdf5_java_doc) + endif () + endif () # Always define the umbrella doxygen target so that scripts and IDEs can