Skip to content

Commit eba96a1

Browse files
authored
Merge pull request #791 from HaoboGu/feat/ci
Refactor the CI system
2 parents a8bc7ab + d8f25f4 commit eba96a1

30 files changed

Lines changed: 892 additions & 720 deletions

File tree

.github/ci/_lib.sh

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# shellcheck shell=bash
2+
#
3+
# Shared bootstrap for RMK CI scripts. Source this from other scripts in
4+
# .github/ci/ to pick up common env, cache, helpers, and example discovery.
5+
#
6+
# source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
7+
#
8+
# Expected preamble in the caller:
9+
#
10+
# #!/bin/bash
11+
# set -euo pipefail
12+
#
13+
14+
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
15+
cd "$repo_root"
16+
17+
export CARGO_TERM_COLOR=always
18+
export CARGO_TERM_PROGRESS_WHEN=never
19+
export CARGO_NET_GIT_FETCH_WITH_CLI=true
20+
export TERM="${TERM:-dumb}"
21+
22+
# Cache root: if RMK_CI_CACHE_ROOT is set (self-hosted runner or local reuse),
23+
# redirect rustup/cargo homes and target dir there. Otherwise fall back to
24+
# an in-tree target/ci dir so local runs don't clobber the default target.
25+
if [[ -n "${RMK_CI_CACHE_ROOT:-}" ]]; then
26+
export RUSTUP_HOME="${RUSTUP_HOME:-$RMK_CI_CACHE_ROOT/rustup}"
27+
export CARGO_HOME="${CARGO_HOME:-$RMK_CI_CACHE_ROOT/cargo}"
28+
export PATH="$CARGO_HOME/bin:$PATH"
29+
mkdir -p "$RMK_CI_CACHE_ROOT"
30+
if [[ -f "$HOME/.cargo/config.toml" && ! -f "$CARGO_HOME/config.toml" ]]; then
31+
mkdir -p "$CARGO_HOME"
32+
cp "$HOME/.cargo/config.toml" "$CARGO_HOME/config.toml"
33+
fi
34+
target_root="$RMK_CI_CACHE_ROOT/target"
35+
else
36+
target_root="$repo_root/target/ci"
37+
fi
38+
mkdir -p "$target_root"
39+
40+
# Embedded Rust targets every stable toolchain build needs.
41+
TARGETS=(
42+
thumbv6m-none-eabi
43+
thumbv7m-none-eabi
44+
thumbv7em-none-eabi
45+
thumbv7em-none-eabihf
46+
thumbv8m.main-none-eabihf
47+
riscv32imc-unknown-none-elf
48+
riscv32imac-unknown-none-elf
49+
)
50+
51+
log_section() {
52+
printf "\n==> %s\n" "$1"
53+
}
54+
55+
ensure_toolchain() {
56+
rustup toolchain install "$1" --profile minimal --no-self-update
57+
}
58+
59+
ensure_stable_toolchain() {
60+
ensure_toolchain stable
61+
rustup component add clippy rustfmt llvm-tools --toolchain stable
62+
rustup target add --toolchain stable "${TARGETS[@]}"
63+
}
64+
65+
# $1=bin $2=crate-name
66+
ensure_cargo_tool() {
67+
if command -v "$1" >/dev/null 2>&1; then
68+
return
69+
fi
70+
cargo +stable install --locked "$2"
71+
}
72+
73+
require_cargo_batch() {
74+
if command -v cargo-batch >/dev/null 2>&1; then
75+
return
76+
fi
77+
cargo install --git https://github.com/embassy-rs/cargo-batch cargo --bin cargo-batch --locked
78+
}
79+
80+
ensure_esp_toolchain() {
81+
ensure_cargo_tool espup espup
82+
espup install
83+
if [[ ! -f "$HOME/export-esp.sh" ]]; then
84+
echo "espup did not create $HOME/export-esp.sh" >&2
85+
exit 1
86+
fi
87+
}
88+
89+
# Examples auto-discovery currently skips over:
90+
# - nrf54lm20_ble: Cargo.toml references local path deps
91+
# (/Users/.../nrf-sdc, /Users/.../nrf-mpsl) that only exist on the author's
92+
# workstation. The git-rev alternatives sit commented-out next to them;
93+
# once those are swapped in, this entry can be removed.
94+
# - esp32_ble_split: dual-target split example that only builds through the
95+
# `build-central` / `build-peripheral` cargo aliases (different chip per
96+
# bin, no [build].target set). Not buildable via a plain `cargo build`.
97+
EXAMPLE_SKIPLIST=(
98+
"examples/use_rust/nrf54lm20_ble"
99+
"examples/use_config/esp32_ble_split"
100+
)
101+
102+
# Echoes Cargo.toml paths for every buildable example, one per line.
103+
# A buildable example is a direct child of examples/use_{rust,config}/ that
104+
# has both a src/ dir and a Cargo.toml (filters out placeholders like fix/),
105+
# and is not listed in EXAMPLE_SKIPLIST.
106+
list_example_manifests() {
107+
local dir stripped skip entry
108+
for dir in examples/use_rust/*/ examples/use_config/*/; do
109+
[[ -d "$dir/src" && -f "$dir/Cargo.toml" ]] || continue
110+
stripped="${dir%/}"
111+
skip=0
112+
for entry in "${EXAMPLE_SKIPLIST[@]}"; do
113+
if [[ "$stripped" == "$entry" ]]; then
114+
skip=1
115+
break
116+
fi
117+
done
118+
(( skip == 0 )) && printf '%s\n' "${dir}Cargo.toml"
119+
done
120+
}
121+
122+
# Echoes the default build target triple declared in the manifest's sibling
123+
# .cargo/config.toml ([build].target). Only the first uncommented occurrence
124+
# is emitted; returns empty if the file or the key is absent. Trailing
125+
# TOML comments on the value are stripped.
126+
get_example_target() {
127+
local manifest="$1"
128+
local dir config
129+
dir="$(dirname "$manifest")"
130+
config="$dir/.cargo/config.toml"
131+
[[ -f "$config" ]] || return 0
132+
awk '
133+
/^\[/ { section = $0; next }
134+
section == "[build]" && /^[[:space:]]*target[[:space:]]*=/ {
135+
sub(/^[[:space:]]*target[[:space:]]*=[[:space:]]*/, "")
136+
sub(/[[:space:]]*#.*$/, "")
137+
sub(/^"/, "")
138+
sub(/"[[:space:]]*$/, "")
139+
print
140+
exit
141+
}
142+
' "$config"
143+
}

.github/ci/bloat-report.sh

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/bin/bash
2+
# Assemble a bloat report from size-data artifact files.
3+
# Input directory structure per example:
4+
# <safe_name>/size-data.txt — dir|bin_label|base_total|head_total|text_diff|data_diff|bss_diff
5+
# <safe_name>/size-head[-bin].txt — full cargo size output (HEAD)
6+
# <safe_name>/size-base[-bin].txt — full cargo size output (BASE)
7+
# <safe_name>/bloaty[-bin].txt — bloaty diff output
8+
#
9+
# Writes markdown to stdout and $GITHUB_STEP_SUMMARY.
10+
set -euo pipefail
11+
12+
artifact_dir="${1:?Usage: bloat-report.sh <artifact-dir>}"
13+
14+
# Format bytes as human-readable KiB with one decimal.
15+
fmt_size() {
16+
awk "BEGIN { printf \"%.1f KiB\", $1 / 1024 }"
17+
}
18+
19+
# Format diff percentage with sign and emoji.
20+
fmt_diff() {
21+
local base=$1 head=$2
22+
if (( base == 0 )); then
23+
echo "new"
24+
return
25+
fi
26+
local diff=$((head - base))
27+
local pct_x100=$(( diff * 10000 / base ))
28+
local sign="+" whole frac
29+
(( pct_x100 < 0 )) && sign="-" && pct_x100=$(( -pct_x100 ))
30+
whole=$(( pct_x100 / 100 ))
31+
frac=$(( pct_x100 % 100 ))
32+
33+
local indicator=""
34+
(( diff > 0 )) && indicator=" ⬆️"
35+
(( diff < 0 )) && indicator=" ⬇️"
36+
37+
printf "%s%d.%02d%%%s" "$sign" "$whole" "$frac" "$indicator"
38+
}
39+
40+
# Format a byte diff with sign (e.g. "+688", "-32", "0").
41+
fmt_bytes_diff() {
42+
local d=$1
43+
if (( d > 0 )); then printf "+%d" "$d"
44+
elif (( d < 0 )); then printf "%d" "$d"
45+
else printf "0"
46+
fi
47+
}
48+
49+
# Collect all size-data entries: dir|bin_label|base|head|d_text|d_data|d_bss|safe_name
50+
entries=()
51+
for f in $(find "$artifact_dir" -name 'size-data.txt' -type f | sort); do
52+
safe_name="$(basename "$(dirname "$f")")"
53+
while IFS='|' read -r dir bin_label base_size head_size d_text d_data d_bss; do
54+
entries+=("$dir|$bin_label|$base_size|$head_size|${d_text:-0}|${d_data:-0}|${d_bss:-0}|$safe_name")
55+
done < "$f"
56+
done
57+
58+
{
59+
# ── Overview table ──
60+
echo "## Size Report"
61+
echo
62+
echo "| Example | main | PR | Diff | .text | .data | .bss |"
63+
echo "| :--- | ---: | ---: | ---: | ---: | ---: | ---: |"
64+
65+
for entry in "${entries[@]}"; do
66+
IFS='|' read -r dir bin_label base_size head_size d_text d_data d_bss safe_name <<< "$entry"
67+
label="${dir#examples/}"
68+
[[ -n "$bin_label" ]] && label="$label ($bin_label)"
69+
printf "| \`%s\` | %s | %s | %s | %s | %s | %s |\n" \
70+
"$label" \
71+
"$(fmt_size "$base_size")" \
72+
"$(fmt_size "$head_size")" \
73+
"$(fmt_diff "$base_size" "$head_size")" \
74+
"$(fmt_bytes_diff "$d_text")" \
75+
"$(fmt_bytes_diff "$d_data")" \
76+
"$(fmt_bytes_diff "$d_bss")"
77+
done
78+
79+
echo
80+
81+
# ── Collapsible details per example ──
82+
prev_safe=""
83+
for entry in "${entries[@]}"; do
84+
IFS='|' read -r dir bin_label base_size head_size safe_name <<< "$entry"
85+
label="${dir#examples/}"
86+
[[ -n "$bin_label" ]] && label="$label ($bin_label)"
87+
88+
bin_suffix=""
89+
[[ -n "$bin_label" ]] && bin_suffix="-$bin_label"
90+
91+
echo "<details>"
92+
echo "<summary><code>$label</code> — $(fmt_size "$base_size")$(fmt_size "$head_size") ($(fmt_diff "$base_size" "$head_size"))</summary>"
93+
echo
94+
95+
# cargo size (PR)
96+
size_head="$artifact_dir/$safe_name/size-head${bin_suffix}.txt"
97+
if [[ -f "$size_head" ]]; then
98+
echo "**cargo size (PR):**"
99+
echo '```'
100+
cat "$size_head"
101+
echo '```'
102+
echo
103+
fi
104+
105+
# cargo size (main)
106+
size_base="$artifact_dir/$safe_name/size-base${bin_suffix}.txt"
107+
if [[ -f "$size_base" ]]; then
108+
echo "**cargo size (main):**"
109+
echo '```'
110+
cat "$size_base"
111+
echo '```'
112+
echo
113+
fi
114+
115+
# bloaty diff
116+
bloaty_file="$artifact_dir/$safe_name/bloaty${bin_suffix}.txt"
117+
if [[ -f "$bloaty_file" ]]; then
118+
echo "**Bloaty diff (PR vs main):**"
119+
echo '```'
120+
cat "$bloaty_file"
121+
echo '```'
122+
echo
123+
fi
124+
125+
echo "</details>"
126+
echo
127+
done
128+
}

.github/ci/build.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
# Build ESP examples that need the xtensa toolchain.
3+
# Stable-toolchain examples are built via the matrix job in ci.yml.
4+
set -euo pipefail
5+
# shellcheck source=_lib.sh
6+
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
7+
8+
ensure_stable_toolchain
9+
ensure_cargo_tool flip-link flip-link
10+
11+
esp_manifests=()
12+
while IFS= read -r manifest; do
13+
case "$manifest" in
14+
*/esp32s3_ble/*) esp_manifests+=("$manifest") ;;
15+
esac
16+
done < <(list_example_manifests)
17+
18+
if (( ${#esp_manifests[@]} > 0 )); then
19+
log_section "Building esp32s3 examples"
20+
ensure_esp_toolchain
21+
# shellcheck source=/dev/null
22+
source "$HOME/export-esp.sh"
23+
for manifest in "${esp_manifests[@]}"; do
24+
target="$(get_example_target "$manifest")"
25+
dir="$(dirname "$manifest")"
26+
(
27+
cd "$dir"
28+
CARGO_UNSTABLE_BUILD_STD=alloc,core \
29+
cargo +esp build --release --target "$target"
30+
)
31+
done
32+
fi

.github/ci/check.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
# shellcheck source=_lib.sh
4+
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
5+
6+
require_cargo_batch
7+
ensure_stable_toolchain
8+
ensure_toolchain beta
9+
ensure_toolchain nightly
10+
11+
# Single source of truth for the rmk feature-set matrix used by check/clippy.
12+
# An empty entry means "--no-default-features" with no extra features on top.
13+
rmk_featuresets=(
14+
""
15+
"log,std"
16+
"storage"
17+
"async_matrix,storage"
18+
"split,vial,storage"
19+
"passkey_entry"
20+
"split,vial,storage,passkey_entry"
21+
)
22+
23+
# Emit "--- <cmd> ..." tuples for rmk (every feature set) plus the other
24+
# workspace crates. Tokens are literal or feature lists with no whitespace,
25+
# so relying on word-splitting at the call site is safe.
26+
emit_batch_args() {
27+
local cmd="$1"
28+
local feats
29+
for feats in "${rmk_featuresets[@]}"; do
30+
if [[ -z "$feats" ]]; then
31+
printf -- '--- %s --manifest-path rmk/Cargo.toml --no-default-features\n' "$cmd"
32+
else
33+
printf -- '--- %s --manifest-path rmk/Cargo.toml --no-default-features --features %s\n' "$cmd" "$feats"
34+
fi
35+
done
36+
printf -- '--- %s --manifest-path rmk-config/Cargo.toml\n' "$cmd"
37+
printf -- '--- %s --manifest-path rmk-macro/Cargo.toml\n' "$cmd"
38+
printf -- '--- %s --manifest-path rmk-types/Cargo.toml\n' "$cmd"
39+
}
40+
41+
log_section "Stable checks"
42+
# shellcheck disable=SC2046
43+
cargo +stable batch --target-dir "$target_root/check-stable" $(emit_batch_args check)
44+
45+
log_section "Beta smoke checks"
46+
cargo +beta batch --target-dir "$target_root/check-beta" \
47+
--- check --manifest-path rmk/Cargo.toml --no-default-features \
48+
--- check --manifest-path rmk/Cargo.toml --no-default-features --features split,vial,storage,passkey_entry
49+
50+
log_section "Nightly smoke checks"
51+
cargo +nightly batch --target-dir "$target_root/check-nightly" \
52+
--- check --manifest-path rmk/Cargo.toml --no-default-features \
53+
--- check --manifest-path rmk/Cargo.toml --no-default-features --features split,vial,storage,passkey_entry
54+
55+
log_section "Clippy"
56+
# cargo-batch only exposes a subset of cargo subcommands (build, check, ...) —
57+
# `clippy` is not supported, so we run it serially. Dedicated target dir
58+
# prevents conflicts with the check-stable fingerprints above.
59+
clippy_target="$target_root/clippy-stable"
60+
clippy_rmk() {
61+
local feats="$1"
62+
if [[ -z "$feats" ]]; then
63+
cargo +stable clippy --target-dir "$clippy_target" \
64+
--manifest-path rmk/Cargo.toml --no-default-features -- -D warnings
65+
else
66+
cargo +stable clippy --target-dir "$clippy_target" \
67+
--manifest-path rmk/Cargo.toml --no-default-features --features "$feats" -- -D warnings
68+
fi
69+
}
70+
for feats in "${rmk_featuresets[@]}"; do
71+
clippy_rmk "$feats"
72+
done
73+
cargo +stable clippy --target-dir "$clippy_target" --manifest-path rmk-config/Cargo.toml -- -D warnings
74+
cargo +stable clippy --target-dir "$clippy_target" --manifest-path rmk-macro/Cargo.toml -- -D warnings
75+
cargo +stable clippy --target-dir "$clippy_target" --manifest-path rmk-types/Cargo.toml -- -D warnings

0 commit comments

Comments
 (0)