Skip to content

🔄 CI/CD với GitHub Actions Production

Automate everything — Build, Test, và Deploy C++ projects trên mọi platform với GitHub Actions.

Tại sao CI/CD cho C++?

┌─────────────────────────────────────────────────────────────────────────┐
│                    CI/CD FOR C++ CODEBASES                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Không có CI:                                                          │
│   ─────────────                                                         │
│   • "Works on my machine" syndrome                                      │
│   • Bugs slip vào main branch                                           │
│   • Release = 3-day manual process                                      │
│   • Platform-specific bugs phát hiện bởi customers                      │
│                                                                         │
│   Có CI/CD:                                                             │
│   ──────────                                                            │
│   • Every PR tested on Ubuntu + Windows + macOS                         │
│   • Sanitizers catch memory bugs trước merge                            │
│   • Auto-release với version tagging                                    │
│   • Binary artifacts for each platform                                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Basic CMake Workflow

Tạo file .github/workflows/cmake.yml:

yaml
# .github/workflows/cmake.yml
name: CMake Build

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Configure CMake
      run: cmake -B build -DCMAKE_BUILD_TYPE=Release

    - name: Build
      run: cmake --build build --parallel

    - name: Test
      working-directory: build
      run: ctest --output-on-failure

Matrix Build — Cross-Platform

yaml
# .github/workflows/cmake-matrix.yml
name: CMake Matrix Build

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    name: ${{ matrix.os }} - ${{ matrix.compiler }}
    runs-on: ${{ matrix.os }}
    
    strategy:
      fail-fast: false  # Không cancel jobs khác khi 1 job fail
      matrix:
        include:
          # Ubuntu với GCC
          - os: ubuntu-latest
            compiler: gcc
            cc: gcc-12
            cxx: g++-12
            
          # Ubuntu với Clang
          - os: ubuntu-latest
            compiler: clang
            cc: clang-15
            cxx: clang++-15
            
          # Windows với MSVC
          - os: windows-latest
            compiler: msvc
            cc: cl
            cxx: cl
            
          # macOS với Apple Clang
          - os: macos-latest
            compiler: apple-clang
            cc: clang
            cxx: clang++

    steps:
    - name: Checkout
      uses: actions/checkout@v4

    # Linux: Install compilers
    - name: Install GCC 12 (Ubuntu)
      if: matrix.os == 'ubuntu-latest' && matrix.compiler == 'gcc'
      run: |
        sudo apt-get update
        sudo apt-get install -y gcc-12 g++-12

    - name: Install Clang 15 (Ubuntu)
      if: matrix.os == 'ubuntu-latest' && matrix.compiler == 'clang'
      run: |
        wget https://apt.llvm.org/llvm.sh
        chmod +x llvm.sh
        sudo ./llvm.sh 15

    # Windows: Setup MSVC
    - name: Setup MSVC (Windows)
      if: matrix.os == 'windows-latest'
      uses: ilammy/msvc-dev-cmd@v1

    # Configure với Ninja (cross-platform)
    - name: Install Ninja
      uses: seanmiddleditch/gha-setup-ninja@v4

    - name: Configure CMake
      env:
        CC: ${{ matrix.cc }}
        CXX: ${{ matrix.cxx }}
      run: >
        cmake -B build -G Ninja
        -DCMAKE_BUILD_TYPE=Release
        -DBUILD_TESTS=ON

    - name: Build
      run: cmake --build build --parallel

    - name: Test
      working-directory: build
      run: ctest --output-on-failure --parallel 4

    # Upload artifacts
    - name: Upload binary
      uses: actions/upload-artifact@v4
      with:
        name: binary-${{ matrix.os }}-${{ matrix.compiler }}
        path: |
          build/bin/*
          build/*.exe

Sanitizer CI Jobs

yaml
# .github/workflows/sanitizers.yml
name: Sanitizer Checks

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  # AddressSanitizer
  asan:
    name: AddressSanitizer
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Install dependencies
      run: |
        sudo apt-get update
        sudo apt-get install -y ninja-build
    
    - name: Configure with ASan
      env:
        CC: clang-15
        CXX: clang++-15
      run: >
        cmake -B build -G Ninja
        -DCMAKE_BUILD_TYPE=Debug
        -DENABLE_ASAN=ON
        -DBUILD_TESTS=ON
    
    - name: Build
      run: cmake --build build
    
    - name: Run tests with ASan
      env:
        ASAN_OPTIONS: detect_leaks=1:abort_on_error=1
      run: |
        cd build && ctest --output-on-failure

  # UndefinedBehaviorSanitizer
  ubsan:
    name: UndefinedBehaviorSanitizer
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Configure with UBSan
      env:
        CC: clang-15
        CXX: clang++-15
      run: >
        cmake -B build -G Ninja
        -DCMAKE_BUILD_TYPE=Debug
        -DENABLE_UBSAN=ON
        -DBUILD_TESTS=ON
    
    - name: Build
      run: cmake --build build
    
    - name: Run tests with UBSan
      env:
        UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
      run: |
        cd build && ctest --output-on-failure

  # ThreadSanitizer
  tsan:
    name: ThreadSanitizer
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Configure with TSan
      env:
        CC: clang-15
        CXX: clang++-15
      run: >
        cmake -B build -G Ninja
        -DCMAKE_BUILD_TYPE=Debug
        -DENABLE_TSAN=ON
        -DBUILD_TESTS=ON
    
    - name: Build
      run: cmake --build build
    
    - name: Run tests with TSan
      env:
        TSAN_OPTIONS: halt_on_error=1
      run: |
        cd build && ctest --output-on-failure

Static Analysis in CI

yaml
# .github/workflows/static-analysis.yml
name: Static Analysis

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  clang-tidy:
    name: clang-tidy
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Install clang-tidy
      run: |
        sudo apt-get update
        sudo apt-get install -y clang-tidy-15
    
    - name: Configure (generate compile_commands.json)
      run: |
        cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
    
    - name: Run clang-tidy
      run: |
        find src include -name '*.cpp' -o -name '*.hpp' | \
        xargs clang-tidy-15 \
          -p build \
          --warnings-as-errors='*' \
          --header-filter='include/.*'

  cppcheck:
    name: cppcheck
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Install cppcheck
      run: sudo apt-get install -y cppcheck
    
    - name: Run cppcheck
      run: |
        cppcheck \
          --enable=warning,performance,portability \
          --error-exitcode=1 \
          --suppress=missingIncludeSystem \
          --inline-suppr \
          -I include \
          src/

Conan Integration

yaml
# .github/workflows/cmake-conan.yml
name: CMake + Conan

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install Conan
      run: |
        pip install conan
        conan profile detect --force
    
    - name: Install Ninja
      uses: seanmiddleditch/gha-setup-ninja@v4
    
    - name: Install dependencies
      run: |
        conan install . \
          --output-folder=build \
          --build=missing \
          -s build_type=Release
    
    - name: Configure CMake
      run: |
        cmake -B build -G Ninja \
          --toolchain build/conan_toolchain.cmake \
          -DCMAKE_BUILD_TYPE=Release
    
    - name: Build
      run: cmake --build build
    
    - name: Test
      run: cd build && ctest

Vcpkg Integration

yaml
# .github/workflows/cmake-vcpkg.yml
name: CMake + Vcpkg

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup vcpkg
      uses: lukka/run-vcpkg@v11
      with:
        vcpkgGitCommitId: 'a1234567...'  # Pin version
    
    - name: Configure CMake
      run: |
        cmake -B build \
          -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake \
          -DCMAKE_BUILD_TYPE=Release
    
    - name: Build
      run: cmake --build build --config Release
    
    - name: Test
      run: ctest --test-dir build --config Release

CCache in CI

yaml
# .github/workflows/cmake-cached.yml
name: CMake Build (Cached)

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Install ccache
      run: sudo apt-get install -y ccache
    
    # Cache ccache directory
    - name: Cache ccache
      uses: actions/cache@v4
      with:
        path: ~/.cache/ccache
        key: ccache-${{ runner.os }}-${{ hashFiles('**/*.cpp', '**/*.hpp') }}
        restore-keys: |
          ccache-${{ runner.os }}-
    
    - name: Configure ccache
      run: |
        ccache --set-config=max_size=500M
        ccache --zero-stats
    
    - name: Configure CMake
      run: |
        cmake -B build -G Ninja \
          -DCMAKE_BUILD_TYPE=Release \
          -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
    
    - name: Build
      run: cmake --build build
    
    - name: Show ccache stats
      run: ccache --show-stats
    
    - name: Test
      run: cd build && ctest

Release Workflow

yaml
# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'  # Trigger on version tags

jobs:
  build:
    runs-on: ${{ matrix.os }}
    
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            artifact_name: myapp-linux
            asset_name: myapp-linux-amd64
          - os: windows-latest
            artifact_name: myapp-windows
            asset_name: myapp-windows-amd64.exe
          - os: macos-latest
            artifact_name: myapp-macos
            asset_name: myapp-macos-arm64
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Configure
      run: |
        cmake -B build \
          -DCMAKE_BUILD_TYPE=Release \
          -DENABLE_LTO=ON
    
    - name: Build
      run: cmake --build build --config Release
    
    - name: Upload artifact
      uses: actions/upload-artifact@v4
      with:
        name: ${{ matrix.artifact_name }}
        path: build/bin/

  release:
    needs: build
    runs-on: ubuntu-latest
    
    steps:
    - name: Download all artifacts
      uses: actions/download-artifact@v4
    
    - name: Create Release
      uses: softprops/action-gh-release@v1
      with:
        files: |
          myapp-linux/*
          myapp-windows/*
          myapp-macos/*
        generate_release_notes: true

Complete Production Pipeline

yaml
# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  CMAKE_BUILD_PARALLEL_LEVEL: 4

jobs:
  # ====================================
  # Stage 1: Format & Lint (Fast fail)
  # ====================================
  lint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Check clang-format
      run: |
        find src include -name '*.cpp' -o -name '*.hpp' | \
        xargs clang-format --dry-run --Werror

  # ====================================
  # Stage 2: Build Matrix
  # ====================================
  build:
    needs: lint
    runs-on: ${{ matrix.os }}
    
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        build_type: [Debug, Release]
    
    steps:
    - uses: actions/checkout@v4
    - uses: seanmiddleditch/gha-setup-ninja@v4
    
    - name: Configure
      run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
    
    - name: Build
      run: cmake --build build
    
    - name: Test
      run: ctest --test-dir build --output-on-failure

  # ====================================
  # Stage 3: Sanitizers (Linux only)
  # ====================================
  sanitizers:
    needs: lint
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        sanitizer: [asan, ubsan, tsan]
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Build with ${{ matrix.sanitizer }}
      env:
        CC: clang-15
        CXX: clang++-15
      run: |
        cmake -B build -G Ninja \
          -DCMAKE_BUILD_TYPE=Debug \
          -DENABLE_${{ matrix.sanitizer | upper }}=ON
        cmake --build build
    
    - name: Test
      run: ctest --test-dir build --output-on-failure

  # ====================================
  # Stage 4: Code Coverage
  # ====================================
  coverage:
    needs: build
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Install lcov
      run: sudo apt-get install -y lcov
    
    - name: Build with coverage
      run: |
        cmake -B build -DCMAKE_BUILD_TYPE=Debug \
          -DCMAKE_CXX_FLAGS="--coverage"
        cmake --build build
    
    - name: Run tests
      run: ctest --test-dir build
    
    - name: Generate coverage report
      run: |
        lcov --capture --directory build --output-file coverage.info
        lcov --remove coverage.info '/usr/*' --output-file coverage.info
    
    - name: Upload to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: coverage.info

Pro Tips

💡 CI OPTIMIZATION TIPS

  1. Cache aggressively: ccache, vcpkg binary cache, Conan cache
  2. Fail fast: Lint/format checks first (< 30 seconds)
  3. Parallel matrix: Run OS builds concurrently
  4. Self-hosted runners: Faster builds cho large projects
  5. Conditional jobs: Sanitizers chỉ on PRs, không on every push

🎉 Module Hoàn thành!

Bạn đã học toàn bộ về Build Systems & CI/CD:

  1. ❌ Tại sao Makefile thất bại
  2. 🚀 Modern CMake target-based
  3. ⚙️ Sanitizers & Static Analysis
  4. 📦 Conan & Vcpkg
  5. ⚡ Ninja & CCache
  6. 🔄 GitHub Actions Matrix Build

Bước tiếp theo: Quay lại C++ Roadmap để tiếp tục học các modules khác!