Decision matrix for choosing between Make, CMake, Gradle, Maven, Bazel, and other build systems based on project requirements, language, and scale.
Last Updated: 2025-10-26
Use this skill when:
Prerequisites: Understanding of project requirements, language ecosystem, team size
What language(s)?
├─ Pure C/C++
│ ├─ Simple project → Make
│ ├─ Cross-platform → CMake
│ └─ Monorepo/Google-scale → Bazel
├─ Pure Java/Kotlin
│ ├─ Convention over configuration → Maven
│ ├─ Flexibility/performance → Gradle
│ └─ Monorepo → Bazel
├─ Pure Rust → Cargo (no alternatives)
├─ Pure Go → go build (no alternatives)
├─ Pure Python → uv, pip-tools, Poetry
├─ Pure JavaScript/TypeScript → npm, pnpm, Bun
├─ Polyglot (multiple languages)
│ ├─ <50k files → Language-specific tools + orchestration
│ └─ >50k files → Bazel or Pants
└─ Monorepo (many projects)
├─ Pure JVM → Gradle multi-project
└─ Mixed languages → Bazel
| Feature | Make | CMake | Maven | Gradle | Bazel | Cargo | Go | |---------|------|-------|-------|--------|-------|-------|------| | Languages | Any | C/C++ | JVM | JVM+ | Any | Rust | Go | | Learning Curve | Medium | Steep | Low | Medium | Steep | Low | Low | | Performance | Good | Good | Slow | Fast | Fastest | Fast | Fast | | Incremental | Good | Good | Good | Excellent | Excellent | Excellent | Excellent | | Caching | Basic | Basic | Local | Local+Remote | Remote | Local | Local | | Cross-platform | Manual | Excellent | Excellent | Excellent | Excellent | Excellent | Excellent | | IDE Support | Basic | Excellent | Excellent | Excellent | Good | Excellent | Excellent | | Dependency Mgmt | Manual | Manual | Excellent | Excellent | Excellent | Excellent | Excellent | | Monorepo | Poor | Medium | Good | Good | Excellent | N/A | N/A | | Ecosystem | Mature | Mature | Mature | Growing | Growing | Growing | Mature |
Clean Build (medium project, ~10k files):
Incremental Build (1% file change):
Note: Bazel with remote cache: ~0.5s (cache hit)
Project Type → Recommendation
─────────────────────────────────────
Simple CLI tool → Make
Cross-platform lib → CMake
Large monorepo → Bazel
Game engine → CMake + custom scripts
Embedded → CMake with toolchains
Make:
CMake:
Bazel:
Example: CMake for Cross-Platform C++ Library
cmake_minimum_required(VERSION 3.20)
project(MyLib VERSION 1.0.0)
add_library(mylib src/lib.cpp)
target_include_directories(mylib PUBLIC include)
# Works on Windows, macOS, Linux
install(TARGETS mylib DESTINATION lib)
Project Type → Recommendation
─────────────────────────────────────
Spring Boot app → Maven or Gradle
Android app → Gradle (only option)
Microservices → Maven (standardization)
Multi-module lib → Gradle (performance)
Large monorepo → Bazel
Maven:
Gradle:
Bazel:
Example: Maven for Spring Boot
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<!-- Convention: no plugin config needed -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Example: Gradle for Multi-Module
// settings.gradle.kts
include("core", "api", "app")
// Fast incremental builds, parallel execution
// ./gradlew build --parallel
Cargo is the ONLY choice
- Integrated with rustc
- Excellent dependency management (crates.io)
- Fast incremental builds
- No alternative needed
# Cargo.toml
[package]
name = "myapp"
version = "0.1.0"
[dependencies]
serde = "1.0"
tokio = { version = "1.35", features = ["full"] }
go build is the standard
- Built into Go toolchain
- Zero configuration for simple projects
- Fast compilation
- Minimal alternatives (Bazel for large monorepos)
// go.mod
module github.com/user/project
go 1.21
require (
github.com/gorilla/mux v1.8.1
)
Project Type → Recommendation
─────────────────────────────────────
Modern app/lib → uv (fastest, 2024)
Legacy project → pip + requirements.txt
Library → Poetry or uv
Monorepo → Bazel or Pants
uv (Recommended, 2024):
Poetry:
pip-tools:
Package Manager → Recommendation
─────────────────────────────────────
New project → pnpm or Bun (fast)
Legacy → npm
Monorepo → pnpm workspaces or Turborepo
pnpm:
Bun:
npm:
Scale → Recommendation
─────────────────────────────────────
Small (<10 projects) → Language-native + Makefiles
Medium (10-50 projects) → Gradle/Maven multi-module
Large (50-200 projects) → Bazel or Pants
Google-scale (>200 projects) → Bazel
Bazel:
Gradle Multi-Project:
Pants (Alternative to Bazel):
Use language-native tools:
- C/C++: CMake or Make
- Java: Maven or Gradle
- Rust: Cargo
- Go: go build
- Python: uv
- JS/TS: pnpm
Orchestrate with CI (GitHub Actions, GitLab CI)
Rationale: Cross-platform support, better dependency management
# Before (Makefile)
gcc -o myapp main.c utils.c -I./include
# After (CMakeLists.txt)
add_executable(myapp main.c utils.c)
target_include_directories(myapp PRIVATE include)
Migration Steps:
Rationale: Performance, multi-module flexibility
Migration Tool:
# Gradle provides migration tool
gradle init # Converts existing Maven project
Manual Migration:
Rationale: Monorepo scale, hermetic builds
Migration Steps:
Tools:
rules_jvm_external for Maven depsmigration-tooling (Bazel repo)[ ] What languages? (Pure vs polyglot)
[ ] Project size? (<1k, 1-10k, 10-100k, >100k files)
[ ] Team size? (1-5, 5-20, 20-100, >100)
[ ] Monorepo or polyrepo?
[ ] Cross-platform requirements?
[ ] Performance critical? (CI time budget?)
[ ] Remote caching needed?
[ ] Existing expertise on team?
[ ] Migration cost vs benefit?
[ ] IDE support requirements?
[ ] Open source or proprietary?
❌ Using Make for cross-platform C++ library
→ Use CMake
❌ Using Bazel for 5-person team, single language
→ Use language-native tool
❌ Using Maven for Android
→ Use Gradle (only option)
❌ Using custom scripts for polyglot monorepo
→ Use Bazel
❌ Using npm for large monorepo (>50 packages)
→ Use pnpm workspaces or Bazel
Phase 1: Small (1-5 developers, <1k files)
→ Language-native tools (Cargo, go build, npm)
→ Simple Makefiles for C/C++
Phase 2: Medium (5-20 developers, 1-10k files)
→ CMake for C/C++
→ Gradle or Maven for JVM
→ Monorepo with multi-module builds
Phase 3: Large (20-100 developers, 10-100k files)
→ Bazel for polyglot monorepo
→ Remote caching for CI acceleration
→ Hermetic builds for reproducibility
Phase 4: Google-scale (>100 developers, >100k files)
→ Bazel with remote execution
→ Build infrastructure team
→ Custom rules and optimizations
WRONG: 3-person team, 500 files, pure Java
→ Use Bazel (overkill, months of setup)
CORRECT: 3-person team, 500 files, pure Java
→ Use Maven (days of setup, team expertise)
WRONG: Pure Rust project
→ Use CMake (fighting ecosystem)
CORRECT: Pure Rust project
→ Use Cargo (designed for Rust)
WRONG: Monorepo with 10 different build systems
→ Each team uses own tool
→ Integration nightmare
CORRECT: Monorepo with unified build
→ Bazel or Gradle multi-project
→ Consistent builds across teams
WRONG: CI rebuilds everything every time
→ 30-minute builds
CORRECT: Enable build cache (Gradle, Bazel)
→ 5-minute incremental builds
| Project Profile | Recommended Build System | Runner-Up | |----------------|-------------------------|-----------| | C++ cross-platform lib | CMake | Bazel | | Java microservices | Maven | Gradle | | Kotlin Android app | Gradle | N/A | | Rust CLI tool | Cargo | N/A | | Go web service | go build | N/A | | Python app | uv | Poetry | | TypeScript React app | pnpm | Bun | | Polyglot monorepo | Bazel | Pants | | 100+ module Java monorepo | Bazel | Gradle | | Legacy C project | Make | CMake |
Make: Ubiquitous, minimal, fast for small C/C++ projects CMake: Cross-platform C/C++, excellent IDE support Maven: Standardized JVM builds, convention over configuration Gradle: Flexible JVM builds, fast incremental, Android Bazel: Large monorepos, hermetic, polyglot, remote cache Cargo: Rust builds, integrated ecosystem go build: Go builds, zero config uv: Python deps, fast, modern pnpm: JavaScript monorepos, efficient
# build_selector.py - Choose build system based on project analysis
import os
import json
def analyze_project():
"""Analyze project to recommend build system."""
languages = set()
# Detect languages
for root, dirs, files in os.walk('.'):
for file in files:
if file.endswith('.java'):
languages.add('java')
elif file.endswith('.cpp') or file.endswith('.h'):
languages.add('cpp')
elif file.endswith('.py'):
languages.add('python')
elif file.endswith('.rs'):
languages.add('rust')
# Count files
file_count = sum(len(files) for _, _, files in os.walk('.'))
# Recommend
if len(languages) == 1:
lang = list(languages)[0]
if lang == 'rust':
return 'Cargo'
elif lang == 'python':
return 'uv'
elif lang == 'java':
return 'Maven' if file_count < 10000 else 'Gradle'
elif lang == 'cpp':
return 'CMake'
elif len(languages) > 1:
return 'Bazel' if file_count > 10000 else 'Language-native + orchestration'
return 'Unknown'
if __name__ == '__main__':
print(f"Recommended build system: {analyze_project()}")
make-fundamentals.md - Make syntax and patternscmake-patterns.md - CMake configurationgradle-jvm-builds.md - Gradle for JVMmaven-configuration.md - Maven for Javabazel-monorepos.md - Bazel for monoreposbuild-optimization.md - Build performancecross-platform-builds.md - Multi-platform strategiesChoosing the right build system is critical for long-term project success:
Key Takeaways:
2024 Trends:
Bottom Line: Start simple, migrate when complexity justifies it. Don't use Bazel for a 3-person team, but don't use Make for a 300-person monorepo.