-
-
Notifications
You must be signed in to change notification settings - Fork 400
395 lines (332 loc) · 16.4 KB
/
release.yml
File metadata and controls
395 lines (332 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
395
name: Release
on:
push:
tags:
- 'v*'
branches:
- test-release # Test on specific branch
workflow_dispatch: # Allow manual testing
jobs:
# 🚧 MANDATORY RELEASE GATES - ALL MUST PASS OR ENTIRE RELEASE STOPS
preflight:
name: "🚧 Release Gates - MANDATORY VALIDATION"
runs-on: ubuntu-latest
env:
CARGO_NET_GIT_FETCH_WITH_CLI: true
outputs:
should_publish: ${{ steps.gates.outputs.should_publish }}
steps:
- uses: actions/checkout@v4
- name: Configure Git for private dependencies
run: |
git config --global url."https://${{ secrets.VISION_PRIVATE_TOKEN }}@github.com/".insteadOf "https://github.com/"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: "🚧 GATE 1/7: Core Build Validation"
run: |
echo "::group::Gate 1: Core Build"
cargo build --release --no-default-features --features huggingface
echo "✅ Core build successful"
echo "::endgroup::"
- name: "🚧 GATE 2/7: CUDA Build Validation (No Timeout - Can Take Hours)"
run: |
echo "::group::Gate 2: CUDA Build"
echo "⏳ CUDA compilation can take 19+ hours - letting it run to natural completion"
# Check if CUDA Toolkit is available first
if command -v nvcc >/dev/null 2>&1; then
echo "✅ CUDA Toolkit found, attempting CUDA build..."
cargo build --release --no-default-features --features llama-cuda
echo "✅ CUDA build completed successfully"
else
echo "⚠️ CUDA Toolkit not found on runner (nvcc not available)"
echo "🔄 Validating CPU-only llama build instead..."
# Validate that CPU-only llama build works
cargo build --release --no-default-features --features llama
echo "✅ CPU-only llama build completed successfully"
echo "📝 Note: CUDA validation skipped - this is expected on standard GitHub runners"
fi
echo "::endgroup::"
- name: "🚧 GATE 3/7: Template Packaging Validation (Issue #60 Protection)"
run: |
echo "::group::Gate 3: Template Packaging"
# Check for Docker templates with OS-agnostic path handling
# Use --allow-dirty to handle uncommitted Cargo.lock changes from dependency resolution
if cargo package --allow-dirty --list | grep -E "(^|[/\\\\])templates[/\\\\]docker[/\\\\]Dockerfile$" > /dev/null; then
echo "✅ Docker templates properly included in package"
else
echo "❌ Required Docker template missing from package - Issue #60 regression!"
echo "Package contents:"
cargo package --allow-dirty --list | grep -i docker || echo "No docker files found"
exit 1
fi
echo "::endgroup::"
- name: "🚧 GATE 4/7: Binary Size Constitutional Limit (20MB)"
run: |
echo "::group::Gate 4: Binary Size"
size=$(stat -c%s target/release/shimmy 2>/dev/null || echo "0")
max_size=$((20 * 1024 * 1024))
if [ "$size" -gt "$max_size" ]; then
echo "❌ Binary size ${size} exceeds constitutional limit of ${max_size} bytes"
exit 1
fi
echo "✅ Binary size ${size} bytes within constitutional limit"
echo "::endgroup::"
- name: "🚧 GATE 5/7: Test Suite Validation"
run: |
echo "::group::Gate 5: Tests"
# Run tests that don't require model files (exclude integration tests needing .gguf files)
cargo test --lib --no-default-features --features huggingface -- --skip test_generate_handler --skip test_chat_completions
echo "✅ Core tests passing"
echo "::endgroup::"
- name: "🚧 GATE 5.5/7: Issue Regression Tests"
run: |
echo "::group::Gate 5.5: Issue Regression Prevention"
echo "🔄 Running issue-specific regression tests to prevent user-reported bug regressions..."
# Test Issue #111 - GPU metrics endpoint
cargo test --test regression_tests test_issue_111_gpu_metrics_endpoint --no-default-features --features huggingface
echo "✅ Issue #111 (GPU metrics): Regression test passed"
# Test Issue #112 - SafeTensors engine selection
cargo test --test regression_tests test_issue_112_safetensors_engine_selection --no-default-features --features huggingface
echo "✅ Issue #112 (SafeTensors): Regression test passed"
# Test Issue #113 - OpenAI API frontend compatibility
cargo test --test regression_tests test_issue_113_openai_api_frontend_compatibility --no-default-features --features huggingface
echo "✅ Issue #113 (OpenAI compatibility): Regression test passed"
# Test Issue #114 - MLX distribution features
cargo test --test regression_tests test_issue_114_mlx_distribution_features --no-default-features --features huggingface
echo "✅ Issue #114 (MLX distribution): Regression test passed"
# Test Issue #13 - Qwen model template detection
cargo test --test regression_tests test_qwen_model_template_detection --no-default-features --features huggingface
echo "✅ Issue #13 (Qwen templates): Regression test passed"
# Test Issue #12 - Custom model directories
cargo test --test regression_tests test_custom_model_directory_environment_variables --no-default-features --features huggingface
echo "✅ Issue #12 (Custom directories): Regression test passed"
echo "✅ All issue regression tests passed - no user-reported bug regressions detected"
echo "::endgroup::"
- name: "🚧 GATE 6/7: Documentation Validation"
run: |
echo "::group::Gate 6: Documentation"
# Check if CUDA Toolkit is available for documentation build
if command -v nvcc >/dev/null 2>&1; then
echo "✅ CUDA Toolkit found, building docs with all features..."
cargo doc --no-deps --all-features
echo "✅ Documentation with all features built successfully"
else
echo "⚠️ CUDA Toolkit not found on runner (nvcc not available)"
echo "🔄 Building documentation without CUDA features..."
# Build docs without CUDA features to avoid build failures
cargo doc --no-deps --features "huggingface,llama,mlx"
echo "✅ Documentation built successfully (CUDA features excluded)"
echo "📝 Note: CUDA documentation skipped - this is expected on standard GitHub runners"
fi
echo "::endgroup::"
- name: "🚧 GATE 7/7: Crates.io Publication Validation"
run: |
echo "::group::Gate 7: Crates.io Validation"
echo "🧪 Testing crates.io publication readiness..."
# Handle uncommitted Cargo.lock (common issue)
if git status --porcelain | grep -q "Cargo.lock"; then
echo "⚠️ Cargo.lock has uncommitted changes (expected from dependency resolution)"
echo "🔄 Using --allow-dirty flag for crates.io validation"
DIRTY_FLAG="--allow-dirty"
else
echo "✅ No uncommitted changes detected"
DIRTY_FLAG=""
fi
# Dry-run validation to catch issues before real release
echo "🔍 Running cargo publish --dry-run..."
if cargo publish --dry-run $DIRTY_FLAG; then
echo "✅ Crates.io dry-run validation PASSED"
echo "📦 Package builds successfully and is ready for publication"
else
echo "❌ Crates.io dry-run validation FAILED"
echo "🚫 Blocking release until crates.io issues are resolved"
exit 1
fi
echo "::endgroup::"
- name: "🎯 RELEASE GATES SUMMARY"
id: gates
run: |
echo "🎉 ALL 8 MANDATORY GATES PASSED!"
echo "✅ Gate 1: Core Build"
echo "✅ Gate 2: CUDA Timeout Protection (Issue #59)"
echo "✅ Gate 3: Template Packaging (Issue #60)"
echo "✅ Gate 4: Binary Size Constitutional Limit"
echo "✅ Gate 5: Test Suite"
echo "✅ Gate 5.5: Issue Regression Prevention"
echo "✅ Gate 6: Documentation"
echo "✅ Gate 7: Crates.io Publication Validation"
echo "should_publish=true" >> $GITHUB_OUTPUT
# 🚀 REUSE GATE BINARIES - NO RECOMPILATION WASTE
- name: "Upload gate binaries as artifacts"
uses: actions/upload-artifact@v4
with:
name: gate-built-binary
path: target/release/shimmy
# 🚀 LINUX BINARY - REUSE FROM GATES (NO RECOMPILATION)
reuse-gate-binary:
needs: preflight
if: needs.preflight.outputs.should_publish == 'true'
runs-on: ubuntu-latest
steps:
- name: Download gate-built binary
uses: actions/download-artifact@v4
with:
name: gate-built-binary
path: ./
- name: Upload as Linux x86_64 artifact
uses: actions/upload-artifact@v4
with:
name: shimmy-linux-x86_64
path: shimmy
# 🚀 CROSS-PLATFORM BUILDS (ONLY WHAT GATES DIDN'T BUILD)
build:
needs: preflight
if: needs.preflight.outputs.should_publish == 'true'
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
binary-name: shimmy
artifact-name: shimmy-linux-x86_64-universal
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
binary-name: shimmy
artifact-name: shimmy-linux-aarch64
use-cross: true
- os: windows-latest
target: x86_64-pc-windows-msvc
binary-name: shimmy.exe
artifact-name: shimmy-windows-x86_64.exe
- os: macos-latest
target: x86_64-apple-darwin
binary-name: shimmy
artifact-name: shimmy-macos-intel
- os: macos-latest
target: aarch64-apple-darwin
binary-name: shimmy
artifact-name: shimmy-macos-arm64
runs-on: ${{ matrix.os }}
env:
CARGO_NET_GIT_FETCH_WITH_CLI: true
steps:
- uses: actions/checkout@v4
- name: Configure Git for private dependencies
run: |
git config --global url."https://${{ secrets.VISION_PRIVATE_TOKEN }}@github.com/".insteadOf "https://github.com/"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install musl-tools (for musl builds)
if: matrix.target == 'x86_64-unknown-linux-musl'
run: |
sudo apt-get update
sudo apt-get install -y musl-tools musl-dev
- name: Install cross for ARM64 cross-compilation
if: matrix.use-cross == true
run: |
cargo install cross --git https://github.com/cross-rs/cross
- name: Build binary
shell: bash
env:
CROSS_NO_WARNINGS: 1
MACOSX_DEPLOYMENT_TARGET: "12.0"
run: |
# Determine features based on platform (Issue #129: GPU support in precompiled binaries)
# Vision feature included for licensed vision capabilities
FEATURES="huggingface,llama,vision"
if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-musl" ]]; then
# musl builds: use huggingface-only to avoid llama.cpp C++ compilation issues
FEATURES="huggingface,vision"
elif [[ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]]; then
# ARM64 Linux: CPU-only features (Issue #131)
FEATURES="huggingface,llama,vision"
elif [[ "${{ matrix.os }}" == "windows-latest" ]]; then
# Windows: Add Vulkan support for GPU acceleration
FEATURES="huggingface,llama,llama-vulkan,vision"
elif [[ "${{ matrix.os }}" == "macos-latest" ]]; then
# macOS: Add MLX support for Apple Silicon GPU acceleration
FEATURES="huggingface,llama,mlx,vision"
fi
echo "Building with features: $FEATURES"
# Use cross for ARM64 cross-compilation, cargo for native builds
if [[ "${{ matrix.use-cross }}" == "true" ]]; then
cross build --release --target ${{ matrix.target }} --no-default-features --features "$FEATURES"
else
cargo build --release --target ${{ matrix.target }} --no-default-features --features "$FEATURES"
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact-name }}
path: target/${{ matrix.target }}/release/${{ matrix.binary-name }}
release:
needs: [preflight, reuse-gate-binary, build]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/') && needs.preflight.outputs.should_publish == 'true'
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: Prepare release files
run: |
mkdir -p release-files
# Copy and rename artifacts
cp artifacts/shimmy-linux-x86_64/shimmy release-files/shimmy-linux-x86_64
cp artifacts/shimmy-linux-x86_64/shimmy release-files/shimmy # Generic name
cp artifacts/shimmy-linux-aarch64/shimmy release-files/shimmy-linux-aarch64
cp artifacts/shimmy-windows-x86_64.exe/shimmy.exe release-files/shimmy-windows-x86_64.exe
cp artifacts/shimmy-windows-x86_64.exe/shimmy.exe release-files/shimmy.exe # Generic name
cp artifacts/shimmy-macos-intel/shimmy release-files/shimmy-macos-intel
cp artifacts/shimmy-macos-arm64/shimmy release-files/shimmy-macos-arm64
# List what we're releasing
echo "Release files:"
ls -la release-files/
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create ${{ github.ref_name }} \
release-files/* \
--title "Shimmy ${{ github.ref_name }}" \
--generate-notes
- name: Install Rust for crates.io publishing
uses: dtolnay/rust-toolchain@stable
- name: "📦 Publish to crates.io"
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
echo "🚀 Publishing shimmy ${{ github.ref_name }} to crates.io..."
# Debug: print working tree state (helps diagnose Cargo.lock vs git status mismatch)
echo "🔍 git status (porcelain):"
git status --porcelain || true
echo "🔍 git diff --name-only against HEAD (if any):"
git diff --name-only || true
# When publishing we always allow dirty state for Cargo.lock differences that appear
# during build/packaging steps (this prevents transient lockfile updates from blocking publish)
echo "⚠️ Publishing with --allow-dirty to tolerate transient changes to Cargo.lock"
DIRTY_FLAG="--allow-dirty"
# Publish to crates.io (dry-run already validated in Gate 7)
cargo publish $DIRTY_FLAG || (echo "❌ cargo publish failed"; exit 1)
echo "✅ Successfully published shimmy ${{ github.ref_name }} to crates.io!"
echo "📦 Users can now install with: cargo install shimmy"
- name: "🐳 Build and Push Docker Image to GHCR"
if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "🐳 Building and pushing Docker image to GitHub Container Registry..."
# Login to GHCR
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
# Build the Docker image
docker build -f deploy/Dockerfile -t ghcr.io/${{ github.repository }}:${{ github.ref_name }} -t ghcr.io/${{ github.repository }}:latest .
# Push both tagged and latest versions
docker push ghcr.io/${{ github.repository }}:${{ github.ref_name }}
docker push ghcr.io/${{ github.repository }}:latest
echo "✅ Docker image published successfully!"
echo "🐳 Users can now run: docker run ghcr.io/${{ github.repository }}:latest"