Skip to content

Commit 8718891

Browse files
committed
Refactor CI: split monolith workflows into reusable components
Before: 2 monolith YAMLs (904 + 1127 lines), duplicated matrices, inconsistent action versions, duplicate build-windows job. After: 5 reusable workflows + 3 lean callers (1091 total lines): - _lint.yml: lint + security-static + codeql-gate - _test.yml: tests on 5 platforms with CBM_SKIP_PERF support - _build.yml: standard + UI + portable builds, all platforms - _smoke.yml: smoke test every binary variant - _soak.yml: quick + ASan soak, parameterized duration Fixes: duplicate build-windows, missing Windows CBM_SKIP_PERF, missing timeout-minutes, inconsistent action versions, VirusTotal check extracted to scripts/ci/check-virustotal.sh.
1 parent c761106 commit 8718891

File tree

9 files changed

+908
-2028
lines changed

9 files changed

+908
-2028
lines changed

.github/workflows/_build.yml

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Reusable: build binaries (standard + UI) on all platforms
2+
name: Build
3+
4+
on:
5+
workflow_call:
6+
inputs:
7+
version:
8+
description: 'Version string (e.g. v0.8.0)'
9+
type: string
10+
default: ''
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
build-unix:
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
include:
21+
- os: ubuntu-latest
22+
goos: linux
23+
goarch: amd64
24+
cc: gcc
25+
cxx: g++
26+
- os: ubuntu-24.04-arm
27+
goos: linux
28+
goarch: arm64
29+
cc: gcc
30+
cxx: g++
31+
- os: macos-14
32+
goos: darwin
33+
goarch: arm64
34+
cc: cc
35+
cxx: c++
36+
- os: macos-15-intel
37+
goos: darwin
38+
goarch: amd64
39+
cc: cc
40+
cxx: c++
41+
runs-on: ${{ matrix.os }}
42+
timeout-minutes: 15
43+
steps:
44+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
45+
46+
- name: Install deps (Ubuntu)
47+
if: startsWith(matrix.os, 'ubuntu')
48+
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
49+
50+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
51+
with:
52+
node-version: "22"
53+
54+
- name: Build standard binary
55+
run: scripts/build.sh ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
56+
57+
- name: Ad-hoc sign macOS binary
58+
if: startsWith(matrix.os, 'macos')
59+
run: codesign --sign - --force build/c/codebase-memory-mcp
60+
61+
- name: Archive standard binary
62+
run: |
63+
cp LICENSE install.sh build/c/
64+
tar -czf codebase-memory-mcp-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz \
65+
-C build/c codebase-memory-mcp LICENSE install.sh
66+
67+
- name: Build UI binary
68+
run: scripts/build.sh --with-ui ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
69+
70+
- name: Ad-hoc sign macOS UI binary
71+
if: startsWith(matrix.os, 'macos')
72+
run: codesign --sign - --force build/c/codebase-memory-mcp
73+
74+
- name: Frontend integrity scan
75+
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
76+
run: scripts/security-ui.sh
77+
78+
- name: Archive UI binary
79+
run: |
80+
cp LICENSE install.sh build/c/
81+
tar -czf codebase-memory-mcp-ui-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz \
82+
-C build/c codebase-memory-mcp LICENSE install.sh
83+
84+
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
85+
with:
86+
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
87+
path: "*.tar.gz"
88+
89+
build-windows:
90+
runs-on: windows-latest
91+
timeout-minutes: 15
92+
steps:
93+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
94+
95+
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2
96+
with:
97+
msystem: CLANG64
98+
path-type: inherit
99+
install: >-
100+
mingw-w64-clang-x86_64-clang
101+
mingw-w64-clang-x86_64-zlib
102+
make
103+
zip
104+
105+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
106+
with:
107+
node-version: "22"
108+
109+
- name: Build standard binary
110+
shell: msys2 {0}
111+
run: scripts/build.sh ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=clang CXX=clang++
112+
113+
- name: Archive standard binary
114+
shell: msys2 {0}
115+
run: |
116+
BIN=build/c/codebase-memory-mcp
117+
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
118+
cp "$BIN" codebase-memory-mcp.exe
119+
zip codebase-memory-mcp-windows-amd64.zip codebase-memory-mcp.exe LICENSE install.ps1
120+
121+
- name: Build UI binary
122+
shell: msys2 {0}
123+
run: scripts/build.sh --with-ui ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=clang CXX=clang++
124+
125+
- name: Archive UI binary
126+
shell: msys2 {0}
127+
run: |
128+
BIN=build/c/codebase-memory-mcp
129+
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
130+
cp "$BIN" codebase-memory-mcp.exe
131+
zip codebase-memory-mcp-ui-windows-amd64.zip codebase-memory-mcp.exe LICENSE install.ps1
132+
133+
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
134+
with:
135+
name: binaries-windows-amd64
136+
path: "*.zip"
137+
138+
build-linux-portable:
139+
strategy:
140+
fail-fast: false
141+
matrix:
142+
include:
143+
- arch: amd64
144+
runner: ubuntu-latest
145+
- arch: arm64
146+
runner: ubuntu-24.04-arm
147+
runs-on: ${{ matrix.runner }}
148+
container:
149+
image: alpine:3.21
150+
timeout-minutes: 15
151+
steps:
152+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
153+
154+
- name: Install musl build deps
155+
run: apk add --no-cache build-base linux-headers zlib-dev zlib-static nodejs npm
156+
157+
- name: Build standard binary (static)
158+
run: scripts/build.sh ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=gcc CXX=g++ STATIC=1
159+
160+
- name: Verify static linking
161+
run: file build/c/codebase-memory-mcp | grep -q "statically linked"
162+
163+
- name: Archive standard binary
164+
run: |
165+
cp LICENSE install.sh build/c/
166+
tar -czf codebase-memory-mcp-linux-${{ matrix.arch }}-portable.tar.gz \
167+
-C build/c codebase-memory-mcp LICENSE install.sh
168+
169+
- name: Build UI binary (static)
170+
run: scripts/build.sh --with-ui ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=gcc CXX=g++ STATIC=1
171+
172+
- name: Archive UI binary
173+
run: |
174+
cp LICENSE install.sh build/c/
175+
tar -czf codebase-memory-mcp-ui-linux-${{ matrix.arch }}-portable.tar.gz \
176+
-C build/c codebase-memory-mcp LICENSE install.sh
177+
178+
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
179+
with:
180+
name: binaries-linux-${{ matrix.arch }}-portable
181+
path: "*.tar.gz"

.github/workflows/_lint.yml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Reusable: lint + security-static + codeql-gate
2+
name: Lint & Security
3+
4+
on:
5+
workflow_call: {}
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
lint:
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 15
14+
steps:
15+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
16+
17+
- name: Install build deps
18+
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev cmake
19+
20+
- name: Install LLVM 20
21+
run: |
22+
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
23+
echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" | sudo tee /etc/apt/sources.list.d/llvm-20.list
24+
sudo apt-get update
25+
sudo apt-get install -y clang-format-20
26+
27+
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
28+
id: cppcheck-cache
29+
with:
30+
path: /opt/cppcheck
31+
key: cppcheck-2.20.0-ubuntu-amd64
32+
33+
- name: Build cppcheck 2.20.0
34+
if: steps.cppcheck-cache.outputs.cache-hit != 'true'
35+
run: |
36+
git clone --depth 1 --branch 2.20.0 https://github.com/danmar/cppcheck.git /tmp/cppcheck
37+
cmake -S /tmp/cppcheck -B /tmp/cppcheck/build -DCMAKE_BUILD_TYPE=Release -DHAVE_RULES=OFF -DCMAKE_INSTALL_PREFIX=/opt/cppcheck
38+
cmake --build /tmp/cppcheck/build -j$(nproc)
39+
cmake --install /tmp/cppcheck/build
40+
41+
- name: Add cppcheck to PATH
42+
run: echo "/opt/cppcheck/bin" >> "$GITHUB_PATH"
43+
44+
- name: Lint
45+
run: scripts/lint.sh CLANG_FORMAT=clang-format-20
46+
47+
security-static:
48+
runs-on: ubuntu-latest
49+
timeout-minutes: 5
50+
steps:
51+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
52+
- name: "Layer 1: Static allow-list audit"
53+
run: scripts/security-audit.sh
54+
- name: "Layer 6: UI security audit"
55+
run: scripts/security-ui.sh
56+
- name: "Layer 8: Vendored dependency integrity"
57+
run: scripts/security-vendored.sh
58+
59+
codeql-gate:
60+
runs-on: ubuntu-latest
61+
timeout-minutes: 50
62+
steps:
63+
- name: Wait for CodeQL on current commit (max 45 min)
64+
env:
65+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66+
run: |
67+
CURRENT_SHA="${{ github.sha }}"
68+
echo "Waiting for CodeQL to complete on $CURRENT_SHA..."
69+
for attempt in $(seq 1 90); do
70+
LATEST=$(gh api repos/${{ github.repository }}/actions/workflows/codeql.yml/runs?per_page=5 \
71+
--jq '.workflow_runs[] | select(.head_sha == "'"$CURRENT_SHA"'") | "\(.conclusion) \(.status)"' 2>/dev/null | head -1 || echo "")
72+
if [ -z "$LATEST" ]; then
73+
echo " $attempt/90: no run yet..."; sleep 30; continue
74+
fi
75+
CONCLUSION=$(echo "$LATEST" | cut -d' ' -f1)
76+
STATUS=$(echo "$LATEST" | cut -d' ' -f2)
77+
if [ "$STATUS" = "completed" ] && [ "$CONCLUSION" = "success" ]; then
78+
echo "=== CodeQL passed ==="; exit 0
79+
elif [ "$STATUS" = "completed" ]; then
80+
echo "BLOCKED: CodeQL $CONCLUSION"; exit 1
81+
fi
82+
echo " $attempt/90: $STATUS..."; sleep 30
83+
done
84+
echo "BLOCKED: CodeQL timeout"; exit 1
85+
86+
- name: Check for open code scanning alerts
87+
env:
88+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89+
run: |
90+
echo "Waiting 60s for alert API to settle..."
91+
sleep 60
92+
ALERTS=$(gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' --jq 'length' 2>/dev/null || echo "0")
93+
sleep 15
94+
ALERTS2=$(gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' --jq 'length' 2>/dev/null || echo "0")
95+
[ "$ALERTS" -lt "$ALERTS2" ] && ALERTS=$ALERTS2
96+
if [ "$ALERTS" -gt 0 ]; then
97+
echo "BLOCKED: $ALERTS open alert(s)"
98+
gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' \
99+
--jq '.[] | " #\(.number) [\(.rule.security_severity_level // .rule.severity)] \(.rule.id) — \(.most_recent_instance.location.path):\(.most_recent_instance.location.start_line)"' 2>/dev/null || true
100+
exit 1
101+
fi
102+
echo "=== CodeQL gate passed (0 alerts) ==="

0 commit comments

Comments
 (0)