-
Notifications
You must be signed in to change notification settings - Fork 0
394 lines (323 loc) · 16.4 KB
/
Copy pathsemver-enforcer.yml
File metadata and controls
394 lines (323 loc) · 16.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
name: ABI Oracle (SemVer Auto-Tagger)
on:
push:
branches:
- main
permissions:
contents: write
concurrency:
group: abi-oracle
cancel-in-progress: false
jobs:
abi-oracle:
name: Calculate ABI and Bump Version
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_TARGET_TOKEN }}
- name: Setup Toob Ecosystem
uses: ./.github/actions/setup-cli
with:
install-native-deps: "true"
- name: Configure Git Identity
run: |
git config user.name "Toob ABI Oracle"
git config user.email "oracle@toob-boot.io"
- name: Detect Subsystem Changes
id: changes
run: |
echo ">>> Detecting changes in recent commit..."
git fetch --prune --unshallow 2>/dev/null || git fetch --all 2>/dev/null || true
# Ensure the registry submodule is initialized
if [ ! -d "toob-registry/.git" ] && [ ! -f "toob-registry/.git" ]; then
git submodule update --init toob-registry
fi
# Check if a monorepo subsystem changed (direct files, not submodules).
# Diffs from the last matching tag to capture accumulated changes.
check_subsystem() {
local tag_prefix=$1
shift
local paths=("$@")
local last_tag=$(git describe --tags --match "${tag_prefix}v*" --abbrev=0 2>/dev/null || echo "")
local diff_files=""
if [ -n "$last_tag" ]; then
diff_files=$(git diff --name-only "$last_tag" HEAD 2>/dev/null || echo "")
else
diff_files=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
if [ -z "$diff_files" ]; then
diff_files=$(git ls-files)
fi
fi
for file in $diff_files; do
for p in "${paths[@]}"; do
if [[ "$file" == $p* ]]; then
echo "true"
return
fi
done
done
echo "false"
}
# Registry subsystem versioning (chips, arch, drivers, toolchains)
# is handled by the Toob-Registry's own CI pipeline:
# main-release.yml → semver_calc.go → build_registry.go
# The enforcer only manages Core SDK, CLI, and Compiler tags.
CHECK_CORE=$(check_subsystem "core/" "toobloader/core/" "sdk/" "common/")
CHECK_CLI=$(check_subsystem "cli/" "cli/toob-cli/")
CHECK_COMPILER=$(check_subsystem "compiler/" "cli/.pipeline-repo/Dockerfile.compiler" "cli/.pipeline-repo/toob-ci-build.sh" "compiler/")
echo "CHECK_CORE=$CHECK_CORE" >> $GITHUB_ENV
echo "CHECK_CLI=$CHECK_CLI" >> $GITHUB_ENV
echo "CHECK_COMPILER=$CHECK_COMPILER" >> $GITHUB_ENV
echo "Core changed: $CHECK_CORE"
echo "CLI changed: $CHECK_CLI"
echo "Compiler changed: $CHECK_COMPILER"
- name: ABI Orakel (Core SDK)
if: env.CHECK_CORE == 'true'
run: |
echo ">>> [Core Oracle] Starting symbol analysis for Core, SDK, and Common..."
LAST_TAG=$(git describe --tags --match "core/v*" --abbrev=0 2>/dev/null || echo "core/v0.0.0")
RAW_VERSION=${LAST_TAG#core/v}
echo ">>> [Core Oracle] Baseline Tag: $LAST_TAG"
MAJOR=$(echo $RAW_VERSION | cut -d. -f1)
MINOR=$(echo $RAW_VERSION | cut -d. -f2)
PATCH=$(echo $RAW_VERSION | cut -d. -f3)
# If no real baseline exists, create the initial tag and skip diff
if [ "$LAST_TAG" = "core/v0.0.0" ]; then
echo ">>> [Core Oracle] No baseline tag. Creating initial patch release."
PATCH=$((PATCH + 1))
NEW_TAG="core/v${MAJOR}.${MINOR}.${PATCH}"
if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then
echo ">>> [Core Oracle] Tag $NEW_TAG already exists. Skipping."
else
git tag "$NEW_TAG"
git push origin "$NEW_TAG" || echo ">>> Tag push failed (concurrent run?)"
echo ">>> [Core Oracle] Initial tag $NEW_TAG pushed!"
fi
exit 0
fi
mkdir -p abi_workspace/current abi_workspace/old
# Build CURRENT code.
# The esp32c6 template is used as a reference target. Core libraries
# are chip-independent — the ABI output is identical for any chip.
echo ">>> Building CURRENT Code..."
toob build --native --manifest toob-registry/chips/esp32c6/template_device.toml
for lib in libtoob_core.a libtoob_libtoob.a libtoob_zcbor.a libtoob_heatshrink.a; do
find . -path '*/builds/*' -name "$lib" -exec cp {} abi_workspace/current/ \;
done
find . -path '*/builds/build_*' -type d -exec rm -rf {} + 2>/dev/null || true
# Build OLD code using git worktree (faster than clone, shares object store)
echo ">>> Building OLD Code from $LAST_TAG..."
git worktree add abi_workspace/old_repo "$LAST_TAG" 2>/dev/null || {
echo ">>> [FATAL] Cannot create worktree for $LAST_TAG"
exit 1
}
cd abi_workspace/old_repo
git submodule update --init toob-registry
toob build --native --manifest toob-registry/chips/esp32c6/template_device.toml || {
echo ">>> [FATAL] Old build failed. Cannot produce ABI baseline."
cd ../../
git worktree remove abi_workspace/old_repo --force 2>/dev/null || true
exit 1
}
for lib in libtoob_core.a libtoob_libtoob.a libtoob_zcbor.a libtoob_heatshrink.a; do
find . -path '*/builds/*' -name "$lib" -exec cp {} ../old/ \;
done
cd ../../
git worktree remove abi_workspace/old_repo --force 2>/dev/null || true
# Symbol-based ABI diff using the cross-compiler's nm.
# This avoids abidiff's crash on cross-compiled RISC-V ELF files.
# Extract public symbols (T=text, D=data, B=bss) and compare sets.
echo ">>> Running symbol-based ABI diff..."
# Locate the cross-compiler nm (installed during build)
NM_BIN=$(find ~/.toob/toolchains -name "riscv32-esp-elf-nm" 2>/dev/null | head -1)
if [ -z "$NM_BIN" ]; then
NM_BIN="nm" # fallback to host nm
fi
echo ">>> Using nm: $NM_BIN"
BUMP="PATCH"
for lib in libtoob_core.a libtoob_libtoob.a libtoob_zcbor.a libtoob_heatshrink.a; do
OLD_LIB="abi_workspace/old/$lib"
NEW_LIB="abi_workspace/current/$lib"
if [ ! -f "$OLD_LIB" ] || [ ! -f "$NEW_LIB" ]; then
continue
fi
# Extract globally-visible defined symbols (type + name)
$NM_BIN --defined-only -g "$OLD_LIB" 2>/dev/null | awk '{print $2, $3}' | sort -u > /tmp/sym_old.txt
$NM_BIN --defined-only -g "$NEW_LIB" 2>/dev/null | awk '{print $2, $3}' | sort -u > /tmp/sym_new.txt
REMOVED=$(comm -23 /tmp/sym_old.txt /tmp/sym_new.txt | wc -l)
ADDED=$(comm -13 /tmp/sym_old.txt /tmp/sym_new.txt | wc -l)
if [ "$REMOVED" -gt 0 ]; then
echo ">>> [RESULT] $lib: $REMOVED symbols REMOVED (MAJOR)"
comm -23 /tmp/sym_old.txt /tmp/sym_new.txt | head -10
BUMP="MAJOR"
elif [ "$ADDED" -gt 0 ] && [ "$BUMP" != "MAJOR" ]; then
echo ">>> [RESULT] $lib: $ADDED symbols ADDED (MINOR)"
comm -13 /tmp/sym_old.txt /tmp/sym_new.txt | head -10
[ "$BUMP" = "PATCH" ] && BUMP="MINOR"
else
echo ">>> [RESULT] $lib: No symbol changes"
fi
done
# Layer 2: Public header diff to catch struct/typedef/enum changes
# that are invisible to nm (same symbol name, different layout).
echo ">>> Running public header analysis..."
HEADER_DIRS="toobloader/core/include sdk/libtoob/include sdk/os_client/include common/include"
for hdir in $HEADER_DIRS; do
[ ! -d "$hdir" ] && continue
DELETED_H=$(git diff --diff-filter=D --name-only "$LAST_TAG" HEAD -- "$hdir" 2>/dev/null | wc -l)
ADDED_H=$(git diff --diff-filter=A --name-only "$LAST_TAG" HEAD -- "$hdir" 2>/dev/null | wc -l)
MODIFIED_H=$(git diff --diff-filter=M --name-only "$LAST_TAG" HEAD -- "$hdir" 2>/dev/null || echo "")
# Deleted public headers = breaking
if [ "$DELETED_H" -gt 0 ]; then
echo ">>> [HEADER] $hdir: $DELETED_H headers DELETED (MAJOR)"
BUMP="MAJOR"
fi
# New public headers = new capability
if [ "$ADDED_H" -gt 0 ] && [ "$BUMP" != "MAJOR" ]; then
echo ">>> [HEADER] $hdir: $ADDED_H new headers (MINOR)"
[ "$BUMP" = "PATCH" ] && BUMP="MINOR"
fi
# Modified headers: check if struct/typedef/enum definitions changed
if [ -n "$MODIFIED_H" ] && [ "$BUMP" != "MAJOR" ]; then
for hfile in $MODIFIED_H; do
# Extract struct, typedef, and enum lines from old and new
OLD_DEFS=$(git show "${LAST_TAG}:${hfile}" 2>/dev/null | grep -E '^\s*(typedef\s|struct\s|enum\s|}\s*\w+)' | sort)
NEW_DEFS=$(cat "$hfile" 2>/dev/null | grep -E '^\s*(typedef\s|struct\s|enum\s|}\s*\w+)' | sort)
if [ "$OLD_DEFS" != "$NEW_DEFS" ]; then
echo ">>> [HEADER] $hfile: type definitions changed (MINOR)"
[ "$BUMP" = "PATCH" ] && BUMP="MINOR"
fi
done
fi
done
echo ">>> [Core Oracle] Final bump type: $BUMP"
case "$BUMP" in
MAJOR) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
MINOR) MINOR=$((MINOR + 1)); PATCH=0 ;;
PATCH) PATCH=$((PATCH + 1)) ;;
esac
NEW_TAG="core/v${MAJOR}.${MINOR}.${PATCH}"
echo ">>> [Core Oracle] Calculated new version: $NEW_TAG"
if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then
echo ">>> [Core Oracle] Tag $NEW_TAG already exists. Skipping."
else
git tag "$NEW_TAG"
git push origin "$NEW_TAG" || echo ">>> Tag push failed (concurrent run?)"
echo ">>> [Core Oracle] Tag $NEW_TAG pushed!"
fi
- name: ABI Orakel (CLI)
if: env.CHECK_CLI == 'true'
run: |
echo ">>> [CLI Oracle] Starting Contract analysis for CLI..."
LAST_TAG=$(git describe --tags --match "cli/v*" --abbrev=0 2>/dev/null || echo "cli/v0.0.0")
RAW_VERSION=${LAST_TAG#cli/v}
echo ">>> [CLI Oracle] Baseline Tag: $LAST_TAG"
MAJOR=$(echo $RAW_VERSION | cut -d. -f1)
MINOR=$(echo $RAW_VERSION | cut -d. -f2)
PATCH=$(echo $RAW_VERSION | cut -d. -f3)
mkdir -p cli_abi_workspace
cp cli/toob-cli/internal/ports/ports.go cli_abi_workspace/ports_current.go
if [ "$LAST_TAG" != "cli/v0.0.0" ]; then
git show $LAST_TAG:cli/toob-cli/internal/ports/ports.go > cli_abi_workspace/ports_old.go 2>/dev/null || touch cli_abi_workspace/ports_old.go
else
touch cli_abi_workspace/ports_old.go
fi
echo ">>> Running AST-based Contract Differ..."
cd cli/toob-cli
go build -o /tmp/semver-tool ./internal/ports/cmd/semver
BUMP_TYPE=$(/tmp/semver-tool ../../cli_abi_workspace/ports_old.go ../../cli_abi_workspace/ports_current.go | tail -n 1)
cd ../../
echo ">>> [RESULT] Contract Differ determined bump type: $BUMP_TYPE"
if [ "$BUMP_TYPE" = "MAJOR" ]; then
MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0
elif [ "$BUMP_TYPE" = "MINOR" ]; then
MINOR=$((MINOR + 1)); PATCH=0
else
PATCH=$((PATCH + 1))
fi
NEW_TAG="cli/v${MAJOR}.${MINOR}.${PATCH}"
echo ">>> [CLI Oracle] Calculated new version: $NEW_TAG"
if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then
echo ">>> [CLI Oracle] Tag $NEW_TAG already exists. Skipping."
else
git tag "$NEW_TAG"
git push origin "$NEW_TAG" || echo ">>> Tag push failed (concurrent run?)"
echo ">>> [CLI Oracle] Tag $NEW_TAG pushed!"
fi
- name: ABI Orakel (Compiler Container)
if: env.CHECK_COMPILER == 'true'
run: |
echo ">>> [Compiler Oracle] Starting manifest-based version analysis..."
LAST_TAG=$(git describe --tags --match "compiler/v*" --abbrev=0 2>/dev/null || echo "compiler/v0.0.0")
echo ">>> [Compiler Oracle] Baseline Tag: $LAST_TAG"
CURRENT_VERSION=$(jq -r '.compiler_version' compiler/compiler_manifest.json)
MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1)
MINOR=$(echo $CURRENT_VERSION | cut -d. -f2)
PATCH=$(echo $CURRENT_VERSION | cut -d. -f3)
BUMP="PATCH"
if [ "$LAST_TAG" != "compiler/v0.0.0" ]; then
OLD_MANIFEST=$(git show $LAST_TAG:compiler/compiler_manifest.json 2>/dev/null || echo '{}')
OLD_PROTOCOL=$(echo "$OLD_MANIFEST" | jq -r '.protocol_version // 0')
NEW_PROTOCOL=$(jq -r '.protocol_version' compiler/compiler_manifest.json)
if [ "$OLD_PROTOCOL" != "$NEW_PROTOCOL" ]; then
BUMP="MAJOR"
fi
if [ "$BUMP" = "PATCH" ]; then
OLD_BASE=$(echo "$OLD_MANIFEST" | jq -r '.base_image.image // ""')
NEW_BASE=$(jq -r '.base_image.image' compiler/compiler_manifest.json)
OLD_SYSPKG=$(echo "$OLD_MANIFEST" | jq -cS '.system_packages // []')
NEW_SYSPKG=$(jq -cS '.system_packages' compiler/compiler_manifest.json)
if [ "$OLD_BASE" != "$NEW_BASE" ] || [ "$OLD_SYSPKG" != "$NEW_SYSPKG" ]; then
BUMP="MINOR"
fi
fi
fi
echo ">>> [Compiler Oracle] Bump type: $BUMP"
if [ "$BUMP" = "MAJOR" ]; then
MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0
elif [ "$BUMP" = "MINOR" ]; then
MINOR=$((MINOR + 1)); PATCH=0
else
PATCH=$((PATCH + 1))
fi
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
NEW_TAG="compiler/v${NEW_VERSION}"
echo ">>> [Compiler Oracle] New version: $NEW_TAG"
# Check tag existence BEFORE writing to manifest to prevent double-bumps
if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then
echo ">>> [Compiler Oracle] Tag $NEW_TAG already exists. Skipping."
exit 0
fi
# Update version in manifest and commit
jq --arg v "$NEW_VERSION" '.compiler_version = $v' \
compiler/compiler_manifest.json > tmp_manifest.json \
&& mv tmp_manifest.json compiler/compiler_manifest.json
git add compiler/compiler_manifest.json
git commit -m "chore(compiler): bump to v${NEW_VERSION} [skip ci]" || true
# Atomic push: commit + tag together to prevent race conditions
git tag "$NEW_TAG"
git push origin HEAD:main "$NEW_TAG" || echo ">>> Push failed (concurrent run?)"
echo ">>> [Compiler Oracle] Tag $NEW_TAG pushed!"
# Create draft release as manual approval gate
CLI_VER=$(jq -r '.cli.version' compiler/compiler_manifest.json)
PROTO_VER=$(jq -r '.protocol_version' compiler/compiler_manifest.json)
BODY="## Compiler v${NEW_VERSION}\n\n"
BODY+="| Property | Value |\n|---|---|\n"
BODY+="| CLI | v${CLI_VER} |\n"
BODY+="| Protocol | v${PROTO_VER} |\n\n"
BODY+="**Publish this release to trigger the Docker image build on the CI server.**"
curl -sf -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ secrets.RELEASE_TARGET_TOKEN }}" \
https://api.github.com/repos/Toob-Boot/Toob-Loader/releases \
-d "{
\"tag_name\": \"${NEW_TAG}\",
\"name\": \"Compiler v${NEW_VERSION}\",
\"body\": \"$(echo -e "$BODY")\",
\"draft\": true,
\"prerelease\": false
}" && echo ">>> [Compiler Oracle] Draft release created for ${NEW_TAG}" \
|| echo ">>> [Compiler Oracle] Draft creation failed (non-critical)"