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
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ jobs:
fail-fast: false
matrix:
include:
# Ubuntu with system HDF5 1.10.x (minimum supported version)
# Ubuntu with conda HDF5 1.10.4 (minimum supported version)
- os: ubuntu-22.04
hdf5: "1.10.4"
hdf5_source: "conda"
# Ubuntu with system HDF5 1.10.x (apt, typically 1.10.7)
- os: ubuntu-22.04
hdf5: "1.10"
hdf5_source: "apt"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Forked from [hdf5-metno](https://github.com/metno/hdf5-rust).
## Features

- **Runtime loading** - No compile-time HDF5 dependency
- **HDF5 1.10.5+ support** - Compatible with Ubuntu 22.04, HDF5.jl, h5py
- **HDF5 1.10.4+ support** - Compatible with Ubuntu 20.04+, HDF5.jl, h5py
- **Thread-safe** - Safe concurrent access to HDF5

Optional features:
Expand Down Expand Up @@ -44,7 +44,7 @@ fn main() -> hdf5_rt::Result<()> {

## Requirements

- **HDF5 1.10.5 or later** installed on your system
- **HDF5 1.10.4 or later** installed on your system
- Rust 1.80.0+

```bash
Expand Down
9 changes: 4 additions & 5 deletions hdf5/src/hl/chunks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,16 @@ pub(crate) fn chunk_info(ds: &Dataset, index: usize) -> Option<ChunkInfo> {
}
h5lock!(ds.space().map_or(None, |s| {
let mut chunk_info = ChunkInfo::new(ds.ndim());
h5check(H5Dget_chunk_info(
H5Dget_chunk_info(
ds.id(),
s.id(),
index as _,
chunk_info.offset.as_mut_ptr(),
&mut chunk_info.filter_mask,
&mut chunk_info.addr,
&mut chunk_info.size,
))
.map(|_| chunk_info)
.ok()
)
.and_then(|ret| h5check(ret).map(|_| chunk_info).ok())
}))
}

Expand All @@ -58,7 +57,7 @@ pub(crate) fn get_num_chunks(ds: &Dataset) -> Option<usize> {
}
h5lock!(ds.space().map_or(None, |s| {
let mut n: hsize_t = 0;
h5check(H5Dget_num_chunks(ds.id(), s.id(), &mut n)).map(|_| n as _).ok()
H5Dget_num_chunks(ds.id(), s.id(), &mut n).and_then(|ret| h5check(ret).map(|_| n as _).ok())
}))
}

Expand Down
2 changes: 1 addition & 1 deletion hdf5/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,6 @@ pub mod tests {

#[test]
pub fn test_minimum_library_version() {
assert!(library_version() >= (1, 10, 5));
assert!(library_version() >= (1, 10, 4));
}
}
139 changes: 94 additions & 45 deletions hdf5/src/sys/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -844,33 +844,58 @@ fn get_library() -> &'static Library {
*LIBRARY.get().expect("HDF5 library not initialized. Call hdf5::sys::init() first.")
}

/// Returns the list of default library names to try when loading HDF5.
fn default_library_candidates() -> Vec<&'static str> {
#[cfg(target_os = "macos")]
{
vec!["/opt/homebrew/lib/libhdf5.dylib", "/usr/local/lib/libhdf5.dylib", "libhdf5.dylib"]
}
#[cfg(target_os = "linux")]
{
vec!["libhdf5.so", "libhdf5_serial.so"]
}
#[cfg(target_os = "windows")]
{
vec!["hdf5.dll"]
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
vec!["libhdf5.so"]
}
}

/// Initialize the HDF5 library by loading it from the specified path.
pub fn init(path: Option<&str>) -> Result<(), String> {
if LIBRARY.get().is_some() {
return Ok(());
}

let lib_path = path.map(|s| s.to_string()).unwrap_or_else(|| {
#[cfg(target_os = "macos")]
{
"/opt/homebrew/lib/libhdf5.dylib".to_string()
}
#[cfg(target_os = "linux")]
{
"libhdf5.so".to_string()
}
#[cfg(target_os = "windows")]
{
"hdf5.dll".to_string()
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
"libhdf5.so".to_string()
let (library, resolved_path) = if let Some(p) = path {
let lib = unsafe { Library::new(p) }
.map_err(|e| format!("Failed to load HDF5 library from {}: {}", p, e))?;
(lib, p.to_string())
} else {
let candidates = default_library_candidates();
let mut last_err = String::new();
let mut loaded = None;
for name in &candidates {
match unsafe { Library::new(*name) } {
Ok(lib) => {
loaded = Some((lib, name.to_string()));
break;
}
Err(e) => {
last_err = format!("{}", e);
}
}
}
});

let library = unsafe { Library::new(&lib_path) }
.map_err(|e| format!("Failed to load HDF5 library from {}: {}", lib_path, e))?;
loaded.ok_or_else(|| {
format!(
"Failed to load HDF5 library. Tried: {:?}. Last error: {}",
candidates, last_err
)
})?
};

// Leak the library handle to prevent dlclose() on exit.
// HDF5 has problematic cleanup routines that can cause "infinite loop closing library"
Expand All @@ -880,20 +905,20 @@ pub fn init(path: Option<&str>) -> Result<(), String> {
let library = Box::leak(Box::new(library));

LIBRARY.set(library).map_err(|_| "Library already initialized".to_string())?;
LIBRARY_PATH.set(lib_path).map_err(|_| "Library path already set".to_string())?;
LIBRARY_PATH.set(resolved_path).map_err(|_| "Library path already set".to_string())?;

// Initialize HDF5
unsafe {
H5open();
}

// Check HDF5 version (require 1.10.5 or later)
// Check HDF5 version (require 1.10.4 or later)
check_hdf5_version()?;

Ok(())
}

/// Check that the HDF5 library version is at least 1.10.5 and store the version.
/// Check that the HDF5 library version is at least 1.10.4 and store the version.
/// Returns an error if the version is too old.
fn check_hdf5_version() -> Result<(), String> {
let mut major: c_uint = 0;
Expand All @@ -907,10 +932,10 @@ fn check_hdf5_version() -> Result<(), String> {
let version = Version { major: major as u8, minor: minor as u8, micro: release as u8 };
let _ = HDF5_RUNTIME_VERSION.set(version);

// Check minimum version: 1.10.5
if major < 1 || (major == 1 && minor < 10) || (major == 1 && minor == 10 && release < 5) {
// Check minimum version: 1.10.4
if major < 1 || (major == 1 && minor < 10) || (major == 1 && minor == 10 && release < 4) {
return Err(format!(
"HDF5 {}.{}.{} is not supported. Minimum required version is 1.10.5",
"HDF5 {}.{}.{} is not supported. Minimum required version is 1.10.4",
major, minor, release
));
}
Expand Down Expand Up @@ -1103,22 +1128,46 @@ hdf5_function!(
hdf5_function!(H5Dset_extent, fn(dset_id: hid_t, size: *const hsize_t) -> herr_t);
hdf5_function!(H5Dflush, fn(dset_id: hid_t) -> herr_t);
hdf5_function!(H5Drefresh, fn(dset_id: hid_t) -> herr_t);
hdf5_function!(
H5Dget_num_chunks,
fn(dset_id: hid_t, fspace_id: hid_t, nchunks: *mut hsize_t) -> herr_t
);
hdf5_function!(
H5Dget_chunk_info,
fn(
dset_id: hid_t,
fspace_id: hid_t,
chk_idx: hsize_t,
offset: *mut hsize_t,
filter_mask: *mut c_uint,
addr: *mut haddr_t,
size: *mut hsize_t,
) -> herr_t
);
/// H5Dget_num_chunks - Available in HDF5 1.10.5+
/// Returns None if the function is not available (HDF5 < 1.10.5)
pub unsafe fn H5Dget_num_chunks(
dset_id: hid_t,
fspace_id: hid_t,
nchunks: *mut hsize_t,
) -> Option<herr_t> {
let lib = get_library();
let func: Option<Symbol<unsafe extern "C" fn(hid_t, hid_t, *mut hsize_t) -> herr_t>> =
lib.get(b"H5Dget_num_chunks").ok();
func.map(|f| f(dset_id, fspace_id, nchunks))
}

/// H5Dget_chunk_info - Available in HDF5 1.10.5+
/// Returns None if the function is not available (HDF5 < 1.10.5)
pub unsafe fn H5Dget_chunk_info(
dset_id: hid_t,
fspace_id: hid_t,
chk_idx: hsize_t,
offset: *mut hsize_t,
filter_mask: *mut c_uint,
addr: *mut haddr_t,
size: *mut hsize_t,
) -> Option<herr_t> {
let lib = get_library();
let func: Option<
Symbol<
unsafe extern "C" fn(
hid_t,
hid_t,
hsize_t,
*mut hsize_t,
*mut c_uint,
*mut haddr_t,
*mut hsize_t,
) -> herr_t,
>,
> = lib.get(b"H5Dget_chunk_info").ok();
func.map(|f| f(dset_id, fspace_id, chk_idx, offset, filter_mask, addr, size))
}
hdf5_function!(
H5Dcreate_anon,
fn(loc_id: hid_t, type_id: hid_t, space_id: hid_t, dcpl_id: hid_t, dapl_id: hid_t) -> hid_t
Expand Down Expand Up @@ -2269,10 +2318,10 @@ mod tests {
// Version should be accessible after init
let version = hdf5_version().expect("Version should be stored after init");

// Version should be at least 1.10.5 (our minimum)
// Version should be at least 1.10.4 (our minimum)
assert!(
hdf5_version_at_least(1, 10, 5),
"Version {}.{}.{} should be at least 1.10.5",
hdf5_version_at_least(1, 10, 4),
"Version {}.{}.{} should be at least 1.10.4",
version.major,
version.minor,
version.micro
Expand Down
Loading