|
| 1 | +name: Build and Release Python Packages to PyPI |
| 2 | + |
| 3 | +on: |
| 4 | + push: |
| 5 | + tags: |
| 6 | + - '[0-9]+.[0-9]+.[0-9]+*' # Matches: 25.10.1, 25.10.1.dev0, 25.10.1.post1, etc. |
| 7 | + |
| 8 | +jobs: |
| 9 | + # Validate version compatibility between tag and pom.xml |
| 10 | + validate-version: |
| 11 | + name: Validate Version Compatibility |
| 12 | + runs-on: ubuntu-24.04 |
| 13 | + outputs: |
| 14 | + python-version: ${{ steps.validate.outputs.python-version }} |
| 15 | + base-version: ${{ steps.validate.outputs.base-version }} |
| 16 | + steps: |
| 17 | + - name: Checkout code |
| 18 | + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 |
| 19 | + |
| 20 | + - name: Set up Python |
| 21 | + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 |
| 22 | + with: |
| 23 | + python-version: '3.12' |
| 24 | + |
| 25 | + - name: Validate version compatibility |
| 26 | + id: validate |
| 27 | + run: | |
| 28 | + cd bindings/python |
| 29 | +
|
| 30 | + # Get version from git tag |
| 31 | + TAG_VERSION="${{ github.ref_name }}" |
| 32 | + echo "📌 Git tag version: $TAG_VERSION" |
| 33 | +
|
| 34 | + # Extract base version from tag (remove .dev0, .post1, etc.) |
| 35 | + TAG_BASE=$(echo "$TAG_VERSION" | sed -E 's/\.(dev|post|rc|a|b)[0-9]+$//') |
| 36 | + echo "📌 Tag base version: $TAG_BASE" |
| 37 | +
|
| 38 | + # Get version from pom.xml |
| 39 | + POM_VERSION=$(python3 extract_version.py --format=docker) |
| 40 | + echo "📌 pom.xml version: $POM_VERSION" |
| 41 | +
|
| 42 | + # Extract base version from pom.xml (remove -SNAPSHOT, etc.) |
| 43 | + POM_BASE=$(echo "$POM_VERSION" | sed 's/-SNAPSHOT$//' | sed 's/-RC.*//') |
| 44 | + echo "📌 pom.xml base version: $POM_BASE" |
| 45 | +
|
| 46 | + # Compare base versions |
| 47 | + if [ "$TAG_BASE" != "$POM_BASE" ]; then |
| 48 | + echo "❌ Version mismatch!" |
| 49 | + echo " Tag base version: $TAG_BASE" |
| 50 | + echo " pom.xml base version: $POM_BASE" |
| 51 | + echo "" |
| 52 | + echo "This prevents accidentally releasing the wrong version." |
| 53 | + echo "For example, tagging 25.9.1.dev0 when pom.xml says 25.10.1-SNAPSHOT" |
| 54 | + exit 1 |
| 55 | + fi |
| 56 | +
|
| 57 | + echo "✅ Version compatibility check passed!" |
| 58 | + echo " Base version: $TAG_BASE" |
| 59 | + echo " Full tag version: $TAG_VERSION" |
| 60 | +
|
| 61 | + # Output for later jobs |
| 62 | + echo "python-version=$TAG_VERSION" >> $GITHUB_OUTPUT |
| 63 | + echo "base-version=$TAG_BASE" >> $GITHUB_OUTPUT |
| 64 | +
|
| 65 | + # Run example tests before building (workflow_call) |
| 66 | + test-examples: |
| 67 | + name: Run Example Tests |
| 68 | + needs: validate-version |
| 69 | + uses: ./.github/workflows/test-python-examples.yml |
| 70 | + with: |
| 71 | + build-version: ${{ needs.validate-version.outputs.python-version }} |
| 72 | + secrets: inherit |
| 73 | + |
| 74 | + # Run unit tests before building |
| 75 | + test: |
| 76 | + name: Run Unit Tests |
| 77 | + needs: validate-version |
| 78 | + uses: ./.github/workflows/test-python-bindings.yml |
| 79 | + with: |
| 80 | + build-version: ${{ needs.validate-version.outputs.python-version }} |
| 81 | + secrets: inherit |
| 82 | + |
| 83 | + publish: |
| 84 | + name: Publish arcadedb-embedded to PyPI (4 platforms) |
| 85 | + needs: [validate-version, test, test-examples] |
| 86 | + runs-on: ubuntu-latest |
| 87 | + continue-on-error: true # Don't block GitHub Release if PyPI upload fails (size limit) |
| 88 | + environment: pypi |
| 89 | + permissions: |
| 90 | + id-token: write |
| 91 | + steps: |
| 92 | + - name: Download wheels for all tested Python versions |
| 93 | + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 |
| 94 | + with: |
| 95 | + pattern: wheel-*-py* |
| 96 | + path: dist/ |
| 97 | + merge-multiple: true |
| 98 | + |
| 99 | + - name: Verify wheels |
| 100 | + run: | |
| 101 | + ls -lh dist/ |
| 102 | + echo "📦 Wheels for PyPI (current platforms):" |
| 103 | + ls dist/*.whl |
| 104 | +
|
| 105 | + # Count wheels (should be 16: 4 platforms x 4 Python versions) |
| 106 | + WHEEL_COUNT=$(ls dist/*.whl | wc -l) |
| 107 | + echo "📊 Wheel count: $WHEEL_COUNT (expected: 16)" |
| 108 | +
|
| 109 | + echo "" |
| 110 | + echo "ℹ️ Current PyPI platforms: linux x86_64, linux arm64, macOS Apple Silicon, windows x86_64" |
| 111 | +
|
| 112 | + if [ "$WHEEL_COUNT" -ne 16 ]; then |
| 113 | + echo "❌ Expected 16 wheels (4 platforms x 4 Python versions), got $WHEEL_COUNT" |
| 114 | + exit 1 |
| 115 | + fi |
| 116 | +
|
| 117 | + # Show checksums to verify wheels are different |
| 118 | + echo "" |
| 119 | + echo "🔐 Wheel checksums (SHA256):" |
| 120 | + sha256sum dist/*.whl |
| 121 | +
|
| 122 | + # Report wheel sizes with actual component breakdown |
| 123 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 124 | + echo "## 📦 Built Wheels" >> $GITHUB_STEP_SUMMARY |
| 125 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 126 | + echo "| Platform | Wheel Size | JRE Size | JARs Size | Installed Size | SHA256 (first 8 chars) |" >> $GITHUB_STEP_SUMMARY |
| 127 | + echo "|----------|------------|----------|-----------|----------------|------------------------|" >> $GITHUB_STEP_SUMMARY |
| 128 | +
|
| 129 | + for WHEEL_FILE in dist/*.whl; do |
| 130 | + WHEEL_NAME=$(basename "$WHEEL_FILE") |
| 131 | + WHEEL_SIZE_BYTES=$(stat -c%s "$WHEEL_FILE") |
| 132 | + WHEEL_SIZE_MB=$(echo "scale=1; $WHEEL_SIZE_BYTES / 1024 / 1024" | bc) |
| 133 | +
|
| 134 | + # Extract platform from filename |
| 135 | + if [[ "$WHEEL_NAME" == *"manylinux"*"x86_64"* ]]; then |
| 136 | + PLATFORM="linux/amd64" |
| 137 | + elif [[ "$WHEEL_NAME" == *"manylinux"*"aarch64"* ]]; then |
| 138 | + PLATFORM="linux/arm64" |
| 139 | + elif [[ "$WHEEL_NAME" == *"macosx"*"arm64"* ]]; then |
| 140 | + PLATFORM="darwin/arm64" |
| 141 | + # elif [[ "$WHEEL_NAME" == *"macosx"*"x86_64"* ]]; then |
| 142 | + # PLATFORM="darwin/amd64" |
| 143 | + elif [[ "$WHEEL_NAME" == *"win_amd64"* ]]; then |
| 144 | + PLATFORM="windows/amd64" |
| 145 | + else |
| 146 | + PLATFORM="unknown" |
| 147 | + fi |
| 148 | +
|
| 149 | + # Analyze wheel contents |
| 150 | + TEMP_DIR=$(mktemp -d) |
| 151 | + unzip -q "$WHEEL_FILE" -d "$TEMP_DIR" |
| 152 | +
|
| 153 | + # Calculate component sizes (find jre directory anywhere) |
| 154 | + JRE_DIR=$(find "$TEMP_DIR" -type d -name "jre" | head -n1) |
| 155 | + if [ -n "$JRE_DIR" ] && [ -d "$JRE_DIR" ]; then |
| 156 | + JRE_SIZE_BYTES=$(du -sb "$JRE_DIR" | cut -f1) |
| 157 | + JRE_SIZE_MB=$(echo "scale=1; $JRE_SIZE_BYTES / 1024 / 1024" | bc) |
| 158 | + else |
| 159 | + JRE_SIZE_MB="N/A" |
| 160 | + fi |
| 161 | +
|
| 162 | + JAR_SIZE_BYTES=$(find "$TEMP_DIR" -name "*.jar" -exec du -cb {} + 2>/dev/null | tail -1 | cut -f1) |
| 163 | + if [ -n "$JAR_SIZE_BYTES" ] && [ "$JAR_SIZE_BYTES" != "0" ]; then |
| 164 | + JAR_SIZE_MB=$(echo "scale=1; $JAR_SIZE_BYTES / 1024 / 1024" | bc) |
| 165 | + else |
| 166 | + JAR_SIZE_MB="N/A" |
| 167 | + fi |
| 168 | +
|
| 169 | + INSTALLED_SIZE_BYTES=$(du -sb "$TEMP_DIR" | cut -f1) |
| 170 | + INSTALLED_SIZE_MB=$(echo "scale=0; $INSTALLED_SIZE_BYTES / 1024 / 1024" | bc) |
| 171 | +
|
| 172 | + # Calculate SHA256 checksum |
| 173 | + CHECKSUM=$(sha256sum "$WHEEL_FILE" | cut -d' ' -f1 | cut -c1-8) |
| 174 | +
|
| 175 | + rm -rf "$TEMP_DIR" |
| 176 | +
|
| 177 | + echo "| $PLATFORM | ${WHEEL_SIZE_MB}M | ${JRE_SIZE_MB}M | ${JAR_SIZE_MB}M | ~${INSTALLED_SIZE_MB}M | \`$CHECKSUM\` |" >> $GITHUB_STEP_SUMMARY |
| 178 | + done |
| 179 | +
|
| 180 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 181 | + echo "**Note**: Sizes are uncompressed except for Wheel Size (compressed)" >> $GITHUB_STEP_SUMMARY |
| 182 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 183 | +
|
| 184 | + - name: Verify wheel versions |
| 185 | + run: | |
| 186 | + TAG_VERSION="${{ needs.validate-version.outputs.python-version }}" |
| 187 | + EXPECTED_VERSION="$TAG_VERSION" |
| 188 | +
|
| 189 | + for WHEEL_FILE in dist/*.whl; do |
| 190 | + echo "📦 Checking: $WHEEL_FILE" |
| 191 | + # Extract version from wheel filename |
| 192 | + WHEEL_VERSION=$(echo "$WHEEL_FILE" | grep -oP '\d+\.\d+\.\d+(\.(dev|post|rc|a|b)\d+)?') |
| 193 | + echo " Version: $WHEEL_VERSION" |
| 194 | +
|
| 195 | + if [ "$WHEEL_VERSION" != "$EXPECTED_VERSION" ]; then |
| 196 | + echo "❌ Wheel version mismatch in $WHEEL_FILE!" |
| 197 | + echo " Expected: $EXPECTED_VERSION" |
| 198 | + exit 1 |
| 199 | + fi |
| 200 | + done |
| 201 | + echo "✅ All wheel versions match expected version ($EXPECTED_VERSION)" |
| 202 | +
|
| 203 | + - name: Publish to PyPI |
| 204 | + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1 |
0 commit comments