Skip to content

Commit 92a882e

Browse files
ci: publish Rust crates in dependency order
1 parent 2bb49d2 commit 92a882e

1 file changed

Lines changed: 186 additions & 29 deletions

File tree

.github/workflows/release-rust.yml

Lines changed: 186 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,8 @@
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

2521
name: Release Rust
2622

@@ -31,19 +27,15 @@ on:
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+
3434
jobs:
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

Comments
 (0)