This is an automated email from the ASF dual-hosted git repository.

XiaoHongbo-Hope pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-mosaic.git


The following commit(s) were added to refs/heads/main by this push:
     new 2824a43  ci: publish Python release after Rust and Java (#42)
2824a43 is described below

commit 2824a434b5602a8394a1e0a15afaf71bdae9132b
Author: QuakeWang <[email protected]>
AuthorDate: Sun May 24 16:14:34 2026 +0800

    ci: publish Python release after Rust and Java (#42)
    
    Signed-off-by: QuakeWang <[email protected]>
---
 .github/workflows/release-java.yml                 |  9 +--
 .github/workflows/release-python-publish.yml       | 89 ++++++++++++++++++++++
 .github/workflows/release-python.yml               | 57 +++-----------
 .github/workflows/release-rust.yml                 | 13 ++--
 .../workflows/{release-rust.yml => release.yml}    | 59 +++++++-------
 docs/creating-a-release.html                       | 24 +++---
 docs/verifying-a-release-candidate.html            |  2 +-
 7 files changed, 156 insertions(+), 97 deletions(-)

diff --git a/.github/workflows/release-java.yml 
b/.github/workflows/release-java.yml
index 519b5b2..961ba7d 100644
--- a/.github/workflows/release-java.yml
+++ b/.github/workflows/release-java.yml
@@ -19,18 +19,15 @@
 name: Release Java
 
 on:
-  push:
-    tags:
-      - "v[0-9]+.[0-9]+.[0-9]+"
-      - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"
+  workflow_call:
   workflow_dispatch:
 
 env:
   JDK_VERSION: 8
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}
-  cancel-in-progress: true
+  group: release-java-${{ github.ref }}
+  cancel-in-progress: false
 
 jobs:
   build-native:
diff --git a/.github/workflows/release-python-publish.yml 
b/.github/workflows/release-python-publish.yml
new file mode 100644
index 0000000..34cab74
--- /dev/null
+++ b/.github/workflows/release-python-publish.yml
@@ -0,0 +1,89 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Publish Python wheels after the orchestrator has confirmed Rust and Java
+# release jobs completed successfully.
+
+name: Release Python Publish
+
+on:
+  workflow_call:
+
+concurrency:
+  group: release-python-publish-${{ github.ref }}
+  cancel-in-progress: false
+
+permissions:
+  actions: read
+  contents: read
+
+jobs:
+  publish:
+    name: Publish to PyPI
+    if: github.repository == 'apache/paimon-mosaic' && startsWith(github.ref, 
'refs/tags/')
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/download-artifact@v5
+        with:
+          pattern: wheels-*
+          merge-multiple: true
+          path: dist
+
+      - name: Verify wheel versions
+        env:
+          TAG_NAME: ${{ github.ref_name }}
+        shell: bash
+        run: |
+          set -euo pipefail
+
+          expected_version="${TAG_NAME#v}"
+          expected_version="${expected_version/-rc/rc}"
+          shopt -s nullglob
+          wheels=(dist/*.whl)
+
+          if [[ "${#wheels[@]}" -eq 0 ]]; then
+            echo "No wheels found in dist" >&2
+            exit 1
+          fi
+
+          for wheel in "${wheels[@]}"; do
+            base="$(basename "$wheel")"
+            case "$base" in
+              paimon_mosaic-"${expected_version}"-*.whl) ;;
+              *)
+                echo "Unexpected wheel for ${TAG_NAME}: ${base}" >&2
+                exit 1
+                ;;
+            esac
+          done
+
+      - name: Publish to TestPyPI
+        if: contains(github.ref_name, '-rc')
+        uses: 
pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
+        with:
+          repository-url: https://test.pypi.org/legacy/
+          skip-existing: true
+          packages-dir: dist
+          password: ${{ secrets.TEST_PYPI_API_TOKEN }}
+
+      - name: Publish to PyPI
+        if: ${{ !contains(github.ref_name, '-') }}
+        uses: 
pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
+        with:
+          skip-existing: true
+          packages-dir: dist
+          password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.github/workflows/release-python.yml 
b/.github/workflows/release-python.yml
index 1b103d0..257f04d 100644
--- a/.github/workflows/release-python.yml
+++ b/.github/workflows/release-python.yml
@@ -15,25 +15,21 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# Publish the paimon-mosaic Python package to PyPI.
+# Build the paimon-mosaic Python release wheels.
 #
-# Trigger: push a version tag (e.g. v0.1.0, v0.1.0-rc1).
-# Pre-release tags (containing '-') publish to TestPyPI; release tags publish 
to PyPI.
-#
-# Token auth: add secrets PYPI_API_TOKEN / TEST_PYPI_API_TOKEN for publishing.
+# Trigger: called by release.yml or manually dispatched.
+# Publishing is handled by release-python-publish.yml after Rust and Java
+# release jobs complete successfully.
 
-name: Release Python
+name: Release Python Wheels
 
 on:
-  push:
-    tags:
-      - "v[0-9]+.[0-9]+.[0-9]+"
-      - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"
+  workflow_call:
   workflow_dispatch:
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
-  cancel-in-progress: true
+  group: release-python-${{ github.ref }}
+  cancel-in-progress: false
 
 permissions:
   contents: read
@@ -53,7 +49,7 @@ jobs:
       - uses: actions/checkout@v6
 
       - name: Inject RC version into pyproject.toml
-        if: contains(github.ref, '-rc')
+        if: startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 
'-rc')
         run: |
           TAG="${GITHUB_REF#refs/tags/v}"
           # Convert 0.1.0-rc1 to PEP 440: 0.1.0rc1
@@ -99,7 +95,7 @@ jobs:
       - uses: actions/checkout@v6
 
       - name: Inject RC version into pyproject.toml
-        if: contains(github.ref, '-rc')
+        if: startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 
'-rc')
         run: |
           TAG="${GITHUB_REF#refs/tags/v}"
           PEP440_VERSION=$(echo "$TAG" | sed 's/-rc/rc/')
@@ -150,7 +146,7 @@ jobs:
       - uses: actions/checkout@v6
 
       - name: Inject RC version into pyproject.toml
-        if: contains(github.ref, '-rc')
+        if: startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 
'-rc')
         shell: bash
         run: |
           TAG="${GITHUB_REF#refs/tags/v}"
@@ -186,34 +182,3 @@ jobs:
         with:
           name: wheels-windows-x86_64
           path: python/dist/*.whl
-
-  release:
-    name: Publish to PyPI
-    runs-on: ubuntu-latest
-    permissions:
-      contents: read
-    needs: [wheels-linux, wheels-macos, wheels-windows]
-    if: startsWith(github.ref, 'refs/tags/')
-    steps:
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: wheels-*
-          merge-multiple: true
-          path: dist
-
-      - name: Publish to TestPyPI
-        if: contains(github.ref, '-')
-        uses: 
pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
-        with:
-          repository-url: https://test.pypi.org/legacy/
-          skip-existing: true
-          packages-dir: dist
-          password: ${{ secrets.TEST_PYPI_API_TOKEN }}
-
-      - name: Publish to PyPI
-        if: ${{ !contains(github.ref, '-') }}
-        uses: 
pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
-        with:
-          skip-existing: true
-          packages-dir: dist
-          password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.github/workflows/release-rust.yml 
b/.github/workflows/release-rust.yml
index 7585019..7a74ef6 100644
--- a/.github/workflows/release-rust.yml
+++ b/.github/workflows/release-rust.yml
@@ -17,7 +17,7 @@
 
 # Publish paimon-mosaic-core crate to crates.io.
 #
-# Trigger: push a version tag (e.g. v0.1.0, v0.1.0-rc1).
+# Trigger: called by release.yml or manually dispatched.
 # Pre-release tags (containing '-') only run dry-run checks without publishing.
 #
 # Token auth: add secret CARGO_REGISTRY_TOKEN for crates.io publishing.
@@ -25,12 +25,13 @@
 name: Release Rust
 
 on:
-  push:
-    tags:
-      - "v[0-9]+.[0-9]+.[0-9]+"
-      - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"
+  workflow_call:
   workflow_dispatch:
 
+concurrency:
+  group: release-rust-${{ github.ref }}
+  cancel-in-progress: false
+
 jobs:
   publish:
     runs-on: ubuntu-latest
@@ -48,7 +49,7 @@ jobs:
         run: cargo publish -p paimon-mosaic-core --dry-run
 
       - name: Publish paimon-mosaic-core to crates.io
-        if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-')
+        if: github.repository == 'apache/paimon-mosaic' && 
startsWith(github.ref, 'refs/tags/') && !contains(github.ref_name, '-')
         run: cargo publish -p paimon-mosaic-core
         env:
           CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
diff --git a/.github/workflows/release-rust.yml b/.github/workflows/release.yml
similarity index 51%
copy from .github/workflows/release-rust.yml
copy to .github/workflows/release.yml
index 7585019..7c1f516 100644
--- a/.github/workflows/release-rust.yml
+++ b/.github/workflows/release.yml
@@ -15,14 +15,10 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# Publish paimon-mosaic-core crate to crates.io.
-#
-# Trigger: push a version tag (e.g. v0.1.0, v0.1.0-rc1).
-# Pre-release tags (containing '-') only run dry-run checks without publishing.
-#
-# Token auth: add secret CARGO_REGISTRY_TOKEN for crates.io publishing.
+# Orchestrate the full release while keeping each language release workflow
+# independently runnable through workflow_dispatch.
 
-name: Release Rust
+name: Release
 
 on:
   push:
@@ -31,24 +27,33 @@ on:
       - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"
   workflow_dispatch:
 
+concurrency:
+  group: release-${{ github.ref }}
+  cancel-in-progress: false
+
+permissions:
+  actions: read
+  contents: read
+
 jobs:
-  publish:
-    runs-on: ubuntu-latest
-    permissions:
-      contents: read
-    steps:
-      - uses: actions/checkout@v6
-
-      - name: Setup Rust toolchain
-        run: |
-          rustup update stable
-          rustup default stable
-
-      - name: Dry run
-        run: cargo publish -p paimon-mosaic-core --dry-run
-
-      - name: Publish paimon-mosaic-core to crates.io
-        if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-')
-        run: cargo publish -p paimon-mosaic-core
-        env:
-          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+  rust:
+    name: Rust release
+    uses: ./.github/workflows/release-rust.yml
+    secrets: inherit
+
+  java:
+    name: Java release
+    uses: ./.github/workflows/release-java.yml
+    secrets: inherit
+
+  python-wheels:
+    name: Python wheels
+    uses: ./.github/workflows/release-python.yml
+    secrets: inherit
+
+  python-publish:
+    name: Python publish
+    needs: [rust, java, python-wheels]
+    if: startsWith(github.ref, 'refs/tags/')
+    uses: ./.github/workflows/release-python-publish.yml
+    secrets: inherit
diff --git a/docs/creating-a-release.html b/docs/creating-a-release.html
index 7895d08..8b69a4d 100644
--- a/docs/creating-a-release.html
+++ b/docs/creating-a-release.html
@@ -69,15 +69,15 @@
             </ol>
 
             <h3>Automated Publishing</h3>
-            <p>When a version tag is pushed, GitHub Actions automatically 
publishes language-specific artifacts:</p>
+            <p>When a version tag is pushed, the <code>Release</code> workflow 
orchestrates language-specific release jobs:</p>
             <table>
                 <thead>
-                    <tr><th>Component</th><th>Tag Pattern</th><th>Published 
To</th><th>Pre-release (<code>-rc</code>) Behavior</th></tr>
+                    <tr><th>Component</th><th>Tag Pattern</th><th>Published 
To</th><th>Pre-release (<code>-rc</code>) Behavior</th><th>Ordering</th></tr>
                 </thead>
                 <tbody>
-                    <tr><td>Rust 
crate</td><td><code>v0.1.0</code></td><td>crates.io</td><td>Dry-run 
only</td></tr>
-                    <tr><td>Java 
binding</td><td><code>v0.1.0</code></td><td>Apache Nexus 
staging</td><td>Deploys to staging</td></tr>
-                    <tr><td>Python 
binding</td><td><code>v0.1.0</code></td><td>PyPI</td><td>Publishes to 
TestPyPI</td></tr>
+                    <tr><td>Rust 
crate</td><td><code>v0.1.0</code></td><td>crates.io</td><td>Dry-run 
only</td><td>Runs in parallel</td></tr>
+                    <tr><td>Java 
binding</td><td><code>v0.1.0</code></td><td>Apache Nexus 
staging</td><td>Deploys to staging</td><td>Runs in parallel</td></tr>
+                    <tr><td>Python 
binding</td><td><code>v0.1.0</code></td><td>PyPI</td><td>Publishes to 
TestPyPI</td><td>Publishes only after Rust and Java jobs succeed</td></tr>
                 </tbody>
             </table>
             <p>The Release Manager's primary responsibility is managing the 
<strong>source release</strong> (tarball + signature) and coordinating the 
community vote. Language artifact publishing is handled by CI once the tag is 
pushed.</p>
@@ -215,12 +215,14 @@ cd ..</code></pre>
 git tag -v ${RC_TAG}
 git push origin ${RELEASE_BRANCH}
 git push origin ${RC_TAG}</code></pre>
-            <p>After pushing, verify in <a 
href="https://github.com/apache/paimon-mosaic/actions";>GitHub Actions</a> that 
all release workflows succeed:</p>
+            <p>After pushing, verify in <a 
href="https://github.com/apache/paimon-mosaic/actions";>GitHub Actions</a> that 
the <code>Release</code> workflow succeeds:</p>
             <ul>
-                <li><strong>Release Rust</strong> &mdash; dry-run check (does 
not publish for RC tags)</li>
-                <li><strong>Release Java</strong> &mdash; builds native JNI 
libraries for 4 platforms, deploys JAR to Apache Nexus staging</li>
-                <li><strong>Release Python</strong> &mdash; builds wheels for 
4 platforms, publishes to TestPyPI</li>
+                <li><strong>Rust release</strong> &mdash; dry-run check (does 
not publish for RC tags)</li>
+                <li><strong>Java release</strong> &mdash; builds native JNI 
libraries for 4 platforms, deploys JAR to Apache Nexus staging</li>
+                <li><strong>Python wheels</strong> &mdash; builds wheels for 4 
platforms</li>
+                <li><strong>Python publish</strong> &mdash; publishes to 
TestPyPI only after the Rust, Java, and Python wheel jobs succeed</li>
             </ul>
+            <p>If only the Python publish job fails due to a transient PyPI or 
TestPyPI issue, use <strong>Re-run failed jobs</strong> on the 
<code>Release</code> workflow. The successful Rust, Java, and Python wheel jobs 
do not need to be repeated.</p>
 
             <h3>Create Source Release Artifacts</h3>
             <p>Create source release artifacts from the same commit as the RC 
tag:</p>
@@ -245,7 +247,7 @@ svn commit -m "Add paimon-mosaic ${RELEASE_VERSION} 
RC${RC_NUM}"</code></pre>
 
             <p><strong>Checklist:</strong></p>
             <ul>
-                <li>RC tag pushed and CI workflows succeeded</li>
+                <li>RC tag pushed and the CI release workflow succeeded</li>
                 <li>Source tarball, signature, and checksum staged to <a 
href="https://dist.apache.org/repos/dist/dev/paimon/";>dist.apache.org 
dev</a></li>
                 <li>Java artifacts deployed to Nexus staging repository</li>
                 <li>Python wheels published to TestPyPI</li>
@@ -308,7 +310,7 @@ svn commit -m "Remove paimon-mosaic ${RELEASE_VERSION} 
RC${RC_NUM} (superseded)"
             <h2 id="finalize-the-release">Finalize the Release</h2>
 
             <h3>Push the Release Tag</h3>
-            <p>Once the vote passes, create and push the final release tag. 
This triggers CI to publish to crates.io and PyPI automatically.</p>
+            <p>Once the vote passes, create and push the final release tag. 
This triggers CI to publish to crates.io and PyPI automatically; PyPI 
publishing runs only after the Rust and Java release jobs succeed.</p>
 <pre><code>git checkout ${RC_TAG}
 git tag -s ${RELEASE_TAG} -m "Release Apache Paimon Mosaic ${RELEASE_VERSION}"
 git tag -v ${RELEASE_TAG}
diff --git a/docs/verifying-a-release-candidate.html 
b/docs/verifying-a-release-candidate.html
index 1ec53e0..6f620de 100644
--- a/docs/verifying-a-release-candidate.html
+++ b/docs/verifying-a-release-candidate.html
@@ -170,7 +170,7 @@ paimon-mosaic-core = { git = 
"https://github.com/apache/paimon-mosaic";, tag = "v
             </ol>
 
             <h3>Python (TestPyPI)</h3>
-            <p>The RC tag publishes wheels to TestPyPI. Install and verify:</p>
+            <p>The RC tag publishes wheels to TestPyPI after the Rust and Java 
release jobs succeed. Install and verify:</p>
 <pre><code>pip install -i https://test.pypi.org/simple/ 
paimon-mosaic==${RELEASE_VERSION}rc${RC_NUM}
 python -c "import mosaic; print('OK')"</code></pre>
             <p>Verify wheels are available for: Linux x86_64, Linux aarch64, 
macOS aarch64, Windows x86_64.</p>

Reply via email to