1515# specific language governing permissions and limitations
1616# under the License.
1717
18- # Publish paimon and paimon-datafusion crates to crates.io.
19- #
20- # Trigger: push a version tag (e.g. v0.1.0, v0.1.0-rc1).
21- # Pre-release tags (containing '-') only run dry-run checks without publishing.
22- #
23- # Token auth: add secret CARGO_REGISTRY_TOKEN for crates.io publishing.
18+ # GA tags publish; RC tags validate only.
19+ # Secret: CARGO_REGISTRY_TOKEN.
2420
2521name : Release Rust
2622
3127 - " v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"
3228 workflow_dispatch :
3329
30+ concurrency :
31+ group : ${{ github.workflow }}-${{ github.ref }}
32+ cancel-in-progress : false
33+
3434jobs :
35- publish :
35+ validate :
3636 runs-on : ubuntu-latest
3737 permissions :
3838 contents : read
39- strategy :
40- # Publish crates sequentially to respect dependency order
41- max-parallel : 1
42- matrix :
43- # Order matters: paimon must be published before paimon-datafusion
44- package :
45- - paimon
46- - paimon-datafusion
4739 steps :
4840 - uses : actions/checkout@v7
4941
@@ -52,24 +44,189 @@ jobs:
5244 rustup update stable
5345 rustup default stable
5446
55- - name : Dry run
56- # Only dry-run paimon since paimon-datafusion depends on it
57- if : matrix.package == 'paimon'
58- run : cargo publish -p ${{ matrix.package }} --all-features --dry-run
47+ - name : Validate release tag
48+ if : github.ref_type == 'tag'
49+ shell : bash
50+ run : |
51+ set -euo pipefail
52+ if [[ ! "${GITHUB_REF_NAME}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)(-rc[0-9]+)?$ ]]; then
53+ echo "::error::invalid release tag: ${GITHUB_REF_NAME}"
54+ exit 1
55+ fi
56+ TAG_VERSION="${BASH_REMATCH[1]}"
57+ WORKSPACE_VERSION=$(
58+ cargo metadata --locked --format-version 1 --no-deps |
59+ jq -er '
60+ [.packages[] |
61+ select(.name == "paimon" or .name == "paimon-datafusion") |
62+ .version] as $versions |
63+ if ($versions | length) == 2 and
64+ ($versions | unique | length) == 1
65+ then $versions[0]
66+ else error("release crate versions differ")
67+ end
68+ '
69+ )
70+ if [ "${TAG_VERSION}" != "${WORKSPACE_VERSION}" ]; then
71+ echo "::error::tag ${TAG_VERSION} does not match workspace ${WORKSPACE_VERSION}"
72+ exit 1
73+ fi
74+
75+ - name : Check package inventory
76+ shell : bash
77+ run : |
78+ set -euo pipefail
79+ for crate_dir in crates/paimon crates/integrations/datafusion; do
80+ for legal_file in LICENSE NOTICE; do
81+ cmp --silent "${legal_file}" "${crate_dir}/${legal_file}" || {
82+ echo "::error::${crate_dir}/${legal_file} differs from ${legal_file}"
83+ exit 1
84+ }
85+ done
86+ done
87+
88+ for package in paimon paimon-datafusion; do
89+ files="/tmp/${package}-package-files.txt"
90+ cargo package --locked -p "${package}" --list > "${files}"
91+ for required in LICENSE NOTICE DEPENDENCIES.rust.tsv test_utils.rs; do
92+ grep --fixed-strings --line-regexp --quiet "${required}" "${files}" || {
93+ echo "::error::${package} package is missing ${required}"
94+ exit 1
95+ }
96+ done
97+ grep --quiet '^testdata/' "${files}" || {
98+ echo "::error::${package} package is missing testdata"
99+ exit 1
100+ }
101+ done
102+
103+ grep --fixed-strings --line-regexp --quiet \
104+ blob_test_utils.rs /tmp/paimon-package-files.txt || {
105+ echo "::error::paimon package is missing blob_test_utils.rs"
106+ exit 1
107+ }
108+
109+ - name : Package release crates
110+ run : >
111+ cargo package --locked
112+ -p paimon
113+ -p paimon-datafusion
114+ --all-features
115+
116+ - name : Dry run paimon
117+ run : cargo publish --locked -p paimon --all-features --dry-run
118+
119+ publish-paimon :
120+ needs : validate
121+ if : startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')
122+ runs-on : ubuntu-latest
123+ permissions :
124+ contents : read
125+ steps :
126+ - uses : actions/checkout@v7
127+
128+ - name : Setup Rust toolchain
129+ run : |
130+ rustup update stable
131+ rustup default stable
59132
60- - name : Publish ${{ matrix.package }} to crates.io
61- if : startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-')
62- run : cargo publish -p ${{ matrix.package }} --all-features
133+ - name : Publish paimon if absent
134+ shell : bash
63135 env :
64136 CARGO_REGISTRY_TOKEN : ${{ secrets.CARGO_REGISTRY_TOKEN }}
137+ run : |
138+ set -euo pipefail
139+ VERSION=$(
140+ cargo metadata --locked --format-version 1 --no-deps |
141+ jq -er '.packages[] | select(.name == "paimon") | .version'
142+ )
143+ USER_AGENT="paimon-rust-release/1.0 (${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY})"
144+ STATUS=$(
145+ curl --silent --show-error --retry 3 \
146+ --user-agent "${USER_AGENT}" \
147+ --output /tmp/paimon-version.json \
148+ --write-out '%{http_code}' \
149+ "https://crates.io/api/v1/crates/paimon/${VERSION}"
150+ )
151+ if [ "${STATUS}" = 200 ]; then
152+ jq -e --arg version "${VERSION}" '.version.num == $version' \
153+ /tmp/paimon-version.json >/dev/null
154+ echo "paimon ${VERSION} already exists; skipping"
155+ elif [ "${STATUS}" = 404 ]; then
156+ cargo publish --locked -p paimon --all-features
157+ else
158+ echo "::error::crates.io returned HTTP ${STATUS} for paimon ${VERSION}"
159+ exit 1
160+ fi
65161
66- - name : Wait for crates.io to update
67- if : startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-') && matrix.package == 'paimon'
162+ - name : Wait for paimon on crates.io
163+ shell : bash
68164 run : |
69- VERSION=${GITHUB_REF_NAME#v}
70- for i in {1..10}; do
71- CURRENT=$(curl -s https://crates.io/api/v1/crates/paimon | jq -r '.crate.max_version')
72- [ "$CURRENT" = "$VERSION" ] && echo "✅ paimon $VERSION available" && exit 0
73- echo "Waiting for $VERSION (currently $CURRENT)... $i/10"
165+ set -euo pipefail
166+ VERSION=$(
167+ cargo metadata --locked --format-version 1 --no-deps |
168+ jq -er '.packages[] | select(.name == "paimon") | .version'
169+ )
170+ USER_AGENT="paimon-rust-release/1.0 (${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY})"
171+ for attempt in {1..20}; do
172+ CURRENT=$(
173+ curl --fail-with-body --silent --show-error --retry 3 \
174+ --user-agent "${USER_AGENT}" \
175+ "https://crates.io/api/v1/crates/paimon/${VERSION}" |
176+ jq -r '.version.num // empty' || true
177+ )
178+ if [ "${CURRENT}" = "${VERSION}" ]; then
179+ echo "paimon ${VERSION} is available"
180+ exit 0
181+ fi
182+ echo "Waiting for paimon ${VERSION}... ${attempt}/20"
74183 sleep 30
75184 done
185+ echo "::error::paimon ${VERSION} did not appear before timeout"
186+ exit 1
187+
188+ publish-paimon-datafusion :
189+ needs : publish-paimon
190+ if : startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')
191+ runs-on : ubuntu-latest
192+ permissions :
193+ contents : read
194+ steps :
195+ - uses : actions/checkout@v7
196+
197+ - name : Setup Rust toolchain
198+ run : |
199+ rustup update stable
200+ rustup default stable
201+
202+ - name : Dry run paimon-datafusion
203+ run : cargo publish --locked -p paimon-datafusion --all-features --dry-run
204+
205+ - name : Publish paimon-datafusion if absent
206+ shell : bash
207+ env :
208+ CARGO_REGISTRY_TOKEN : ${{ secrets.CARGO_REGISTRY_TOKEN }}
209+ run : |
210+ set -euo pipefail
211+ VERSION=$(
212+ cargo metadata --locked --format-version 1 --no-deps |
213+ jq -er '.packages[] | select(.name == "paimon-datafusion") | .version'
214+ )
215+ USER_AGENT="paimon-rust-release/1.0 (${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY})"
216+ STATUS=$(
217+ curl --silent --show-error --retry 3 \
218+ --user-agent "${USER_AGENT}" \
219+ --output /tmp/paimon-datafusion-version.json \
220+ --write-out '%{http_code}' \
221+ "https://crates.io/api/v1/crates/paimon-datafusion/${VERSION}"
222+ )
223+ if [ "${STATUS}" = 200 ]; then
224+ jq -e --arg version "${VERSION}" '.version.num == $version' \
225+ /tmp/paimon-datafusion-version.json >/dev/null
226+ echo "paimon-datafusion ${VERSION} already exists; skipping"
227+ elif [ "${STATUS}" = 404 ]; then
228+ cargo publish --locked -p paimon-datafusion --all-features
229+ else
230+ echo "::error::crates.io returned HTTP ${STATUS} for paimon-datafusion ${VERSION}"
231+ exit 1
232+ fi
0 commit comments