This is an automated email from the ASF dual-hosted git repository.
yuxia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss-rust.git
The following commit(s) were added to refs/heads/main by this push:
new 499fa8c build: add how to release doc (#268)
499fa8c is described below
commit 499fa8c65c6e970cfbb07c822b8f8656ebe03aaa
Author: yuxia Luo <[email protected]>
AuthorDate: Sun Feb 8 09:01:45 2026 +0800
build: add how to release doc (#268)
---
.github/actions/verify-tag-version/action.yml | 37 ++
.github/release.yml | 42 ++
.github/workflows/release_python.yml | 174 ++++++++
.github/workflows/release_rust.yml | 60 +++
Cargo.toml | 12 +-
README.md | 4 +
bindings/cpp/Cargo.toml | 6 +-
bindings/python/Cargo.toml | 8 +-
bindings/python/pyproject.toml | 7 +-
crates/examples/Cargo.toml | 2 +-
crates/fluss/Cargo.toml | 9 +-
docs/assets/release-guide.png | Bin 0 -> 363329 bytes
docs/creating-a-release.md | 453 +++++++++++++++++++++
docs/generate-release-note.md | 28 ++
justfile | 28 ++
scripts/bump-version.sh | 54 +++
crates/examples/Cargo.toml => scripts/constants.py | 43 +-
scripts/dependencies.py | 96 +++++
scripts/release.sh | 66 +++
19 files changed, 1093 insertions(+), 36 deletions(-)
diff --git a/.github/actions/verify-tag-version/action.yml
b/.github/actions/verify-tag-version/action.yml
new file mode 100644
index 0000000..ad16b4f
--- /dev/null
+++ b/.github/actions/verify-tag-version/action.yml
@@ -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.
+
+# Verify that the pushed tag version matches the workspace package version in
Cargo.toml.
+# Tag v0.2.0 or v0.2.0-rc1; Cargo 0.2.0. Compare base version (strip -rc*):
both pass when Cargo is 0.2.0.
+# Requires: checkout before this step (Cargo.toml in workspace root). Use on
tag push (GITHUB_REF like refs/tags/v0.1.0).
+
+name: 'Verify tag matches crate version'
+description: 'Exits with error if GITHUB_REF tag base version does not match
[workspace.package] version in Cargo.toml (strips -rc*).'
+
+runs:
+ using: 'composite'
+ steps:
+ - run: |
+ TAG_VERSION="${GITHUB_REF#refs/tags/v}"
+ CRATE_VERSION=$(sed -n '/^\[workspace.package\]/,/^\[/p' Cargo.toml |
grep '^\s*version\s*=' | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
+ base() { echo "$1" | sed -E 's/-rc(\.[0-9]+|[0-9]+)$//'; }
+ if [ "$(base "$TAG_VERSION")" != "$(base "$CRATE_VERSION")" ]; then
+ echo "::error::Tag version ($TAG_VERSION) does not match Cargo.toml
version ($CRATE_VERSION). Run scripts/bump-version.sh before tagging, or tag
the version that is in Cargo.toml."
+ exit 1
+ fi
+ echo "Tag and crate version match: $TAG_VERSION"
+ shell: bash
diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 0000000..3ca2be6
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,42 @@
+# 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.
+#
+# Configures "Generate release notes" on GitHub Releases.
+#
https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
+
+changelog:
+ categories:
+ - title: Added
+ labels:
+ - feat
+ - feature
+ - title: Changed
+ labels:
+ - refactor
+ - title: Fixed
+ labels:
+ - fix
+ - bugfix
+ - title: Docs
+ labels:
+ - docs
+ - documentation
+ - title: CI / Build
+ labels:
+ - ci
+ - build
+ - title: Chore
+ labels:
+ - chore
diff --git a/.github/workflows/release_python.yml
b/.github/workflows/release_python.yml
new file mode 100644
index 0000000..59c8f8c
--- /dev/null
+++ b/.github/workflows/release_python.yml
@@ -0,0 +1,174 @@
+# 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 the fluss Python binding to PyPI.
+# Trigger: push tag only (e.g. v0.1.0).
+# Pre-release tags (containing '-') publish to TestPyPI; release tags publish
to PyPI.
+#
+# Token auth: set repo variable PYPI_USE_TOKEN_AUTH = 'true' and add secrets
PYPI_API_TOKEN / TEST_PYPI_API_TOKEN.
+# Trusted Publishing (OIDC): leave PYPI_USE_TOKEN_AUTH unset; do not pass
password so the action uses OIDC.
+
+name: Release Python
+
+on:
+ push:
+ tags:
+ - "v*" # Only version-like tags (e.g. v0.1.0, v0.1.0-rc1); avoids
running on arbitrary tags
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ version-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ./.github/actions/verify-tag-version
+
+ sdist:
+ runs-on: ubuntu-latest
+ needs: [version-check]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install protoc
+ run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
+
+ - uses: PyO3/maturin-action@v1
+ with:
+ working-directory: bindings/python
+ command: sdist
+ args: -o dist
+
+ - name: Upload sdist
+ uses: actions/upload-artifact@v4
+ with:
+ name: wheels-sdist
+ path: bindings/python/dist
+
+ wheels:
+ runs-on: ${{ matrix.os }}
+ needs: [version-check]
+ strategy:
+ matrix:
+ include:
+ - { os: windows-latest }
+ - { os: macos-15-intel, target: "x86_64-apple-darwin" }
+ - { os: macos-15, target: "aarch64-apple-darwin" }
+ - { os: ubuntu-latest, target: "x86_64" }
+ - { os: ubuntu-latest, target: "aarch64", manylinux:
"manylinux_2_28" }
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install protoc (Linux)
+ if: runner.os == 'Linux'
+ run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
+
+ - name: Install protoc (macOS)
+ if: runner.os == 'macOS'
+ run: brew install protobuf
+
+ - name: Install protoc (Windows)
+ if: runner.os == 'Windows'
+ run: choco install protobuf -y
+ shell: pwsh
+
+ - uses: PyO3/maturin-action@v1
+ with:
+ working-directory: bindings/python
+ target: ${{ matrix.target }}
+ command: build
+ args: --release -o dist -i python3.9
+ manylinux: ${{ matrix.manylinux || 'auto' }}
+ - uses: PyO3/maturin-action@v1
+ with:
+ working-directory: bindings/python
+ target: ${{ matrix.target }}
+ command: build
+ args: --release -o dist -i python3.10
+ manylinux: ${{ matrix.manylinux || 'auto' }}
+ - uses: PyO3/maturin-action@v1
+ with:
+ working-directory: bindings/python
+ target: ${{ matrix.target }}
+ command: build
+ args: --release -o dist -i python3.11
+ manylinux: ${{ matrix.manylinux || 'auto' }}
+ - uses: PyO3/maturin-action@v1
+ with:
+ working-directory: bindings/python
+ target: ${{ matrix.target }}
+ command: build
+ args: --release -o dist -i python3.12
+ manylinux: ${{ matrix.manylinux || 'auto' }}
+
+ - name: Upload wheels
+ uses: actions/upload-artifact@v4
+ with:
+ name: wheels-${{ matrix.os }}-${{ matrix.target || 'native' }}
+ path: bindings/python/dist
+
+ release:
+ name: Publish to PyPI
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
+ needs: [version-check, sdist, wheels]
+ if: startsWith(github.ref, 'refs/tags/')
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ pattern: wheels-*
+ merge-multiple: true
+ path: bindings/python/dist
+
+ - name: Publish to TestPyPI (token)
+ if: contains(github.ref, '-') && vars.PYPI_USE_TOKEN_AUTH == 'true'
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ repository-url: https://test.pypi.org/legacy/
+ skip-existing: true
+ packages-dir: bindings/python/dist
+ password: ${{ secrets.TEST_PYPI_API_TOKEN }}
+
+ - name: Publish to TestPyPI (Trusted Publishing)
+ if: contains(github.ref, '-') && vars.PYPI_USE_TOKEN_AUTH != 'true'
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ repository-url: https://test.pypi.org/legacy/
+ skip-existing: true
+ packages-dir: bindings/python/dist
+
+ - name: Publish to PyPI (token)
+ if: ${{ !contains(github.ref, '-') && vars.PYPI_USE_TOKEN_AUTH ==
'true' }}
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ skip-existing: true
+ packages-dir: bindings/python/dist
+ password: ${{ secrets.PYPI_API_TOKEN }}
+
+ - name: Publish to PyPI (Trusted Publishing)
+ if: ${{ !contains(github.ref, '-') && vars.PYPI_USE_TOKEN_AUTH !=
'true' }}
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ skip-existing: true
+ packages-dir: bindings/python/dist
diff --git a/.github/workflows/release_rust.yml
b/.github/workflows/release_rust.yml
new file mode 100644
index 0000000..0a567a6
--- /dev/null
+++ b/.github/workflows/release_rust.yml
@@ -0,0 +1,60 @@
+# 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 the fluss Rust crate to crates.io.
+# Trigger: push tag only (e.g. v0.1.0).
+# Pre-release tags (containing '-') do not publish; release tags publish to
crates.io.
+#
+# Token auth: set repo variable CARGO_USE_TOKEN_AUTH = 'true' and add secret
CARGO_REGISTRY_TOKEN.
+# Trusted Publishing (OIDC): leave CARGO_USE_TOKEN_AUTH unset; token is
obtained via OIDC (no secret).
+
+name: Release Rust
+
+on:
+ push:
+ tags:
+ - "v*" # Only version-like tags (e.g. v0.1.0, v0.1.0-rc1); avoids
running on arbitrary tags
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: ./.github/actions/verify-tag-version
+
+ - name: Install protoc
+ run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
+
+ - name: Dry run (crates/fluss)
+ run: cargo publish -p fluss-rs --dry-run
+
+ - name: Get crates.io token (OIDC)
+ if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-')
&& vars.CARGO_USE_TOKEN_AUTH != 'true'
+ uses: rust-lang/crates-io-auth-action@v1
+ id: auth
+ with:
+ token-type: publish
+
+ - name: Publish fluss-rs to crates.io
+ if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-')
+ run: cargo publish -p fluss-rs
+ env:
+ CARGO_REGISTRY_TOKEN: "${{ vars.CARGO_USE_TOKEN_AUTH == 'true' &&
secrets.CARGO_REGISTRY_TOKEN || steps.auth.outputs.token }}"
diff --git a/Cargo.toml b/Cargo.toml
index 4155ea8..dfddd8d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,24 +16,26 @@
# under the License.
[workspace.package]
-categories = ["command-line-utilities"]
+authors = ["Apache Fluss <[email protected]>"]
+categories = ["api-bindings", "database"]
description = "The rust implementation of fluss"
-repository = "https://github.com/apache/fluss-rust"
edition = "2024"
-version = "0.1.0"
+homepage = "https://fluss.apache.org/"
license = "Apache-2.0"
+repository = "https://github.com/apache/fluss-rust"
rust-version = "1.85"
+version = "0.1.0"
+keywords = ["fluss", "streaming-storage", "datalake"]
[workspace]
resolver = "2"
members = ["crates/fluss", "crates/examples", "bindings/python",
"bindings/cpp"]
[workspace.dependencies]
-fluss = { version = "0.1.0", path = "./crates/fluss" }
+fluss = { package = "fluss-rs", version = "0.1.0", path = "crates/fluss",
features = ["storage-all"] }
tokio = { version = "1.44.2", features = ["full"] }
clap = { version = "4.5.37", features = ["derive"] }
arrow = { version = "57.0.0", features = ["ipc_compression"] }
-chrono = { version = "0.4", features = ["clock", "std", "wasmbind"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
diff --git a/README.md b/README.md
index 5e771d9..b1116f1 100644
--- a/README.md
+++ b/README.md
@@ -133,6 +133,10 @@ Then, stop your Fluss cluster. Go to your Fluss home, stop
it via the following
./bin/local-cluster.sh stop
```
+## Documentation
+
+- [Development Guide](DEVELOPMENT.md) – Build, test, and contribute to
fluss-rust.
+- [Release Guide](docs/creating-a-release.md) – How to build, release, and
sign official Fluss client packages (Rust, Python, C++).
## License
diff --git a/bindings/cpp/Cargo.toml b/bindings/cpp/Cargo.toml
index 0b83de9..8606a22 100644
--- a/bindings/cpp/Cargo.toml
+++ b/bindings/cpp/Cargo.toml
@@ -17,7 +17,7 @@
[package]
name = "fluss-cpp"
-version = "0.1.0"
+version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
@@ -30,8 +30,8 @@ crate-type = ["staticlib"]
anyhow = "1.0"
arrow = { workspace = true, features = ["ffi"] }
cxx = "1.0"
-fluss = { path = "../../crates/fluss" }
-tokio = { version = "1.27", features = ["rt-multi-thread", "macros"] }
+fluss = { workspace = true, features = ["storage-all"] }
+tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
[build-dependencies]
cxx-build = "1.0"
diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml
index 0a0daff..804e1bb 100644
--- a/bindings/python/Cargo.toml
+++ b/bindings/python/Cargo.toml
@@ -17,10 +17,10 @@
[package]
name = "fluss_python"
-edition = "2024"
-version = "0.1.0"
+edition.workspace = true
+version.workspace = true
license.workspace = true
-rust-version = "1.85"
+rust-version.workspace = true
[lib]
name = "fluss"
@@ -28,7 +28,7 @@ crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.26.0", features = ["extension-module"] }
-fluss = { path = "../../crates/fluss" }
+fluss = { workspace = true, features = ["storage-all"] }
tokio = { workspace = true }
arrow = { workspace = true }
arrow-pyarrow = "57.0.0"
diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml
index e28b3d2..0e61b23 100644
--- a/bindings/python/pyproject.toml
+++ b/bindings/python/pyproject.toml
@@ -20,9 +20,9 @@ requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[project]
-name = "fluss"
-description = "Python bindings for Fluss on fluss-rust with Pandas integration"
-authors = [{name = "Fluss Team"}]
+name = "pyfluss"
+description = "Apache Fluss (incubating) Python Binding"
+authors = [{name = "Apache Fluss", email = "[email protected]"}]
license = {text = "Apache-2.0"}
readme = "README.md"
requires-python = ">=3.9"
@@ -42,6 +42,7 @@ dependencies = [
]
[project.urls]
+Homepage = "https://fluss.apache.org/"
Repository = "https://github.com/apache/fluss-rust"
[project.optional-dependencies]
diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml
index 16629be..cce6faf 100644
--- a/crates/examples/Cargo.toml
+++ b/crates/examples/Cargo.toml
@@ -24,7 +24,7 @@ version = { workspace = true }
[dependencies]
-fluss = { workspace = true }
+fluss = { workspace = true, features = ["storage-all"] }
tokio = { workspace = true }
clap = { workspace = true }
[[example]]
diff --git a/crates/fluss/Cargo.toml b/crates/fluss/Cargo.toml
index c923594..4d9be02 100644
--- a/crates/fluss/Cargo.toml
+++ b/crates/fluss/Cargo.toml
@@ -20,6 +20,14 @@ edition = { workspace = true }
license.workspace = true
rust-version = { workspace = true }
version = { workspace = true }
+name = "fluss-rs"
+authors = { workspace = true }
+description = { workspace = true }
+homepage = { workspace = true }
+repository = { workspace = true }
+keywords = { workspace = true }
+
+[lib]
name = "fluss"
[features]
@@ -54,7 +62,6 @@ dashmap = "6.1.0"
bigdecimal = { version = "0.4", features = ["serde"] }
ordered-float = { version = "5", features = ["serde"] }
parse-display = "0.10"
-ref-cast = "1.0"
jiff = { workspace = true }
opendal = "0.55.0"
url = "2.5.7"
diff --git a/docs/assets/release-guide.png b/docs/assets/release-guide.png
new file mode 100644
index 0000000..bf7602d
Binary files /dev/null and b/docs/assets/release-guide.png differ
diff --git a/docs/creating-a-release.md b/docs/creating-a-release.md
new file mode 100644
index 0000000..95714d9
--- /dev/null
+++ b/docs/creating-a-release.md
@@ -0,0 +1,453 @@
+<!--
+ 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.
+-->
+
+# Creating a Fluss Rust Client Release
+
+This document describes in detail how to create a release of the **Fluss
clients** (fluss-rust, fluss-python, fluss-cpp) from the
[fluss-rust](https://github.com/apache/fluss-rust) repository. It is based on
the [Creating a Fluss
Release](https://fluss.apache.org/community/how-to-release/creating-a-fluss-release/)
guide of the Apache Fluss project and the [release guide of Apache
OpenDAL](https://nightlies.apache.org/opendal/opendal-docs-stable/community/release/);
releases are source ar [...]
+
+Publishing software has legal consequences. This guide complements the
foundation-wide [Product Release
Policy](https://www.apache.org/legal/release-policy.html) and [Release
Distribution Policy](https://infra.apache.org/release-distribution.html).
+
+## Overview
+
+
+
+The release process consists of:
+
+1. [Decide to release](#decide-to-release)
+2. [Prepare for the release](#prepare-for-the-release)
+3. [Build a release candidate](#build-a-release-candidate)
+4. [Vote on the release candidate](#vote-on-the-release-candidate)
+5. [If necessary, fix any issues and go back to step 3](#fix-any-issues)
+6. [Finalize the release](#finalize-the-release)
+7. [Promote the release](#promote-the-release)
+
+## Decide to release
+
+Deciding to release and selecting a Release Manager is the first step. This is
a consensus-based decision of the community.
+
+Anybody can propose a release (e.g. on the dev mailing list), giving a short
rationale and nominating a committer as Release Manager (including themselves).
Any objections should be resolved by consensus before starting.
+
+**Checklist to proceed**
+
+- [ ] Community agrees to release
+- [ ] A Release Manager is selected
+
+## Prepare for the release
+
+### 0. One-time Release Manager setup
+
+Before your first release, perform one-time configuration. See **[Release
Manager
Preparation](https://fluss.apache.org/community/how-to-release/release-manager-preparation/)**
(GPG key, etc.). For fluss-rust you do **not** need Nexus/Maven; you only need
GPG for signing the source archive and (optionally) git signing.
+
+**Checklist (one-time)**
+
+- [ ] GPG key set up and published to
[KEYS](https://downloads.apache.org/incubator/fluss/KEYS) or Apache account
+- [ ] Git configured to use your GPG key for signing tags
+
+### 1. Install Rust (and optional: just)
+
+The release script (`just release` or `./scripts/release.sh`) uses `git
archive` and `gpg`; building or verifying the project locally requires
**Rust**. Install the [Rust toolchain](https://rustup.rs/) (the version should
match
[rust-toolchain.toml](https://github.com/apache/fluss-rust/blob/main/rust-toolchain.toml)
in the repo). The dependency list script (`scripts/dependencies.py`) requires
**Python 3.11+**.
+
+```bash
+rustc --version
+cargo --version
+```
+
+To use `just release`, install [just](https://github.com/casey/just) (e.g.
`cargo install just` or your system package manager). If you prefer not to use
just, run `./scripts/release.sh $RELEASE_VERSION` instead.
+
+### 2. Optional: Create a new Milestone in GitHub
+
+If the project uses GitHub milestones for release tracking, create a new
milestone for the **next** version (e.g. `v0.2` if you are releasing `0.1.x`).
This helps contributors target issues to the correct release.
+
+### 3. Optional: Triage release-blocking issues
+
+Check open issues that might block the release. Resolve, defer to the next
milestone, or mark as blocker and do not proceed until they are fixed.
+
+### 4. Clone fluss-rust into a fresh workspace
+
+Use a clean clone to avoid local changes affecting the release.
+
+```bash
+git clone https://github.com/apache/fluss-rust.git
+cd fluss-rust
+```
+
+### 5. Set up environment variables
+
+Set these once and use them in all following commands. (Bash syntax.)
+
+```bash
+export RELEASE_VERSION="0.1.0"
+export RELEASE_TAG="v${RELEASE_VERSION}"
+export SVN_RELEASE_DIR="fluss-rust-${RELEASE_VERSION}"
+# Only set if there is a previous release (for compare link in DISCUSS /
release notes)
+export LAST_VERSION="0.0.9"
+export NEXT_VERSION="0.2.0"
+```
+
+For the **first release** there is no previous version; leave `LAST_VERSION`
unset or omit it when using the compare link in the DISCUSS thread and release
notes.
+
+### 6. Generate dependencies list
+
+[ASF release policy](https://www.apache.org/legal/release-policy.html)
requires that every release comply with [ASF licensing
policy](https://www.apache.org/legal/resolved.html) and that an **audit be
performed before a full release**. Generating and committing a dependency list
(and using cargo-deny) documents third-party components and supports this
requirement.
+
+Do this on `main` **before** creating the release branch. Then both the
release branch (when created from `main`) and `main` will have the same
dependency list.
+
+1. Download and set up
[cargo-deny](https://embarkstudios.github.io/cargo-deny/cli/index.html) (see
cargo-deny docs).
+2. Run the script to update the dependency list (requires **Python 3.11+** for
the release tooling), then commit on `main`:
+
+```bash
+git checkout main
+git pull
+python3 scripts/dependencies.py generate
+git add **/DEPENDENCIES*.tsv
+# Bash: run shopt -s globstar first so ** matches subdirs
+git commit -m "chore: update dependency list for release ${RELEASE_VERSION}"
+git push origin main
+```
+
+To only check licenses (no file update): `python3 scripts/dependencies.py
check`.
+
+### 7. Optional: Start a [DISCUSS] thread
+
+On [Fluss Discussions](https://github.com/apache/fluss-rust/discussions) or
the dev list:
+
+- **Subject:** `[DISCUSS] Release Apache Fluss clients (fluss-rust,
fluss-python, fluss-cpp) $RELEASE_VERSION`
+- **Body:** Short rationale; if there is a previous release, add compare link:
`https://github.com/apache/fluss-rust/compare/v${LAST_VERSION}...main`. Ask for
comments.
+
+### 8. Create a release branch
+
+From `main`, create a release branch. All release artifacts will be built from
this branch. The tag (RC or release) is created later when building the release
candidate.
+
+```bash
+git checkout main
+git pull
+git checkout -b release-${RELEASE_VERSION}
+git push origin release-${RELEASE_VERSION}
+```
+
+Do **not** create or push the release/RC tag yet; that happens in [Build a
release candidate](#build-a-release-candidate) after the source artifacts are
staged.
+
+### 9. Bump version on main for the next development cycle
+
+So that `main` moves to the next version immediately after the release branch
is cut, run the bump script and commit:
+
+```bash
+git checkout main
+git pull
+
+./scripts/bump-version.sh $RELEASE_VERSION $NEXT_VERSION
+
+git add Cargo.toml
+git commit -m "Bump version to ${NEXT_VERSION}"
+git push origin main
+```
+
+The script updates the root `Cargo.toml` ([workspace.package] and
[workspace.dependencies] fluss-rs). crates/fluss and bindings inherit `version`
from the workspace.
+
+### 10. Optional: Create PRs for release blog and download page
+
+You can open a pull request in the **Apache Fluss** repository for the release
blog (announcement). If the project website has a download page, also create a
PR to add the new version there. **Do not merge these PRs until the release is
finalized.**
+
+---
+
+**Checklist to proceed to the next step**
+
+- [ ] Rust (and optionally just) installed and on PATH
+- [ ] Python 3.11+ for dependency list script
+- [ ] No release-blocking issues (or triaged)
+- [ ] Environment variables set
+- [ ] Release branch created and pushed
+- [ ] Main branch bumped to `NEXT_VERSION` and pushed
+- [ ] Dependencies list generated and committed on main
+- [ ] (Optional) DISCUSS thread and/or tracking issue created
+- [ ] (Optional) PRs for blog and download page created but not merged
+
+## Build a release candidate
+
+Each release candidate is built from the release branch, signed, and staged to
the dev area of dist.apache.org. If an RC fails the vote, fix issues and repeat
this section with an incremented `RC_NUM` (see [Fix any
issues](#fix-any-issues)).
+
+### 1. Set RC environment variables
+
+Set these when building a **release candidate**. Start with `RC_NUM=1`; if the
vote fails and you build a new candidate, increment to `2`, then `3`, etc.
+
+```bash
+export RC_NUM="1"
+export RC_TAG="v${RELEASE_VERSION}-rc${RC_NUM}"
+export SVN_RC_DIR="fluss-rust-${RELEASE_VERSION}-rc${RC_NUM}"
+```
+
+For a **direct release** (no RC), skip these and use `RELEASE_TAG` and
`SVN_RELEASE_DIR` from the Prepare step instead.
+
+### 2. Check out the release branch and create the tag
+
+Check out the release branch at the commit you want to release, create the
signed tag, then push it. Use `RC_TAG` for a release candidate or `RELEASE_TAG`
for a direct release. Pushing the tag triggers GitHub Actions (for an RC tag,
fluss-python is published to TestPyPI).
+
+```bash
+git checkout release-${RELEASE_VERSION}
+git pull
+git tag -s $RC_TAG -m "${RC_TAG}"
+git push origin $RC_TAG
+```
+
+Check CI: [Actions](https://github.com/apache/fluss-rust/actions) (Release
Rust, Release Python).
+
+### 3. Create source release artifacts
+
+From the repository root (on the release branch, at the commit you tagged):
+
+```bash
+just release $RELEASE_VERSION
+# Or: ./scripts/release.sh $RELEASE_VERSION
+```
+
+This creates under `dist/`:
+
+- `fluss-rust-${RELEASE_VERSION}.tar.gz`
+- `fluss-rust-${RELEASE_VERSION}.tar.gz.sha512`
+- `fluss-rust-${RELEASE_VERSION}.tar.gz.asc`
+
+Verify with: `gpg --verify dist/fluss-rust-${RELEASE_VERSION}.tar.gz.asc
dist/fluss-rust-${RELEASE_VERSION}.tar.gz`
+
+### 4. Stage artifacts to SVN (dist.apache.org dev)
+
+From the **fluss-rust** repo root, check out the Fluss dev area and add the
release artifacts.
+
+```bash
+svn checkout https://dist.apache.org/repos/dist/dev/incubator/fluss
fluss-dist-dev --depth=immediates
+cd fluss-dist-dev
+mkdir $SVN_RC_DIR
+cp ../dist/fluss-rust-${RELEASE_VERSION}.* $SVN_RC_DIR/
+svn add $SVN_RC_DIR
+svn status
+svn commit -m "Add fluss-rust ${RELEASE_VERSION} RC${RC_NUM}"
+```
+
+Verify:
[https://dist.apache.org/repos/dist/dev/incubator/fluss/](https://dist.apache.org/repos/dist/dev/incubator/fluss/)
+
+---
+
+**Checklist to proceed to the next step**
+
+- [ ] Source distribution built and signed under `dist/`
+- [ ] Artifacts staged to [dist.apache.org
dev](https://dist.apache.org/repos/dist/dev/incubator/fluss/) under
`$SVN_RC_DIR`
+- [ ] RC (or release) tag pushed to GitHub
+- [ ] CI for Release Rust / Release Python succeeded
+
+## Vote on the release candidate
+
+Share the release candidate for community review. If the project is in
incubation, a [two-phase
vote](https://incubator.apache.org/cookbook/#two_phase_vote_on_podling_releases)
(Fluss community then Incubator PMC) may be required; otherwise one community
vote is enough.
+
+### Fluss community vote
+
+Start the vote on the dev@ mailing list.
+
+**Subject:** `[VOTE] Release Apache Fluss clients (fluss-rust, fluss-python,
fluss-cpp) ${RELEASE_VERSION} (RC${RC_NUM})`
+
+**Body template:**
+
+```
+Hi everyone,
+
+Please review and vote on release candidate #${RC_NUM} for Apache Fluss
clients (fluss-rust, fluss-python, fluss-cpp) ${RELEASE_VERSION}.
+
+[ ] +1 Approve the release
+[ ] +0 No opinion
+[ ] -1 Do not approve (please provide specific comments)
+
+The release candidate (source distribution) is available at:
+* https://dist.apache.org/repos/dist/dev/incubator/fluss/$SVN_RC_DIR/
+
+KEYS for signature verification:
+* https://downloads.apache.org/incubator/fluss/KEYS
+
+Git tag:
+* https://github.com/apache/fluss-rust/releases/tag/$RC_TAG
+
+PyPI (release) / TestPyPI (RC):
+* https://pypi.org/project/pyfluss/
+* https://test.pypi.org/project/pyfluss/
+
+Please download, verify, and test. Verification steps are in the project docs
(todo: add how to verify release).
+
+The vote will be open for at least 72 hours. It is adopted by majority
approval with at least 3 PPMC affirmative votes (or as per project policy).
+
+Thanks,
+Release Manager
+```
+
+If issues are found, cancel the vote and go to [Fix any
issues](#fix-any-issues). If the vote passes, close it and tally the result in
a follow-up:
+
+**Subject:** `[RESULT][VOTE] Release Apache Fluss clients ${RELEASE_VERSION}
(RC${RC_NUM})`
+
+**Body:** Summarize binding and non-binding votes and link to the vote thread.
+
+### Incubator PMC vote (if applicable)
+
+If the project is in incubation, start a vote on [email protected]
after the Fluss community vote passes. Use the same structure: link to the
community vote thread, release candidate URL, KEYS, tag, and ask IPMC to vote
within 72 hours. Then send the result to the same list.
+
+---
+
+**Checklist to proceed to finalization**
+
+- [ ] Community vote passed (at least 3 binding +1, more +1 than -1)
+- [ ] (If incubating) Incubator PMC vote passed
+
+## Fix any issues
+
+If the vote revealed issues:
+
+1. Fix them on `main` (or the release branch) via normal PRs; cherry-pick
fixes into the release branch as needed.
+2. Remove the old RC from dist.apache.org dev (optional but recommended):
+
+```bash
+svn checkout https://dist.apache.org/repos/dist/dev/incubator/fluss
fluss-dist-dev --depth=immediates
+cd fluss-dist-dev
+svn remove $SVN_RC_DIR
+svn commit -m "Remove fluss-rust ${RELEASE_VERSION} RC${RC_NUM} (superseded)"
+```
+
+3. Increment `RC_NUM` (e.g. set `RC_NUM="2"`), recreate `RC_TAG` and
`SVN_RC_DIR`, then go back to [Build a release
candidate](#build-a-release-candidate) and repeat until a candidate is approved.
+
+**Checklist**
+
+- [ ] Issues resolved and changes merged/cherry-picked to the release branch
+- [ ] New RC built and voted on (or same RC re-voted if only minor fixes)
+
+## Finalize the release
+
+Once a release candidate has been approved, finalize the release.
+
+### 1. Push the release git tag (if the vote was on an RC)
+
+If the community voted on an RC tag, create and push the formal release tag so
CI publishes to crates.io and PyPI:
+
+```bash
+git checkout $RC_TAG
+git tag -s $RELEASE_TAG -m "Release fluss-rust, fluss-python, fluss-cpp
${RELEASE_VERSION}"
+git push origin $RELEASE_TAG
+```
+
+### 2. Deploy source artifacts to the release repository
+
+Move the staged artifacts from dev to release:
+
+```bash
+svn mv -m "Release fluss-rust ${RELEASE_VERSION}" \
+ https://dist.apache.org/repos/dist/dev/incubator/fluss/$SVN_RC_DIR \
+ https://dist.apache.org/repos/dist/release/incubator/fluss/$SVN_RELEASE_DIR
+```
+
+(Only PPMC members may have write access to the release repository; if you get
permission errors, ask on the mailing list.)
+
+### 3. Remove old RC(s) from dev (optional)
+
+Clean up the dev area so only the current RC or the moved release remains:
+
+```bash
+cd fluss-dist-dev
+svn remove $SVN_RC_DIR
+svn commit -m "Remove RC after release fluss-rust ${RELEASE_VERSION}"
+```
+
+### 4. Verify language artifacts
+
+- **fluss-rust:**
[crates.io/crates/fluss-rs](https://crates.io/crates/fluss-rs) shows version
`$RELEASE_VERSION`
+- **fluss-python:** [PyPI – pyfluss](https://pypi.org/project/pyfluss/) shows
version `$RELEASE_VERSION`
+- **fluss-cpp:** Distributed via the source archive; no separate registry
+
+### 5. Create GitHub Release
+
+1. Go to [Releases → New
release](https://github.com/apache/fluss-rust/releases/new).
+2. Choose tag `$RELEASE_TAG`.
+3. Set the target to the release branch `release-${RELEASE_VERSION}` (i.e.,
the branch/commit used to create `$RELEASE_TAG`).
+4. Click **Generate release notes**, then add: notable changes, breaking
changes (if any) from component upgrade docs, **official download link**
(source archive and verification), and install instructions for fluss-rust,
fluss-python, fluss-cpp.
+ - **Download link:**
`https://downloads.apache.org/incubator/fluss/fluss-rust-${RELEASE_VERSION}/`
(or the project download page). In the release description, include checksums
and GPG verification steps.
+5. Click **Publish release**.
+
+### 6. Update CHANGELOG.md on main
+
+Add an entry for `$RELEASE_VERSION` with the list of changes (use [Generate
Release Note](generate-release-note.md) from the release tag). Commit and push
to `main`.
+
+---
+
+**Checklist to proceed to promotion**
+
+- [ ] Release tag pushed; CI published to crates.io and PyPI
+- [ ] Source artifacts in [dist
release](https://dist.apache.org/repos/dist/release/incubator/fluss/)
+- [ ] GitHub Release created
+- [ ] CHANGELOG.md updated on main
+
+## Promote the release
+
+### Merge website PRs
+
+Merge the pull requests for the release blog and download page that were
created in [Prepare for the
release](#10-optional-create-prs-for-release-blog-and-download-page).
+
+### Announce the release
+
+Wait at least 24 hours after finalizing, per [ASF release
policy](https://www.apache.org/legal/release-policy.html#release-announcements).
+
+- Announce on the dev mailing list that the release is complete.
+- Announce on [Fluss Discussions –
Announcements](https://github.com/apache/fluss-rust/discussions) (if that
category exists).
+- Send the release announcement to **[email protected]**.
+
+Use the `@apache.org` email address and **plain text** for the body; otherwise
the list may reject the message.
+
+**Subject:** `[ANNOUNCE] Release Apache Fluss clients (fluss-rust,
fluss-python, fluss-cpp) ${RELEASE_VERSION}`
+
+**Body template:**
+
+```
+The Apache Fluss community is pleased to announce the release of Apache Fluss
clients (fluss-rust, fluss-python, fluss-cpp) ${RELEASE_VERSION}.
+
+This release includes ...
+(Notable changes; link to CHANGELOG or release notes.)
+
+Download and verification:
+* https://downloads.apache.org/incubator/fluss/$SVN_RELEASE_DIR/
+* KEYS: https://downloads.apache.org/incubator/fluss/KEYS (or
https://downloads.apache.org/fluss/KEYS after graduation)
+
+Rust: cargo add fluss-rs
+Python: pip install pyfluss
+C++: build from source (see project documentation)
+
+Release notes: https://github.com/apache/fluss-rust/releases/tag/$RELEASE_TAG
+
+Thanks to all contributors!
+
+Release Manager
+```
+
+---
+
+**Checklist to declare the process completed**
+
+- [ ] Release announced on dev list and (if applicable) user list
+- [ ] Release announced on [email protected]
+- [ ] Release blog published (if applicable)
+- [ ] Download page updated (if applicable)
+
+## Improve the process
+
+After finishing the release, consider what could be improved (simplifications,
clearer steps, automation). Propose changes on the dev list or via a pull
request to this guide.
+
+## See also
+
+- [Release Manager
Preparation](https://fluss.apache.org/community/how-to-release/release-manager-preparation/)
— GPG and one-time setup
+- [Verifying a Fluss
Release](https://fluss.apache.org/community/how-to-release/verifying-a-fluss-release/)
— How to verify artifacts (adapt for fluss-rust source tarball)
+- [ASF Release Policy](https://www.apache.org/legal/release-policy.html)
diff --git a/docs/generate-release-note.md b/docs/generate-release-note.md
new file mode 100644
index 0000000..1167f4c
--- /dev/null
+++ b/docs/generate-release-note.md
@@ -0,0 +1,28 @@
+<!--
+ 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.
+-->
+
+# Generate Release Note
+
+Use GitHub's **Generate release notes** to produce a draft from merged PRs
between tags. Categories (Added, Fixed, Docs, etc.) are configured in
[.github/release.yml](../.github/release.yml).
+
+1. Go to [Create a new
release](https://github.com/apache/fluss-rust/releases/new).
+2. In **Choose a tag**, pick the release tag (e.g. `v0.1.0`).
+3. Click **Generate release notes**.
+4. Copy the generated content for **CHANGELOG.md** or the GitHub Release
description. When publishing the release, add the official download link,
checksums/verification, and install instructions (see
[creating-a-release.md](creating-a-release.md)).
+
+See [creating-a-fluss-rust-release.md](creating-a-fluss-rust-release.md) and
[GitHub: Automatically generated release
notes](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes).
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..c4e1a76
--- /dev/null
+++ b/justfile
@@ -0,0 +1,28 @@
+# 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 dist/.
+# Check out the release tag first (e.g. git checkout v0.1.0-rc1).
+# Usage: just release [version]
+# If version is omitted, read from Cargo.toml.
+
+# [version]: optional; if omitted, script reads from Cargo.toml
+release [version]:
+ ./scripts/release.sh {{version}}
+
+# Bump version on main for next development cycle. Run from main after cutting
release branch.
+# Usage: just bump-version <current> <next> e.g. just bump-version 0.1.0
0.1.1
+bump-version from to:
+ ./scripts/bump-version.sh {{from}} {{to}}
diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh
new file mode 100755
index 0000000..347b286
--- /dev/null
+++ b/scripts/bump-version.sh
@@ -0,0 +1,54 @@
+#!/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.
+#
+# Bump version in root Cargo.toml ([workspace.package] and
[workspace.dependencies] fluss-rs).
+# Run from repo root. Use after cutting a release branch so main is set to the
next version.
+#
+# Usage: ./scripts/bump-version.sh <current_version> <next_version>
+# e.g. ./scripts/bump-version.sh 0.1.0 0.1.1
+# Or with env vars: ./scripts/bump-version.sh $RELEASE_VERSION $NEXT_VERSION
+
+set -e
+
+if [ -z "$1" ] || [ -z "$2" ]; then
+ echo "Usage: $0 <current_version> <next_version>"
+ echo " e.g. $0 0.1.0 0.1.1"
+ exit 1
+fi
+
+FROM_VERSION="$1"
+TO_VERSION="$2"
+
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+cd "$REPO_ROOT"
+
+if [ ! -f Cargo.toml ]; then
+ echo "Cargo.toml not found. Run from repo root."
+ exit 1
+fi
+
+# Replace version = "X.Y.Z" with version = "TO_VERSION" (all occurrences in
root Cargo.toml)
+case "$(uname -s)" in
+ Darwin)
+ sed -i '' "s/version = \"${FROM_VERSION}\"/version = \"${TO_VERSION}\"/g"
Cargo.toml
+ ;;
+ *)
+ sed -i "s/version = \"${FROM_VERSION}\"/version = \"${TO_VERSION}\"/g"
Cargo.toml
+ ;;
+esac
+
+echo "Bumped version from ${FROM_VERSION} to ${TO_VERSION} in Cargo.toml"
+echo "Review with: git diff Cargo.toml"
diff --git a/crates/examples/Cargo.toml b/scripts/constants.py
similarity index 50%
copy from crates/examples/Cargo.toml
copy to scripts/constants.py
index 16629be..4a23e6a 100644
--- a/crates/examples/Cargo.toml
+++ b/scripts/constants.py
@@ -1,3 +1,4 @@
+#!/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
@@ -15,26 +16,30 @@
# specific language governing permissions and limitations
# under the License.
-[package]
-edition = { workspace = true }
-license = { workspace = true }
-name = "fluss-examples"
-rust-version = { workspace = true }
-version = { workspace = true }
+import tomllib
+from pathlib import Path
+ROOT_DIR = Path(__file__).resolve().parent.parent
-[dependencies]
-fluss = { workspace = true }
-tokio = { workspace = true }
-clap = { workspace = true }
-[[example]]
-name = "example-table"
-path = "src/example_table.rs"
-[[example]]
-name = "example-upsert-lookup"
-path = "src/example_kv_table.rs"
+def list_packages():
+ """Package directories from [workspace].members in root Cargo.toml, plus
workspace root.
+ Each gets a DEPENDENCIES.rust.tsv. Avoids scanning target/, .git/, etc.
+ Requires Python 3.11+ (tomllib).
+ """
+ root_cargo = ROOT_DIR / "Cargo.toml"
+ if not root_cargo.exists():
+ return ["."]
+ with open(root_cargo, "rb") as f:
+ data = tomllib.load(f)
+ members = data.get("workspace", {}).get("members", [])
+ if not isinstance(members, list):
+ return ["."]
+ packages = ["."]
+ for m in members:
+ if isinstance(m, str) and m:
+ packages.append(m)
+ return packages
-[[example]]
-name = "example-partitioned-upsert-lookup"
-path = "src/example_partitioned_kv_table.rs"
\ No newline at end of file
+
+PACKAGES = list_packages()
diff --git a/scripts/dependencies.py b/scripts/dependencies.py
new file mode 100644
index 0000000..ec77469
--- /dev/null
+++ b/scripts/dependencies.py
@@ -0,0 +1,96 @@
+#!/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.
+#
+# Release tooling: requires Python 3.11+ (constants.py uses tomllib).
+
+import sys
+
+if sys.version_info < (3, 11):
+ sys.exit(
+ "This script requires Python 3.11 or newer (uses tomllib). "
+ f"Current: {sys.version}. Use python3.11+ or see docs for release
requirements."
+ )
+
+import subprocess
+from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
+
+from constants import PACKAGES, ROOT_DIR
+
+
+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 {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)
+ 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(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 dependencies"
+ )
+ parser_check.set_defaults(func=check_deps)
+
+ parser_generate = subparsers.add_parser(
+ "generate", description="Generate dependencies", help="Generate
dependencies"
+ )
+ 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/scripts/release.sh b/scripts/release.sh
new file mode 100755
index 0000000..8187d1f
--- /dev/null
+++ b/scripts/release.sh
@@ -0,0 +1,66 @@
+#!/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 dist/:
+# fluss-rust-{version}.tar.gz
+# fluss-rust-{version}.tar.gz.asc
+# fluss-rust-{version}.tar.gz.sha512
+# Run from repo root. Check out the release tag first (e.g. git checkout
v0.1.0-rc1).
+# Usage: ./scripts/release.sh [version]
+# If version is omitted, it is read from Cargo.toml
(workspace.package.version).
+
+set -e
+
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+cd "$REPO_ROOT"
+
+if [ -n "$1" ]; then
+ VERSION="$1"
+else
+ VERSION=$(grep -E '^version\s*=' Cargo.toml | head -1 | sed
's/.*"\([^"]*\)".*/\1/')
+ if [ -z "$VERSION" ]; then
+ echo "Could not read version from Cargo.toml. Pass version as argument: $0
<version>"
+ exit 1
+ fi
+fi
+
+PREFIX="fluss-rust-${VERSION}"
+DIST_DIR="${REPO_ROOT}/dist"
+TARBALL="${PREFIX}.tar.gz"
+
+echo "Creating ASF source release for fluss-rust ${VERSION}"
+mkdir -p "$DIST_DIR"
+
+echo "Creating source archive: ${TARBALL}"
+git archive --format=tar.gz --prefix="${PREFIX}/" -o "${DIST_DIR}/${TARBALL}"
HEAD
+
+echo "Generating SHA-512 checksum: ${TARBALL}.sha512"
+if command -v shasum >/dev/null 2>&1; then
+ (cd "$DIST_DIR" && shasum -a 512 "$TARBALL" > "${TARBALL}.sha512")
+else
+ (cd "$DIST_DIR" && sha512sum "$TARBALL" > "${TARBALL}.sha512")
+fi
+
+echo "Signing with GPG: ${TARBALL}.asc"
+(cd "$DIST_DIR" && gpg --armor --detach-sig "$TARBALL")
+
+echo "Verifying signature"
+(cd "$DIST_DIR" && gpg --verify "${TARBALL}.asc" "$TARBALL")
+
+echo "Done. Artifacts in dist/:"
+ls -la "${DIST_DIR}/"
+echo ""
+echo "Next: upload contents of dist/ to SVN (see docs/creating-a-release.md)."