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

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-vector-index.git


The following commit(s) were added to refs/heads/main by this push:
     new 054d736  Add CI release infrastructure for Java/Rust/Python (#39)
054d736 is described below

commit 054d7369114195d21d5a3abcb1b3f2d5ded6e26a
Author: jianguotian <[email protected]>
AuthorDate: Fri Jun 12 14:49:25 2026 +0800

    Add CI release infrastructure for Java/Rust/Python (#39)
---
 .github/workflows/publish_snapshot.yml       | 198 +++++++++++++++++++++++++++
 .github/workflows/release-java.yml           | 179 ++++++++++++++++++++++++
 .github/workflows/release-python-publish.yml |  89 ++++++++++++
 .github/workflows/release-python.yml         | 183 +++++++++++++++++++++++++
 .github/workflows/release-rust.yml           |  55 ++++++++
 .github/workflows/release.yml                |  59 ++++++++
 deploysettings.xml                           |  37 +++++
 java/pom.xml                                 | 127 +++++++++++++++++
 tools/create_release_branch.sh               |  57 ++++++++
 tools/create_source_release.sh               |  90 ++++++++++++
 tools/dependencies.py                        | 124 +++++++++++++++++
 tools/update_branch_version.sh               |  69 ++++++++++
 12 files changed, 1267 insertions(+)

diff --git a/.github/workflows/publish_snapshot.yml 
b/.github/workflows/publish_snapshot.yml
new file mode 100644
index 0000000..c5af5b6
--- /dev/null
+++ b/.github/workflows/publish_snapshot.yml
@@ -0,0 +1,198 @@
+################################################################################
+#  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.
+################################################################################
+
+name: Publish Snapshot
+
+on:
+  schedule:
+    # At the end of every day
+    - cron: '0 0 * * *'
+  workflow_dispatch:
+
+env:
+  JDK_VERSION: 8
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event_name }}-${{ 
github.event.number || github.run_id }}
+  cancel-in-progress: true
+
+jobs:
+  build-native:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - os: ubuntu-latest
+            target: x86_64-unknown-linux-gnu
+            os_name: linux
+            arch: x86_64
+            lib_name: libpaimon_vindex_jni.so
+          - os: ubuntu-24.04-arm
+            target: aarch64-unknown-linux-gnu
+            os_name: linux
+            arch: aarch64
+            lib_name: libpaimon_vindex_jni.so
+          - os: macos-latest
+            target: aarch64-apple-darwin
+            os_name: macos
+            arch: aarch64
+            lib_name: libpaimon_vindex_jni.dylib
+          - os: windows-latest
+            target: x86_64-pc-windows-msvc
+            os_name: windows
+            arch: x86_64
+            lib_name: paimon_vindex_jni.dll
+    steps:
+      - uses: actions/checkout@v6
+
+      - name: Setup Rust toolchain
+        run: |
+          rustup update stable
+          rustup default stable
+
+      - name: Cache Rust dependencies
+        uses: actions/cache@v5
+        with:
+          path: |
+            ~/.cargo/registry
+            ~/.cargo/git
+            target
+          key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ 
hashFiles('**/Cargo.lock') }}
+          restore-keys: |
+            ${{ runner.os }}-${{ matrix.target }}-cargo-
+
+      - name: Setup Python
+        if: matrix.os_name == 'linux'
+        uses: actions/setup-python@v5
+        with:
+          python-version: "3.12"
+
+      - name: Setup Linux zigbuild
+        if: matrix.os_name == 'linux'
+        run: pip install cargo-zigbuild
+
+      - name: Build Linux JNI library
+        if: matrix.os_name == 'linux'
+        shell: bash
+        run: |
+          set -euo pipefail
+
+          rustup target add "${{ matrix.target }}"
+          unset ZSTD_SYS_USE_PKG_CONFIG
+
+          cargo zigbuild --release -p paimon-vindex-jni --target "${{ 
matrix.target }}.2.17"
+
+          artifact="target/${{ matrix.target }}/release/${{ matrix.lib_name }}"
+          test -f "$artifact"
+
+          version_info="$(readelf --version-info "$artifact")"
+          max_glibc="$(
+            printf '%s\n' "$version_info" \
+              | grep -Eo 'GLIBC_[0-9][0-9.]*' \
+              | sort -Vu \
+              | tail -n1 \
+              || true
+          )"
+          echo "Maximum required GLIBC version: ${max_glibc:-none}"
+
+          if [[ -n "$max_glibc" ]]; then
+            allowed_glibc="GLIBC_2.17"
+            highest_glibc="$(printf '%s\n%s\n' "$allowed_glibc" "$max_glibc" | 
sort -Vu | tail -n1)"
+            if [[ "$highest_glibc" != "$allowed_glibc" ]]; then
+              echo "Linux GNU artifact requires $max_glibc, expected <= 
$allowed_glibc" >&2
+              exit 1
+            fi
+          fi
+
+      - name: Build JNI library
+        if: matrix.os_name != 'linux'
+        run: |
+          rustup target add "${{ matrix.target }}"
+          cargo build --release -p paimon-vindex-jni --target "${{ 
matrix.target }}"
+
+      - name: Upload native library
+        uses: actions/upload-artifact@v5
+        with:
+          name: native-${{ matrix.os_name }}-${{ matrix.arch }}
+          path: target/${{ matrix.target }}/release/${{ matrix.lib_name }}
+
+  publish-snapshot:
+    if: github.repository == 'apache/paimon-vector-index'
+    runs-on: ubuntu-latest
+    needs: [build-native]
+    steps:
+      - uses: actions/checkout@v6
+
+      - name: Download linux x86_64 native library
+        uses: actions/download-artifact@v5
+        with:
+          name: native-linux-x86_64
+          path: java/src/main/resources/native/linux/x86_64
+
+      - name: Download linux aarch64 native library
+        uses: actions/download-artifact@v5
+        with:
+          name: native-linux-aarch64
+          path: java/src/main/resources/native/linux/aarch64
+
+      - name: Download macOS aarch64 native library
+        uses: actions/download-artifact@v5
+        with:
+          name: native-macos-aarch64
+          path: java/src/main/resources/native/macos/aarch64
+
+      - name: Download windows x86_64 native library
+        uses: actions/download-artifact@v5
+        with:
+          name: native-windows-x86_64
+          path: java/src/main/resources/native/windows/x86_64
+
+      - name: Verify native libraries
+        run: find java/src/main/resources/native -type f | sort
+
+      - name: Set up JDK ${{ env.JDK_VERSION }}
+        uses: actions/setup-java@v4
+        with:
+          java-version: ${{ env.JDK_VERSION }}
+          distribution: 'temurin'
+
+      - name: Cache local Maven repository
+        uses: actions/cache@v5
+        with:
+          path: ~/.m2/repository
+          key: snapshot-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            snapshot-maven-
+
+      - name: Publish snapshot
+        env:
+          ASF_USERNAME: ${{ secrets.NEXUS_USER }}
+          ASF_PASSWORD: ${{ secrets.NEXUS_PW }}
+          MAVEN_OPTS: -Xmx4096m
+        working-directory: java
+        run: |
+          tmp_settings="tmp-settings.xml"
+          echo "<settings><servers><server>" > $tmp_settings
+          echo 
"<id>apache.snapshots.https</id><username>$ASF_USERNAME</username>" >> 
$tmp_settings
+          echo "<password>$ASF_PASSWORD</password>" >> $tmp_settings
+          echo "</server></servers></settings>" >> $tmp_settings
+
+          mvn --settings $tmp_settings clean deploy -Dgpg.skip -Drat.skip 
-DskipTests
+
+          rm $tmp_settings
diff --git a/.github/workflows/release-java.yml 
b/.github/workflows/release-java.yml
new file mode 100644
index 0000000..17f85aa
--- /dev/null
+++ b/.github/workflows/release-java.yml
@@ -0,0 +1,179 @@
+################################################################################
+#  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.
+################################################################################
+
+name: Release Java
+
+on:
+  workflow_call:
+  workflow_dispatch:
+
+env:
+  JDK_VERSION: 8
+
+concurrency:
+  group: release-java-${{ github.ref }}
+  cancel-in-progress: false
+
+jobs:
+  build-native:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - os: ubuntu-latest
+            target: x86_64-unknown-linux-gnu
+            os_name: linux
+            arch: x86_64
+            lib_name: libpaimon_vindex_jni.so
+          - os: ubuntu-24.04-arm
+            target: aarch64-unknown-linux-gnu
+            os_name: linux
+            arch: aarch64
+            lib_name: libpaimon_vindex_jni.so
+          - os: macos-latest
+            target: aarch64-apple-darwin
+            os_name: macos
+            arch: aarch64
+            lib_name: libpaimon_vindex_jni.dylib
+          - os: windows-latest
+            target: x86_64-pc-windows-msvc
+            os_name: windows
+            arch: x86_64
+            lib_name: paimon_vindex_jni.dll
+    steps:
+      - uses: actions/checkout@v6
+
+      - name: Setup Rust toolchain
+        run: |
+          rustup update stable
+          rustup default stable
+
+      - name: Setup Python
+        if: matrix.os_name == 'linux'
+        uses: actions/setup-python@v5
+        with:
+          python-version: "3.12"
+
+      - name: Setup Linux zigbuild
+        if: matrix.os_name == 'linux'
+        run: pip install cargo-zigbuild
+
+      - name: Build Linux JNI library
+        if: matrix.os_name == 'linux'
+        shell: bash
+        run: |
+          set -euo pipefail
+
+          rustup target add "${{ matrix.target }}"
+          unset ZSTD_SYS_USE_PKG_CONFIG
+
+          cargo zigbuild --release -p paimon-vindex-jni --target "${{ 
matrix.target }}.2.17"
+
+          artifact="target/${{ matrix.target }}/release/${{ matrix.lib_name }}"
+          test -f "$artifact"
+
+          version_info="$(readelf --version-info "$artifact")"
+          max_glibc="$(
+            printf '%s\n' "$version_info" \
+              | grep -Eo 'GLIBC_[0-9][0-9.]*' \
+              | sort -Vu \
+              | tail -n1 \
+              || true
+          )"
+          echo "Maximum required GLIBC version: ${max_glibc:-none}"
+
+          if [[ -n "$max_glibc" ]]; then
+            allowed_glibc="GLIBC_2.17"
+            highest_glibc="$(printf '%s\n%s\n' "$allowed_glibc" "$max_glibc" | 
sort -Vu | tail -n1)"
+            if [[ "$highest_glibc" != "$allowed_glibc" ]]; then
+              echo "Linux GNU artifact requires $max_glibc, expected <= 
$allowed_glibc" >&2
+              exit 1
+            fi
+          fi
+
+      - name: Build JNI library
+        if: matrix.os_name != 'linux'
+        run: |
+          rustup target add "${{ matrix.target }}"
+          cargo build --release -p paimon-vindex-jni --target "${{ 
matrix.target }}"
+
+      - name: Upload native library
+        uses: actions/upload-artifact@v5
+        with:
+          name: native-${{ matrix.os_name }}-${{ matrix.arch }}
+          path: target/${{ matrix.target }}/release/${{ matrix.lib_name }}
+
+  deploy-staging:
+    if: github.repository == 'apache/paimon-vector-index' && 
startsWith(github.ref, 'refs/tags/')
+    runs-on: ubuntu-latest
+    needs: [build-native]
+    steps:
+      - uses: actions/checkout@v6
+
+      - name: Download linux x86_64 native library
+        uses: actions/download-artifact@v5
+        with:
+          name: native-linux-x86_64
+          path: java/src/main/resources/native/linux/x86_64
+
+      - name: Download linux aarch64 native library
+        uses: actions/download-artifact@v5
+        with:
+          name: native-linux-aarch64
+          path: java/src/main/resources/native/linux/aarch64
+
+      - name: Download macOS aarch64 native library
+        uses: actions/download-artifact@v5
+        with:
+          name: native-macos-aarch64
+          path: java/src/main/resources/native/macos/aarch64
+
+      - name: Download windows x86_64 native library
+        uses: actions/download-artifact@v5
+        with:
+          name: native-windows-x86_64
+          path: java/src/main/resources/native/windows/x86_64
+
+      - name: Verify native libraries
+        run: find java/src/main/resources/native -type f | sort
+
+      - name: Set up JDK ${{ env.JDK_VERSION }}
+        uses: actions/setup-java@v4
+        with:
+          java-version: ${{ env.JDK_VERSION }}
+          distribution: 'temurin'
+          server-id: apache.releases.https
+          server-username: MAVEN_USERNAME
+          server-password: MAVEN_PASSWORD
+          gpg-private-key: ${{ secrets.GPG_SECRET_KEY }}
+          gpg-passphrase: MAVEN_GPG_PASSPHRASE
+
+      - name: Show Maven version
+        run: mvn --version
+
+      - name: Deploy to Apache Nexus staging
+        working-directory: java
+        run: |
+          mvn clean deploy \
+            -Prelease \
+            -DskipTests
+        env:
+          MAVEN_USERNAME: ${{ secrets.NEXUS_STAGE_DEPLOYER_USER }}
+          MAVEN_PASSWORD: ${{ secrets.NEXUS_STAGE_DEPLOYER_PW }}
+          MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
diff --git a/.github/workflows/release-python-publish.yml 
b/.github/workflows/release-python-publish.yml
new file mode 100644
index 0000000..44b1cc8
--- /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 the wheel
+# build 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-vector-index' && 
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_vindex-"${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
new file mode 100644
index 0000000..39dc779
--- /dev/null
+++ b/.github/workflows/release-python.yml
@@ -0,0 +1,183 @@
+# 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.
+
+# Build the paimon-vindex Python release wheels.
+#
+# Trigger: called by release.yml or manually dispatched.
+# Publishing is handled by release-python-publish.yml after wheels are built.
+
+name: Release Python Wheels
+
+on:
+  workflow_call:
+  workflow_dispatch:
+
+concurrency:
+  group: release-python-${{ github.ref }}
+  cancel-in-progress: false
+
+permissions:
+  contents: read
+
+jobs:
+  wheels-linux:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - os: ubuntu-latest
+            arch: x86_64
+          - os: ubuntu-24.04-arm
+            arch: aarch64
+    steps:
+      - uses: actions/checkout@v6
+
+      - name: Inject RC version into pyproject.toml
+        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
+          PEP440_VERSION=$(echo "$TAG" | sed 's/-rc/rc/')
+          sed -i "s/^version = .*/version = \"${PEP440_VERSION}\"/" 
python/pyproject.toml
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: "3.12"
+
+      - name: Build wheels via cibuildwheel
+        uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e
+        with:
+          package-dir: python
+          output-dir: wheelhouse
+        env:
+          CIBW_BUILD: "cp39-manylinux_${{ matrix.arch }}"
+          CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
+          CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
+          CIBW_BEFORE_ALL_LINUX: >
+            curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s 
-- -y &&
+            source $HOME/.cargo/env &&
+            cargo build --release -p paimon-vindex-ffi &&
+            cp target/release/libpaimon_vindex_ffi.so {package}/paimon_vindex/
+
+      - name: Upload wheels
+        uses: actions/upload-artifact@v5
+        with:
+          name: wheels-linux-${{ matrix.arch }}
+          path: wheelhouse/*.whl
+
+  wheels-macos:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - os: macos-latest
+            target: aarch64-apple-darwin
+            lib_name: libpaimon_vindex_ffi.dylib
+    steps:
+      - uses: actions/checkout@v6
+
+      - name: Inject RC version into pyproject.toml
+        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/')
+          sed -i '' "s/^version = .*/version = \"${PEP440_VERSION}\"/" 
python/pyproject.toml
+
+      - name: Setup Rust toolchain
+        run: |
+          rustup update stable
+          rustup default stable
+
+      - name: Build native library
+        run: cargo build --release -p paimon-vindex-ffi
+
+      - name: Copy native library into package
+        run: cp target/release/${{ matrix.lib_name }} python/paimon_vindex/
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: "3.12"
+
+      - name: Install build tools
+        run: pip install build wheel setuptools delocate
+
+      - name: Build wheel
+        working-directory: python
+        env:
+          _PYTHON_HOST_PLATFORM: macosx-11.0-arm64
+          ARCHFLAGS: "-arch arm64"
+          MACOSX_DEPLOYMENT_TARGET: "11.0"
+        run: python -m build --wheel
+
+      - name: Repair wheel
+        run: |
+          delocate-wheel --require-archs arm64 -w python/dist/repaired 
python/dist/*.whl
+          rm python/dist/*.whl
+          mv python/dist/repaired/*.whl python/dist/
+
+      - name: Upload wheel
+        uses: actions/upload-artifact@v5
+        with:
+          name: wheels-${{ matrix.os }}-${{ matrix.target }}
+          path: python/dist/*.whl
+
+  wheels-windows:
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v6
+
+      - name: Inject RC version into pyproject.toml
+        if: startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 
'-rc')
+        shell: bash
+        run: |
+          TAG="${GITHUB_REF#refs/tags/v}"
+          PEP440_VERSION=$(echo "$TAG" | sed 's/-rc/rc/')
+          sed -i "s/^version = .*/version = \"${PEP440_VERSION}\"/" 
python/pyproject.toml
+
+      - name: Setup Rust toolchain
+        run: |
+          rustup update stable
+          rustup default stable
+
+      - name: Build native library
+        run: cargo build --release -p paimon-vindex-ffi
+
+      - name: Copy native library into package
+        shell: bash
+        run: cp target/release/paimon_vindex_ffi.dll python/paimon_vindex/
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: "3.12"
+
+      - name: Install build tools
+        run: pip install build wheel setuptools
+
+      - name: Build wheel
+        working-directory: python
+        run: python -m build --wheel
+
+      - name: Upload wheel
+        uses: actions/upload-artifact@v5
+        with:
+          name: wheels-windows-x86_64
+          path: python/dist/*.whl
diff --git a/.github/workflows/release-rust.yml 
b/.github/workflows/release-rust.yml
new file mode 100644
index 0000000..2fd540c
--- /dev/null
+++ b/.github/workflows/release-rust.yml
@@ -0,0 +1,55 @@
+# 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 paimon-vindex-core crate to crates.io.
+#
+# 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.
+
+name: Release Rust
+
+on:
+  workflow_call:
+  workflow_dispatch:
+
+concurrency:
+  group: release-rust-${{ github.ref }}
+  cancel-in-progress: false
+
+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-vindex-core --dry-run
+
+      - name: Publish paimon-vindex-core to crates.io
+        if: github.repository == 'apache/paimon-vector-index' && 
startsWith(github.ref, 'refs/tags/') && !contains(github.ref_name, '-')
+        run: cargo publish -p paimon-vindex-core
+        env:
+          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..4fd2491
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,59 @@
+# 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.
+
+# Orchestrate the full release while keeping each language release workflow
+# independently runnable through workflow_dispatch.
+
+name: Release
+
+on:
+  push:
+    tags:
+      - "v[0-9]+.[0-9]+.[0-9]+"
+      - "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:
+  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: [python-wheels]
+    if: startsWith(github.ref, 'refs/tags/')
+    uses: ./.github/workflows/release-python-publish.yml
+    secrets: inherit
diff --git a/deploysettings.xml b/deploysettings.xml
new file mode 100644
index 0000000..6026958
--- /dev/null
+++ b/deploysettings.xml
@@ -0,0 +1,37 @@
+<!--
+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.
+
+-->
+
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0";
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd";>
+  <servers>
+    <server>
+      <id>apache.snapshots.https</id>
+      <username>${sonatype_user}</username>
+      <password>${sonatype_pw}</password>
+    </server>
+    <server>
+      <id>apache.releases.https</id>
+      <username>${sonatype_user}</username>
+      <password>${sonatype_pw}</password>
+    </server>
+  </servers>
+</settings>
diff --git a/java/pom.xml b/java/pom.xml
index d649d49..a2b7008 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -22,17 +22,51 @@
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
https://maven.apache.org/xsd/maven-4.0.0.xsd";>
     <modelVersion>4.0.0</modelVersion>
 
+    <parent>
+        <groupId>org.apache</groupId>
+        <artifactId>apache</artifactId>
+        <version>23</version>
+    </parent>
+
     <groupId>org.apache.paimon</groupId>
     <artifactId>paimon-vector-index-java</artifactId>
     <version>0.1.0-SNAPSHOT</version>
     <packaging>jar</packaging>
 
     <name>Apache Paimon Vector Index Java</name>
+    <description>Vector index for Java (backed by Rust via JNI)</description>
+    <url>https://paimon.apache.org</url>
+    <inceptionYear>2026</inceptionYear>
+
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <scm>
+        <url>https://github.com/apache/paimon-vector-index</url>
+        <connection>[email protected]:apache/paimon-vector-index.git</connection>
+        
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/paimon-vector-index.git</developerConnection>
+    </scm>
+
+    <developers>
+        <developer>
+            <name>Apache Paimon Contributors</name>
+            <email>[email protected]</email>
+            <organization>Apache Software Foundation</organization>
+            <organizationUrl>https://www.apache.org</organizationUrl>
+        </developer>
+    </developers>
 
     <properties>
         <maven.compiler.source>8</maven.compiler.source>
         <maven.compiler.target>8</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <!-- Used by maven-remote-resources-plugin when generating 
META-INF/NOTICE. -->
+        
<project.build.outputTimestamp>2026-01-01T00:00:00Z</project.build.outputTimestamp>
     </properties>
 
     <build>
@@ -67,4 +101,97 @@
             </plugin>
         </plugins>
     </build>
+
+    <profiles>
+        <profile>
+            <id>release</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-gpg-plugin</artifactId>
+                        <version>3.2.8</version>
+                        <executions>
+                            <execution>
+                                <id>sign-artifacts</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>sign</goal>
+                                </goals>
+                                <configuration>
+                                    <bestPractices>true</bestPractices>
+                                    <gpgArguments>
+                                        <arg>--pinentry-mode</arg>
+                                        <arg>loopback</arg>
+                                    </gpgArguments>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-source-plugin</artifactId>
+                        <version>3.3.1</version>
+                        <executions>
+                            <execution>
+                                <id>attach-sources</id>
+                                <goals>
+                                    <goal>jar-no-fork</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-javadoc-plugin</artifactId>
+                        <version>3.6.3</version>
+                        <configuration>
+                            <quiet>true</quiet>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>attach-javadocs</id>
+                                <goals>
+                                    <goal>jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>enforce-maven</id>
+                                <goals>
+                                    <goal>enforce</goal>
+                                </goals>
+                                <configuration>
+                                    <rules>
+                                        <requireJavaVersion>
+                                            <version>1.8.0</version>
+                                        </requireJavaVersion>
+                                        <requireMavenVersion>
+                                            <version>[3.1.1,)</version>
+                                        </requireMavenVersion>
+                                    </rules>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.sonatype.plugins</groupId>
+                        <artifactId>nexus-staging-maven-plugin</artifactId>
+                        <version>1.7.0</version>
+                        <extensions>true</extensions>
+                        <configuration>
+                            <serverId>apache.releases.https</serverId>
+                            <nexusUrl>https://repository.apache.org/</nexusUrl>
+                            
<autoReleaseAfterClose>false</autoReleaseAfterClose>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
 </project>
diff --git a/tools/create_release_branch.sh b/tools/create_release_branch.sh
new file mode 100755
index 0000000..71fd87c
--- /dev/null
+++ b/tools/create_release_branch.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+##
+## Variables with defaults (if not overwritten by environment)
+##
+RELEASE_CANDIDATE=${RELEASE_CANDIDATE:-none}
+MVN=${MVN:-mvn}
+
+# fail immediately
+set -o errexit
+set -o nounset
+# print command before executing
+set -o xtrace
+
+CURR_DIR=`pwd`
+if [[ `basename ${CURR_DIR}` != "tools" ]] ; then
+  echo "You have to call the script from the tools/ dir"
+  exit 1
+fi
+
+###########################
+
+if [ -z "${RELEASE_VERSION}" ]; then
+       echo "RELEASE_VERSION is unset"
+       exit 1
+fi
+
+cd ..
+
+target_branch=release-${RELEASE_VERSION}
+if [ "${RELEASE_CANDIDATE}" != "none" ]; then
+  target_branch=${target_branch}-rc${RELEASE_CANDIDATE}
+fi
+
+git checkout -b ${target_branch}
+
+RELEASE_HASH=`git rev-parse HEAD`
+echo "Echo created release hash $RELEASE_HASH"
+
+echo "Done. Don't forget to create the release tag on GitHub and push the 
changes."
diff --git a/tools/create_source_release.sh b/tools/create_source_release.sh
new file mode 100755
index 0000000..cc04991
--- /dev/null
+++ b/tools/create_source_release.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+# Create ASF source release artifacts under tools/release/:
+#   apache-paimon-vector-index-{version}-src.tgz
+#   apache-paimon-vector-index-{version}-src.tgz.asc
+#   apache-paimon-vector-index-{version}-src.tgz.sha512
+#
+# Usage: cd tools && RELEASE_VERSION=0.1.0 ./create_source_release.sh
+
+##
+## Variables with defaults (if not overwritten by environment)
+##
+MVN=${MVN:-mvn}
+
+# fail immediately
+set -o errexit
+set -o nounset
+set -o pipefail
+# print command before executing
+set -o xtrace
+
+CURR_DIR=`pwd`
+if [[ `basename $CURR_DIR` != "tools" ]] ; then
+  echo "You have to call the script from the tools/ dir"
+  exit 1
+fi
+
+if [ "$(uname)" == "Darwin" ]; then
+    SHASUM="shasum -a 512"
+else
+    SHASUM="sha512sum"
+fi
+
+###########################
+
+RELEASE_VERSION=${RELEASE_VERSION}
+
+if [ -z "${RELEASE_VERSION}" ]; then
+       echo "RELEASE_VERSION is unset"
+       exit 1
+fi
+
+rm -rf release
+mkdir release
+cd ..
+
+echo "Creating source package"
+
+ARCHIVE="apache-paimon-vector-index-${RELEASE_VERSION}-src.tgz"
+# Archive from Git objects so filesystem metadata such as macOS xattrs is not 
included.
+git archive --format=tar --prefix="paimon-vector-index-${RELEASE_VERSION}/" 
'HEAD^{tree}' . \
+  ':(exclude).gitignore' ':(exclude).gitattributes' \
+  ':(exclude).asf.yaml' ':(exclude).github' \
+  ':(exclude)deploysettings.xml' ':(exclude)target' \
+  ':(exclude).idea' ':(exclude)*.iml' ':(exclude).DS_Store' \
+  | gzip -n > "tools/release/${ARCHIVE}"
+
+cd tools/release
+
+gpg --armor --detach-sig "${ARCHIVE}"
+$SHASUM "${ARCHIVE}" > "${ARCHIVE}.sha512"
+
+echo "Verifying GPG signature"
+gpg --verify "${ARCHIVE}.asc" "${ARCHIVE}"
+
+echo "Verifying tarball integrity"
+tar tzf "${ARCHIVE}" > /dev/null
+
+echo ""
+echo "Source release created successfully. Artifacts in tools/release/:"
+ls -la ${CURR_DIR}/release/apache-paimon-vector-index-*
+echo ""
+echo "Next: upload contents to SVN (see docs/creating-a-release.html)."
diff --git a/tools/dependencies.py b/tools/dependencies.py
new file mode 100755
index 0000000..a4f7be1
--- /dev/null
+++ b/tools/dependencies.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+#
+# 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.
+#
+
+"""Check and generate Rust dependency license information for ASF release 
compliance.
+
+Requires cargo-deny: cargo install cargo-deny
+Requires Python 3.11+ (uses tomllib).
+
+Usage:
+    python3 tools/dependencies.py check      # Verify all deps have approved 
licenses
+    python3 tools/dependencies.py generate   # Generate DEPENDENCIES.rust.tsv
+"""
+
+import sys
+
+if sys.version_info < (3, 11):
+    sys.exit(
+        "This script requires Python 3.11 or newer (uses tomllib). "
+        f"Current: {sys.version}."
+    )
+
+import subprocess
+import tomllib
+from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
+from pathlib import Path
+
+ROOT_DIR = Path(__file__).resolve().parent.parent
+
+PACKAGES = ["."]
+root_cargo = ROOT_DIR / "Cargo.toml"
+if root_cargo.exists():
+    with open(root_cargo, "rb") as f:
+        data = tomllib.load(f)
+    members = data.get("workspace", {}).get("members", [])
+    if isinstance(members, list):
+        for m in members:
+            if isinstance(m, str) and m:
+                PACKAGES.append(m)
+
+
+def check_single_package(root):
+    pkg_dir = ROOT_DIR / root if root != "." else ROOT_DIR
+    if (pkg_dir / "Cargo.toml").exists():
+        print(f"Checking dependencies of {root}")
+        subprocess.run(
+            ["cargo", "deny", "check", "license"],
+            cwd=pkg_dir,
+            check=True,
+        )
+    else:
+        print(f"Skipping {root} as Cargo.toml does not exist")
+
+
+def check_deps():
+    for d in PACKAGES:
+        check_single_package(d)
+
+
+def generate_single_package(root):
+    pkg_dir = ROOT_DIR / root if root != "." else ROOT_DIR
+    if (pkg_dir / "Cargo.toml").exists():
+        print(f"Generating dependencies for {root}")
+        result = subprocess.run(
+            ["cargo", "deny", "list", "-f", "tsv", "-t", "0.6"],
+            cwd=pkg_dir,
+            capture_output=True,
+            text=True,
+        )
+        if result.returncode != 0:
+            raise RuntimeError(
+                f"cargo deny list failed in {root}: {result.stderr or 
result.stdout}"
+            )
+        out_file = pkg_dir / "DEPENDENCIES.rust.tsv"
+        out_file.write_text(result.stdout)
+        print(f"  Written to {out_file}")
+    else:
+        print(f"Skipping {root} as Cargo.toml does not exist")
+
+
+def generate_deps():
+    for d in PACKAGES:
+        generate_single_package(d)
+
+
+if __name__ == "__main__":
+    parser = ArgumentParser(
+        description="Check and generate Rust dependency license information",
+        formatter_class=ArgumentDefaultsHelpFormatter,
+    )
+    parser.set_defaults(func=parser.print_help)
+    subparsers = parser.add_subparsers()
+
+    parser_check = subparsers.add_parser(
+        "check", description="Check dependencies", help="Check dependency 
licenses"
+    )
+    parser_check.set_defaults(func=check_deps)
+
+    parser_generate = subparsers.add_parser(
+        "generate",
+        description="Generate dependencies",
+        help="Generate DEPENDENCIES.rust.tsv",
+    )
+    parser_generate.set_defaults(func=generate_deps)
+
+    args = parser.parse_args()
+    arg_dict = dict(vars(args))
+    del arg_dict["func"]
+    args.func(**arg_dict)
diff --git a/tools/update_branch_version.sh b/tools/update_branch_version.sh
new file mode 100755
index 0000000..03ec8a8
--- /dev/null
+++ b/tools/update_branch_version.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+##
+## Variables with defaults (if not overwritten by environment)
+##
+MVN=${MVN:-mvn}
+
+# fail immediately
+set -o errexit
+set -o nounset
+# print command before executing
+set -o xtrace
+
+CURR_DIR=`pwd`
+if [[ `basename $CURR_DIR` != "tools" ]] ; then
+  echo "You have to call the script from the tools/ dir"
+  exit 1
+fi
+
+###########################
+
+OLD_VERSION=${OLD_VERSION}
+NEW_VERSION=${NEW_VERSION}
+
+
+if [ -z "${OLD_VERSION}" ]; then
+       echo "OLD_VERSION is unset"
+       exit 1
+fi
+
+if [ -z "${NEW_VERSION}" ]; then
+       echo "NEW_VERSION is unset"
+       exit 1
+fi
+
+cd ..
+
+# For Cargo.toml and pyproject.toml, strip any -SNAPSHOT suffix (not valid in 
those ecosystems)
+NEW_VERSION_CLEAN=$(echo "$NEW_VERSION" | sed 's/-SNAPSHOT//')
+
+#change version in all pom files (match both exact and -SNAPSHOT suffix)
+find . -name 'pom.xml' -type f -exec perl -pi -e 
's#<version>'$OLD_VERSION'(-SNAPSHOT)?</version>#<version>'$NEW_VERSION'</version>#'
 {} \;
+
+#change version in Cargo.toml files
+find . -name 'Cargo.toml' -not -path '*/target/*' -type f -exec perl -pi -e 
's#^version = "'$OLD_VERSION'"#version = "'$NEW_VERSION_CLEAN'"#' {} \;
+
+#change version in pyproject.toml
+perl -pi -e 's#^version = "'$OLD_VERSION'"#version = "'$NEW_VERSION_CLEAN'"#' 
python/pyproject.toml
+
+git commit -am "Update version to $NEW_VERSION"
+
+echo "Don't forget to push the change."


Reply via email to