From 5711c7466f6ad9018e54dd5c527d463e5ba9abb8 Mon Sep 17 00:00:00 2001 From: Scot Breitenfeld Date: Wed, 21 Jan 2026 23:02:49 -0600 Subject: [PATCH] Update the version to 2.1 (#6147) Update version to 2.1 and derive version information from H5public.h, removing h5vers script and updating CMake and Java configurations. Versioning: Update version to 2.1 in H5public.h. Derive version strings in H5public.h using macros. CMake: Extract version from H5public.h in HDF5config.cmake and HDF5AsSubdirMacros.cmake. Configure README.md and CHANGELOG.md using CMakeLists.txt. Java: Generate H5Version.java from H5public.h for version consistency. Update H5.java to use H5Version for version constants. Removals: Delete bin/h5vers script, previously used for version management. --- .github/workflows/tarball.yml | 6 +- .github/workflows/update-badge.sh | 214 +++++++ .github/workflows/update-progress.py | 147 ++++- .github/workflows/update-progress.yml | 129 +---- CITATION.cff | 4 + CMakeLists.txt | 6 + HDF5Examples/JAVA/README-MAVEN.md | 627 ++++----------------- README.md | 202 ++++--- bin/README.md | 1 - bin/h5vers | 538 ------------------ bin/release | 106 +++- config/cmake/HDF5VersionParsing.cmake | 114 ++++ config/cmake/scripts/HDF5config.cmake | 22 +- config/examples/HDF5AsSubdirMacros.cmake | 24 +- config/lt_vers.am | 5 +- doxygen/Doxyfile.in | 1 + java/hdf/hdf5lib/CMakeLists.txt | 13 + java/hdf/hdf5lib/H5.java | 6 +- java/src-jni/hdf/hdf5lib/CMakeLists.txt | 13 + java/src-jni/hdf/hdf5lib/H5.java | 6 +- java/templates/H5Version.java.in | 42 ++ release_docs/AutotoolsToCMakeOptions.md | 2 +- release_docs/CHANGELOG.md | 16 +- release_docs/Cmake_logo.svg | 70 --- release_docs/README_HPC.md | 2 +- release_docs/RELEASE_PROCESS.md | 19 +- release_docs/img/release-schedule.plantuml | 23 +- release_docs/img/release-schedule.png | Bin 22618 -> 12641 bytes src/H5public.h | 29 +- 29 files changed, 985 insertions(+), 1402 deletions(-) create mode 100755 .github/workflows/update-badge.sh delete mode 100755 bin/h5vers create mode 100644 config/cmake/HDF5VersionParsing.cmake create mode 100644 java/templates/H5Version.java.in delete mode 100644 release_docs/Cmake_logo.svg diff --git a/.github/workflows/tarball.yml b/.github/workflows/tarball.yml index bea662e53f5..4dc45b7e46a 100644 --- a/.github/workflows/tarball.yml +++ b/.github/workflows/tarball.yml @@ -106,7 +106,11 @@ jobs: id: version run: | cd "$GITHUB_WORKSPACE/hdfsrc" - echo "SOURCE_TAG=$(bin/h5vers)" >> $GITHUB_OUTPUT + major=$(grep '^#define H5_VERS_MAJOR' src/H5public.h | awk '{print $3}') + minor=$(grep '^#define H5_VERS_MINOR' src/H5public.h | awk '{print $3}') + release=$(grep '^#define H5_VERS_RELEASE' src/H5public.h | awk '{print $3}') + subrelease=$(grep '^#define H5_VERS_SUBRELEASE' src/H5public.h | cut -d'"' -f2) + echo "SOURCE_TAG=${major}.${minor}.${release}${subrelease}" >> $GITHUB_OUTPUT - name: Set file base name id: set-file-base diff --git a/.github/workflows/update-badge.sh b/.github/workflows/update-badge.sh new file mode 100755 index 00000000000..72882213453 --- /dev/null +++ b/.github/workflows/update-badge.sh @@ -0,0 +1,214 @@ +#!/bin/bash +# +# Badge Generation and Gist Update Script +# Generates badges for release blocker and must-do progress and updates GitHub Gist +# +# Environment Variables Required: +# GITHUB_TOKEN - GitHub token for Gist API access +# GIST_ID - ID of the Gist to update +# GITHUB_REPOSITORY - Full repository name (owner/repo) +# GITHUB_REPOSITORY_OWNER - Repository owner +# PERCENTAGE - Overall completion percentage +# DONE - Number of completed items +# TOTAL - Total number of items +# BLOCKER_DONE - Number of completed blockers +# BLOCKER_TOTAL - Total number of blockers +# MUSTDO_DONE - Number of completed must-dos +# MUSTDO_TOTAL - Total number of must-dos +# + +set -euo pipefail + +# Validate required environment variables +required_vars=( + "GITHUB_TOKEN" + "GIST_ID" + "GITHUB_REPOSITORY" + "GITHUB_REPOSITORY_OWNER" + "PERCENTAGE" + "DONE" + "TOTAL" + "BLOCKER_DONE" + "BLOCKER_TOTAL" + "MUSTDO_DONE" + "MUSTDO_TOTAL" +) + +for var in "${required_vars[@]}"; do + if [ -z "${!var:-}" ]; then + echo "::error::Required environment variable $var is not set" + exit 1 + fi +done + +echo "::notice::Updating badge with: ${PERCENTAGE}% (${DONE}/${TOTAL})" + +# Determine badge color and status based on percentage +PERCENTAGE_INT="${PERCENTAGE%.*}" +if [ "$PERCENTAGE_INT" -ge 90 ]; then + COLOR="brightgreen" + STATUS="๐ŸŸข Readying for Deployment" +elif [ "$PERCENTAGE_INT" -ge 60 ]; then + COLOR="yellow" + STATUS="๐ŸŸก Nearing Completion" +elif [ "$PERCENTAGE_INT" -ge 40 ]; then + COLOR="orange" + STATUS="๐ŸŸ  In Development" +else + COLOR="red" + STATUS="๐Ÿ”ด Initial Phase" +fi + +echo "::notice title=Release Progress::${PERCENTAGE}% Complete (${DONE}/${TOTAL}) - ${STATUS}" + +# Function to determine badge color based on percentage +get_badge_color() { + local percentage_int="${1%.*}" + if [ "$percentage_int" -ge 90 ]; then + echo "brightgreen" + elif [ "$percentage_int" -ge 60 ]; then + echo "yellow" + elif [ "$percentage_int" -ge 40 ]; then + echo "orange" + else + echo "red" + fi +} + +# Function to create badge JSON +create_badge_json() { + local label="$1" + local done="$2" + local total="$3" + local percentage="$4" + local color="$5" + + jq -n \ + --arg label "$label" \ + --arg percentage "$percentage" \ + --arg done "$done" \ + --arg total "$total" \ + --arg color "$color" \ + '{ + "schemaVersion": 1, + "label": $label, + "message": "\($done)/\($total) (\($percentage)%)", + "color": $color, + "style": "flat-square" + }' +} + +# Calculate percentages for each category +BLOCKER_PERCENTAGE=$(awk "BEGIN {printf \"%.1f\", ($BLOCKER_DONE / $BLOCKER_TOTAL * 100)}" 2>/dev/null || echo "0") +MUSTDO_PERCENTAGE=$(awk "BEGIN {printf \"%.1f\", ($MUSTDO_DONE / $MUSTDO_TOTAL * 100)}" 2>/dev/null || echo "0") + +# Determine colors using the shared function +BLOCKER_COLOR=$(get_badge_color "$BLOCKER_PERCENTAGE") +MUSTDO_COLOR=$(get_badge_color "$MUSTDO_PERCENTAGE") + +# Create badge JSONs using the shared function +BLOCKER_BADGE_JSON=$(create_badge_json "Release Blockers" "$BLOCKER_DONE" "$BLOCKER_TOTAL" "$BLOCKER_PERCENTAGE" "$BLOCKER_COLOR") +MUSTDO_BADGE_JSON=$(create_badge_json "Release Must Do" "$MUSTDO_DONE" "$MUSTDO_TOTAL" "$MUSTDO_PERCENTAGE" "$MUSTDO_COLOR") + +# Validate JSONs were created successfully +if [ -z "$BLOCKER_BADGE_JSON" ] || ! echo "$BLOCKER_BADGE_JSON" | jq empty 2>/dev/null; then + echo "::error::Failed to generate valid blocker badge JSON" + exit 1 +fi +if [ -z "$MUSTDO_BADGE_JSON" ] || ! echo "$MUSTDO_BADGE_JSON" | jq empty 2>/dev/null; then + echo "::error::Failed to generate valid must-do badge JSON" + exit 1 +fi + +# The filenames in the Gist +BLOCKER_GIST_NAME="release-blocker-${GITHUB_REPOSITORY##*/}.json" +MUSTDO_GIST_NAME="release-mustdo-${GITHUB_REPOSITORY##*/}.json" +echo "::notice::Updating Gist files: $BLOCKER_GIST_NAME, $MUSTDO_GIST_NAME" + +# Create the request payload with both files +REQUEST_PAYLOAD=$(jq -n \ + --arg blocker_filename "$BLOCKER_GIST_NAME" \ + --arg mustdo_filename "$MUSTDO_GIST_NAME" \ + --argjson blocker_content "$BLOCKER_BADGE_JSON" \ + --argjson mustdo_content "$MUSTDO_BADGE_JSON" \ + '{ + "files": { + ($blocker_filename): { + "content": ($blocker_content | tostring) + }, + ($mustdo_filename): { + "content": ($mustdo_content | tostring) + } + } + }') + +# Update the existing Gist with response validation +echo "Updating Gist: ${GIST_ID}" +RESPONSE=$(curl -s -w "\n%{http_code}" -L -X PATCH \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/gists/${GIST_ID}" \ + -d "$REQUEST_PAYLOAD") + +# Extract HTTP status code (last line) and response body +HTTP_CODE=$(echo "$RESPONSE" | tail -n1) +RESPONSE_BODY=$(echo "$RESPONSE" | head -n -1) + +# Validate API response +if [ "$HTTP_CODE" != "200" ]; then + echo "::error::Gist update failed with HTTP status $HTTP_CODE" + echo "Response body: $RESPONSE_BODY" + exit 1 +fi + +echo "::notice::Gist updated successfully" + +# Generate badge URLs for use in README +BLOCKER_BADGE_URL="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/${GITHUB_REPOSITORY_OWNER}-Bot/${GIST_ID}/raw/${BLOCKER_GIST_NAME}" +MUSTDO_BADGE_URL="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/${GITHUB_REPOSITORY_OWNER}-Bot/${GIST_ID}/raw/${MUSTDO_GIST_NAME}" +PROJECT_URL="https://github.com/${GITHUB_REPOSITORY}/projects/39" + +echo "::notice::Blocker Badge URL: $BLOCKER_BADGE_URL" +echo "::notice::Must-Do Badge URL: $MUSTDO_BADGE_URL" + +# Output to GitHub Actions if GITHUB_OUTPUT is set +if [ -n "${GITHUB_OUTPUT:-}" ]; then + echo "blocker_badge_url=$BLOCKER_BADGE_URL" >> "$GITHUB_OUTPUT" + echo "mustdo_badge_url=$MUSTDO_BADGE_URL" >> "$GITHUB_OUTPUT" + echo "project_url=$PROJECT_URL" >> "$GITHUB_OUTPUT" +fi + +# Create enhanced step summary if GITHUB_STEP_SUMMARY is set +if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then + cat >> "$GITHUB_STEP_SUMMARY" << EOF +## ๐Ÿ“Š Release Progress: ${PERCENTAGE}% + +**Status:** ${STATUS} +**Overall Progress:** ${DONE} of ${TOTAL} items completed + +### ๐Ÿšซ Release Blockers +**Progress:** ${BLOCKER_DONE} of ${BLOCKER_TOTAL} completed (${BLOCKER_PERCENTAGE}%) +**Badge Color:** ${BLOCKER_COLOR} + +### โœ… Release Must Do +**Progress:** ${MUSTDO_DONE} of ${MUSTDO_TOTAL} completed (${MUSTDO_PERCENTAGE}%) +**Badge Color:** ${MUSTDO_COLOR} + +**Gist ID:** ${GIST_ID} + +### Badge URLs +**Blocker Markdown:** \`[![Release Blocker Progress](${BLOCKER_BADGE_URL})](${PROJECT_URL})\` +**Must-Do Markdown:** \`[![Release Must Do Progress](${MUSTDO_BADGE_URL})](${PROJECT_URL})\` + +### Badge JSON Preview +\`\`\`json +// Blocker Badge +${BLOCKER_BADGE_JSON} + +// Must-Do Badge +${MUSTDO_BADGE_JSON} +\`\`\` +EOF +fi + +echo "::notice::Badge update completed successfully" diff --git a/.github/workflows/update-progress.py b/.github/workflows/update-progress.py index 45c6b93f37b..09f6c4a9ec2 100644 --- a/.github/workflows/update-progress.py +++ b/.github/workflows/update-progress.py @@ -4,12 +4,38 @@ GitHub Project Release Blocker Progress Tracker Fetches release blocker issues from the HDF5 project and calculates completion percentage. """ +import os +import sys + import requests from typing import Dict, Any, Optional + +class ProjectFieldMissingError(Exception): + """Raised when a critical project field is missing or renamed.""" + pass + + +class ProjectDataError(Exception): + """Raised when project data is invalid or incomplete.""" + pass + +# Configuration: Expected field names in GitHub Project +# Update these if the project field names change +FIELD_RELEASE_GATING = "Release gating" +FIELD_STATUS = "Status" + +# Expected values for Release gating field +VALUE_RELEASE_BLOCKER = "Release_Blocker" +VALUE_RELEASE_MUST_DO = "Release_Must Do" + +# Expected value for Status field when an item is completed +VALUE_STATUS_DONE = "Done" + + class GitHubProjectTracker: """Tracks release blocker progress in GitHub projects.""" - + def __init__(self, token: str, owner: str, project_number: int): self.api_url = "https://api.github.com/graphql" self.headers = { @@ -89,22 +115,29 @@ class GitHubProjectTracker: def fetch_release_blocker_stats(self) -> Dict[str, int]: """ - Fetches release blocker statistics from the GitHub project. - + Fetches release blocker and must-do statistics from the GitHub project. + Returns: - Dict with 'total', 'done', and 'percentage' keys + Dict with 'total', 'done', 'percentage', 'blocker_total', 'blocker_done', + 'mustdo_total', 'mustdo_done' keys """ - total = 0 - done = 0 + blocker_total = 0 + blocker_done = 0 + mustdo_total = 0 + mustdo_done = 0 cursor = None - + + # Track if we've seen the expected fields at least once + seen_release_gating = False + seen_status = False + while True: variables = { "owner": self.owner, "projectNumber": self.project_number, "cursor": cursor } - + try: response = requests.post( self.api_url, @@ -114,48 +147,102 @@ class GitHubProjectTracker: ) response.raise_for_status() result = response.json() - + if "errors" in result: raise Exception(f"GraphQL errors: {result['errors']}") - + except requests.RequestException as e: raise Exception(f"API request failed: {e}") - + # Parse response project = result.get("data", {}).get("organization", {}).get("projectV2", {}) items = project.get("items", {}) - + for item in items.get("nodes", []): if not item.get("content"): continue - + fields = self._parse_item_fields(item) - - if fields.get("Release gating") == "Release_Blocker": - total += 1 - if fields.get("Status") == "Done": - done += 1 - + + # Validate expected fields exist + if FIELD_RELEASE_GATING in fields: + seen_release_gating = True + if FIELD_STATUS in fields: + seen_status = True + + release_gating = fields.get(FIELD_RELEASE_GATING, "") + status = fields.get(FIELD_STATUS, "") + + if release_gating == VALUE_RELEASE_BLOCKER: + blocker_total += 1 + if status == VALUE_STATUS_DONE: + blocker_done += 1 + elif release_gating == VALUE_RELEASE_MUST_DO: + mustdo_total += 1 + if status == VALUE_STATUS_DONE: + mustdo_done += 1 + # Check for next page page_info = items.get("pageInfo", {}) if not page_info.get("hasNextPage", False): break cursor = page_info.get("endCursor") - - percentage = round((done / total * 100), 1) if total > 0 else 0 - + + # Validate that expected fields were found - FAIL HARD if missing + # This prevents false positives where field renames would cause 0 blockers to be reported + if not seen_release_gating: + print(f"ERROR: Critical field '{FIELD_RELEASE_GATING}' not found in any project items.", + file=sys.stderr) + print("This field is required to identify release blockers and must-do items.", + file=sys.stderr) + print("Possible causes:", file=sys.stderr) + print(f" 1. Field '{FIELD_RELEASE_GATING}' was renamed in the project", file=sys.stderr) + print(" 2. Project structure changed", file=sys.stderr) + print(" 3. Project is empty or inaccessible", file=sys.stderr) + print("Action required: Update FIELD_RELEASE_GATING constant in this script.", file=sys.stderr) + raise ProjectFieldMissingError(f"Critical field '{FIELD_RELEASE_GATING}' not found") + + if not seen_status: + print(f"ERROR: Critical field '{FIELD_STATUS}' not found in any project items.", + file=sys.stderr) + print("This field is required to determine completion status.", file=sys.stderr) + print("Possible causes:", file=sys.stderr) + print(f" 1. Field '{FIELD_STATUS}' was renamed in the project", file=sys.stderr) + print(" 2. Project structure changed", file=sys.stderr) + print(" 3. Project is empty or inaccessible", file=sys.stderr) + print("Action required: Update FIELD_STATUS constant in this script.", file=sys.stderr) + raise ProjectFieldMissingError(f"Critical field '{FIELD_STATUS}' not found") + + total = blocker_total + mustdo_total + done = blocker_done + mustdo_done + + # Validate that we found at least some items + # If total is 0, either the project is empty or field matching failed + if total == 0: + print("ERROR: No release blocker or must-do items found (total=0).", file=sys.stderr) + print("This likely indicates:", file=sys.stderr) + print(f" 1. The '{FIELD_RELEASE_GATING}' field values changed", file=sys.stderr) + print(f" Expected values: '{VALUE_RELEASE_BLOCKER}' or '{VALUE_RELEASE_MUST_DO}'", file=sys.stderr) + print(" 2. Project has no items with these field values", file=sys.stderr) + print(" 3. Field matching logic needs to be updated", file=sys.stderr) + print("Refusing to report 0% or 100% with no items to prevent false positives.", file=sys.stderr) + raise ProjectDataError("No release items found - refusing to report false completion status") + + percentage = round((done / total * 100), 1) + return { 'total': total, 'done': done, - 'percentage': percentage + 'percentage': percentage, + 'blocker_total': blocker_total, + 'blocker_done': blocker_done, + 'mustdo_total': mustdo_total, + 'mustdo_done': mustdo_done } def main(): """Main function to run the tracker.""" - import os - import sys - # Configuration - can be overridden by environment variables TOKEN = os.getenv("GITHUB_TOKEN") OWNER = os.getenv("GITHUB_OWNER", "HDFGroup") @@ -172,11 +259,17 @@ def main(): f.write(f"percentage={stats['percentage']}\n") f.write(f"done={stats['done']}\n") f.write(f"total={stats['total']}\n") - + f.write(f"blocker_total={stats['blocker_total']}\n") + f.write(f"blocker_done={stats['blocker_done']}\n") + f.write(f"mustdo_total={stats['mustdo_total']}\n") + f.write(f"mustdo_done={stats['mustdo_done']}\n") + # Also output to stdout for local testing print(f"percentage={stats['percentage']}") print(f"Calculated progress: {stats['percentage']}%") print(f"Done / Total: {stats['done']} / {stats['total']}") + print(f"Blockers: {stats['blocker_done']} / {stats['blocker_total']}") + print(f"Must Do: {stats['mustdo_done']} / {stats['mustdo_total']}") except Exception as e: print(f"Error: {e}", file=sys.stderr) diff --git a/.github/workflows/update-progress.yml b/.github/workflows/update-progress.yml index e0af91d4236..fb1d99d8640 100644 --- a/.github/workflows/update-progress.yml +++ b/.github/workflows/update-progress.yml @@ -115,12 +115,23 @@ jobs: exit 1 fi + # Extract blocker and must-do counts + BLOCKER_DONE=$(grep "^blocker_done=" progress_output.txt | cut -d'=' -f2 | head -1) + BLOCKER_TOTAL=$(grep "^blocker_total=" progress_output.txt | cut -d'=' -f2 | head -1) + MUSTDO_DONE=$(grep "^mustdo_done=" progress_output.txt | cut -d'=' -f2 | head -1) + MUSTDO_TOTAL=$(grep "^mustdo_total=" progress_output.txt | cut -d'=' -f2 | head -1) + # Set outputs for use in subsequent steps echo "percentage=$PERCENTAGE" >> $GITHUB_OUTPUT echo "done=$DONE" >> $GITHUB_OUTPUT echo "total=$TOTAL" >> $GITHUB_OUTPUT - + echo "blocker_done=$BLOCKER_DONE" >> $GITHUB_OUTPUT + echo "blocker_total=$BLOCKER_TOTAL" >> $GITHUB_OUTPUT + echo "mustdo_done=$MUSTDO_DONE" >> $GITHUB_OUTPUT + echo "mustdo_total=$MUSTDO_TOTAL" >> $GITHUB_OUTPUT + echo "::notice::Progress calculation successful: ${PERCENTAGE}% (${DONE}/${TOTAL})" + echo "::notice:: Blockers: ${BLOCKER_DONE}/${BLOCKER_TOTAL}, Must Do: ${MUSTDO_DONE}/${MUSTDO_TOTAL}" # Clean up rm -f progress_output.txt @@ -134,113 +145,17 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GIST_TOKEN }} GIST_ID: ${{ secrets.GIST_ID }} + PERCENTAGE: ${{ steps.progress.outputs.percentage }} + DONE: ${{ steps.progress.outputs.done }} + TOTAL: ${{ steps.progress.outputs.total }} + BLOCKER_DONE: ${{ steps.progress.outputs.blocker_done }} + BLOCKER_TOTAL: ${{ steps.progress.outputs.blocker_total }} + MUSTDO_DONE: ${{ steps.progress.outputs.mustdo_done }} + MUSTDO_TOTAL: ${{ steps.progress.outputs.mustdo_total }} run: | - PERCENTAGE="${{ steps.progress.outputs.percentage }}" - DONE="${{ steps.progress.outputs.done }}" - TOTAL="${{ steps.progress.outputs.total }}" - - echo "::notice::Updating badge with: ${PERCENTAGE}% (${DONE}/${TOTAL})" - - # Use integer comparison for more reliable thresholds - PERCENTAGE_INT="${PERCENTAGE%.*}" - # Determine badge color and status with cleaner logic - if [ "$PERCENTAGE_INT" -ge 90 ]; then - COLOR="brightgreen" - STATUS="๐ŸŸข Readying for Deployment" - elif [ "$PERCENTAGE_INT" -ge 60 ]; then - COLOR="yellow" - STATUS="๐ŸŸก Nearing Completion" - elif [ "$PERCENTAGE_INT" -ge 40 ]; then - COLOR="orange" - STATUS="๐ŸŸ  In Development" - else - COLOR="red" - STATUS="๐Ÿ”ด Initial Phase" - fi - - echo "::notice title=Release Progress::${PERCENTAGE}% Complete (${DONE}/${TOTAL}) - ${STATUS}" - - # Create badge JSON for shields.io endpoint with proper escaping (\($done)/\($total)) - BADGE_JSON=$(jq -n \ - --arg percentage "$PERCENTAGE" \ - --arg done "$DONE" \ - --arg total "$TOTAL" \ - --arg color "$COLOR" \ - '{ - "schemaVersion": 1, - "label": "Release Progress", - "message": "\($percentage)%", - "color": $color, - "style": "flat-square" - }') - - # Validate JSON was created successfully - if [ -z "$BADGE_JSON" ] || ! echo "$BADGE_JSON" | jq empty 2>/dev/null; then - echo "::error::Failed to generate valid badge JSON" - exit 1 - fi - - # The filename in the Gist must match the one created manually - GIST_NAME="release-progress-${GITHUB_REPOSITORY##*/}.json" - echo "::notice::Updating Gist file: $GIST_NAME" - - # Create the request payload - REQUEST_PAYLOAD=$(jq -n \ - --arg filename "$GIST_NAME" \ - --argjson content "$BADGE_JSON" \ - '{ - "files": { - ($filename): { - "content": ($content | tostring) - } - } - }') - - # Update the existing Gist with response validation - echo "Updating Gist: ${GIST_ID}" - RESPONSE=$(curl -s -w "\n%{http_code}" -L -X PATCH \ - -H "Authorization: token ${GITHUB_TOKEN}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/gists/${GIST_ID}" \ - -d "$REQUEST_PAYLOAD") - - # Extract HTTP status code (last line) and response body - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - RESPONSE_BODY=$(echo "$RESPONSE" | head -n -1) - - # Validate API response - if [ "$HTTP_CODE" != "200" ]; then - echo "::error::Gist update failed with HTTP status $HTTP_CODE" - echo "Response body: $RESPONSE_BODY" - exit 1 - fi - - echo "::notice::Gist updated successfully" - - # Generate badge URL for use in README - BADGE_URL="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/${GITHUB_REPOSITORY_OWNER}-Bot/${GIST_ID}/raw/${GIST_NAME}" - PROJECT_URL="https://github.com/${GITHUB_REPOSITORY}/projects/39" - - echo "::notice::Badge URL generated: $BADGE_URL" - echo "badge_url=$BADGE_URL" >> $GITHUB_OUTPUT - echo "project_url=$PROJECT_URL" >> $GITHUB_OUTPUT - - # Create enhanced step summary - echo "## ๐Ÿ“Š Release Progress: ${PERCENTAGE}%" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Status:** ${STATUS}" >> $GITHUB_STEP_SUMMARY - echo "**Progress:** ${DONE} of ${TOTAL} release blockers completed" >> $GITHUB_STEP_SUMMARY - echo "**Badge Color:** ${COLOR}" >> $GITHUB_STEP_SUMMARY - echo "**Gist ID:** ${GIST_ID}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Badge URLs" >> $GITHUB_STEP_SUMMARY - echo "**Markdown:** \`[![Release Progress](${BADGE_URL})](${PROJECT_URL})\`" >> $GITHUB_STEP_SUMMARY - echo "**Image Only:** \`![Release Progress](${BADGE_URL})\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Badge JSON Preview" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - echo "$BADGE_JSON" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + # Execute dedicated badge generation script + # This separates concerns: YAML orchestrates, scripts implement logic + bash .github/workflows/update-badge.sh - name: Cleanup on failure if: failure() diff --git a/CITATION.cff b/CITATION.cff index c96341f138c..20d7348c128 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -10,3 +10,7 @@ authors: repository-code: 'https://github.com/HDFGroup/hdf5' url: 'https://www.hdfgroup.org/HDF5/' repository-artifact: 'https://support.hdfgroup.org/downloads/index.html' +identifiers: + - type: doi + value: 10.5281/zenodo.17808614 + description: 'Zenodo DOI for all versions' diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c4532a2b77..f6f2b821586 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,6 +350,12 @@ string (REGEX REPLACE ".*#define[ \t]+H5_VERS_RELEASE[ \t]+([0-9]*).*$" "\\1" H5_VERS_RELEASE ${_h5public_h_contents}) string (REGEX REPLACE ".*#define[ \t]+H5_VERS_SUBRELEASE[ \t]+\"([0-9A-Za-z._-]*)\".*$" "\\1" H5_VERS_SUBRELEASE ${_h5public_h_contents}) +# Validate that H5_VERS_SUBRELEASE was properly extracted (must be a quoted string in H5public.h) +if (H5_VERS_SUBRELEASE STREQUAL _h5public_h_contents) + message (FATAL_ERROR "Failed to extract H5_VERS_SUBRELEASE from src/H5public.h. " + "Ensure H5_VERS_SUBRELEASE is defined as a quoted string literal, " + "e.g., #define H5_VERS_SUBRELEASE \"\" or #define H5_VERS_SUBRELEASE \"-snap0\"") +endif () message (TRACE "VERSION: ${H5_VERS_MAJOR}.${H5_VERS_MINOR}.${H5_VERS_RELEASE}-${H5_VERS_SUBRELEASE}") #----------------------------------------------------------------------------- diff --git a/HDF5Examples/JAVA/README-MAVEN.md b/HDF5Examples/JAVA/README-MAVEN.md index c08d5823e54..d9386ba274c 100644 --- a/HDF5Examples/JAVA/README-MAVEN.md +++ b/HDF5Examples/JAVA/README-MAVEN.md @@ -1,581 +1,178 @@ -# HDF5 Java Examples Maven Integration +# HDF5 Java Examples & Maven Integration -This directory contains Java examples demonstrating the usage of HDF5 Java bindings, organized into categories and deployable as a Maven artifact. +This directory contains Java examples demonstrating the usage of HDF5 Java bindings (JNI and FFM), organized into categories and deployable as a Maven artifact. ## Directory Structure -``` +```text HDF5Examples/JAVA/ โ”œโ”€โ”€ H5D/ # Dataset operations examples โ”œโ”€โ”€ H5T/ # Datatype operations examples โ”œโ”€โ”€ H5G/ # Group operations examples โ”œโ”€โ”€ TUTR/ # Tutorial examples -โ”œโ”€โ”€ pom-examples.xml.in # Maven POM template for examples -โ”œโ”€โ”€ CMakeLists.txt # CMake configuration -โ””โ”€โ”€ README-MAVEN.md # This file +โ”œโ”€โ”€ pom-examples.xml.in # Maven POM template +โ””โ”€โ”€ README.md # This file ``` -## Maven Artifact Usage +## 1. Getting Started -### Using Examples as Dependency +To run these examples, you need two things: +1. **The Java Dependencies** (Managed by Maven) +2. **The Native HDF5 Libraries** (Installed on your OS) + +### Step 1: Maven Dependency Configuration +Add the examples and the specific platform binding to your `pom.xml`. + +**Note:** Replace `X.Y.Z` with your target HDF5 version (e.g., `1.14.0`). ```xml - - org.hdfgroup - hdf5-java-examples - 2.0.0 - + + + + org.hdfgroup + hdf5-java-examples + X.Y.Z + + + + + + + org.hdfgroup + hdf5-java + X.Y.Z + linux-x86_64 + + + + + org.hdfgroup + hdf5-java + X.Y.Z + windows-x86_64 + + + + + org.hdfgroup + hdf5-java + X.Y.Z + macos-x86_64 + + ``` -### Platform-Specific Dependencies +### Step 2: Native Library Configuration (Crucial) +Maven provides the Java JARs, but **it does not install the system-level HDF5 libraries** required for execution. You must have the HDF5 binaries installed or built on your system. -The examples depend on platform-specific HDF5 Java libraries: +**If you see `UnsatisfiedLinkError: no hdf5_java`, the application cannot find your native HDF5 installation.** -```xml - - - org.hdfgroup - hdf5-java - 2.0.0 - linux-x86_64 - +#### Method A: Using Environment Variables (Recommended) +If you have built HDF5 from source or installed it to a custom location, point to it: - - - org.hdfgroup - hdf5-java - 2.0.0 - windows-x86_64 - +* **Linux/macOS:** `export LD_LIBRARY_PATH=/path/to/hdf5/lib:$LD_LIBRARY_PATH` +* **Windows:** Add `C:\path\to\hdf5\bin` to your system `PATH`. - - - org.hdfgroup - hdf5-java - 2.0.0 - macos-x86_64 - +#### Method B: System Installation +If you installed via package manager (e.g., `apt install libhdf5-dev` or `brew install hdf5`), standard paths generally work automatically. - - - org.hdfgroup - hdf5-java - 2.0.0 - macos-aarch64 - -``` +--- -## Building Examples with Maven +## 2. Building and Running Examples ### Compile All Examples - ```bash cd HDF5Examples/JAVA mvn compile -f pom-examples.xml ``` -### Run Representative Examples +### Run a Specific Example +To run an example (e.g., `H5Ex_D_ReadWrite`), use the `exec:java` goal. Ensure your native library path is set (see Step 2 above). +**JNI (Standard):** ```bash -mvn test -Prun-examples -f pom-examples.xml +mvn exec:java -Dexec.mainClass="H5Ex_D_ReadWrite" -f pom-examples.xml ``` -### Create Examples JAR +**FFM (Foreign Function & Memory - Requires Java 25+):** +```bash +# Note: Ensure you are using a JDK supporting FFM +mvn exec:java -Dexec.mainClass="H5Ex_D_ReadWrite" -f pom-examples.xml +``` +### Create a Standalone JAR ```bash mvn package -f pom-examples.xml ``` +This generates `target/hdf5-java-examples-{version}.jar` containing all compiled examples. -This creates: -- `hdf5-java-examples-{version}.jar` - Compiled examples -- `hdf5-java-examples-{version}-sources.jar` - Source code -- `hdf5-java-examples-{version}-javadoc.jar` - Documentation +--- -## Testing Maven Artifacts +## 3. Example Categories -Two standalone scripts are provided to test HDF5 Maven artifacts against the examples in this directory: +| Directory | Description | Key Concepts | +| :--- | :--- | :--- | +| **H5D** | **Dataset Operations** | Read/write, chunking, filters (gzip, nbit), fill values, allocation. | +| **H5T** | **Datatype Operations** | Compound types, arrays, enums, opaque types, variable-length strings. | +| **H5G** | **Group Operations** | Creating groups, iteration, hierarchy traversal, links. | +| **TUTR** | **Tutorials** | Step-by-step introductory examples. | -### test-maven-jni.sh - Test JNI Implementation +--- -Tests the JNI (Java Native Interface) implementation, compatible with Java 11+. +## 4. Repo Verification & Testing Scripts +*(For Maintainers and CI/CD)* + +We provide scripts to verify the integrity of Maven artifacts against these examples. These scripts simulate a clean environment to ensure the JARs are structured correctly. + +### Script: `test-maven-jni.sh` (Standard) +Tests the JNI implementation (Requires Java 11+). **Usage:** ```bash -./test-maven-jni.sh [VERSION] [REPOSITORY_URL] [BUILD_DIR] +./test-maven-jni.sh [VERSION] [REPO_URL] [BUILD_DIR] ``` -**Examples:** -```bash -# Test latest snapshot from HDFGroup -./test-maven-jni.sh 2.0.1-SNAPSHOT +**Workflow:** +1. Downloads the `hdf5-java-jni` artifact. +2. Compiles 55+ examples. +3. **Note:** During this *artifact structure test*, an `UnsatisfiedLinkError` regarding native libraries is **expected behavior** if the runner lacks a local HDF5 installation. The test verifies that the Java classes load, not that the local machine has the binaries. -# Test specific version from custom repository -./test-maven-jni.sh 2.0.0 https://maven.pkg.github.com/myorg/hdf5 - -# Use custom build directory -./test-maven-jni.sh 2.0.1-SNAPSHOT https://maven.pkg.github.com/HDFGroup/hdf5 /tmp/test -``` - -**What it does:** -1. Downloads `hdf5-java-jni` artifact from Maven repository -2. Verifies JAR contains HDF5 classes (not just dependencies) -3. Compiles all 55 HDF5 v2.0+ examples from `compat/` subdirectories -4. Executes 12 comprehensive tests covering major HDF5 features -5. Reports results with detailed pass/fail summary - -**Prerequisites:** -- Java 21 or later (class version 65.0) -- Maven 3.6.0 or later -- GitHub authentication (for GitHub Packages) -- Optional: HDF5 native libraries or `HDF5_HOME` for execution tests - -### test-maven-ffm.sh - Test FFM Implementation - -Tests the FFM (Foreign Function & Memory) implementation, requires Java 25+. +### Script: `test-maven-ffm.sh` (Experimental) +Tests the Foreign Function & Memory implementation (Requires Java 25+). **Usage:** ```bash -./test-maven-ffm.sh [VERSION] [REPOSITORY_URL] [BUILD_DIR] +./test-maven-ffm.sh [VERSION] [REPO_URL] [BUILD_DIR] ``` -**Examples:** -```bash -# Test FFM snapshot -./test-maven-ffm.sh 2.0.1-SNAPSHOT - -# Test specific version -./test-maven-ffm.sh 2.0.0-3 https://maven.pkg.github.com/HDFGroup/hdf5 -``` - -**What it does:** -1. Downloads `hdf5-java-ffm` artifact from Maven repository -2. Verifies JAR contains FFM bindings (`org.hdfgroup.javahdf5.*`) -3. Compiles 52 HDF5 v2.0+ examples from `compat/` subdirectories -4. Executes 12 comprehensive tests covering major HDF5 features -5. Reports results with detailed pass/fail summary - -**Note:** 3 callback-based examples are excluded (H5Ex_G_Visit, H5Ex_G_Intermediate, H5Ex_G_Traverse) as FFM callback handling differs from JNI and these examples have not yet been adapted. - -**Prerequisites:** -- Java 25 or later (class version 69.0) -- Maven 3.6.0 or later -- GitHub authentication (for GitHub Packages) -- Optional: HDF5 native libraries or `HDF5_HOME` for execution tests - -### Build Directory Pattern - -Both scripts use a separate build directory to keep the source tree clean: - -**Default locations:** -- JNI: `HDF5Examples/JAVA/build/maven-test-jni/` -- FFM: `HDF5Examples/JAVA/build/maven-test-ffm/` - -**Generated files:** -``` -build/ -โ”œโ”€โ”€ maven-test-jni/ -โ”‚ โ”œโ”€โ”€ pom-examples.xml # Generated Maven POM -โ”‚ โ”œโ”€โ”€ target/ # Compiled classes -โ”‚ โ”‚ โ””โ”€โ”€ classes/ -โ”‚ โ””โ”€โ”€ *.h5 # Output HDF5 files -โ””โ”€โ”€ maven-test-ffm/ - โ”œโ”€โ”€ pom-examples.xml - โ”œโ”€โ”€ target/ - โ””โ”€โ”€ *.h5 -``` - -**Benefits:** -- โœ… Source tree stays clean (no generated files) -- โœ… Easy cleanup: `rm -rf build/` -- โœ… Multiple parallel tests possible -- โœ… CMake-like out-of-source build pattern +**Notes:** +* Excludes callback-based examples (`H5Ex_G_Visit`, etc.) as FFM callback handling differs from JNI. +* Requires valid GitHub authentication if pulling from GitHub Packages. ### GitHub Authentication - -For testing artifacts from GitHub Packages, authentication is required: - -**Option 1: GitHub CLI (Recommended)** +For testing artifacts hosted on GitHub Packages: ```bash +# Recommended: GitHub CLI gh auth login gh auth refresh --scopes read:packages ``` -Scripts automatically detect GitHub CLI authentication. +--- -**Option 2: Maven settings.xml** -```bash -# Scripts can create settings.xml automatically if gh is authenticated -# Or create manually: -cat > ~/.m2/settings.xml < - - - github-hdfgroup-hdf5 - YOUR_GITHUB_USERNAME - YOUR_GITHUB_TOKEN - - - -EOF -``` +## 5. Troubleshooting -### Running Additional Examples +**Q: `UnsatisfiedLinkError: no hdf5_java in java.library.path`** +* **Cause:** Java found the classes, but not the native C library. +* **Fix:** Ensure HDF5 is installed and `LD_LIBRARY_PATH` (Linux), `DYLD_LIBRARY_PATH` (macOS), or `PATH` (Windows) includes the folder containing `libhdf5.so`, `libhdf5.dylib`, or `hdf5.dll`. -After initial test succeeds, you can run more examples: +**Q: `package org.hdfgroup.hdf5 does not exist`** +* **Cause:** Maven dependencies are not resolving. +* **Fix:** Run `mvn dependency:resolve` and check your `pom.xml` version matches the available release. -**JNI:** -```bash -cd build/maven-test-jni -mvn exec:java -Dexec.mainClass="H5Ex_T_String" -f pom-examples.xml -``` - -**FFM:** -```bash -cd build/maven-test-ffm -mvn exec:java -Dexec.mainClass="H5Ex_T_String" -f pom-examples.xml -``` - -### Cleanup - -**Remove single test build:** -```bash -rm -rf build/maven-test-jni -rm -rf build/maven-test-ffm -``` - -**Remove all test builds:** -```bash -rm -rf build/ -``` - -**Clean with Maven (keeps directory structure):** -```bash -mvn clean -f build/maven-test-jni/pom-examples.xml -mvn clean -f build/maven-test-ffm/pom-examples.xml -``` - -### Troubleshooting Test Scripts - -**"Failed to download artifact"** -- Check GitHub authentication: `gh auth status` -- Verify repository URL is correct -- Ensure version exists in repository - -**"JAR does not contain HDF5 classes"** -- Indicates incomplete Maven artifact (build issue) -- This is what the verification step catches! -- Report to maintainers if public artifact is incomplete - -**"Java version too old"** -- JNI requires Java 11+ -- FFM requires Java 25+ -- Check: `java -version` - -**"UnsatisfiedLinkError: no hdf5_java"** -- This is expected during Maven-only testing -- Indicates JAR structure is correct -- Native libraries would be needed for full execution - -## Example Categories - -### H5D - Dataset Operations -- Basic read/write operations -- Chunking and compression -- External storage -- Fill values and allocation -- Filters (gzip, checksum, nbit, etc.) - -### H5T - Datatype Operations -- Array datatypes -- Compound datatypes -- Enumerated datatypes -- Opaque datatypes -- String handling -- Variable-length datatypes - -### H5G - Group Operations -- Creating and managing groups -- Group iteration -- Intermediate group creation -- Group hierarchy traversal - -### TUTR - Tutorial Examples -- Step-by-step learning examples -- Basic concepts demonstration -- Progressive complexity - -## CI/CD Integration - -The examples are automatically tested in CI: - -1. **Compilation Testing**: All examples must compile successfully -2. **Execution Testing**: Examples are run and output validated -3. **Cross-Platform Testing**: Tested on Linux, Windows, and macOS -4. **Maven Integration Testing**: Tests against staging Maven artifacts - -### Maven-Only Testing Behavior - -**Expected Native Library Errors**: During Maven-only testing (without HDF5 installation), examples will compile successfully but fail at runtime with: -``` -UnsatisfiedLinkError: no hdf5_java in java.library.path -``` - -This is **expected behavior** and indicates: -- โœ… **JAR structure is correct** -- โœ… **Dependencies resolve properly** -- โœ… **Compilation succeeds** -- โš ๏ธ **Native HDF5 libraries not available** (expected in Maven-only environment) - -### Running Examples Successfully - -To actually execute examples (not just compile them), you need HDF5 native libraries installed: - -#### Option 1: Install HDF5 from Package Manager (Recommended) - -**Linux (Ubuntu/Debian):** -```bash -sudo apt-get update -sudo apt-get install libhdf5-dev hdf5-tools -``` - -**Linux (Fedora/RHEL):** -```bash -sudo dnf install hdf5 hdf5-devel -``` - -**macOS (Homebrew):** -```bash -brew install hdf5 -``` - -**Windows:** -- Download pre-built binaries from [HDF Group Downloads](https://www.hdfgroup.org/downloads/hdf5/) -- Set `HDF5_HOME` environment variable to installation directory -- Alternatively, add HDF5 `bin` directory to system PATH - -#### Option 2: Build HDF5 from Source - -Build HDF5 with Java support enabled: - -```bash -# Clone HDF5 repository -git clone https://github.com/HDFGroup/hdf5.git -cd hdf5 - -# Build with Java (JNI) -cmake --preset ci-StdShar-GNUC --fresh -cmake --build build/ci-StdShar-GNUC -sudo cmake --install build/ci-StdShar-GNUC - -# Or build with Java (FFM) - requires Java 25+ -cmake --preset ci-StdShar-GNUC-FFM --fresh -cmake --build build/ci-StdShar-GNUC-FFM -sudo cmake --install build/ci-StdShar-GNUC-FFM -``` - -#### Option 3: Set HDF5_HOME (Recommended for Custom Installations) - -If HDF5 is installed in a non-standard location, set `HDF5_HOME`: - -**Linux/macOS:** -```bash -# Point to HDF5 installation directory -export HDF5_HOME=/path/to/hdf5/installation - -# Then run examples (scripts automatically find libraries) -cd HDF5Examples/JAVA -./test-maven-jni.sh 2.0.1-SNAPSHOT - -# Or run Maven directly -cd build/maven-test-jni -mvn exec:java -Dexec.mainClass="H5Ex_D_ReadWrite" -f pom-examples.xml -``` - -**Windows (PowerShell):** -```powershell -# Set HDF5_HOME environment variable -$env:HDF5_HOME = "C:\path\to\hdf5\installation" - -# Run Maven examples -cd build\maven-test-jni -mvn exec:java -Dexec.mainClass="H5Ex_D_ReadWrite" -f pom-examples.xml -``` - -**Windows (CMD):** -```cmd -REM Set HDF5_HOME environment variable -set HDF5_HOME=C:\path\to\hdf5\installation - -REM Run Maven examples -cd build\maven-test-jni -mvn exec:java -Dexec.mainClass="H5Ex_D_ReadWrite" -f pom-examples.xml -``` - -**Note:** The test scripts automatically add `${HDF5_HOME}/lib` (Unix) or `%HDF5_HOME%\bin` (Windows) to the library path. - -#### Option 4: Specify Library Path in Java (Advanced) - -**Note:** This is an advanced option. Prefer using `HDF5_HOME` (Option 3) instead. - -```bash -# Run with explicit library path -java -Djava.library.path=/path/to/hdf5/lib \ - -cp "target/classes:~/.m2/repository/org/hdfgroup/hdf5-java-jni/2.0.1-SNAPSHOT/*" \ - H5Ex_D_ReadWrite -``` - -#### Verify Native Libraries Are Found - -After installing HDF5, verify the libraries are accessible: - -**Linux:** -```bash -# Check library is in system path -ldconfig -p | grep hdf5 - -# Or find library location -find /usr -name "libhdf5.so*" 2>/dev/null -``` - -**macOS:** -```bash -# Check library location -find /usr/local -name "libhdf5*.dylib" 2>/dev/null -``` - -**Windows:** -```cmd -# Check PATH includes HDF5 bin directory -echo %PATH% - -# Verify DLL exists -where hdf5.dll -``` - -#### Running Examples After Library Installation - -Once native libraries are installed, examples should run successfully: - -**JNI Examples:** -```bash -cd build/maven-test-jni -mvn exec:java -Dexec.mainClass="H5Ex_D_ReadWrite" -f pom-examples.xml - -# Expected output: -# Dataset successfully created and written -# Data read from dataset: [1, 2, 3, 4, ...] -``` - -**FFM Examples:** -```bash -cd build/maven-test-ffm -mvn exec:java -Dexec.mainClass="H5Ex_D_ReadWrite" -f pom-examples.xml - -# Expected output: -# Dataset successfully created and written -# Data read from dataset: [1, 2, 3, 4, ...] -``` - -#### Why Maven Artifacts Don't Include Native Libraries - -Maven artifacts contain only: -- โœ… Java bytecode (.class files) -- โœ… Java source code (in -sources.jar) -- โœ… Javadoc (in -javadoc.jar) - -They do **not** include: -- โŒ Native shared libraries (.so, .dll, .dylib) -- โŒ Platform-specific binaries - -**Reason:** Native libraries are platform-specific and typically hundreds of MB. Maven artifacts should be small (~2-5 MB) and platform-independent where possible. The JNI/FFM bindings provide the Java interface, but you must install the native HDF5 libraries separately. - -### Pattern-Based Output Validation - -Examples are validated using pattern matching for: -- **Success patterns**: `dataset|datatype|group|success|created|written|read` -- **Expected failures**: `UnsatisfiedLinkError.*hdf5_java.*java.library.path` (Maven-only testing) -- **Unexpected failures**: Other errors indicating JAR or compilation issues - -### Non-Blocking Failures - -- Individual example failures don't block CI -- Native library errors are treated as **expected** in Maven-only testing -- Multi-platform failures for the same example trigger alerts -- Results are uploaded as artifacts for debugging - -## Development Workflow - -### Adding New Examples - -1. Add `.java` file to appropriate category directory -2. Update CMakeLists.txt if needed -3. Examples are automatically discovered by Maven and CI - -### Testing Changes - -```bash -# Test specific category -cd H5D && javac -cp "../../../maven-artifacts/*.jar" *.java - -# Run example -java -cp ".:../../../maven-artifacts/*" H5Ex_D_ReadWrite -``` - -### Expected Output Files - -Expected outputs for validation are stored in version control: -- `tfiles/min_hdf_version/H5Ex_D_ReadWrite.txt` -- Pattern-based validation for flexibility -- Platform-specific outputs handled automatically - -## Deployment - -Examples are deployed alongside main HDF5 Maven artifacts: - -1. Built during Maven staging workflow -2. Tested in dedicated Java examples workflow -3. Deployed to GitHub Packages -4. Available for Maven Central deployment - -## Usage in Projects - -### Quick Start - -```java -import hdf.hdf5lib.H5; -import hdf.hdf5lib.HDF5Constants; - -// Use examples as reference -// Source code available in JAR resources at examples/ -``` - -### Maven Archetype (Future) - -```bash -mvn archetype:generate \ - -DgroupId=com.example \ - -DartifactId=my-hdf5-project \ - -DarchetypeGroupId=org.hdfgroup \ - -DarchetypeArtifactId=hdf5-java-archetype -``` - -## Troubleshooting - -### Common Issues - -1. **Platform Mismatch**: Ensure correct classifier for your platform -2. **Native Library Path**: HDF5 native libraries loaded automatically -3. **Java Version**: Requires Java 11 or higher - -### Debug Information - -Examples JAR includes manifest entries: -- `HDF5-Version`: HDF5 library version -- `HDF5-Platform`: Target platform -- `Examples-Count`: Number of included examples +**Q: "Java version too old"** +* **Cause:** FFM examples require cutting-edge Java versions. +* **Fix:** Use JNI for standard production environments (Java 11+). Only use FFM if you are targeting Java 25+. ## Support - -- GitHub Issues: https://github.com/HDFGroup/hdf5/issues -- Documentation: https://support.hdfgroup.org/documentation/ -- Examples Source: Included in JAR resources +* **Issues:** [GitHub Issue Tracker](https://github.com/HDFGroup/hdf5/issues) +* **Documentation:** [HDF Support Portal](https://support.hdfgroup.org/documentation/) diff --git a/README.md b/README.md index 7c8aae66a54..4624f0cf60d 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,16 @@ -HDF5 version 2.0.1 currently under development - -> [!WARNING] -> **Heads Up: HDF5 Dropped Autotools March 10th** -> -> It's happenedโ€”the day we've all been dreadingโ€”or eagerly anticipating, depending on your perspective. Yes, we have switched to CMake-only builds in HDF5. -> -> The [PR stripping all autotools](https://github.com/HDFGroup/hdf5/pull/5308) was merged into the "develop" branch on **March 10, 2025**. Starting with HDF5 2.0, *only* the CMake build system is supported. +
![HDF5 Logo][u3] -[![develop cmake build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/call-workflows.yml?branch=develop&label=HDF5%20develop%20CMake%20CI)](https://github.com/HDFGroup/hdf5/actions/workflows/call-workflows.yml?query=branch%3Adevelop) -[![HDF5 develop daily build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/daily-schedule.yml?branch=develop&label=HDF5%20develop%20daily%20build)](https://github.com/HDFGroup/hdf5/actions/workflows/daily-schedule.yml?query=branch%3Adevelop) -[![HDF-EOS5 build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/hdfeos5.yml?branch=develop&label=HDF-EOS5)](https://github.com/HDFGroup/hdf5/actions/workflows/hdfeos5.yml?query=branch%3Adevelop) -[![netCDF build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/netcdf.yml?branch=develop&label=netCDF)](https://github.com/HDFGroup/hdf5/actions/workflows/netcdf.yml?query=branch%3Adevelop) -[![h5py build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/h5py.yml?branch=develop&label=h5py)](https://github.com/HDFGroup/hdf5/actions/workflows/h5py.yml?query=branch%3Adevelop) -[![CVE regression](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/cve.yml?branch=develop&label=CVE)](https://github.com/HDFGroup/hdf5/actions/workflows/cve.yml?query=branch%3Adevelop) -[![HDF5 VOL connectors build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/vol.yml?branch=develop&label=HDF5-VOL)](https://github.com/HDFGroup/hdf5/actions/workflows/vol.yml?query=branch%3Adevelop) -[![HDF5 VFD build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/vfd.yml?branch=develop&label=HDF5-VFD)](https://github.com/HDFGroup/hdf5/actions/workflows/vfd.yml?query=branch%3Adevelop) [![BSD](https://img.shields.io/badge/License-BSD-blue.svg)](https://github.com/HDFGroup/hdf5/blob/develop/LICENSE) -[![OSS-Fuzz Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/hdf5.svg)](https://oss-fuzz-build-logs.storage.googleapis.com/index.html#hdf5) -[![Link Checker Status](https://github.com/HDFGroup/hdf5/actions/workflows/linkchecker.yml/badge.svg)](https://github.com/HDFGroup/hdf5/actions/workflows/linkchecker.yml) -[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.17808614.svg)](https://doi.org/10.5281/zenodo.17808614) +[![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.17808614-blue)](https://doi.org/10.5281/zenodo.17808614) +[![develop cmake build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/call-workflows.yml?branch=develop&label=CMake%20CI)](https://github.com/HDFGroup/hdf5/actions/workflows/call-workflows.yml?query=branch%3Adevelop) -[HPC configure/build/test results](https://my.cdash.org/index.php?project=HDF5) +
-*Please refer to the release_docs/INSTALL file for installation/usage instructions.* +--- + +## What is HDF5? This repository contains a high-performance library's source code and a file format specification that implements the HDF5ยฎ data model. The model has been adopted across @@ -33,115 +19,157 @@ in science, engineering, and research communities worldwide. The HDF Group is the developer, maintainer, and steward of HDF5 software. Find more information about The HDF Group, the HDF5 Community, and other HDF5 software projects, -tools, and services at [The HDF Group's website](https://www.hdfgroup.org/). +tools, and services at [The HDF Group's website](https://www.hdfgroup.org/). + +## Quick Start + +- **New to HDF5?** Start with the [INSTALL](release_docs/INSTALL) guide for compilation and installation instructions. + +- **Ready to build?** See [INSTALL_CMake.txt](release_docs/INSTALL_CMake.txt) for CMake-based builds. + +- **Running on HPC?** Check out [README_HPC.md](release_docs/README_HPC.md) for parallel HDF5 configuration. + +## Table of Contents + +- [What is HDF5?](#what-is-hdf5) +- [Quick Start](#quick-start) +- [Documentation](#documentation) +- [Help and Support](#help-and-support) +- [Forum and News](#forum-and-news) +- [Release Schedule](#release-schedule) +- [Downloads and Source Code](#downloads-and-source-code) +- [Java Maven Artifacts](#java-maven-artifacts) +- [Contributing](#contributing) +- [How to Cite HDF5](#how-to-cite-hdf5) +- [Build Status](#build-status) + +## Documentation -DOCUMENTATION -------------- Documentation for all HDF software is available at: - - https://support.hdfgroup.org/documentation/index.html - -The latest documentation for the HDF5 library can be found at: - - https://support.hdfgroup.org/documentation/hdf5/latest +- **All HDF Documentation**: https://support.hdfgroup.org/documentation/index.html +- **Latest HDF5 Library**: https://support.hdfgroup.org/documentation/hdf5/latest See the [CHANGELOG.md][u1] file in the [release_docs/][u4] directory for information specific to the features and updates included in this release of the library. -Several more files are located within the [release_docs/][u4] directory with specific -details for several common platforms and configurations. -- INSTALL - Start Here. General instructions for compiling and installing the library or using an installed library -- INSTALL_CMAKE - instructions for building with CMake (Kitware.com) -- README_HPC.md - instructions for building and configuring Parallel HDF5 on HPC systems -- INSTALL_Windows and INSTALL_Cygwin - MS Windows installations. -- USING_HDF5_CMake - Build and Install HDF5 Applications with CMake -- USING_CMake_Examples - Build and Test HDF5 Examples with CMake +### Platform-Specific Guides +Several files in the [release_docs/][u4] directory provide platform-specific details: +| File | Description | +|------|-------------| +| [INSTALL](release_docs/INSTALL) | General compilation and installation instructions (start here) | +| [INSTALL_CMake.txt](release_docs/INSTALL_CMake.txt) | Building with CMake | +| [README_HPC.md](release_docs/README_HPC.md) | Building and configuring Parallel HDF5 on HPC systems | +| [INSTALL_Windows.txt](release_docs/INSTALL_Windows.txt) | Windows installation | +| [INSTALL_Cygwin.txt](release_docs/INSTALL_Cygwin.txt) | Cygwin installation | +| [USING_HDF5_CMake.txt](release_docs/USING_HDF5_CMake.txt) | Building HDF5 applications with CMake | +| [USING_CMake_Examples.txt](release_docs/USING_CMake_Examples.txt) | Building and testing HDF5 examples with CMake | -HELP AND SUPPORT ----------------- -The HDF Group staffs a free Help Desk accessible at [https://help.hdfgroup.org](https://help.hdfgroup.org) and also monitors the [Forum](https://forum.hdfgroup.org). Our free support service is community-based and handled as time allows. Weโ€™ll do our best to respond to your question as soon as possible, but please note that response times may vary depending on the complexity of the issue and staff availability. +## Help and Support + +The HDF Group staffs a free Help Desk accessible at https://help.hdfgroup.org and also monitors the [Forum](https://forum.hdfgroup.org). Our free support service is community-based and handled as time allows. We'll do our best to respond to your question as soon as possible, but please note that response times may vary depending on the complexity of the issue and staff availability. If you're interested in guaranteed response and resolution times, a dedicated technical account manager, and more benefits (all while supporting the open-source work of The HDF Group), please check out [Priority Support](https://www.hdfgroup.org/solutions/priority-support/). +## Forum and News - -FORUM and NEWS --------------- The [HDF Forum](https://forum.hdfgroup.org) is provided for public announcements, technical questions, and discussions of interest to the general HDF5 Community. - - News and Announcements - https://forum.hdfgroup.org/c/news-and-announcements-from-the-hdf-group - - - HDF5 Topics - https://forum.hdfgroup.org/c/hdf5 +- [News and Announcements](https://forum.hdfgroup.org/c/news-and-announcements-from-the-hdf-group) +- [HDF5 Topics](https://forum.hdfgroup.org/c/hdf5) These forums are provided as an open and public service for searching and reading. Posting requires completing a simple registration and allows one to join in the -conversation. Please read the [instructions](https://forum.hdfgroup.org/t/quickstart-guide-welcome-to-the-new-hdf-forum -) for more information on how to get started. +conversation. Please read the [quickstart guide](https://forum.hdfgroup.org/t/quickstart-guide-welcome-to-the-new-hdf-forum) for more information on how to get started. -RELEASE SCHEDULE ----------------- +## Release Schedule -![HDF5 release schedule][u2] +![HDF5 release schedule][u2] HDF5 does not follow a regular release schedule. Instead, updates are based on the introduction of new features and the resolution of bugs. However, we aim to have at least one annual release for each maintenance branch. -| Release | New Features | -| ------- | ------------ | -| 2.0.0 | Drop Autotools support, drop the HDF5 <--> GIF tools, add complex number support, update library defaults (cache sizes, etc.) | -| FUTURE | Multi-threaded HDF5, crashproofing / metadata journaling, Full (VFD) SWMR, encryption, digital signatures, sparse datasets, improved storage for variable-length datatypes, better Unicode support (especially on Windows) | - ### Release Progress -[![Release Progress](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/HDFGroup-Bot/0ad2eabb63b28eb90d69f5e5b2c1496f/raw/release-progress-hdf5.json)](https://github.com/orgs/HDFGroup/projects/39/views/24) +[![Release Blockers](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/brtnfld/a4fbdde293677bf3f63f3cfd3aef6b44/raw/release-blockers.json)](https://github.com/orgs/HDFGroup/projects/39) -The badge above shows the current progress of release-blocking issues with colors that reflect completion status: +[![Release Must Do](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/brtnfld/8a077a919d048ce5dc9f56350bec10bc/raw/release-mustdo.json)](https://github.com/orgs/HDFGroup/projects/39) -- **๐ŸŸข Green (90%+)**: Readying for Deployment - most blockers completed -- **๐ŸŸก Yellow (60-89%)**: Nearing Completion - on track for release -- **๐ŸŸ  Orange (40-59%)**: In Development - attention needed -- **๐Ÿ”ด Red (<40%)**: Initial Phase - significant blockers remain +The badges above show the current progress of **release-blocking** and **must-do** issues with colors that reflect completion status: -Click the badge to view the detailed project board with current release-blocking issues. +- ๐ŸŸข **Green (90%+)**: Readying for Deployment - most issues completed +- ๐ŸŸก **Yellow (60-89%)**: Nearing Completion - on track for release +- ๐ŸŸ  **Orange (40-59%)**: In Development - attention needed +- ๐Ÿ”ด **Red (<40%)**: Initial Phase - significant issues remain -SNAPSHOTS, PREVIOUS RELEASES AND SOURCE CODE --------------------------------------------- -Periodically development code snapshots are provided at the following URL: +Click the badges to view the detailed project board with current release items. - https://github.com/HDFGroup/hdf5/releases/tag/snapshot +## Downloads and Source Code -Source packages for current and previous releases are located at: +### Snapshots and Releases - [Latest HDF5 release](https://github.com/HDFGroup/hdf5/releases) - [Previous releases](https://support.hdfgroup.org/archive/support/ftp/HDF5/releases/index.html) +- **Development Snapshots**: https://github.com/HDFGroup/hdf5/releases/tag/snapshot +- **Latest Release**: https://github.com/HDFGroup/hdf5/releases +- **Previous Releases**: https://support.hdfgroup.org/archive/support/ftp/HDF5/releases/index.html +- **Development Code**: https://github.com/HDFGroup/hdf5.git -Maven artifacts for Java bindings and examples are available at: +### HPC Testing Results - GitHub Packages: - https://maven.pkg.github.com/HDFGroup/hdf5 +[View HPC configure/build/test results on CDash](https://my.cdash.org/index.php?project=HDF5) - Maven Central (coming soon): - https://central.sonatype.com/artifact/org.hdfgroup/hdf5-java +## Java Maven Artifacts -Java Examples Maven Integration: - - **org.hdfgroup:hdf5-java** - HDF5 Java bindings with platform-specific JARs (linux-x86_64, windows-x86_64, macos-x86_64, macos-aarch64) - - **org.hdfgroup:hdf5-java-examples** - Complete collection of Java examples (platform-independent) - - Cross-platform CI/CD testing and deployment - - Comprehensive Maven integration with automated testing - - See HDF5Examples/JAVA/README-MAVEN.md for complete usage instructions +HDF5 Java bindings and examples are available as Maven artifacts. For detailed usage instructions including dependency configuration, repository setup, and platform-specific builds, see [HDF5Examples/JAVA/README-MAVEN.md](HDF5Examples/JAVA/README-MAVEN.md). -Development code is available at our Github location: +## Contributing - https://github.com/HDFGroup/hdf5.git +We welcome contributions to HDF5! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated. + +### How to Contribute + +1. **Report Issues**: Use our [GitHub Issues](https://github.com/HDFGroup/hdf5/issues) to report bugs or request features +2. **Submit Pull Requests**: Fork the repository, make your changes, and submit a PR +3. **Join Discussions**: Participate in the [HDF Forum](https://forum.hdfgroup.org) + +For detailed contribution guidelines, please contact us through the [Help Desk](https://help.hdfgroup.org). + +## How to Cite HDF5 + +If you use HDF5 in your research, please cite it. This repository includes a [`CITATION.cff`](CITATION.cff) file containing standard citation metadata. + +**Quick DOI:** [10.5281/zenodo.17808614](https://doi.org/10.5281/zenodo.17808614) + +## Build Status + +
+Click to expand detailed build status + +### Continuous Integration + +[![HDF5 develop daily build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/daily-schedule.yml?branch=develop&label=Daily%20Build)](https://github.com/HDFGroup/hdf5/actions/workflows/daily-schedule.yml?query=branch%3Adevelop) +[![CVE regression](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/cve.yml?branch=develop&label=CVE%20Tests)](https://github.com/HDFGroup/hdf5/actions/workflows/cve.yml?query=branch%3Adevelop) +[![OSS-Fuzz Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/hdf5.svg)](https://oss-fuzz-build-logs.storage.googleapis.com/index.html#hdf5) +[![Link Checker Status](https://github.com/HDFGroup/hdf5/actions/workflows/linkchecker.yml/badge.svg)](https://github.com/HDFGroup/hdf5/actions/workflows/linkchecker.yml) + +### Integration Testing + +[![HDF-EOS5 build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/hdfeos5.yml?branch=develop&label=HDF-EOS5)](https://github.com/HDFGroup/hdf5/actions/workflows/hdfeos5.yml?query=branch%3Adevelop) +[![netCDF build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/netcdf.yml?branch=develop&label=netCDF)](https://github.com/HDFGroup/hdf5/actions/workflows/netcdf.yml?query=branch%3Adevelop) +[![h5py build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/h5py.yml?branch=develop&label=h5py)](https://github.com/HDFGroup/hdf5/actions/workflows/h5py.yml?query=branch%3Adevelop) + +### VOL and VFD Testing + +[![HDF5 VOL connectors build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/vol.yml?branch=develop&label=VOL%20Connectors)](https://github.com/HDFGroup/hdf5/actions/workflows/vol.yml?query=branch%3Adevelop) +[![HDF5 VFD build status](https://img.shields.io/github/actions/workflow/status/HDFGroup/hdf5/vfd.yml?branch=develop&label=VFD%20Tests)](https://github.com/HDFGroup/hdf5/actions/workflows/vfd.yml?query=branch%3Adevelop) + +
+ +--- [u1]: https://github.com/HDFGroup/hdf5/blob/develop/release_docs/CHANGELOG.md [u2]: https://github.com/HDFGroup/hdf5/blob/develop/release_docs/img/release-schedule.png [u3]: https://github.com/HDFGroup/hdf5/blob/develop/doxygen/img/HDF5.png [u4]: https://github.com/HDFGroup/hdf5/blob/develop/release_docs - diff --git a/bin/README.md b/bin/README.md index a32eccc8309..842e1c4b126 100644 --- a/bin/README.md +++ b/bin/README.md @@ -9,7 +9,6 @@ |`genparser`|Creates the flex/bison-based parser files in the high-level library| |`h5cc.in`|Input file from which h5cc is created| |`h5redeploy.in`|Input file from which h5redeploy is created| -|`h5vers`|Updates the library version number| |`make_err`|Generates the H5E header files| |`make_vers`|Generates H5version.h| |`make_overflow`|Generates H5overflow.h| diff --git a/bin/h5vers b/bin/h5vers deleted file mode 100755 index 4ad2d193a63..00000000000 --- a/bin/h5vers +++ /dev/null @@ -1,538 +0,0 @@ -#! /bin/sh -perl -x -S $0 "$@" -exit - -#! perl -require 5.003; -use strict; - -# 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. -# - -### Purpose -# Increments the hdf5 version number by changing the value of -# constants in the src/H5public.h file. The new version number is -# printed on the standard output. An alternate source file name can be -# specified as an argument. In any case, the original file is saved -# by appending a tilde `~' to the name. - -### Usage: -# h5vers [OPTIONS] [FILE] - -# Without options this program only displays the current version and -# doesn't modify any files or create backups. The default is to print -# the version number like X.Y.Z-A where X is the major version number, -# Y is the minor version number, Z is the release number, and A is -# a short annotation string (the `-' is printed only if A is not empty). -# If the `-v' switch is given the version will be printed like: -# -# version X.Y release Z (A) -# -# The space and parentheses around A are only printed if A is not empty. -# -# The `-s VERSION' switch will set the version as specified. If the -# string contains a dotted triple then it will be used as the version -# number, otherwise up to three numbers will be read from the end of -# the string and used as the major version, minor version, and release -# number. If any numbers are missing then zero is assumed. This -# allows versions to be specified like `-s "version 2.1 release 8"' or -# `-s hdf5-2.1.8.tar.bz2'. If the new version is less than the old -# version then a warning message is generated on standard error. The -# annotation string, A, is set only if it appears immediately after the -# third number, separated by a dash (e.g., `1.2.3-pre1') or in parentheses -# (e.g., `version 1.2 release 3 (pre1)'). -# -# The `-i [major|minor|release|annot|last]' option increments the major -# number, minor number, release number, or annotation string. The `last' -# switch increments the annotation string if present, otherwise the -# release number. If the release number is incremented then the annotation -# string is cleared. If the minor number is incremented then the release -# number is set to zero and the annotation string is cleared; if the major -# number is incremented then the minor and release numbers are set to zero -# and the annotation string is cleared. -# -# If a file is specified then that file is used instead of -# ./H5public.h or ./src/H5public.h. -# -# If the version number is changed (either `-s' or `-i' was used on -# the command line) then the version line of the README.md and CHANGELOG.md files -# one directory above the H5public.h file is also modified so it looks -# something like: This is hdf5-2.0.1-1 currently under development. -# Version changes are also reflected in the Windows-maintained H5pubconf.h -# file. -# -# Whenever the version changes, this script will increment the revision -# field in HDF5's libtool shared library version in config/lt_vers.am, -# which is included in src/Makefile.am. Incrementing the revision field -# indicates that the source code has changed since the last version -# (which it probably has). -############################################################################## - -sub getvers { - local ($_) = @_; - my (@vers); - - ($vers[0]) = /^\#\s*define\s+H5_VERS_MAJOR\s+(\d+)/m; - ($vers[1]) = /^\#\s*define\s+H5_VERS_MINOR\s+(\d+)/m; - ($vers[2]) = /^\#\s*define\s+H5_VERS_RELEASE\s+(\d+)/m; - ($vers[3]) = /^\#\s*define\s+H5_VERS_SUBRELEASE\s+\"([^\"]*)\"/m; - return @vers; -} - -sub setvers { - my ($contents, @vers) = @_; - $_[0] =~ s/^(\#\s*define\s+H5_VERS_MAJOR\s+)\d+/$1$vers[0]/m; - $_[0] =~ s/^(\#\s*define\s+H5_VERS_MINOR\s+)\d+/$1$vers[1]/m; - $_[0] =~ s/^(\#\s*define\s+H5_VERS_RELEASE\s+)\d+/$1$vers[2]/m; - $_[0] =~ s/^(\#\s*define\s+H5_VERS_SUBRELEASE\s+\")[^\"]*/$1$vers[3]/m; - $_[0] =~ s/^(\#\s*define\s+H5_VERS_STR\s+\")[^\"]*/ - sprintf("%s%d.%d.%d%s%s", $1, @vers[0,1,2], $vers[3]?"-":"", $vers[3])/me; - $_[0] =~ s/^(\#\s*define\s+H5_VERS_INFO\s+\")[^\"]*/ - sprintf("%sHDF5 library version: %d.%d.%d%s%s", $1, @vers[0,1,2], - $vers[3]?"-":"", $vers[3])/me; -} - -sub usage { - my ($prog) = $0 =~ /([^\/]+)$/; - print STDERR <; -close FILE; -my (@curver) = getvers $contents; - -# Determine the new version number. -my @newver; #new version -if ($set) { - if ($set =~ /(\d+)\.(\d+)\.(\d+)(-([\da-zA-Z]\w*))?/) { - @newver = ($1, $2, $3, $5); - } elsif ($set =~ /(\d+)\D+(\d+)\D+(\d+)(\s*\(([a-zA-Z]\w*)\))?\D*$/) { - @newver = ($1, $2, $3, $5); - } elsif ($set =~ /(\d+)\D+(\d+)\D*$/) { - @newver = ($1, $2, 0, ""); - } elsif ($set =~ /(\d+)\D*$/) { - @newver = ($1, 0, 0, ""); - } else { - die "illegal version number specified: $set\n"; - } -} elsif ($inc) { - $inc = $curver[3] eq "" ? 'release' : 'annot' if $inc eq 'last'; - if ($inc eq "major") { - $newver[0] = $curver[0]+1; - @newver[1,2,3] = (0,0,""); - } elsif ($inc eq "minor") { - $newver[0] = $curver[0]; - $newver[1] = $curver[1]+1; - @newver[2,3] = (0,""); - } elsif ($inc eq "release") { - @newver[0,1] = @curver[0,1]; - $newver[2] = $curver[2]+1; - $newver[3] = ""; - } elsif ($inc eq "annot") { - @newver[0,1,2] = @curver[0,1,2]; - $newver[3] = $curver[3]; - $newver[3] =~ s/(\d+)\D*$/$1+1/e or - die "Annotation \"".$newver[3]."\" cannot be incremented.\n"; - } else { - die "unknown increment field: $inc\n"; - } -} else { - # Nothing to do but print result - $README = ""; - $CHANGELOG = ""; - $LT_VERS = ""; - $HDF5CONFIGCMAKE = ""; - $HDF5EXCONFCMAKE = ""; - @newver = @curver; -} - -# Note if the version increased or decreased -my $version_increased=""; -# Print a warning if the version got smaller (don't check annot field) -if ($newver[0]*1000000 + $newver[1]*1000 + $newver[2] < - $curver[0]*1000000 + $curver[1]*1000 + $curver[2]) { - printf STDERR "Warning: version decreased from %d.%d.%d to %d.%d.%d\n", - @curver[0,1,2], @newver[0,1,2]; -} -if ($newver[0]*1000000 + $newver[1]*1000 + $newver[2] > - $curver[0]*1000000 + $curver[1]*1000 + $curver[2]) { - $version_increased="true"; -} - -# Update the version number if it changed. -if ($newver[0]!=$curver[0] || - $newver[1]!=$curver[1] || - $newver[2]!=$curver[2] || - $newver[3]ne$curver[3]) { - setvers $contents, @newver or die "unable to set version\n"; - rename $file, "$file~" or die "unable to save backup file\n"; - open FILE, ">$file" or die "unable to open $file but backup saved!\n"; - print FILE $contents; - close FILE; -} - -# Update the libtool shared library version in src/Makefile.am if -# the version number has increased. -if ($LT_VERS && $version_increased) { - open FILE, $LT_VERS or die "$LT_VERS: $!\n"; - my ($contentsy) = join "", ; - close FILE; - - local($_) = $contentsy; - -# As of the HDF5 v1.8.16 release, h5vers should not increment -# the LT_VERS numbers, so the next 6 lines are commented out. -# A future version may copy the numbers to H5public.h, so this -# section is retained for future reference. -# my ($lt_revision) = /^LT_VERS_REVISION\s*=\s*(\d+)/m; -# my $new_lt_revision = $lt_revision+1; -# ($contentsy) =~ s/^(LT_VERS_REVISION\s*=\s*)\d+/$1$new_lt_revision/m; - -# open FILE, ">$LT_VERS" or die "$LT_VERS: $!\n"; -# print FILE $contentsy; -# close FILE; -} - -# Update the README.md file -if ($README) { - open FILE, $README or die "$README: $!\n"; - my @contents = ; - close FILE; - $contents[0] = sprintf("HDF5 version %d.%d.%d%s %s", - @newver[0,1,2], - $newver[3] eq "" ? "" : "-".$newver[3], - "currently under development\n"); - open FILE, ">$README" or die "$README: $!\n"; - print FILE @contents; - close FILE; -} - -# Update the release_docs/CHANGELOG.md file -if ($CHANGELOG) { - open FILE, $CHANGELOG or die "$CHANGELOG: $!\n"; - my @contents = ; - close FILE; - $contents[0] = sprintf("HDF5 version %d.%d.%d%s %s", - @newver[0,1,2], - $newver[3] eq "" ? "" : "-".$newver[3], - "currently under development\n"); - - for (my $i = 0; $i < $#contents; ++$i) { - if ($contents[$i] =~ /^# ๐Ÿ”† Executive Summary: HDF5 Version/) { - $contents[$i] = sprintf("# ๐Ÿ”† Executive Summary: HDF5 Version %d.%d.%d\n", @newver[0,1,2]); - last; - } - } - - open FILE, ">$CHANGELOG" or die "$CHANGELOG: $!\n"; - print FILE @contents; - close FILE; -} - -# Update the config/cmake/scripts/HDF5config.cmake file -if ($HDF5CONFIGCMAKE) { - my $data = read_file($HDF5CONFIGCMAKE); -# my $sub_rel_ver_str = ""; - my $sub_rel_ver_str = ( - $newver[3] eq "" - ? sprintf("\"%s\"", "") - : sprintf("\"%s\"", "-".$newver[3]) - ); - my $version_string = sprintf("\"%d.%d.%d\"", @newver[0,1,2]); - - $data =~ s/set \(CTEST_SOURCE_VERSION .*\)/set \(CTEST_SOURCE_VERSION $version_string\)/; - $data =~ s/set \(CTEST_SOURCE_VERSEXT .*\)/set \(CTEST_SOURCE_VERSEXT $sub_rel_ver_str\)/; - - write_file($HDF5CONFIGCMAKE, $data); -} - -# Update the config/examples/HDF5AsSubdirMacros.cmake file -if ($HDF5EXCONFCMAKE) { - my $data = read_file($HDF5EXCONFCMAKE); -# my $sub_rel_ver_str = ""; - my $sub_rel_ver_str = ( - $newver[3] eq "" - ? sprintf("\"%s\"", "") - : sprintf("\"%s\"", "-".$newver[3]) - ); - my $version_string = sprintf("\"%d.%d.%d\"", @newver[0,1,2]); - - $data =~ s/set \(HDF5_VERSION .*\)/set \(HDF5_VERSION $version_string\)/; - $data =~ s/set \(HDF5_VERSEXT .*\)/set \(HDF5_VERSEXT $sub_rel_ver_str\)/; - - write_file($HDF5EXCONFCMAKE, $data); -} - -# Update the java/hdf/hdf5lib/H5.java file -if ($H5_JAVA) { - my $data = read_file($H5_JAVA); -# my $sub_rel_ver_str = ""; - my $sub_rel_ver_str = ( - $newver[3] eq "" - ? sprintf("\"%s\"", "") - : sprintf("\"%s\"", "-".$newver[3].", currently under development") - ); - my $version_string1 = sprintf("%d.%d.%d", @newver[0,1,2]); - my $version_string2 = sprintf("%d, %d, %d", @newver[0,1,2]); - - $data =~ s/\@version HDF5 .*
/\@version HDF5 $version_string1
/; - $data =~ s/ public final static int LIB_VERSION\[\] = \{\d*,.\d*,.\d*\};/ public final static int LIB_VERSION[] = \{$version_string2\};/; - - write_file($H5_JAVA, $data); -} - -# Update the java/test/TestH5.java file -if ($TESTH5_JAVA) { - my $data = read_file($TESTH5_JAVA); -# my $sub_rel_ver_str = ""; - my $sub_rel_ver_str = ( - $newver[3] eq "" - ? sprintf("\"%s\"", "") - : sprintf("\"%s\"", "-".$newver[3].", currently under development") - ); - my $version_string1 = sprintf("%d, %d, %d", @newver[0,1,2]); - my $version_string2 = sprintf("int majnum = %d, minnum = %d, relnum = %d", @newver[0,1,2]); - - $data =~ s/ int libversion\[\] = \{.*\};/ int libversion\[\] = \{$version_string1\};/; - $data =~ s/ int majnum = \d*, minnum = \d*, relnum = \d*;/ $version_string2;/; - - write_file($TESTH5_JAVA, $data); -} - -# Update the java/src-jni/hdf/hdf5lib/H5.java file -if ($H5_JNI_JAVA) { - my $data = read_file($H5_JNI_JAVA); -# my $sub_rel_ver_str = ""; - my $sub_rel_ver_str = ( - $newver[3] eq "" - ? sprintf("\"%s\"", "") - : sprintf("\"%s\"", "-".$newver[3].", currently under development") - ); - my $version_string1 = sprintf("%d.%d.%d", @newver[0,1,2]); - my $version_string2 = sprintf("%d, %d, %d", @newver[0,1,2]); - - $data =~ s/\@version HDF5 .*
/\@version HDF5 $version_string1
/; - $data =~ s/ public final static int LIB_VERSION\[\] = \{\d*,.\d*,.\d*\};/ public final static int LIB_VERSION[] = \{$version_string2\};/; - - write_file($H5_JNI_JAVA, $data); -} - -# Update the java/src-jni/test/TestH5.java file -if ($TESTH5_JNI_JAVA) { - my $data = read_file($TESTH5_JNI_JAVA); -# my $sub_rel_ver_str = ""; - my $sub_rel_ver_str = ( - $newver[3] eq "" - ? sprintf("\"%s\"", "") - : sprintf("\"%s\"", "-".$newver[3].", currently under development") - ); - my $version_string1 = sprintf("%d, %d, %d", @newver[0,1,2]); - my $version_string2 = sprintf("int majnum = %d, minnum = %d, relnum = %d", @newver[0,1,2]); - - $data =~ s/ int libversion\[\] = \{.*\};/ int libversion\[\] = \{$version_string1\};/; - $data =~ s/ int majnum = \d*, minnum = \d*, relnum = \d*;/ $version_string2;/; - - write_file($TESTH5_JNI_JAVA, $data); -} - -# Update the tools/test/h5repack/expected/h5repack_layout.h5-plugin_version_test.ddl file -if ($REPACK_LAYOUT_PLUGIN_VERSION) { - my $data = read_file($REPACK_LAYOUT_PLUGIN_VERSION); - my $version_string = sprintf("%d %d %d", @newver[0,1,2]); - - $data =~ s/ PARAMS \{ 9 \d* \d* \d* \}/ PARAMS \{ 9 $version_string \}/g; - - write_file($REPACK_LAYOUT_PLUGIN_VERSION, $data); -} - -# helper function to read the file for updating -# config/cmake/scripts/HDF5Config.cmake, and java files. -# The version string in that file is not at the top, so the string replacement -# is not for the first line, and reading/writing the entire file as one string -# facilitates the substring replacement. -#Presumably these will also work for resetting the version in HDF5config.cmake. -sub read_file { - my ($filename) = @_; - - open my $in, $filename or die "Could not open '$filename' for reading $!"; - local $/ = undef; - my $all = <$in>; - close $in; - - return $all; -} - -# helper function to write the file for updating -# config/cmake/scripts/HDF5config.cmake and java files. -sub write_file { - my ($filename, $content) = @_; - - open my $out, ">$filename" or die "Could not open '$filename' for writing $!";; - print $out $content; - close $out; - - return; -} - -sub gen_h5pubconf { - my ($name, $pubconf, @vers) = @_; - - my $namelc = lc($name); - my $nameuc = uc($name); - - open FILE, $pubconf or die "$pubconf: $!\n"; - my @contents = ; - close FILE; - - for (my $i = 0; $i < $#contents; ++$i) { - if ($contents[$i] =~ /\#\s*define\s+H5_PACKAGE\s+/) { - $contents[$i] = "\#define H5_PACKAGE \"$namelc\"\n"; - } elsif ($contents[$i] =~ /\#\s*define\s+H5_PACKAGE_NAME\s+/) { - $contents[$i] = "\#define H5_PACKAGE_NAME \"$nameuc\"\n"; - } elsif ($contents[$i] =~ /\#\s*define\s+H5_PACKAGE_STRING\s+/) { - $contents[$i] = sprintf("\#define H5_PACKAGE_STRING \"$nameuc %d.%d.%d%s\"\n", - @vers[0,1,2], - $newver[3] eq "" ? "" : "-".$newver[3]); - } elsif ($contents[$i] =~ /\#\s*define\s+H5_PACKAGE_TARNAME\s+/) { - $contents[$i] = "\#define H5_PACKAGE_TARNAME \"$namelc\"\n"; - } elsif ($contents[$i] =~ /\#\s*define\s+H5_PACKAGE_VERSION\s+/) { - $contents[$i] = sprintf("\#define H5_PACKAGE_VERSION \"%d.%d.%d%s\"\n", - @vers[0,1,2], - $newver[3] eq "" ? "" : "-".$newver[3]); - } elsif ($contents[$i] =~ /\#\s*define\s+H5_VERSION\s+/) { - $contents[$i] = sprintf("\#define H5_VERSION \"%d.%d.%d%s\"\n", - @vers[0,1,2], - $newver[3] eq "" ? "" : "-".$newver[3]); - } - } - - open FILE, ">$pubconf" or die "$pubconf: $!\n"; - print FILE @contents; - close FILE; -} - -# Print the new version number -if ($verbose) { - printf("version %d.%d release %d%s\n", @newver[0,1,2], - $newver[3] eq "" ? "" : " (".$newver[3].")"); -} else { - printf("%d.%d.%d%s\n", @newver[0,1,2], - $newver[3] eq "" ? "" : "-".$newver[3]); -} - -exit 0; - -# Because the first line of this file looks like a Bourne shell script, we -# must tell XEmacs explicitly that this is really a perl script. -# -# Local Variables: -# mode:perl -# End: diff --git a/bin/release b/bin/release index 4c05bf78651..d85993f747b 100755 --- a/bin/release +++ b/bin/release @@ -15,6 +15,102 @@ # Function definitions # +# Function to get version from H5public.h +get_version() +{ + local major=$(grep '^#define H5_VERS_MAJOR' src/H5public.h | awk '{print $3}') + local minor=$(grep '^#define H5_VERS_MINOR' src/H5public.h | awk '{print $3}') + local release=$(grep '^#define H5_VERS_RELEASE' src/H5public.h | awk '{print $3}') + local subrelease=$(grep '^#define H5_VERS_SUBRELEASE' src/H5public.h | cut -d'"' -f2) + + echo "${major}.${minor}.${release}${subrelease}" +} + +# Function to set version in H5public.h +# Usage: set_version "2.0.1-of20250116" +set_version() +{ + if [ $# -ne 1 ]; then + echo "usage: set_version " + return 1 + fi + + new_vers=$1 + + # Validate version string format + if ! echo "$new_vers" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9._-]+)?$'; then + echo "Error: Invalid version format '$new_vers'. Expected format: major.minor.release[-subrelease]" + return 1 + fi + + # Parse version string (format: major.minor.release or major.minor.release-subrelease) + major=$(echo $new_vers | sed 's/^\([0-9]*\)\..*/\1/') + minor=$(echo $new_vers | sed 's/^[0-9]*\.\([0-9]*\)\..*/\1/') + release=$(echo $new_vers | sed 's/^[0-9]*\.[0-9]*\.\([0-9]*\).*/\1/') + # Note: subrelease includes the leading dash (e.g., "-snap0") to match H5_VERS_STR concatenation + subrelease=$(echo $new_vers | sed 's/^[0-9]*\.[0-9]*\.[0-9]*\(.*\)/\1/') + + # If subrelease is same as new_vers, there was no subrelease part + if [ "$subrelease" = "$new_vers" ]; then + subrelease="" + fi + + # Validate that all components were extracted + if [ -z "$major" ] || [ -z "$minor" ] || [ -z "$release" ]; then + echo "Error: Failed to parse version components from '$new_vers'" + echo " major=$major, minor=$minor, release=$release, subrelease=$subrelease" + return 1 + fi + + # Verify H5public.h exists and has the expected defines + if [ ! -f src/H5public.h ]; then + echo "Error: src/H5public.h not found" + return 1 + fi + + for def in H5_VERS_MAJOR H5_VERS_MINOR H5_VERS_RELEASE H5_VERS_SUBRELEASE; do + if ! grep -q "^#define $def" src/H5public.h; then + echo "Error: #define $def not found in src/H5public.h" + return 1 + fi + done + + # Update H5public.h + # Note: H5_VERS_STR and H5_VERS_INFO are automatically derived from the base version macros + sed -i.bak \ + -e "s/^\(#define H5_VERS_MAJOR[[:space:]]*\)[0-9]*/\1$major/" \ + -e "s/^\(#define H5_VERS_MINOR[[:space:]]*\)[0-9]*/\1$minor/" \ + -e "s/^\(#define H5_VERS_RELEASE[[:space:]]*\)[0-9]*/\1$release/" \ + -e "s/^\(#define H5_VERS_SUBRELEASE[[:space:]]*\)\".*\"/\1\"$subrelease\"/" \ + src/H5public.h + + if [ $? -ne 0 ]; then + echo "Error: sed command failed" + mv src/H5public.h.bak src/H5public.h 2>/dev/null + return 1 + fi + + # Verify the changes were applied correctly + new_major=$(grep '^#define H5_VERS_MAJOR' src/H5public.h | sed 's/^#define H5_VERS_MAJOR[[:space:]]*\([0-9]*\).*/\1/') + new_minor=$(grep '^#define H5_VERS_MINOR' src/H5public.h | sed 's/^#define H5_VERS_MINOR[[:space:]]*\([0-9]*\).*/\1/') + new_release=$(grep '^#define H5_VERS_RELEASE' src/H5public.h | sed 's/^#define H5_VERS_RELEASE[[:space:]]*\([0-9]*\).*/\1/') + new_subrelease=$(grep '^#define H5_VERS_SUBRELEASE' src/H5public.h | sed 's/^#define H5_VERS_SUBRELEASE[[:space:]]*"\(.*\)".*/\1/') + + if [ "$new_major" != "$major" ] || [ "$new_minor" != "$minor" ] || \ + [ "$new_release" != "$release" ] || [ "$new_subrelease" != "$subrelease" ]; then + echo "Error: Version update verification failed" + echo " Expected: major=$major, minor=$minor, release=$release, subrelease=$subrelease" + echo " Got: major=$new_major, minor=$new_minor, release=$new_release, subrelease=$new_subrelease" + mv src/H5public.h.bak src/H5public.h + return 1 + fi + + # Success - remove backup + rm -f src/H5public.h.bak + echo "Successfully updated version to $new_vers" + return 0 +} + # Print Usage page USAGE() { @@ -139,7 +235,7 @@ tar2zip() # Defaults DEST=releases -VERS=`perl bin/h5vers` +VERS=$(get_version) VERS_OLD= test "$VERS" || exit 1 verbose=yes @@ -156,7 +252,7 @@ RESTORE_VERSION() echo restoring version information back to $VERS_OLD rm -f config/lt_vers.am cp $tmpdir/lt_vers.am config/lt_vers.am - bin/h5vers -s $VERS_OLD + set_version $VERS_OLD VERS_OLD= fi } @@ -219,10 +315,9 @@ if [ X$pmode = Xyes ]; then # "undo" changes to it. cp config/lt_vers.am $tmpdir # Set version information to m.n.r-of$today. - # (h5vers does not correctly handle just m.n.r-$today.) VERS=`echo $VERS | sed -e s/-.*//`-of$today echo Private release of $VERS - bin/h5vers -s $VERS + set_version $VERS fi if [ X$revmode = Xyes ]; then @@ -236,12 +331,11 @@ if [ X$revmode = Xyes ]; then fi revision=`git rev-parse --short HEAD` # Set version information to m.n.r-r$revision. - # (h5vers does not correctly handle just m.n.r-$today.) VERS=`echo $VERS | sed -e s/-.*//`-$revision echo Private release of $VERS HDF5_VERS=hdf5-$BRANCHNAME-$revision echo file base of $HDF5_VERS - bin/h5vers -s $VERS + set_version $VERS # use a generic directory name for revision releases HDF5_IN_VERS=hdfsrc else diff --git a/config/cmake/HDF5VersionParsing.cmake b/config/cmake/HDF5VersionParsing.cmake new file mode 100644 index 00000000000..0356cd83c2b --- /dev/null +++ b/config/cmake/HDF5VersionParsing.cmake @@ -0,0 +1,114 @@ +# +# 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. +# + +# +# HDF5VersionParsing.cmake +# +# Provides a macro to parse version information from H5public.h +# This ensures consistent version extraction across all CMake scripts. +# + +#[=======================================================================[.rst: +HDF5VersionParsing +------------------ + +Provides macros for extracting HDF5 version information from H5public.h + +parse_hdf5_version +^^^^^^^^^^^^^^^^^^ + +Parses version constants from H5public.h and sets variables in parent scope. + +.. code-block:: cmake + + parse_hdf5_version( + MAJOR_VAR + MINOR_VAR + RELEASE_VAR + [SUBRELEASE_VAR ]) + +Reads the specified H5public.h file and extracts version numbers from the +H5_VERS_MAJOR, H5_VERS_MINOR, H5_VERS_RELEASE, and optionally H5_VERS_SUBRELEASE +macros. The extracted values are set in the specified variables in the parent scope. + +Arguments: + - ````: Path to the H5public.h file to parse + - ``MAJOR_VAR``: Variable name to store H5_VERS_MAJOR value + - ``MINOR_VAR``: Variable name to store H5_VERS_MINOR value + - ``RELEASE_VAR``: Variable name to store H5_VERS_RELEASE value + - ``SUBRELEASE_VAR``: (Optional) Variable name to store H5_VERS_SUBRELEASE value + +Example: + .. code-block:: cmake + + include(HDF5VersionParsing) + parse_hdf5_version("${CMAKE_SOURCE_DIR}/src/H5public.h" + MAJOR_VAR H5_VERS_MAJOR + MINOR_VAR H5_VERS_MINOR + RELEASE_VAR H5_VERS_RELEASE + SUBRELEASE_VAR H5_VERS_SUBRELEASE) + message(STATUS "HDF5 Version: ${H5_VERS_MAJOR}.${H5_VERS_MINOR}.${H5_VERS_RELEASE}") + +#]=======================================================================] + +macro(parse_hdf5_version H5PUBLIC_H_PATH) + # Parse arguments + set(options "") + set(oneValueArgs MAJOR_VAR MINOR_VAR RELEASE_VAR SUBRELEASE_VAR) + set(multiValueArgs "") + cmake_parse_arguments(PARSE_VER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Validate required arguments + if(NOT PARSE_VER_MAJOR_VAR OR NOT PARSE_VER_MINOR_VAR OR NOT PARSE_VER_RELEASE_VAR) + message(FATAL_ERROR "parse_hdf5_version requires MAJOR_VAR, MINOR_VAR, and RELEASE_VAR arguments") + endif() + + # Validate H5public.h exists + if(NOT EXISTS "${H5PUBLIC_H_PATH}") + message(FATAL_ERROR "H5public.h not found at: ${H5PUBLIC_H_PATH}") + endif() + + # Read H5public.h + file(STRINGS "${H5PUBLIC_H_PATH}" _h5_vers_contents REGEX "^#define H5_VERS_(MAJOR|MINOR|RELEASE|SUBRELEASE)") + + # Extract version numbers using regex + string(REGEX MATCH "H5_VERS_MAJOR[ \t]+([0-9]+)" _match "${_h5_vers_contents}") + if(NOT CMAKE_MATCH_1) + message(FATAL_ERROR "Failed to parse H5_VERS_MAJOR from ${H5PUBLIC_H_PATH}") + endif() + set(${PARSE_VER_MAJOR_VAR} ${CMAKE_MATCH_1} PARENT_SCOPE) + + string(REGEX MATCH "H5_VERS_MINOR[ \t]+([0-9]+)" _match "${_h5_vers_contents}") + if(NOT CMAKE_MATCH_1) + message(FATAL_ERROR "Failed to parse H5_VERS_MINOR from ${H5PUBLIC_H_PATH}") + endif() + set(${PARSE_VER_MINOR_VAR} ${CMAKE_MATCH_1} PARENT_SCOPE) + + string(REGEX MATCH "H5_VERS_RELEASE[ \t]+([0-9]+)" _match "${_h5_vers_contents}") + if(NOT CMAKE_MATCH_1) + message(FATAL_ERROR "Failed to parse H5_VERS_RELEASE from ${H5PUBLIC_H_PATH}") + endif() + set(${PARSE_VER_RELEASE_VAR} ${CMAKE_MATCH_1} PARENT_SCOPE) + + # Extract subrelease if requested + if(PARSE_VER_SUBRELEASE_VAR) + string(REGEX MATCH "H5_VERS_SUBRELEASE[ \t]+\"([^\"]*)\"" _match "${_h5_vers_contents}") + if(NOT CMAKE_MATCH_1) + message(FATAL_ERROR "Failed to parse H5_VERS_SUBRELEASE from ${H5PUBLIC_H_PATH}") + endif() + set(${PARSE_VER_SUBRELEASE_VAR} ${CMAKE_MATCH_1} PARENT_SCOPE) + endif() + + # Clean up temporary variables + unset(_h5_vers_contents) + unset(_match) +endmacro() diff --git a/config/cmake/scripts/HDF5config.cmake b/config/cmake/scripts/HDF5config.cmake index 5df69b3d361..30ec71818e4 100644 --- a/config/cmake/scripts/HDF5config.cmake +++ b/config/cmake/scripts/HDF5config.cmake @@ -38,8 +38,26 @@ cmake_minimum_required (VERSION 3.26) # CTEST_SOURCE_NAME - source folder ############################################################################## -set (CTEST_SOURCE_VERSION "2.0.1") -set (CTEST_SOURCE_VERSEXT "") +#----------------------------------------------------------------------------- +# Version is extracted from H5public.h at configure time +# If not set in parent scope, read it from H5public.h now +#----------------------------------------------------------------------------- +if (NOT DEFINED H5_VERS_MAJOR) + # Use shared version parsing module + include(${CTEST_SCRIPT_DIRECTORY}/../HDF5VersionParsing.cmake) + parse_hdf5_version("${CTEST_SCRIPT_DIRECTORY}/../src/H5public.h" + MAJOR_VAR H5_VERS_MAJOR + MINOR_VAR H5_VERS_MINOR + RELEASE_VAR H5_VERS_RELEASE + SUBRELEASE_VAR H5_VERS_SUBRELEASE) +endif () + +set (CTEST_SOURCE_VERSION "${H5_VERS_MAJOR}.${H5_VERS_MINOR}.${H5_VERS_RELEASE}") +if (H5_VERS_SUBRELEASE) + set (CTEST_SOURCE_VERSEXT "-${H5_VERS_SUBRELEASE}") +else () + set (CTEST_SOURCE_VERSEXT "") +endif () ############################################################################## # handle input parameters to script. diff --git a/config/examples/HDF5AsSubdirMacros.cmake b/config/examples/HDF5AsSubdirMacros.cmake index 62e46c547ac..d03d08773a4 100644 --- a/config/examples/HDF5AsSubdirMacros.cmake +++ b/config/examples/HDF5AsSubdirMacros.cmake @@ -17,9 +17,27 @@ # and build it. The HDF5 options should be set after the FetchContent_Declare command and before # the add_subdirectory command.. macro (EXTERNAL_HDF5_LIBRARY compress_type) - set (HDF5_VERSION "2.0.1") - set (HDF5_VERSEXT "") - set (HDF5_VERSION_MAJOR "2.0") + #----------------------------------------------------------------------------- + # Version is extracted from H5public.h + # If not set in parent scope, read it from H5public.h now + #----------------------------------------------------------------------------- + if (NOT DEFINED H5_VERS_MAJOR) + # Use shared version parsing module + include(${CMAKE_CURRENT_LIST_DIR}/../cmake/HDF5VersionParsing.cmake) + parse_hdf5_version("${CMAKE_CURRENT_LIST_DIR}/../../src/H5public.h" + MAJOR_VAR H5_VERS_MAJOR + MINOR_VAR H5_VERS_MINOR + RELEASE_VAR H5_VERS_RELEASE + SUBRELEASE_VAR H5_VERS_SUBRELEASE) + endif () + + set (HDF5_VERSION "${H5_VERS_MAJOR}.${H5_VERS_MINOR}.${H5_VERS_RELEASE}") + if (H5_VERS_SUBRELEASE) + set (HDF5_VERSEXT "-${H5_VERS_SUBRELEASE}") + else () + set (HDF5_VERSEXT "") + endif () + set (HDF5_VERSION_MAJOR "${H5_VERS_MAJOR}.${H5_VERS_MINOR}") set (HDF5LIB_TGZ_NAME "hdf5.tar.gz" CACHE STRING "Use HDF5LIB from compressed file" FORCE) set (HDF5LIB_TGZ_ORIGPATH "https://github.com/HDFGroup/hdf5/releases/download/snapshot" CACHE STRING "Use HDF5LIB from original location" FORCE) set (HDF5LIB_USE_LOCALCONTENT ON CACHE BOOL "Use local file for HDF5LIB FetchContent" FORCE) diff --git a/config/lt_vers.am b/config/lt_vers.am index f2674c0fc90..0a05473dca7 100644 --- a/config/lt_vers.am +++ b/config/lt_vers.am @@ -27,9 +27,8 @@ LT_VERS_REVISION = 0 ## version. ## ## 4. If the source changes but there are no API changes, increment -## LT_VERS_REVISION. This will happen automatically when -## bin/h5vers is run, but doing it manually shouldn't hurt -## anything. +## LT_VERS_REVISION. This should be done manually when updating the +## version in src/H5public.h. ## ## Note that this versioning system doesn't attempt to handle ## the effects of the H5_V1_x_COMPAT flag. diff --git a/doxygen/Doxyfile.in b/doxygen/Doxyfile.in index 0551a1452c1..467663c4633 100644 --- a/doxygen/Doxyfile.in +++ b/doxygen/Doxyfile.in @@ -804,6 +804,7 @@ EXCLUDE_PATTERNS = */fortran/test/* \ */hl/fortran/src/*.h \ */HDF5Examples/FORTRAN/* \ */sanitizer/* \ + */README.md \ */CONTRIBUTING.md \ */CHANGELOG.md \ */HISTORY-*.md diff --git a/java/hdf/hdf5lib/CMakeLists.txt b/java/hdf/hdf5lib/CMakeLists.txt index 76e0f7ddc44..87a2a2d7e0b 100644 --- a/java/hdf/hdf5lib/CMakeLists.txt +++ b/java/hdf/hdf5lib/CMakeLists.txt @@ -102,11 +102,24 @@ set (HDF5_JAVADOC_HDF_HDF5_STRUCTS_SOURCES structs/package-info.java ) +#----------------------------------------------------------------------------- +# Generate H5Version.java with version information from H5public.h +# This ensures the Java version constants match the C library version +# Uses shared template from java/templates/ +#----------------------------------------------------------------------------- +configure_file ( + ${HDF5_SOURCE_DIR}/java/templates/H5Version.java.in + ${CMAKE_CURRENT_BINARY_DIR}/H5Version.java + @ONLY +) + +# Use sources from both source and binary directories set (HDF5_JAVA_HDF_HDF5_SOURCES HDFArray.java HDF5Constants.java HDFNativeData.java H5.java + ${CMAKE_CURRENT_BINARY_DIR}/H5Version.java VLDataConverter.java ) diff --git a/java/hdf/hdf5lib/H5.java b/java/hdf/hdf5lib/H5.java index 317f8414bd6..233cfd67c87 100644 --- a/java/hdf/hdf5lib/H5.java +++ b/java/hdf/hdf5lib/H5.java @@ -296,9 +296,11 @@ public class H5 implements java.io.Serializable { *
  • LIB_VERSION[1]: The minor version of the library.
  • *
  • LIB_VERSION[2]: The release number of the library.
  • * - * Make sure to update the versions number when a different library is used. + * NOTE: This version is automatically synchronized with H5public.h via the auto-generated + * H5Version class. To update the version, edit src/H5public.h (H5_VERS_MAJOR, + * H5_VERS_MINOR, H5_VERS_RELEASE). Do NOT manually edit the version numbers. */ - public final static int LIB_VERSION[] = {2, 0, 1}; + public final static int LIB_VERSION[] = {H5Version.MAJOR, H5Version.MINOR, H5Version.RELEASE}; private final static LinkedHashSet OPEN_IDS = new LinkedHashSet(); private static boolean isLibraryLoaded = false; diff --git a/java/src-jni/hdf/hdf5lib/CMakeLists.txt b/java/src-jni/hdf/hdf5lib/CMakeLists.txt index 33889754889..4aac46026f2 100644 --- a/java/src-jni/hdf/hdf5lib/CMakeLists.txt +++ b/java/src-jni/hdf/hdf5lib/CMakeLists.txt @@ -103,11 +103,24 @@ set (HDF5_JAVADOC_HDF_HDF5_STRUCTS_SOURCES structs/package-info.java ) +#----------------------------------------------------------------------------- +# Generate H5Version.java with version information from H5public.h +# This ensures the Java version constants match the C library version +# Uses shared template from java/templates/ +#----------------------------------------------------------------------------- +configure_file ( + ${HDF5_SOURCE_DIR}/java/templates/H5Version.java.in + ${CMAKE_CURRENT_BINARY_DIR}/H5Version.java + @ONLY +) + +# Use sources from both source and binary directories set (HDF5_JAVA_HDF_HDF5_SOURCES HDFArray.java HDF5Constants.java HDFNativeData.java H5.java + ${CMAKE_CURRENT_BINARY_DIR}/H5Version.java ) set (HDF5_JAVADOC_HDF_HDF5_SOURCES diff --git a/java/src-jni/hdf/hdf5lib/H5.java b/java/src-jni/hdf/hdf5lib/H5.java index d22f103ef8a..3a70628f12c 100644 --- a/java/src-jni/hdf/hdf5lib/H5.java +++ b/java/src-jni/hdf/hdf5lib/H5.java @@ -271,9 +271,11 @@ public class H5 implements java.io.Serializable { *
  • LIB_VERSION[1]: The minor version of the library.
  • *
  • LIB_VERSION[2]: The release number of the library.
  • * - * Make sure to update the versions number when a different library is used. + * NOTE: This version is automatically synchronized with H5public.h via the auto-generated + * H5Version class. To update the version, edit src/H5public.h (H5_VERS_MAJOR, + * H5_VERS_MINOR, H5_VERS_RELEASE). Do NOT manually edit the version numbers. */ - public final static int LIB_VERSION[] = {2, 0, 1}; + public final static int LIB_VERSION[] = {H5Version.MAJOR, H5Version.MINOR, H5Version.RELEASE}; /** * @ingroup JH5 diff --git a/java/templates/H5Version.java.in b/java/templates/H5Version.java.in new file mode 100644 index 00000000000..8e9f174c471 --- /dev/null +++ b/java/templates/H5Version.java.in @@ -0,0 +1,42 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * 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 hdf.hdf5lib; + +/** + * HDF5 library version information. + * + * This file is automatically generated from src/H5public.h by CMake. + * DO NOT EDIT THIS FILE DIRECTLY - it will be overwritten during the build. + * + * To update the version, edit src/H5public.h and modify: + * H5_VERS_MAJOR + * H5_VERS_MINOR + * H5_VERS_RELEASE + * H5_VERS_SUBRELEASE + */ +public class H5Version { + /** Major version number */ + public static final int MAJOR = @H5_VERS_MAJOR@; + + /** Minor version number */ + public static final int MINOR = @H5_VERS_MINOR@; + + /** Release number */ + public static final int RELEASE = @H5_VERS_RELEASE@; + + /** Subrelease string (e.g., "-snap0" or empty string for releases) */ + public static final String SUBRELEASE = "@H5_VERS_SUBRELEASE@"; + + /** Full version string matching the native library version */ + public static final String VERSION = "@H5_VERS_MAJOR@.@H5_VERS_MINOR@.@H5_VERS_RELEASE@@H5_VERS_SUBRELEASE@"; +} diff --git a/release_docs/AutotoolsToCMakeOptions.md b/release_docs/AutotoolsToCMakeOptions.md index 7b3f4130709..de694e88e61 100644 --- a/release_docs/AutotoolsToCMakeOptions.md +++ b/release_docs/AutotoolsToCMakeOptions.md @@ -1,4 +1,4 @@ -# Cmake logo CMake Installations +# CMake Installations CMake produces the following set of folders; bin, include, lib and share. The LICENSE and CHANGELOG.md file are placed in the share folder. diff --git a/release_docs/CHANGELOG.md b/release_docs/CHANGELOG.md index 9c65fe4ee46..9dca3397183 100644 --- a/release_docs/CHANGELOG.md +++ b/release_docs/CHANGELOG.md @@ -1,4 +1,4 @@ -HDF5 version 2.0.1 currently under development +v2.1.0 --- January X , 2026 # ๐Ÿ”บ HDF5 Changelog All notable changes to this project will be documented in this file. This document describes the differences between this release and the previous @@ -21,7 +21,7 @@ For releases prior to version 2.0.0, please see the release.txt file and for mor * [Platforms Tested](CHANGELOG.md#%EF%B8%8F-platforms-tested) * [Known Problems](CHANGELOG.md#-known-problems) -# ๐Ÿ”† Executive Summary: HDF5 Version 2.0.1 +# ๐Ÿ”† Executive Summary: HDF5 Version 2.1.0 ## Performance Enhancements: @@ -45,7 +45,7 @@ For releases prior to version 2.0.0, please see the release.txt file and for mor ## Acknowledgements: -We would like to thank the many HDF5 community members who contributed to HDF5 2.0. +We would like to thank the many HDF5 community members who contributed to this release of HDF5. # โš ๏ธ Breaking Changes @@ -62,12 +62,12 @@ We would like to thank the many HDF5 community members who contributed to HDF5 2 ### Added predefined datatypes for FP6 data - Predefined datatypes have been added for FP6 data in E2M3 and E3M2 formats (https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf). + Predefined datatypes have been added for FP6 data in [E2M3 and E3M2 formats](https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf). The following new macros have been added: - - H5T_FLOAT_F6E2M3 - - H5T_FLOAT_F6E3M2 + - `H5T_FLOAT_F6E2M3` + - `H5T_FLOAT_F6E3M2` These macros map to IDs of HDF5 datatypes representing a 6-bit floating-point datatype with 1 sign bit and either 2 exponent bits and 3 mantissa bits (E2M3 format) or 3 exponent bits and 2 mantissa bits (E3M2 format). @@ -77,11 +77,11 @@ We would like to thank the many HDF5 community members who contributed to HDF5 2 ### Added predefined datatype for FP4 data - A predefined datatype has been added for FP4 data in E2M1 format (https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf). + A predefined datatype has been added for FP4 data in [E2M1 format](https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf). The following new macro has been added: - - H5T_FLOAT_F4E2M1 + - `H5T_FLOAT_F4E2M1` This macro maps to the ID of an HDF5 datatype representing a 4-bit floating-point datatype with 1 sign bit, 2 exponent bits and 1 mantissa bit. diff --git a/release_docs/Cmake_logo.svg b/release_docs/Cmake_logo.svg deleted file mode 100644 index 42b7be8e518..00000000000 --- a/release_docs/Cmake_logo.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/release_docs/README_HPC.md b/release_docs/README_HPC.md index 10de30201be..40b7b6913c4 100644 --- a/release_docs/README_HPC.md +++ b/release_docs/README_HPC.md @@ -79,7 +79,7 @@ For release or snapshot tar files, extract them to your working directory. > **Note:** When using the ctest automated build method (Section 4), the source > directory should be named `hdf5-`, where version uses format `1.xx.xx` -> or `2.xx.xx`. Use `bin/h5vers` to determine the version string if needed. +> or `2.xx.xx`. Check `H5_VERS_STR` in `src/H5public.h` to determine the version string if needed. --- diff --git a/release_docs/RELEASE_PROCESS.md b/release_docs/RELEASE_PROCESS.md index a263deffee2..49eeb6d92bc 100644 --- a/release_docs/RELEASE_PROCESS.md +++ b/release_docs/RELEASE_PROCESS.md @@ -86,15 +86,15 @@ For more information on the HDF5 versioning and backward and forward compatibili - or create the new branch in GitHub GUI. 4. Check that required CMake files point to the specific versions of the third-party software (szip, zlib and plugins) that they depend on. - Update as needed. -5. Change the **support** branch to X.Y.{Z+1}-1 (\1) using the [bin/h5vers][u10] script: +5. Change the **support** branch to X.Y.{Z+1} by editing [src/H5public.h][u11]: - `$ git checkout hdf5_X_Y` - - `$ bin/h5vers -s X.Y.{Z+1}-1;` + - Edit `src/H5public.h` to update version defines: `H5_VERS_MAJOR`, `H5_VERS_MINOR`, `H5_VERS_RELEASE`, `H5_VERS_SUBRELEASE`, `H5_VERS_STR`, and `H5_VERS_INFO` - `$ git commit -m "Updated support branch version number to X.Y.{Z+1}-1"` - `$ git push` -6. Change the **release preparation branch**'s version number to X.Y.Z.1 using the [bin/h5vers][u10]/bin/h5vers script: - - `$ git checkout hdf5_X_Y_Z;` - - `$ bin/h5vers -s X.Y.Z.1;` - - `$ git commit -m "Updated release preparation branch version number to X.Y.Z.1"` +6. Change the **release preparation branch**'s version number to X.Y.Z by editing [src/H5public.h][u11]: + - `$ git checkout hdf5_X_Y_Z;` + - Edit `src/H5public.h` to update version defines: `H5_VERS_MAJOR`, `H5_VERS_MINOR`, `H5_VERS_RELEASE`, `H5_VERS_SUBRELEASE`, `H5_VERS_STR`, and `H5_VERS_INFO` + - `$ git commit -m "Updated release preparation branch version number to X.Y.Z"` - `$ git push` 7. ** OBSOLETE CURRENTLY ** Update default configuration mode @@ -178,8 +178,8 @@ For more information on the HDF5 versioning and backward and forward compatibili 3. Update Release Notes in **release** branch (Release Manager) ### 10. Package and Distribute Release (Release Manager) -1. h5vers could run genparser, which can change the generated files if certain code files have been changed since the files generated by genparser were committed on the release branch. This should be checked by running `git status --ignored;`, then running genparser, then repeating `git status --ignored;`. If there are modified files from either git status command, they should be committed (or deleted if there are backup files or an autom4te.cache directory), and at least minimal testing should be done to see that the software is still good with the changes. -2. Set version for release, removing the subrelease string, initially `$ bin/h5vers -s X.Y.Z;`. Any subsequent patch releases will need the subrelease number. +1. Running genparser can change the generated files if certain code files have been changed since the files generated by genparser were committed on the release branch. This should be checked by running `git status --ignored;`, then running genparser, then repeating `git status --ignored;`. If there are modified files from either git status command, they should be committed (or deleted if there are backup files or an autom4te.cache directory), and at least minimal testing should be done to see that the software is still good with the changes. +2. Set version for release by editing `src/H5public.h`, removing the subrelease string (set `H5_VERS_SUBRELEASE` to `""`). Update `H5_VERS_STR` to `"X.Y.Z"` and `H5_VERS_INFO` to `"HDF5 library version: X.Y.Z"`. Any subsequent patch releases will need the subrelease number. 3. Run `bin/release` (similar to 8.2) and commit all the changed files. 4. Select the actions tab and the release build workflow, then click the 'Run workflow' drop-down. - Choose the release branch @@ -224,8 +224,7 @@ For more information on the HDF5 versioning and backward and forward compatibili [u7]: https://github.com/HDFGroup/hdf5/blob/develop/release_docs/INSTALL_CMake.txt [u8]: https://github.com/HDFGroup/hdf5/blob/develop/.github/workflows/release.yml [u9]: https://github.com/HDFGroup/hdf5/blob/develop/config/lt_vers.am -[u10]: https://github.com/HDFGroup/hdf5/blob/develop/bin/h5vers -[u11]: https://github.com/HDFGroup/hdf5/blob/develop/src/CMakeLists.txt +[u11]: https://github.com/HDFGroup/hdf5/blob/develop/src/H5public.h [u12]: https://github.com/HDFGroup/hdf5/blob/develop/configure.ac [u13]: https://support.hdfgroup.org/documentation/hdf5/latest/api-compat-macros.html [u14]: https://github.com/HDFGroup/hdf5/releases/tag/snapshot-1.14 diff --git a/release_docs/img/release-schedule.plantuml b/release_docs/img/release-schedule.plantuml index 17c1c009a2a..d930f85362f 100644 --- a/release_docs/img/release-schedule.plantuml +++ b/release_docs/img/release-schedule.plantuml @@ -10,37 +10,36 @@ projectscale monthly Project starts 2023-01-01 [1.8] starts 2023-01-01 and lasts 5 weeks -[1.8.23] happens 2023-01-31 +[1.8.23 (EOL)] happens 2023-01-31 [1.8] is colored in #F76969 [1.10] starts 2023-01-01 and lasts 39 weeks [1.10.10] happens 2023-03-31 -[1.10.11] happens 2023-09-30 -[1.10.11] displays on same row as [1.10.10] +[1.10.11 (EOL)] happens 2023-09-30 +[1.10.11 (EOL)] displays on same row as [1.10.10] [1.10] is colored in #F6DD60 [1.12] starts 2023-01-01 and lasts 48 weeks -[1.12.3] happens 2023-11-30 +[1.12.3 (EOL)] happens 2023-11-30 [1.12] is colored in #88CCEE [1.14] starts at 2023-01-01 and lasts 110 weeks [1.14.1] happens at 2023-04-30 [1.14.2] happens at 2023-08-31 [1.14.3] happens at 2023-10-31 -[1.14.4.2] happens at 2024-04-15 -[1.14.4.3] happens at 2024-05-22 [1.14.5] happens at 2024-09-30 -[1.14.6] happens at 2025-01-30 +[1.14.6 (EOL)] happens at 2025-01-30 [1.14.1] displays on same row as [1.14.1] [1.14.2] displays on same row as [1.14.1] [1.14.3] displays on same row as [1.14.1] -[1.14.4.2] displays on same row as [1.14.1] -[1.14.5] displays on same row as [1.14.1] -[1.14.6] displays on same row as [1.14.1] +[1.14.5] displays on same row as [1.14.1] +[1.14.6 (EOL)] displays on same row as [1.14.1] [1.14] is colored in #B187CF -[2.0] starts at 2024-09-30 and lasts 64 weeks +[2.0 Active] starts at 2024-09-30 and lasts 85 weeks [2.0.0] happens at 2025-09-30 -[2.0] is colored in #02BFA0 +[2.1.0] happens at 2026-1-30 +[2.1.0] displays on same row as [2.0.0] +[2.0 Active] is colored in #02BFA0 @endgantt \ No newline at end of file diff --git a/release_docs/img/release-schedule.png b/release_docs/img/release-schedule.png index 3c7d5290aaa10dd389b17289198e64fe27c35b4a..a8740b1d0630b340fdf6f93ecfc82b3ce30dcc63 100644 GIT binary patch literal 12641 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