2.3: CI/CD Pipeline Setup
What You'll Learn
By the end of this chapter, you'll be able to:
- Build a CI/CD pipeline that generates ASPICE work products automatically
- Set up quality gates that catch problems before they become assessment findings
- Configure artifact retention for audit trails
- Automate release processes with proper evidence packaging
Introduction
Here's a scenario I've witnessed more than once: it's 2 AM before an ASPICE assessment, and the team is frantically generating test reports, compiling coverage data, and trying to prove that yes, they did run MISRA checks on that code from three months ago.
There's a better way.
A well-designed CI/CD pipeline doesn't just build and test your code—it generates ASPICE evidence automatically, enforces quality gates in real-time, and maintains a complete audit trail. When the assessor asks for your SWE.4 test reports, you don't scramble. You point them at your artifact store.
Let's build that pipeline.
The ASPICE-Compliant CI/CD Architecture
The following diagram shows the complete ASPICE-compliant CI/CD pipeline architecture, with quality gates enforcing build, test, analysis, and deployment standards at each stage.
The Quality Gates (Your Safety Net):
- ✅ Build succeeds
- ✅ All unit tests pass
- ✅ Coverage ≥ 80% (or your ASIL-specific target)
- ✅ Zero MISRA mandatory violations
- ✅ SonarQube quality gate passed
- ✅ Security scan clean
If any gate fails, the PR doesn't merge. Period.
The Complete GitHub Actions Pipeline
This isn't a toy example—it's a production-ready pipeline you can copy and adapt. Here's the whole thing:
.github/workflows/aspice-ci.yml
name: ASPICE CI/CD Pipeline
on:
push:
branches: [main, develop, 'release/**']
pull_request:
branches: [main, develop]
schedule:
- cron: '0 2 * * *' # Nightly full analysis
env:
BUILD_TYPE: Release
TARGET_PLATFORM: arm-none-eabi
jobs:
# ============================================================================
# STAGE 1: BUILD
# ============================================================================
build:
name: Build (SWE.3 BP2)
runs-on: ubuntu-latest
container:
image: gcc-arm-none-eabi:latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for traceability
- name: Install dependencies
run: |
apt-get update
apt-get install -y cmake ninja-build
- name: Configure CMake
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DCMAKE_TOOLCHAIN_FILE=cmake/arm-toolchain.cmake \
-DENABLE_COVERAGE=ON \
-DENABLE_MISRA=ON
- name: Build
run: cmake --build build --parallel 4
- name: Generate build artifacts
run: |
mkdir -p artifacts
cp build/door_lock_ctrl.elf artifacts/
cp build/door_lock_ctrl.hex artifacts/
cp build/door_lock_ctrl.map artifacts/
# Generate build report (ASPICE work product)
echo "# Build Report" > artifacts/build_report.md
echo "Date: $(date)" >> artifacts/build_report.md
echo "Commit: $GITHUB_SHA" >> artifacts/build_report.md
echo "Branch: $GITHUB_REF_NAME" >> artifacts/build_report.md
arm-none-eabi-size build/door_lock_ctrl.elf >> artifacts/build_report.md
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: artifacts/
retention-days: 90 # Keep for ASPICE audit trail
# ============================================================================
# STAGE 2: UNIT TESTS (SWE.4)
# ============================================================================
unit-tests:
name: Unit Tests (SWE.4 BP3)
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install test framework
run: |
sudo apt-get update
sudo apt-get install -y ruby
gem install ceedling
- name: Run unit tests
run: |
cd tests/unit
ceedling test:all
ceedling gcov:all
- name: Generate coverage report
run: |
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file coverage_filtered.info
genhtml coverage_filtered.info --output-directory coverage_html
- name: Check coverage threshold
run: |
COVERAGE=$(lcov --summary coverage_filtered.info | grep lines | awk '{print $2}' | sed 's/%//')
echo "Coverage: $COVERAGE%"
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "❌ ERROR: Coverage $COVERAGE% below 80% threshold"
exit 1
fi
echo "✅ Coverage threshold met!"
- name: Upload coverage report
uses: actions/upload-artifact@v3
with:
name: coverage-report
path: coverage_html/
- name: Upload to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage_filtered.info
flags: unittests
name: codecov-umbrella
# ============================================================================
# STAGE 3: STATIC ANALYSIS (SWE.3 BP7)
# ============================================================================
static-analysis:
name: Static Analysis (MISRA C)
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Cppcheck with MISRA addon
run: |
cppcheck --addon=misra \
--enable=all \
--std=c11 \
--platform=unix32 \
--suppress=missingIncludeSystem \
--xml \
--xml-version=2 \
src/ 2> cppcheck-misra.xml
- name: Check MISRA violations
run: |
# Count mandatory rule violations
MANDATORY_VIOLATIONS=$(grep -c 'misra-c2012.*mandatory' cppcheck-misra.xml || true)
if [ "$MANDATORY_VIOLATIONS" -gt 0 ]; then
echo "❌ ERROR: $MANDATORY_VIOLATIONS MISRA mandatory violations found"
exit 1
fi
echo "✅ MISRA mandatory rules: PASS"
- name: Upload MISRA report
uses: actions/upload-artifact@v3
with:
name: misra-report
path: cppcheck-misra.xml
sonarqube:
name: SonarQube Analysis
needs: [build, unit-tests]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones disable blame
- name: Download coverage
uses: actions/download-artifact@v3
with:
name: coverage-report
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
- name: SonarQube Quality Gate
uses: sonarsource/sonarqube-quality-gate-action@master
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# ============================================================================
# STAGE 4: INTEGRATION TESTS (SWE.5)
# ============================================================================
integration-tests:
name: Integration Tests (SWE.5 BP3)
needs: [build, unit-tests]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install test dependencies
run: |
pip install pytest pytest-html robotframework cantools
- name: Run integration tests
run: |
cd tests/integration
pytest --html=report.html --self-contained-html
- name: Upload integration test report
uses: actions/upload-artifact@v3
with:
name: integration-test-report
path: tests/integration/report.html
# ============================================================================
# STAGE 5: SECURITY SCAN
# ============================================================================
security-scan:
name: Security Vulnerability Scan
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Bandit (Python)
run: |
pip install bandit
bandit -r tools/ scripts/ -f json -o bandit-report.json || true
- name: Run Trivy (Container/Dependencies)
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload security reports
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: trivy-results.sarif
# ============================================================================
# STAGE 6: TRACEABILITY GENERATION (SUP.10 BP5)
# ============================================================================
traceability:
name: Generate Traceability Matrix
needs: [build, unit-tests]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Need full history
- name: Generate traceability matrix
run: |
python tools/scripts/generate_traceability.py \
--since v2.0.0 \
--output traceability_matrix.csv
- name: Upload traceability matrix
uses: actions/upload-artifact@v3
with:
name: traceability-matrix
path: traceability_matrix.csv
# ============================================================================
# STAGE 7: RELEASE (SUP.8 BP5)
# ============================================================================
release:
name: Create Release
needs: [build, unit-tests, integration-tests, static-analysis, security-scan]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v3
- name: Generate release notes
id: changelog
run: |
echo "## Release ${{ github.ref_name }}" > RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "### Build Artifacts" >> RELEASE_NOTES.md
echo "- door_lock_ctrl.hex - Production firmware" >> RELEASE_NOTES.md
echo "- door_lock_ctrl.elf - Debug symbols" >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "### Quality Metrics" >> RELEASE_NOTES.md
echo "- Unit Test Coverage: $(grep 'lines' coverage_filtered.info)" >> RELEASE_NOTES.md
echo "- MISRA Compliance: $(grep -c 'misra' cppcheck-misra.xml) violations" >> RELEASE_NOTES.md
echo "" >> RELEASE_NOTES.md
echo "### Traceability" >> RELEASE_NOTES.md
echo "See attached traceability_matrix.csv" >> RELEASE_NOTES.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
body_path: RELEASE_NOTES.md
files: |
build-artifacts/door_lock_ctrl.hex
build-artifacts/door_lock_ctrl.elf
build-artifacts/build_report.md
coverage-report/index.html
misra-report/cppcheck-misra.xml
traceability-matrix/traceability_matrix.csv
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
What This Pipeline Does For You
The ASPICE value of each pipeline stage breaks down as follows:
| Pipeline Stage | ASPICE Process | Evidence Generated |
|---|---|---|
| Build | SWE.3 BP2 | Build report, .elf, .hex, .map |
| Unit Tests | SWE.4 BP3 | Test logs, coverage report |
| Static Analysis | SWE.3 BP7 | MISRA violation report |
| Integration Tests | SWE.5 BP3 | Integration test report |
| Security Scan | SUP.1 | Security vulnerability report |
| Traceability | SUP.10 BP5 | Traceability matrix |
| Release | SUP.8 BP5 | Release notes, artifact package |
Every push generates evidence. Every release packages it neatly.
Summary
Your CI/CD pipeline is now an ASPICE evidence factory:
- Automated Quality Gates: Build, test, coverage, MISRA, security—all enforced
- Work Product Generation: Reports generated automatically, not manually
- Fail Fast: Bad code gets stopped at the PR, not discovered at assessment
- Audit Trail: 90-day artifact retention for complete traceability
- Release Automation: Tag → evidence package, ready for auditors
Your Best Practices Checklist:
- 🏃 Run lightweight checks first (build, unit tests) before expensive ones
- ⚡ Parallelize independent jobs for faster feedback
- 💾 Cache dependencies to speed up builds
- 🚫 Enforce quality gates—don't let bad code merge, ever
- 📦 Retain artifacts for at least 90 days (check your ASPICE retention requirements)
With your pipeline in place, let's talk about choosing the right tools to feed into it.