An incremental build system guide that saves time by only building changed packages in local development environments.
Overview#
| Scenario | Full Build | Incremental Build | Savings |
|---|---|---|---|
| Single feature change | 60min+ | 5-10min | ~85% |
| shared package change | 60min+ | 20-30min | ~50% |
| No changes | 10min+ | 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#
| Command | Description | Usage 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:verbose | Detailed log output | When debugging |
build:incremental:reset | Delete cache only | When starting fresh |
build:sync-from-ci | Sync CI cache to local | After 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#
- Download
build-metadatafrom GitHub Actions artifacts - Copy hash files to
.build-state/directory - 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 Variable | Default | Description |
|---|---|---|
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:
| Priority | Path Pattern | Example |
|---|---|---|
| 1 | shared/* | config, dependencies, flavor |
| 2 | package/life*, package/auth* | Auth-related |
| 3 | package/* | core, resources, coui |
| 4 | feature/common/* | auth, settings, splash |
| 5 | feature/application/*, feature/console/* |
Feature modules |
| 6 | backend/* | (N/A) |
| 7 | app/* | 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#
- Source file modification (
.dartfiles) pubspec.yamlmodification- Using
--forceoption - 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#
| File | Role |
|---|---|
scripts/detect_local_changes.sh | Detect changed packages |
scripts/local_incremental_build.sh | Local build orchestrator |
scripts/sync_cache_from_ci.sh | CI cache local sync |
CI Scripts#
| File | Role |
|---|---|
.github/scripts/detect_changes.sh | CI 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#
- Auto-fallback: Automatically switches to content hash-based detection on Git diff failure
-
CI sync: Download CI cache with
build:sync-from-cifor improved accuracy
Recommended Workflow#
# 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
jqis installed - CI build metadata is stored as artifacts for 7 days