Skip to content

Merge pull request #78 from aupadhyay/fix/metal-coregraphics-linkage #215

Merge pull request #78 from aupadhyay/fix/metal-coregraphics-linkage

Merge pull request #78 from aupadhyay/fix/metal-coregraphics-linkage #215

Workflow file for this run

name: CI
on:
push:
branches: ["**"]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
RUSTC_WRAPPER: ""
jobs:
test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- name: Cache cargo registry & build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- name: Run tests with coverage
run: cargo llvm-cov --lib --test unit --test protocol -p cake-core --json --output-path coverage.json
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.os }}-cpu
path: coverage.json
test-android:
name: Test (Android aarch64)
runs-on: ubuntu-latest
permissions:
contents: read
env:
ANDROID_NDK_VERSION: r27c
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android
components: clippy, llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- name: Set up Android NDK ${{ env.ANDROID_NDK_VERSION }}
id: setup-ndk
uses: nttld/setup-ndk@v1
with:
ndk-version: ${{ env.ANDROID_NDK_VERSION }}
add-to-path: false
- name: Cache cargo registry & build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: android-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: android-cargo-
- name: Cache cargo-ndk binary
id: cache-cargo-ndk
uses: actions/cache@v4
with:
path: ~/.cargo/bin/cargo-ndk
key: cargo-ndk-bin-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: Install cargo-ndk
if: steps.cache-cargo-ndk.outputs.cache-hit != 'true'
run: cargo install cargo-ndk --locked
- name: Build cake-mobile (Android aarch64)
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: cargo ndk -t aarch64-linux-android build -p cake-mobile
- name: Clippy (cake-core + cake-mobile, Android)
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: cargo ndk -t aarch64-linux-android clippy -p cake-core --lib --no-default-features --features "llama,qwen2,qwen3_5" -p cake-mobile -- -D warnings
- name: Run tests with coverage
run: cargo llvm-cov --lib --test unit --test protocol -p cake-core --json --output-path coverage.json
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-android-cpu
path: coverage.json
test-ios:
name: Test (iOS arm64)
runs-on: macos-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-ios
components: clippy, llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- name: Cache cargo registry & build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ios-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ios-cargo-
- name: Build cake-mobile (iOS arm64)
env:
IPHONEOS_DEPLOYMENT_TARGET: "16.0"
run: cargo build --target=aarch64-apple-ios -p cake-mobile --features metal
- name: Clippy (cake-core + cake-mobile, iOS)
env:
IPHONEOS_DEPLOYMENT_TARGET: "16.0"
run: cargo clippy --target=aarch64-apple-ios -p cake-core --lib --no-default-features --features "llama,qwen2,qwen3_5,metal" -p cake-mobile --features metal -- -D warnings
- name: Run tests with coverage (Metal)
run: cargo llvm-cov --lib --test unit --test protocol -p cake-core --features metal --json --output-path coverage.json
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-ios-metal
path: coverage.json
test-features:
name: Test (${{ matrix.name }})
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- name: linux-cuda
os: ubuntu-latest
features: cuda
cuda: "12.4.0"
compute_cap: "89"
- name: linux-flash-attn
os: ubuntu-latest
features: flash-attn
cuda: "12.4.0"
compute_cap: "89"
- name: linux-vulkan
os: ubuntu-latest
features: vulkan
cuda: ""
compute_cap: ""
- name: linux-rocm
os: ubuntu-latest
features: rocm
cuda: ""
compute_cap: ""
- name: macos-metal
os: macos-latest
features: metal
cuda: ""
compute_cap: ""
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y lld
# Vulkan SDK needed for --features vulkan
if [ "${{ matrix.features }}" = "vulkan" ]; then
sudo apt-get install -y libvulkan-dev
fi
- name: Install CUDA toolkit
if: matrix.cuda != ''
uses: Jimver/cuda-toolkit@v0.2.30
with:
cuda: ${{ matrix.cuda }}
method: local
linux-local-args: '["--toolkit"]'
log-file-suffix: '${{ matrix.name }}.txt'
- name: Create CUDA runtime stub
if: matrix.cuda != ''
run: |
STUBS_DIR="${CUDA_PATH}/lib64/stubs"
sudo ln -s "${STUBS_DIR}/libcuda.so" "${STUBS_DIR}/libcuda.so.1"
echo "LD_LIBRARY_PATH=${STUBS_DIR}:${LD_LIBRARY_PATH}" >> "$GITHUB_ENV"
- name: Cache cargo registry & build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ matrix.name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-${{ matrix.name }}-
- name: Run tests with coverage
env:
CUDA_COMPUTE_CAP: ${{ matrix.compute_cap }}
run: cargo llvm-cov --lib --test unit --test protocol -p cake-core --features "${{ matrix.features }}" --json --output-path coverage.json
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.name }}
path: coverage.json
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y lld libvulkan-dev
- name: Cache cargo registry & build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-clippy-
- name: Clippy (cake-core + cake-cli, all features)
run: cargo clippy -p cake-core --lib --tests -p cake-cli --features "vulkan,rocm" -- -D warnings
coverage-report:
name: Coverage Report
runs-on: ubuntu-latest
if: ${{ always() }}
needs: [test, test-android, test-ios, test-features]
permissions:
contents: read
pull-requests: write
steps:
- name: Download coverage artifacts
uses: actions/download-artifact@v4
with:
pattern: coverage-*
path: coverage-artifacts
- name: Generate coverage table
run: |
python3 << 'PYEOF'
import json, os, sys
artifact_dir = 'coverage-artifacts'
# NOTE: artifact names must match the upload step names in each test job.
# If matrix.os values or artifact names change, update this map accordingly.
name_map = {
'coverage-ubuntu-latest-cpu': ('Linux', 'CPU'),
'coverage-macos-latest-cpu': ('macOS', 'CPU'),
'coverage-windows-latest-cpu': ('Windows', 'CPU'),
'coverage-android-cpu': ('Linux', 'CPU (Android host)'),
'coverage-ios-metal': ('macOS', 'Metal (iOS host)'),
'coverage-linux-cuda': ('Linux', 'CUDA'),
'coverage-linux-flash-attn': ('Linux', 'Flash Attention'),
'coverage-linux-vulkan': ('Linux', 'Vulkan'),
'coverage-linux-rocm': ('Linux', 'ROCm'),
'coverage-macos-metal': ('macOS', 'Metal'),
}
rows = []
if os.path.isdir(artifact_dir):
for dirname in sorted(os.listdir(artifact_dir)):
cov_file = os.path.join(artifact_dir, dirname, 'coverage.json')
if not os.path.isfile(cov_file):
continue
try:
with open(cov_file) as f:
data = json.load(f)
totals = data['data'][0]['totals']
except (json.JSONDecodeError, KeyError, IndexError) as e:
print(f"WARNING: skipping {dirname}: {e}", file=sys.stderr)
continue
if dirname not in name_map:
print(f"WARNING: unmapped artifact '{dirname}', using raw name", file=sys.stderr)
platform, accel = name_map.get(dirname, (dirname, 'Unknown'))
rows.append((platform, accel, totals['lines'], totals['functions'], totals['regions']))
lines_out = []
lines_out.append('## 📊 Code Coverage by Platform & Acceleration\n')
if rows:
lines_out.append('| Platform | Acceleration | Line Coverage | Function Coverage | Region Coverage |')
lines_out.append('|----------|-------------|---------------|-------------------|------------------|')
for platform, accel, lines, funcs, regions in rows:
lp = f"{lines['percent']:.1f}% ({lines['covered']}/{lines['count']})"
fp = f"{funcs['percent']:.1f}% ({funcs['covered']}/{funcs['count']})"
rp = f"{regions['percent']:.1f}% ({regions['covered']}/{regions['count']})"
lines_out.append(f'| {platform} | {accel} | {lp} | {fp} | {rp} |')
else:
lines_out.append('No coverage data available.')
table = '\n'.join(lines_out) + '\n'
# Print to job log
print(table)
# Write to job summary
summary_path = os.environ.get('GITHUB_STEP_SUMMARY')
if summary_path:
with open(summary_path, 'a') as f:
f.write(table)
# Save for PR comment step
with open('coverage-table.md', 'w') as f:
f.write(table)
PYEOF
- name: Post coverage comment on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const body = fs.readFileSync('coverage-table.md', 'utf8');
const marker = '<!-- coverage-report -->';
const fullBody = marker + '\n' + body;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body.startsWith(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: fullBody,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: fullBody,
});
}