diff --git a/.github/actions/setup-jextract/action.yml b/.github/actions/setup-jextract/action.yml index c3dba35d19d..360d69862b4 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)" @@ -79,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..." + + if ($env:INPUT_JAVA_VERSION -ne "25") { + Write-Host "ERROR: Only jextract 25 is supported (requested: $env:INPUT_JAVA_VERSION)" + exit 1 + } - # 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" - ) + # 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" - $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: $_" - } + 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 + + # 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 + } + + $JextractRoot = $JextractBat.Directory.Parent.FullName + Get-ChildItem -Path $JextractRoot | ForEach-Object { + Move-Item -Path $_.FullName -Destination $JextractHome -Force } - 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" + # 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 } @@ -150,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 diff --git a/.github/scripts/install-hdf5-maven-local.sh b/.github/scripts/install-hdf5-maven-local.sh new file mode 100755 index 00000000000..7f9fef31c3c --- /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 0117c6445ac..aa25ca5932d 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 76ed526df6e..f071939ffe0 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/scripts/verify-vendored-java-lib-jars.sh b/.github/scripts/verify-vendored-java-lib-jars.sh new file mode 100755 index 00000000000..16b669e6e06 --- /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/ctest.yml b/.github/workflows/ctest.yml index 04b6e67517b..f76fbec6d90 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 3f23c5194f8..d5d41dc2899 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 9aa6ff2dc18..4f9219aace4 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/.github/workflows/verify-vendored-java-lib.yml b/.github/workflows/verify-vendored-java-lib.yml new file mode 100644 index 00000000000..a6108de4c3e --- /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/.gitignore b/.gitignore index facf067ae73..0df67e0ce99 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/CMakeLists.txt b/CMakeLists.txt index 5cb58250215..6654b5515e9 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) @@ -1379,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 () @@ -1490,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 diff --git a/CMakePresets.json b/CMakePresets.json index fb4609cf44a..0a3037a7438 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" ] }, diff --git a/config/cmake/HDF5ExampleCache.cmake b/config/cmake/HDF5ExampleCache.cmake index 3129cd5dd15..8384d1f40f1 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/docs/INSTALL_CMake.md b/docs/INSTALL_CMake.md index 0fd7b1f777a..90785a7dbdd 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 9445b517a09..f518fc29cad 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/java/CMakeLists.txt b/java/CMakeLists.txt index 615914b19a6..dc69282a97d 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/HDF5JavaJavadoc.cmake b/java/cmake/HDF5JavaJavadoc.cmake new file mode 100644 index 00000000000..6d2b5dd3e39 --- /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 () diff --git a/java/cmake/HDF5JavaNativeBundles.cmake b/java/cmake/HDF5JavaNativeBundles.cmake new file mode 100644 index 00000000000..cb09b02ec1c --- /dev/null +++ b/java/cmake/HDF5JavaNativeBundles.cmake @@ -0,0 +1,396 @@ +#----------------------------------------------------------------------------- +# 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 () + +# 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} +) + +# 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 () + +# 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 () 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 00000000000..0f10b9fe436 --- /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 00000000000..777a740cdbf --- /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 00000000000..0fa931ab1c6 --- /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/cmake/pom-jni-native.xml.in b/java/cmake/pom-jni-native.xml.in new file mode 100644 index 00000000000..f57aaa5c086 --- /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 00000000000..de991f1f6c0 --- /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 00000000000..fd7cd14917e --- /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 00000000000..3acb808f27d --- /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 ac7d34cb7ef..382c9508876 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 ) @@ -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 @@ -313,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 "") diff --git a/java/hdf/hdf5lib/H5.java b/java/hdf/hdf5lib/H5.java index 628a543711c..ac5703efc2e 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 6c444399fed..bce4e5778a5 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 00000000000..7abefa47be9 --- /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 a8bfbe0a6a5..eaf220e01e1 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 fbed495c656..7019883145f 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/jtest/CMakeLists.txt b/java/jtest/CMakeLists.txt index ca3b0a16be3..8e494e744ab 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 00000000000..0c20072d37e --- /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); + } + } +} diff --git a/java/lib/NOTICES.txt b/java/lib/NOTICES.txt new file mode 100644 index 00000000000..4d3fe51e7c6 --- /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/native-lib-loader-2.5.0.jar b/java/lib/native-lib-loader-2.5.0.jar new file mode 100644 index 00000000000..e368e627d6b Binary files /dev/null and b/java/lib/native-lib-loader-2.5.0.jar differ diff --git a/java/lib/vendored-jars.sha256 b/java/lib/vendored-jars.sha256 new file mode 100644 index 00000000000..db7bf852d63 --- /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 diff --git a/java/src-jni/hdf/hdf5lib/CMakeLists.txt b/java/src-jni/hdf/hdf5lib/CMakeLists.txt index 9db942db4ad..5b34378f3d7 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}) @@ -263,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 "") diff --git a/java/src-jni/hdf/hdf5lib/H5.java b/java/src-jni/hdf/hdf5lib/H5.java index 069e80a0b94..f708341c8f0 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 551c2a71191..5ce88626e60 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 + diff --git a/java/src-jni/test/CMakeLists.txt b/java/src-jni/test/CMakeLists.txt index d2389483183..ebbae507ac8 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 318200c62ea..17aed018ca6 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}) diff --git a/release_docs/CHANGELOG.md b/release_docs/CHANGELOG.md index c9d5ac47ad6..271cc9b2c85 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 132e2a78f5f..f00fec171ab 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`