| # 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. |
| |
| # GA tags publish; RC tags validate only. |
| # Secret: CARGO_REGISTRY_TOKEN. |
| |
| name: Release Rust |
| |
| on: |
| push: |
| tags: |
| - "v[0-9]+.[0-9]+.[0-9]+" |
| - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" |
| workflow_dispatch: |
| |
| concurrency: |
| group: ${{ github.workflow }}-${{ github.ref }} |
| cancel-in-progress: false |
| |
| jobs: |
| validate: |
| runs-on: ubuntu-latest |
| permissions: |
| contents: read |
| steps: |
| - uses: actions/checkout@v7 |
| |
| - name: Setup Rust toolchain |
| run: | |
| rustup update stable |
| rustup default stable |
| |
| - name: Validate release tag |
| if: github.ref_type == 'tag' |
| shell: bash |
| run: | |
| set -euo pipefail |
| if [[ ! "${GITHUB_REF_NAME}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)(-rc[0-9]+)?$ ]]; then |
| echo "::error::invalid release tag: ${GITHUB_REF_NAME}" |
| exit 1 |
| fi |
| TAG_VERSION="${BASH_REMATCH[1]}" |
| WORKSPACE_VERSION=$( |
| cargo metadata --locked --format-version 1 --no-deps | |
| jq -er ' |
| [.packages[] | |
| select(.name == "paimon" or .name == "paimon-datafusion") | |
| .version] as $versions | |
| if ($versions | length) == 2 and |
| ($versions | unique | length) == 1 |
| then $versions[0] |
| else error("release crate versions differ") |
| end |
| ' |
| ) |
| if [ "${TAG_VERSION}" != "${WORKSPACE_VERSION}" ]; then |
| echo "::error::tag ${TAG_VERSION} does not match workspace ${WORKSPACE_VERSION}" |
| exit 1 |
| fi |
| |
| - name: Check package inventory |
| shell: bash |
| run: | |
| set -euo pipefail |
| for crate_dir in crates/paimon crates/integrations/datafusion; do |
| for legal_file in LICENSE NOTICE; do |
| cmp --silent "${legal_file}" "${crate_dir}/${legal_file}" || { |
| echo "::error::${crate_dir}/${legal_file} differs from ${legal_file}" |
| exit 1 |
| } |
| done |
| done |
| |
| for package in paimon paimon-datafusion; do |
| files="/tmp/${package}-package-files.txt" |
| cargo package --locked -p "${package}" --list > "${files}" |
| for required in LICENSE NOTICE DEPENDENCIES.rust.tsv test_utils.rs; do |
| grep --fixed-strings --line-regexp --quiet "${required}" "${files}" || { |
| echo "::error::${package} package is missing ${required}" |
| exit 1 |
| } |
| done |
| grep --quiet '^testdata/' "${files}" || { |
| echo "::error::${package} package is missing testdata" |
| exit 1 |
| } |
| done |
| |
| grep --fixed-strings --line-regexp --quiet \ |
| blob_test_utils.rs /tmp/paimon-package-files.txt || { |
| echo "::error::paimon package is missing blob_test_utils.rs" |
| exit 1 |
| } |
| |
| - name: Package release crates |
| # Cargo verifies workspace dependencies through its temporary registry. |
| run: > |
| cargo package --locked |
| -p paimon |
| -p paimon-datafusion |
| --all-features |
| |
| - name: Dry run paimon |
| run: cargo publish --locked -p paimon --all-features --dry-run |
| |
| publish-paimon: |
| needs: validate |
| if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-') |
| runs-on: ubuntu-latest |
| permissions: |
| contents: read |
| steps: |
| - uses: actions/checkout@v7 |
| |
| - name: Setup Rust toolchain |
| run: | |
| rustup update stable |
| rustup default stable |
| |
| - name: Publish paimon if absent |
| shell: bash |
| env: |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} |
| run: | |
| set -euo pipefail |
| VERSION=$( |
| cargo metadata --locked --format-version 1 --no-deps | |
| jq -er '.packages[] | select(.name == "paimon") | .version' |
| ) |
| USER_AGENT="paimon-rust-release/1.0 (${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY})" |
| STATUS=$( |
| curl --silent --show-error --retry 3 \ |
| --user-agent "${USER_AGENT}" \ |
| --output /tmp/paimon-version.json \ |
| --write-out '%{http_code}' \ |
| "https://crates.io/api/v1/crates/paimon/${VERSION}" |
| ) |
| if [ "${STATUS}" = 200 ]; then |
| jq -e --arg version "${VERSION}" '.version.num == $version' \ |
| /tmp/paimon-version.json >/dev/null |
| echo "paimon ${VERSION} already exists; skipping" |
| elif [ "${STATUS}" = 404 ]; then |
| cargo publish --locked -p paimon --all-features |
| else |
| echo "::error::crates.io returned HTTP ${STATUS} for paimon ${VERSION}" |
| exit 1 |
| fi |
| |
| - name: Wait for paimon on crates.io |
| shell: bash |
| run: | |
| set -euo pipefail |
| VERSION=$( |
| cargo metadata --locked --format-version 1 --no-deps | |
| jq -er '.packages[] | select(.name == "paimon") | .version' |
| ) |
| USER_AGENT="paimon-rust-release/1.0 (${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY})" |
| for attempt in {1..20}; do |
| CURRENT=$( |
| curl --fail-with-body --silent --show-error --retry 3 \ |
| --user-agent "${USER_AGENT}" \ |
| "https://crates.io/api/v1/crates/paimon/${VERSION}" | |
| jq -r '.version.num // empty' || true |
| ) |
| if [ "${CURRENT}" = "${VERSION}" ]; then |
| echo "paimon ${VERSION} is available" |
| exit 0 |
| fi |
| echo "Waiting for paimon ${VERSION}... ${attempt}/20" |
| sleep 30 |
| done |
| echo "::error::paimon ${VERSION} did not appear before timeout" |
| exit 1 |
| |
| publish-paimon-datafusion: |
| needs: publish-paimon |
| if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-') |
| runs-on: ubuntu-latest |
| permissions: |
| contents: read |
| steps: |
| - uses: actions/checkout@v7 |
| |
| - name: Setup Rust toolchain |
| run: | |
| rustup update stable |
| rustup default stable |
| |
| - name: Dry run paimon-datafusion |
| run: cargo publish --locked -p paimon-datafusion --all-features --dry-run |
| |
| - name: Publish paimon-datafusion if absent |
| shell: bash |
| env: |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} |
| run: | |
| set -euo pipefail |
| VERSION=$( |
| cargo metadata --locked --format-version 1 --no-deps | |
| jq -er '.packages[] | select(.name == "paimon-datafusion") | .version' |
| ) |
| USER_AGENT="paimon-rust-release/1.0 (${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY})" |
| STATUS=$( |
| curl --silent --show-error --retry 3 \ |
| --user-agent "${USER_AGENT}" \ |
| --output /tmp/paimon-datafusion-version.json \ |
| --write-out '%{http_code}' \ |
| "https://crates.io/api/v1/crates/paimon-datafusion/${VERSION}" |
| ) |
| if [ "${STATUS}" = 200 ]; then |
| jq -e --arg version "${VERSION}" '.version.num == $version' \ |
| /tmp/paimon-datafusion-version.json >/dev/null |
| echo "paimon-datafusion ${VERSION} already exists; skipping" |
| elif [ "${STATUS}" = 404 ]; then |
| cargo publish --locked -p paimon-datafusion --all-features |
| else |
| echo "::error::crates.io returned HTTP ${STATUS} for paimon-datafusion ${VERSION}" |
| exit 1 |
| fi |