Skip to content

Incremental Builds

Overview

CCGO provides smart incremental build detection that automatically rebuilds only changed files and their dependencies. This dramatically improves rebuild times by avoiding unnecessary recompilation.

Benefits

  • Faster Rebuilds - Only recompile changed files (10-50x speedup)
  • 🎯 Smart Detection - Tracks file changes, config changes, and dependency changes
  • 🔍 Change Analysis - Shows exactly what changed since last build
  • 🚀 Zero Configuration - Works automatically, no setup required
  • 💾 Persistent State - Build state survives terminal sessions

How It Works

Build State Tracking

CCGO maintains build state for each platform and link type:

cmake_build/release/linux/
  ├── .ccgo_build_state.json    # Build state (file hashes, metadata)
  └── CMakeCache.txt              # CMake build cache

The build state tracks: - File Hashes - SHA256 checksums of all source/header files - Config Hash - CCGO.toml configuration changes - Options Hash - Build flags and options changes - CMake Cache - CMake configuration changes - Last Build Time - Timestamp of successful build

Change Detection

On each build, CCGO:

  1. Loads Previous State - Reads .ccgo_build_state.json if exists
  2. Scans Current Files - Hashes all source/header files
  3. Compares Hashes - Detects modified, added, and removed files
  4. Checks Configuration - Detects CCGO.toml or build option changes
  5. Decides Build Strategy:
  6. Incremental Build - Only changed files if possible
  7. Full Rebuild - If config/options changed or CMake cache missing

Usage

Automatic Incremental Builds

Incremental builds work automatically with no configuration:

# First build (full)
ccgo build linux
# ✓ Build completed in 45.2s

# Modify one source file
echo "// comment" >> src/mylib.cpp

# Second build (incremental)
ccgo build linux
# 📊 Incremental build - 1 files changed:
#      Modified: 1
# ✓ Build completed in 3.8s (11.9x faster!)

Build Output Examples

No Changes

$ ccgo build linux

    No source changes detected, using cached build
    Build completed in 0.5s

Incremental Build

$ ccgo build linux

   📊 Incremental build - 3 files changed:
      Modified: 2
      Added:    1
    Rebuilding affected files...
    Build completed in 4.2s

Full Rebuild Required

$ ccgo build linux

   🔄 Full rebuild required: CCGO.toml configuration changed
    Building all files...
    Build completed in 42.8s

What Triggers Full Rebuild

Configuration Changes

Any change to CCGO.toml triggers full rebuild:

[package]
version = "1.0.1"  # Changed → Full rebuild

[dependencies]
# Added new dependency → Full rebuild
fmt = "10.1.1"

Build Option Changes

Different build options require full rebuild:

# First build with 4 jobs
ccgo build linux --jobs 4

# Second build with 8 jobs → Full rebuild
ccgo build linux --jobs 8

# Different architectures → Full rebuild
ccgo build linux --arch x86_64
ccgo build linux --arch arm64  # Full rebuild

# Feature changes → Full rebuild
ccgo build linux --features networking
ccgo build linux --features advanced  # Full rebuild

CMake Cache Changes

If CMake reconfigures, full rebuild occurs:

# Clear CMake cache → Full rebuild next time
rm -rf cmake_build/release/linux/CMakeCache.txt
ccgo build linux

New/Removed Files

Adding or removing source files triggers CMake reconfiguration:

# Add new source file
touch src/new_feature.cpp

# Next build detects file addition
ccgo build linux
# 📊 Incremental build - 1 files changed:
#      Added: 1
# 🔧 CMake reconfiguration needed

Build State Files

Location

Build state is stored per platform and build mode:

cmake_build/
  ├── release/
  │   ├── linux/.ccgo_build_state.json
  │   ├── macos/.ccgo_build_state.json
  │   └── windows/.ccgo_build_state.json
  └── debug/
      └── linux/.ccgo_build_state.json

State File Format

.ccgo_build_state.json contains:

{
  "project": "myproject",
  "platform": "linux",
  "link_type": "static",
  "last_build_time": 1737433200,
  "config_hash": "a1b2c3...",
  "options_hash": "d4e5f6...",
  "cmake_cache_hash": "g7h8i9...",
  "file_hashes": {
    "src/mylib.cpp": "sha256_hash...",
    "src/utils.cpp": "sha256_hash...",
    "include/mylib.h": "sha256_hash..."
  }
}

Manual State Management

# View build state
cat cmake_build/release/linux/.ccgo_build_state.json

# Force full rebuild by removing state
rm cmake_build/release/linux/.ccgo_build_state.json
ccgo build linux

# Or use clean command
ccgo clean

Performance Comparison

Typical rebuild speedups with incremental builds:

Scenario Files Changed Full Build Incremental Speedup
No changes 0 45s 0.5s 90x faster
Single file 1 45s 3.8s 11.9x faster
Few files (5%) 10/200 45s 8.2s 5.5x faster
Many files (25%) 50/200 45s 18.5s 2.4x faster
Header change 1 (affects 50) 45s 22.1s 2.0x faster
All files 200/200 45s 44s ~1x (full rebuild)

Note: Speedup depends on: - Project size and complexity - Number of changed files - Dependency relationships (headers) - Compiler cache (ccache/sccache) effectiveness - Hardware (CPU, disk speed)

Best Practices

DO

Let CCGO decide - Incremental builds are automatic and smart ✅ Use with compiler cache - Combine with --cache sccache for maximum speed ✅ Commit regularly - Smaller changes = faster rebuilds ✅ Separate header changes - Headers trigger more rebuilds ✅ Trust the system - CCGO ensures correctness

DON'T

Don't manually edit build state - Files are auto-generated ❌ Don't share build state - State is machine-specific ❌ Don't disable - No way to disable (always beneficial) ❌ Don't modify build directories - Let CCGO manage them

CI/CD Integration

GitHub Actions

name: Build

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      # Cache CMake build directory for incremental builds
      - name: Cache CMake Build
        uses: actions/cache@v3
        with:
          path: cmake_build/
          key: ${{ runner.os }}-cmake-${{ hashFiles('CCGO.toml', 'src/**') }}
          restore-keys: |
            ${{ runner.os }}-cmake-

      # Incremental build will work if cache hit
      - name: Build
        run: ccgo build linux

Benefits in CI: - 🚀 Faster PR builds - Only rebuild changed files - 💰 Reduced CI costs - Less compute time - ⚡ Quicker feedback - Developers get results faster

GitLab CI

build:
  image: rust:latest

  cache:
    paths:
      - cmake_build/
    key:
      files:
        - CCGO.toml
        - src/**/*.cpp

  script:
    - ccgo build linux

Troubleshooting

Incremental Build Not Working

Symptom: Every build is full rebuild

Causes & Solutions:

  1. Build state file missing

    # Check if state file exists
    ls cmake_build/release/linux/.ccgo_build_state.json
    
    # If missing, one full build will create it
    ccgo build linux
    

  2. Config or options changing

    # Check what changed
    git diff CCGO.toml
    
    # Verify you're using same build options
    

  3. CMake cache cleared

    # Check if CMakeCache.txt exists
    ls cmake_build/release/linux/CMakeCache.txt
    
    # Don't manually delete cmake_build/ between builds
    

Incorrect Incremental Build

Symptom: Build succeeds but changes not reflected in output

Solution: This shouldn't happen - incremental system is conservative. If you suspect issues:

# Force full rebuild
ccgo clean
ccgo build linux

# Or just remove build state
rm cmake_build/release/linux/.ccgo_build_state.json
ccgo build linux

Build State Corruption

Symptom: Unexpected errors during incremental build

Solution:

# Clean and rebuild
ccgo clean -y
ccgo build linux

# Or manually remove state
rm -rf cmake_build/
ccgo build linux

Under the Hood

Change Detection Algorithm

// Pseudocode
fn can_incremental_build() -> bool {
    // Load previous build state
    let old_state = load_build_state()?;

    // Check configuration
    if old_state.config_hash != current_config_hash() {
        return false; // Config changed
    }

    // Check build options
    if old_state.options_hash != current_options_hash() {
        return false; // Options changed
    }

    // Check CMake cache
    if !cmake_cache_exists() || old_state.cmake_cache_hash != current_cmake_cache_hash() {
        return false; // CMake needs reconfigure
    }

    true // Incremental build possible
}

fn analyze_changes() -> ChangeAnalysis {
    let mut changes = ChangeAnalysis::new();

    // Scan current source files
    for file in scan_source_files() {
        let current_hash = hash_file(file);

        match old_state.file_hashes.get(file) {
            Some(old_hash) if old_hash != current_hash => {
                changes.modified_files.push(file);
            }
            None => {
                changes.added_files.push(file);
            }
            _ => {} // Unchanged
        }
    }

    // Detect removed files
    for old_file in old_state.file_hashes.keys() {
        if !current_files.contains(old_file) {
            changes.removed_files.push(old_file);
        }
    }

    changes
}

File Hashing

CCGO uses SHA256 for file content hashing:

use sha2::{Digest, Sha256};

fn hash_file(path: &Path) -> String {
    let content = std::fs::read(path)?;
    let mut hasher = Sha256::new();
    hasher.update(&content);
    format!("{:x}", hasher.finalize())
}

Why SHA256? - Fast enough for source files (< 1ms per file) - Collision-resistant (no false positives) - Standard and well-tested - Available in Rust std lib

CMake Integration

Incremental builds leverage CMake's built-in incremental compilation:

  1. CMake detects file changes - Checks modification times
  2. CCGO detects config changes - Prevents stale builds
  3. Combined approach - Best of both worlds

CCGO's change detection is conservative - when in doubt, full rebuild.

Advanced Topics

Multiple Platforms

Each platform has independent build state:

# Build Linux (incremental if possible)
ccgo build linux

# Build macOS (independent state, may be full rebuild)
ccgo build macos

Platform changes don't affect each other.

Debug vs Release

Debug and release builds have separate state:

# Release build
ccgo build linux

# Debug build (separate state)
ccgo build linux --debug

Static and shared builds share the same build state:

# Build static
ccgo build linux --build-as static

# Build shared (incremental, shared source compilation)
ccgo build linux --build-as shared

Both link types use the same source files, so changes propagate.

Future Enhancements

Planned features for future releases:

  • Dependency graph tracking for header changes
  • Parallel incremental compilation
  • Remote build cache (share across team)
  • Build time predictions
  • Automatic cleanup of old build states
  • Visual dependency graph
  • Per-file build time tracking

See Also

Changelog

v3.0.12 (2026-01-21)

  • ✅ Implemented incremental build detection
  • ✅ Build state tracking with file hashing (SHA256)
  • ✅ Configuration and option change detection
  • ✅ CMake cache tracking
  • ✅ Change analysis with modified/added/removed files
  • ✅ Automatic build state persistence
  • ✅ Per-platform and per-link-type state management
  • ✅ Last 100 builds auto-pruning for analytics

Incremental builds make your development workflow significantly faster by rebuilding only what changed.