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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 68 additions & 8 deletions .github/workflows/build-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,24 @@ jobs:
Remove-Item -Recurse -Force $JPACKAGE_INPUT
}
New-Item -ItemType Directory -Path $JPACKAGE_INPUT | Out-Null

# Stage the core DLLs at the app-image root so that they take
# priority over other HDF libraries in the system PATH
$JPACKAGE_CORELIBS = "hdfview\target\jpackage-corelibs"
Write-Host "Creating core library directory: $JPACKAGE_CORELIBS"
if (Test-Path $JPACKAGE_CORELIBS) {
Remove-Item -Recurse -Force $JPACKAGE_CORELIBS
}
New-Item -ItemType Directory -Path $JPACKAGE_CORELIBS | Out-Null

# JNI wrappers stay in the input directory
function Copy-NativeLibraries {
param([System.IO.FileInfo[]]$Dlls)
foreach ($dll in $Dlls) {
$dest = if ($dll.Name -like '*_java.dll') { $JPACKAGE_INPUT } else { $JPACKAGE_CORELIBS }
Copy-Item -LiteralPath $dll.FullName -Destination $dest -ErrorAction Stop
}
}

# Copy main JARs from libs/
Write-Host "Copying main JARs from libs/..."
Expand All @@ -562,7 +580,7 @@ jobs:
if (Test-Path "$env:HDF5LIB_PATH\bin") {
$hdf5Dlls = Get-ChildItem "$env:HDF5LIB_PATH\bin\*.dll" -ErrorAction SilentlyContinue
if ($hdf5Dlls) {
Copy-Item "$env:HDF5LIB_PATH\bin\*.dll" $JPACKAGE_INPUT\ -ErrorAction Stop
Copy-NativeLibraries -Dlls $hdf5Dlls
Write-Host " ✓ Copied $($hdf5Dlls.Count) HDF5 DLLs"
} else {
Write-Warning " ⚠ No HDF5 DLLs found in $env:HDF5LIB_PATH\bin"
Expand All @@ -577,7 +595,7 @@ jobs:
if (Test-Path "$env:HDF4LIB_PATH\bin") {
$hdf4Dlls = Get-ChildItem "$env:HDF4LIB_PATH\bin\*.dll" -ErrorAction SilentlyContinue
if ($hdf4Dlls) {
Copy-Item "$env:HDF4LIB_PATH\bin\*.dll" $JPACKAGE_INPUT\ -ErrorAction Stop
Copy-NativeLibraries -Dlls $hdf4Dlls
Write-Host " ✓ Copied $($hdf4Dlls.Count) HDF4 DLLs"
} else {
Write-Warning " ⚠ No HDF4 DLLs found in $env:HDF4LIB_PATH\bin"
Expand Down Expand Up @@ -627,12 +645,25 @@ jobs:
Write-Warning " ⚠ docs\UsersGuide directory not found"
}

# Use --app-content to copy files to the app-image root.
# Use absolute paths since jpackage runs
# from the hdfview module directory
$coreLibs = @(Get-ChildItem $JPACKAGE_CORELIBS -Filter *.dll -File)
if ($coreLibs.Count -eq 0) {
Write-Error " ✗ No core HDF libraries staged in $JPACKAGE_CORELIBS"
exit 1
}
$appContent = ($coreLibs.FullName -join ',')
"JPACKAGE_APP_CONTENT=$appContent" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8

Write-Host ""
Write-Host "=== Staging directory prepared ==="
Write-Host "Total files: $((Get-ChildItem $JPACKAGE_INPUT -Recurse -File).Count)"
Write-Host "Total size: $([math]::Round((Get-ChildItem $JPACKAGE_INPUT -Recurse -File | Measure-Object -Property Length -Sum).Sum / 1MB, 2)) MB"
Write-Host "JAR files: $((Get-ChildItem $JPACKAGE_INPUT -Filter *.jar).Count)"
Write-Host "DLL files: $((Get-ChildItem $JPACKAGE_INPUT -Recurse -Filter *.dll).Count)"
Write-Host "JNI wrappers: $((Get-ChildItem $JPACKAGE_INPUT -Filter *.dll).Count)"
Write-Host "Core libraries for the app-image root: $($coreLibs.Count)"
$coreLibs | ForEach-Object { Write-Host " $($_.Name)" }
Write-Host ""

- name: Create jpackage App Image
Expand All @@ -647,6 +678,8 @@ jobs:
# -Dpmd.skip=true: Skip PMD (static analysis not needed for release)
# -Ddependency-check.skip=true: Skip OWASP (runs in separate security workflow)
# -Dmaven.antrun.skip=true: Skip Ant plugin (we manually prepared the input directory)
# -Djpackage.app.content: The core HDF DLLs, which jpackage places beside the launcher.
# Normally produced by the Ant staging target, which is skipped here.
Write-Host "Running verify phase with jpackage profiles to create app-image..."
mvn verify "-Pjpackage-app-image,jpackage-win-base" `
"-pl" "hdfview" `
Expand All @@ -655,22 +688,49 @@ jobs:
"-Dpmd.skip=true" `
"-Ddependency-check.skip=true" `
"-Dmaven.antrun.skip=true" `
"-Djpackage.app.content=$env:JPACKAGE_APP_CONTENT" `
-B

Write-Host "Verifying jpackage output..."
if (Test-Path "hdfview\target\dist\HDFView") {
$appImage = "hdfview\target\dist\HDFView"
if (Test-Path $appImage) {
Write-Host "✓ jpackage app-image created successfully"
$size = (Get-ChildItem -Recurse "hdfview\target\dist\HDFView" | Measure-Object -Property Length -Sum).Sum / 1MB
$size = (Get-ChildItem -Recurse $appImage | Measure-Object -Property Length -Sum).Sum / 1MB
Write-Host "Package size: $([math]::Round($size, 2)) MB"
$dlls = (Get-ChildItem -Recurse "hdfview\target\dist\HDFView\lib\app\*.dll" -ErrorAction SilentlyContinue).Count
Write-Host "Native libraries: $dlls .dll files"
$jars = (Get-ChildItem -Recurse "hdfview\target\dist\HDFView\lib\app\*.jar" -ErrorAction SilentlyContinue).Count
$wrappers = (Get-ChildItem "$appImage\app\*.dll" -ErrorAction SilentlyContinue).Count
Write-Host "JNI wrappers in app\: $wrappers .dll files"
$jars = (Get-ChildItem -Recurse "$appImage\app\*.jar" -ErrorAction SilentlyContinue).Count
Write-Host "JAR files: $jars JARs"
} else {
Write-Error "✗ jpackage app-image creation failed"
exit 1
}

$rootDlls = @(Get-ChildItem "$appImage\*.dll" -File -ErrorAction SilentlyContinue)
Write-Host "Core libraries beside HDFView.exe: $($rootDlls.Count)"
$rootDlls | ForEach-Object { Write-Host " $($_.Name)" }

$failures = @()
foreach ($staged in $env:JPACKAGE_APP_CONTENT -split ',') {
$name = Split-Path $staged -Leaf
if (-not (Test-Path "$appImage\$name")) {
$failures += "$name was staged as app content but is not beside HDFView.exe"
}
}
$strays = @($rootDlls | Where-Object { $_.Name -like '*_java.dll' })
if ($strays.Count -gt 0) {
$failures += "JNI wrappers were placed at the root, out of reach of java.library.path: $($strays.Name -join ', ')"
}
if ((Get-ChildItem "$appImage\app\*_java.dll" -ErrorAction SilentlyContinue).Count -eq 0) {
$failures += "no JNI wrappers in app\, where java.library.path looks for them"
}
if ($failures.Count -gt 0) {
foreach ($failure in $failures) { Write-Host " FAILED: $failure" }
Write-Error "✗ Native library layout is wrong; see above"
exit 1
}
Write-Host "✓ Core libraries sit beside HDFView.exe, ahead of the system folder and PATH"

- name: Install Microsoft Trusted Signing
if: github.repository == 'HDFGroup/hdfview' && env.AZURE_TENANT_ID != ''
shell: pwsh
Expand Down
8 changes: 6 additions & 2 deletions docs/UsersGuide/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ All notable changes to this project will be documented in this file. This docume

## Major Bug Fixes

* **Windows: other HDF4/HDF5 installations no longer prevent HDFView from starting**: HDFView ships its own copies of the HDF4 and HDF5 libraries, but Windows could load a different installation in their place if one was on `PATH`. Windows DLLs carry no version in their name, so any `hdf5.dll` on `PATH` would satisfy the reference regardless of which version it was, and HDFView usually failed to start, reporting "failed to launch JVM".

The bundled DLLs are now installed next to `HDFView.exe`, whose directory Windows searches ahead of both the system folder and `PATH`, so the bundled copies are used regardless of what else is installed on the machine.

Consequently, `PATH` can no longer be used to make an installed HDFView load a *different* HDF4/HDF5 build. This applies to the packaged application only, as running HDFView from a source build is unchanged, and still uses the libraries named in `build.properties`.

## Minor Bug Fixes

# ☑️ Platforms Tested
Expand All @@ -41,8 +47,6 @@ Current test results and detailed platform information are available in the [Git

# ⛔ Known Problems

* **PATH pointing to other HDF4/5 installations**: If the environment path points to a directory including HDF4/5 installations, then these installations may be loaded by HDFView instead of the bundled HDF4/5 versions, causing the application to fail to launch with a "failed to launch JVM" error. This can be resolved by either removing those directories from the PATH, or removing the HDF4/5 installations from that directory.

* **Large Dataset Handling**: HDFView currently cannot nicely handle large datasets when using the default display mode, as the data is loaded in its entirety. To view large datasets, it is recommended to right click on a data object and use the "Open As" menu item, where a subset of data to view can be selected.

* **Object/Region References in Compound Types**: Object/region references can't be opened by a double-click or by right-clicking and choosing "Show As Table/Image" when inside a compound datatype.
Expand Down
48 changes: 48 additions & 0 deletions hdfview/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
<properties>
<module-name>org.hdfgroup.hdfview</module-name>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Which staged native libraries are held back from jpackage's input directory -->
<!-- and placed beside the launcher instead. Overridden by jpackage-win-base, the -->
<!-- only profile that passes them on to jpackage; patterns that match nothing -->
<!-- leave every native library in the input directory. -->
<jpackage.corelib.include>no-core-libraries-on-this-platform</jpackage.corelib.include>
<jpackage.corelib.exclude>no-core-libraries-on-this-platform</jpackage.corelib.exclude>
</properties>

<dependencies>
Expand Down Expand Up @@ -267,6 +273,8 @@
<goal>run</goal>
</goals>
<configuration>
<!-- Publishes jpackage.app.content for the jpackage invocation below. -->
<exportAntProperties>true</exportAntProperties>
<target>
<echo message="Preparing jpackage input directory..."/>

Expand Down Expand Up @@ -316,6 +324,33 @@
</fileset>
</copy>

<!-- Hold back the core libraries that the JNI wrappers depend on. -->
<!-- Windows has no rpath, and a wrapper's dependencies are resolved by -->
<!-- module name through the loader's search order: the launcher's own -->
<!-- directory, then the system folder, then PATH. jpackage stages the -->
<!-- input directory at <root>\app, which the launcher only appends to -->
<!-- PATH, so an hdf5.dll earlier in PATH would win. Handing these to -->
<!-- the app-content option instead puts them at <root>, beside the -->
<!-- launcher, where the loader looks first. Matches nothing on Linux -->
<!-- and macOS, where the soname and install name already pin the -->
<!-- bundled copies. -->
<property name="jpackage.corelibs" value="${project.build.directory}/jpackage-corelibs"/>
<delete dir="${jpackage.corelibs}"/>
<mkdir dir="${jpackage.corelibs}"/>
<move todir="${jpackage.corelibs}" failonerror="false">
<fileset dir="${jpackage.input}">
<include name="${jpackage.corelib.include}"/>
<exclude name="${jpackage.corelib.exclude}"/>
</fileset>
</move>
<!-- Exported to Maven for the jpackage app-content argument. Left -->
<!-- unset when nothing was held back, so a platform that does not pass -->
<!-- the option never sees an empty value. -->
<pathconvert property="jpackage.app.content" pathsep="," setonempty="false">
<fileset dir="${jpackage.corelibs}" includes="*"/>
</pathconvert>
<echo message="Core libraries for --app-content: ${jpackage.app.content}"/>

<!-- Copy HDF5 plugins -->
<echo message="Copying HDF5 plugins from ${hdf5.plugin.dir}..."/>
<mkdir dir="${jpackage.input}/plugin"/>
Expand Down Expand Up @@ -421,6 +456,15 @@
<!-- Only use when creating app-image, NOT when creating installers from app-image -->
<profile>
<id>jpackage-win-base</id>
<properties>
<!-- The core DLLs are every native library except the JNI wrappers, which -->
<!-- stay in the input directory so that java.library.path can find them. -->
<!-- Held back here rather than in platform-windows, so that the staging -->
<!-- target only separates them out when this profile is also passing them -->
<!-- to jpackage. -->
<jpackage.corelib.include>*.dll</jpackage.corelib.include>
<jpackage.corelib.exclude>*_java.dll</jpackage.corelib.exclude>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's anything particularly wrong with this approach, but it would probably be simpler for this workflow's logic to just put everything next to the .exe and adjust java.library.path

</properties>
<build>
<plugins>
<plugin>
Expand All @@ -433,6 +477,10 @@
<arguments combine.children="append">
<argument>--icon</argument>
<argument>${project.basedir}/../package_files/hdfview.ico</argument>
<!-- Use app-content to copy the core DLLs into the app-image root. -->
<!-- This gives bundled HDF libs priority over ones in system PATH -->
<argument>--app-content</argument>
<argument>${jpackage.app.content}</argument>
</arguments>
</configuration>
</execution>
Expand Down
Loading