Skip to content

Commit ed81699

Browse files
authored
Merge pull request #1 from AElfProject/codex/init-aelf-sdk-alpha
Codex/init aelf sdk alpha
2 parents f27d1cd + 9458201 commit ed81699

7 files changed

Lines changed: 260 additions & 11 deletions

File tree

.github/workflows/publish.yml

Lines changed: 171 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ on:
88
required: true
99
default: true
1010
type: boolean
11+
packages:
12+
description: "Comma-separated package list; empty publishes the full workspace"
13+
required: false
14+
default: ""
15+
type: string
16+
skip_published:
17+
description: "Skip versions that already exist on crates.io during a real publish"
18+
required: true
19+
default: true
20+
type: boolean
1121

1222
permissions:
1323
contents: read
@@ -19,11 +29,166 @@ jobs:
1929
- uses: actions/checkout@v4
2030
- uses: dtolnay/rust-toolchain@stable
2131
- uses: Swatinem/rust-cache@v2
22-
- name: verify workspace publish
23-
if: inputs.dry_run
24-
run: cargo publish --workspace --dry-run --locked
25-
- name: publish workspace crates
26-
if: ${{ !inputs.dry_run }}
32+
- name: publish crates
2733
env:
34+
DRY_RUN: ${{ inputs.dry_run }}
35+
SELECTED_PACKAGES: ${{ inputs.packages }}
36+
SKIP_PUBLISHED: ${{ inputs.skip_published }}
2837
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
29-
run: cargo publish --workspace --locked --token "$CARGO_REGISTRY_TOKEN"
38+
run: |
39+
set -euo pipefail
40+
41+
ordered_packages=(
42+
aelf-proto
43+
aelf-crypto
44+
aelf-client
45+
aelf-keystore
46+
aelf-contract
47+
aelf-sdk
48+
)
49+
50+
declare -A allowed_packages=()
51+
declare -A requested_packages=()
52+
53+
for pkg in "${ordered_packages[@]}"; do
54+
allowed_packages["$pkg"]=1
55+
done
56+
57+
if [[ -n "${SELECTED_PACKAGES//[[:space:]]/}" ]]; then
58+
IFS=',' read -r -a raw_packages <<< "$SELECTED_PACKAGES"
59+
for raw_pkg in "${raw_packages[@]}"; do
60+
pkg="${raw_pkg//[[:space:]]/}"
61+
if [[ -z "$pkg" ]]; then
62+
continue
63+
fi
64+
if [[ -z "${allowed_packages[$pkg]:-}" ]]; then
65+
echo "::error::Unsupported package '$pkg'. Allowed packages: ${ordered_packages[*]}"
66+
exit 1
67+
fi
68+
requested_packages["$pkg"]=1
69+
done
70+
71+
if [[ "${#requested_packages[@]}" -eq 0 ]]; then
72+
echo "::error::No valid packages were provided."
73+
exit 1
74+
fi
75+
fi
76+
77+
package_selected() {
78+
local pkg="$1"
79+
if [[ "${#requested_packages[@]}" -eq 0 ]]; then
80+
return 0
81+
fi
82+
[[ -n "${requested_packages[$pkg]:-}" ]]
83+
}
84+
85+
crate_version() {
86+
local pkgid
87+
local version
88+
89+
pkgid="$(cargo pkgid -p "$1")"
90+
version="${pkgid##*@}"
91+
if [[ "$version" == "$pkgid" ]]; then
92+
version="${pkgid##*#}"
93+
fi
94+
echo "$version"
95+
}
96+
97+
crate_registry_state() {
98+
local pkg="$1"
99+
local version="$2"
100+
local output
101+
local cmd_status
102+
103+
set +e
104+
output="$(cargo info "${pkg}@${version}" --registry crates-io 2>&1)"
105+
cmd_status=$?
106+
set -e
107+
108+
if [[ "$cmd_status" -eq 0 ]]; then
109+
echo "present"
110+
return 0
111+
fi
112+
113+
if grep -qi "could not find" <<< "$output"; then
114+
echo "missing"
115+
return 0
116+
fi
117+
118+
echo "::error::Failed to query crates.io index for ${pkg} ${version}: ${output}" >&2
119+
return 1
120+
}
121+
122+
ensure_crate_visible() {
123+
local pkg="$1"
124+
local version="$2"
125+
local max_attempts=30
126+
local sleep_seconds=10
127+
128+
for ((attempt = 1; attempt <= max_attempts; attempt++)); do
129+
state="$(crate_registry_state "$pkg" "$version")"
130+
case "$state" in
131+
present)
132+
echo "${pkg} ${version} is visible on crates.io."
133+
return 0
134+
;;
135+
missing)
136+
echo "Waiting for ${pkg} ${version} to become visible on crates.io (${attempt}/${max_attempts})..."
137+
sleep "$sleep_seconds"
138+
;;
139+
*)
140+
echo "::error::Unexpected crates.io registry state '${state}' while checking ${pkg} ${version}."
141+
return 1
142+
;;
143+
esac
144+
done
145+
146+
echo "::error::Timed out waiting for ${pkg} ${version} to become visible on crates.io."
147+
return 1
148+
}
149+
150+
if [[ "$DRY_RUN" == "true" && "${#requested_packages[@]}" -eq 0 ]]; then
151+
echo "Running workspace dry-run for the full publish set."
152+
cargo publish --workspace --dry-run --locked
153+
exit 0
154+
fi
155+
156+
if [[ "$DRY_RUN" != "true" && -z "${CARGO_REGISTRY_TOKEN:-}" ]]; then
157+
echo "::error::CARGO_REGISTRY_TOKEN is required when dry_run=false."
158+
exit 1
159+
fi
160+
161+
for pkg in "${ordered_packages[@]}"; do
162+
if ! package_selected "$pkg"; then
163+
continue
164+
fi
165+
166+
version="$(crate_version "$pkg")"
167+
echo "Processing ${pkg} ${version}"
168+
169+
if [[ "$DRY_RUN" == "true" ]]; then
170+
cargo publish -p "$pkg" --dry-run --locked
171+
continue
172+
fi
173+
174+
state="$(crate_registry_state "$pkg" "$version")"
175+
case "$state" in
176+
present)
177+
if [[ "$SKIP_PUBLISHED" == "true" ]]; then
178+
echo "Skipping ${pkg} ${version}; version already exists on crates.io."
179+
continue
180+
fi
181+
echo "::error::${pkg} ${version} already exists on crates.io."
182+
exit 1
183+
;;
184+
missing)
185+
;;
186+
*)
187+
echo "::error::Unexpected crates.io registry state '${state}' while checking ${pkg} ${version}."
188+
exit 1
189+
;;
190+
esac
191+
192+
cargo publish -p "$pkg" --locked --token "$CARGO_REGISTRY_TOKEN"
193+
ensure_crate_visible "$pkg" "$version"
194+
done

Cargo.lock

Lines changed: 65 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ prost = "0.14.3"
3939
prost-build = "0.14.3"
4040
prost-reflect = { version = "0.16.3", features = ["serde"] }
4141
prost-types = "0.14.3"
42+
protoc-bin-vendored = "3.2.0"
4243
rand = "0.9.2"
4344
reqwest = { version = "0.13.2", default-features = false, features = ["json", "query", "rustls", "http2"] }
4445
salsa20 = "0.10.2"

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,15 @@ Release flow:
329329

330330
1. Sync upstream proto files with `scripts/sync_proto.sh`
331331
2. Run `cargo fmt`, `cargo +1.85.0 check --workspace --all-targets --all-features --locked`, `cargo clippy --workspace --all-targets --all-features`, `cargo audit`, `cargo check --workspace --examples`, `cargo test --workspace`
332-
3. Review `CHANGELOG.md`, then run the manual `publish` GitHub Actions workflow with `dry_run=true`
333-
4. Re-run the `publish` workflow with `dry_run=false` after confirming the crates.io token is configured
332+
3. Review `CHANGELOG.md`, then run the manual `publish` GitHub Actions workflow with `dry_run=true`. Leave `packages` empty for a full release dry-run, or set `packages=aelf-sdk` to verify a targeted recovery path.
333+
4. Re-run the `publish` workflow with `dry_run=false` after confirming the crates.io token is configured. Leave `skip_published=true` so retries can safely resume after a partial publish.
334+
5. If crates.io returns a transient error after some crates are already published, rerun the workflow with `dry_run=false`, `packages=<remaining-crates>`, and `skip_published=true`. For the March 10, 2026 incident, the recovery command is `packages=aelf-sdk`.
335+
336+
Publishing notes:
337+
338+
- The publish workflow releases crates in dependency order: `aelf-proto`, `aelf-crypto`, `aelf-client`, `aelf-keystore`, `aelf-contract`, `aelf-sdk`.
339+
- Full-workspace dry-runs still use `cargo publish --workspace --dry-run --locked` so unpublished interdependent versions can be validated together.
340+
- crates.io releases are immutable. If a published version is wrong, it must be `yank`ed and replaced with a new version.
334341

335342
CI is defined in `.github/workflows/ci.yml`.
336343
Publishing is defined in `.github/workflows/publish.yml` and expects the `CARGO_REGISTRY_TOKEN` repository secret.

README.zh.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,15 @@ tests/fixtures/
329329

330330
1.`scripts/sync_proto.sh` 同步 upstream proto
331331
2. 执行 `cargo fmt``cargo +1.85.0 check --workspace --all-targets --all-features --locked``cargo clippy --workspace --all-targets --all-features``cargo audit``cargo check --workspace --examples``cargo test --workspace`
332-
3. 检查 `CHANGELOG.md`,然后先手动运行一次 `publish` GitHub Actions workflow,并把 `dry_run` 设为 `true`
333-
4. 确认 crates.io token 已配置后,再以 `dry_run=false` 重新运行 `publish` workflow
332+
3. 检查 `CHANGELOG.md`,然后先手动运行一次 `publish` GitHub Actions workflow,并把 `dry_run` 设为 `true`。全量发版时保持 `packages` 为空;如果只是验证恢复路径,可设置 `packages=aelf-sdk`
333+
4. 确认 crates.io token 已配置后,再以 `dry_run=false` 重新运行 `publish` workflow。保留 `skip_published=true`,这样在部分发布成功后可以安全重试
334+
5. 如果 crates.io 在部分 crate 已发布后返回瞬时错误,重新运行 workflow,并设置 `dry_run=false``packages=<剩余-crates>``skip_published=true`。针对 2026 年 3 月 10 日这次事故,恢复时应使用 `packages=aelf-sdk`
335+
336+
发布说明:
337+
338+
- 发布 workflow 会按依赖顺序发布:`aelf-proto``aelf-crypto``aelf-client``aelf-keystore``aelf-contract``aelf-sdk`
339+
- 全量 dry-run 仍然使用 `cargo publish --workspace --dry-run --locked`,这样可以一起验证尚未发布但彼此依赖的 workspace 版本
340+
- crates.io 已发布版本不可覆盖;如果某个版本发布内容有误,只能先 `yank`,再发布新版本
334341

335342
CI 定义在 `.github/workflows/ci.yml`
336343
发布流程定义在 `.github/workflows/publish.yml`,需要配置仓库 secret `CARGO_REGISTRY_TOKEN`

crates/aelf-proto/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pbjson-build.workspace = true
2020
prost.workspace = true
2121
prost-build.workspace = true
2222
prost-types.workspace = true
23+
protoc-bin-vendored.workspace = true
2324

2425
[lints]
2526
workspace = true

crates/aelf-proto/build.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::path::PathBuf;
55
fn main() -> Result<(), Box<dyn std::error::Error>> {
66
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
77
let proto_root = manifest_dir.join("proto/upstream");
8+
let protoc = protoc_bin_vendored::protoc_bin_path()?;
9+
let protoc_include = protoc_bin_vendored::include_path()?;
810
let mut files = collect_proto_files(&proto_root)?;
911
files.sort();
1012

@@ -20,7 +22,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
2022
config.compile_well_known_types();
2123
config.extern_path(".google.protobuf", "::pbjson_types");
2224
config.include_file("_includes.rs");
23-
config.compile_protos(&files, &[proto_root])?;
25+
config.protoc_executable(protoc);
26+
config.compile_protos(&files, &[proto_root, protoc_include])?;
2427

2528
let descriptor_bytes = fs::read(&descriptor_path)?;
2629
let packages = collect_packages(&descriptor_bytes)?;

0 commit comments

Comments
 (0)