LogoSkills

Incremental Build Rules

An incremental build system guide that saves time by only building changed packages in local development environments.

An incremental build system guide that saves time by only building changed packages in local development environments.

Overview#

ScenarioFull BuildIncremental BuildSavings
Single feature change60min+5-10min~85%
shared package change60min+20-30min~50%
No changes10min+Instant~99%

Level-based Parallel Build#

Incremental build optimizes performance by building packages at the same priority level in parallel.

๋ ˆ๋ฒจ [1]: shared/* (4๊ฐœ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€ ๋ณ‘๋ ฌ ์‹คํ–‰
                                  โ†“ ๋ ˆ๋ฒจ 1 ์™„๋ฃŒ
๋ ˆ๋ฒจ [3]: package/* (8๊ฐœ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€ ๋ณ‘๋ ฌ ์‹คํ–‰
                                  โ†“ ๋ ˆ๋ฒจ 3 ์™„๋ฃŒ
๋ ˆ๋ฒจ [4]: feature/common/* (6๊ฐœ) โ”€โ”ฌโ”€ ๋ณ‘๋ ฌ ์‹คํ–‰
                                  โ†“ ๋ ˆ๋ฒจ 4 ์™„๋ฃŒ
๋ ˆ๋ฒจ [5]: feature/application/*  โ”€โ”ฌโ”€ ๋ณ‘๋ ฌ ์‹คํ–‰
                                  โ†“ ๋ ˆ๋ฒจ 5 ์™„๋ฃŒ
๋ ˆ๋ฒจ [7]: app/* (1๊ฐœ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€ ๋นŒ๋“œ

Parallel concurrency control:

# Set concurrent build count (default: 4)
BUILD_CONCURRENCY=6 melos run build:incremental

# Limit to 1 in low-resource environments
BUILD_CONCURRENCY=1 melos run build:incremental:low-resources

Melos Commands#

Basic Commands#

# Incremental build of changed packages + reverse dependencies only
melos run build:incremental

# Check changed packages without building
melos run build:incremental:dry-run

# Check build status
melos run build:incremental:status

Advanced Commands#

CommandDescriptionUsage Scenario
build:incremental:force Build after cache reset When cache issues occur
build:incremental:low-resources Memory saving mode CI environments, low-spec PCs
build:incremental:clean Build after build_runner clean When generated file conflicts
build:incremental:verboseDetailed log outputWhen debugging
build:incremental:resetDelete cache onlyWhen starting fresh
build:sync-from-ciSync CI cache to localAfter squash merge
build:sync-from-ci:dry-run CI cache sync preview Before downloading

CI Cache Synchronization#

Syncs CI build cache to local, enabling accurate incremental builds even after squash merges.

Usage Scenarios#

PR ๋ธŒ๋žœ์น˜:  A โ†’ B โ†’ C  (last-commit.sha = C)
                โ†“ squash merge
main:       X โ†’ D      (D๋Š” ์ƒˆ๋กœ์šด SHA, C๋Š” orphan)

After squash merge, when pulling main locally, the stored commit SHA (C) no longer exists so Git diff may fail. In this case, hash-based fallback automatically activates.

Commands#

# Sync CI cache to local
melos run build:sync-from-ci

# Preview sync (without downloading)
melos run build:sync-from-ci:dry-run

Prerequisites#

  • GitHub CLI (gh) installation required: brew install gh
  • GitHub authentication required: gh auth login

How It Works#

  1. Download build-metadata from GitHub Actions artifacts
  2. Copy hash files to .build-state/ directory
  3. Enables accurate change detection in next incremental build

Change Detection Modes#

Auto Mode (Default)#

# Automatically select optimal mode
melos run build:incremental

Automatically selects Git-based inside Git repo, hash-based outside.

Squash merge handling: Automatically falls back to hash-based detection when stored commits are orphaned.

Git Mode#

# Git diff-based change detection
scripts/local_incremental_build.sh --mode=git
  • Compare last build commit with current HEAD
  • Include working directory changes
  • Detect both staged/unstaged files

Time/Hash Mode#

# File hash-based change detection
scripts/local_incremental_build.sh --mode=time
  • Compare source file SHA-256 hashes
  • Auto-exclude generated files (.g.dart, .freezed.dart, etc.)
  • Works without Git

Environment Variables#

Environment VariableDefaultDescription
BUILD_RUNNER_OPTS -d build_runner additional options
BUILD_CONCURRENCY 4 Parallel build concurrency per level

Usage Examples#

# Build without delete option
BUILD_RUNNER_OPTS= " "   melos run build:incremental

# Build in low-resources mode
BUILD_RUNNER_OPTS= " --low-resources-mode -d "   melos run build:incremental

Build Priority#

Incremental build builds in dependency order:

PriorityPath PatternExample
1shared/*config, dependencies, flavor
2package/life*, package/auth*Auth-related
3package/*core, resources, coui
4feature/common/*auth, settings, splash
5 feature/application/*, feature/console/* Feature modules
6backend/*(N/A)
7app/*good_teacher, widgetbook

Cache Structure#

Storage Location#

.build-state/
โ”œโ”€โ”€ last-commit.sha        # ๋งˆ์ง€๋ง‰ ๋นŒ๋“œ ์ปค๋ฐ‹ SHA
โ”œโ”€โ”€ last-build.timestamp   # ๋งˆ์ง€๋ง‰ ๋นŒ๋“œ ์‹œ๊ฐ„
โ”œโ”€โ”€ package-feature_console_console_sales_analysis.hash
โ”œโ”€โ”€ package-package_core.hash
โ””โ”€โ”€ ...

Cache File Format#

  • File๋ช…: package-{Path_์Šฌ๋ž˜when๋ฅผ_์–ธ๋”์Šค์ฝ”์–ด๋กœ}.hash
  • ๋‚ด์šฉ: ์†Œ์Šค File๋“คof the SHA-256 ํ•ดwhen

Cache Invalidation Conditions#

  1. Source file modification (.dart files)
  2. pubspec.yaml modification
  3. Using --force option
  4. Deleting .build-state/ directory

Troubleshooting#

Cache Issues#

# Full cache reset
melos run build:incremental:reset

# Force rebuild
melos run build:incremental:force

Change Detection Failure#

# Check with detailed logs
melos run build:incremental:verbose

# Direct script execution
scripts/detect_local_changes.sh --verbose --mode=time

Insufficient Memory#

# Use low-resources mode
melos run build:incremental:low-resources

# Or set via environment variables
BUILD_RUNNER_OPTS= " --low-resources-mode "   melos run build:incremental

Build Specific Package Only#

# Direct specification instead of incremental build
melos exec --scope=console_sales_analysis -- dart run build_runner build -d

Script Structure#

Local Scripts#

FileRole
scripts/detect_local_changes.shDetect changed packages
scripts/local_incremental_build.shLocal build orchestrator
scripts/sync_cache_from_ci.shCI cache local sync

CI Scripts#

FileRole
.github/scripts/detect_changes.shCI change detection
.github/scripts/smart_incremental_build.sh CI incremental build (shared)

Script Relationships#

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ melos run build:incremental                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                    โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ scripts/local_incremental_build.sh               โ”‚
โ”‚ - ์˜ต์…˜ ํŒŒ์‹ฑ (--dry-run, --force, --low-resources)โ”‚
โ”‚ - ๋ณ€๊ฒฝ ๊ฐ์ง€ ํ˜ธ์ถœ                                  โ”‚
โ”‚ - CI ์Šคํฌ๋ฆฝํŠธ ์žฌ์‚ฌ์šฉ                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
         โ–ผ                     โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ detect_local_   โ”‚  โ”‚ .github/scripts/            โ”‚
โ”‚ changes.sh      โ”‚  โ”‚ smart_incremental_build.sh  โ”‚
โ”‚ (๋ณ€๊ฒฝ ๊ฐ์ง€)      โ”‚  โ”‚ (์‹ค์ œ ๋นŒ๋“œ ์‹คํ–‰)             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Squash Merge Handling#

Problem#

When squash merging a PR, original commits are replaced with a new single commit. The commit stored in .build-state/last-commit.sha becomes orphaned, causing Git diff to fail.

Solution#

  1. Auto-fallback: Automatically switches to content hash-based detection on Git diff failure
  2. CI sync: Download CI cache with build:sync-from-ci for improved accuracy
# After squash merge
git checkout main
git pull

# Option 1: Use auto-fallback (hash-based)
melos run build:incremental

# Option 2: Build after CI cache sync (more accurate)
melos run build:sync-from-ci
melos run build:incremental

Notes#

  • .build-state/ directory is included in .gitignore
  • CI uses a separate cache strategy (4-layer cache)
  • Reverse dependency analysis enabled when jq is installed
  • CI build metadata is stored as artifacts for 7 days