π mbake
A Makefile formatter and linter. It only took 50 years!
A Makefile formatter and linter. It only took 50 years!
Table of Contents
Features
Configurable rules via ~/.bake.toml
CI/CD integration with check mode
Extensible plugin architecture
Rich terminal output with progress indicators
Syntax validation before and after formatting
Smart .PHONY detection with automatic insertion
Formatting Rules
Indentation & Spacing
Tabs for recipes : Recipe lines use tabs instead of spaces
: Recipe lines use tabs instead of spaces Assignment operators : Normalized spacing around := , = , += , ?=
: Normalized spacing around , , , Target colons : Consistent spacing around target dependency colons
: Consistent spacing around target dependency colons Trailing whitespace: Removes unnecessary trailing spaces
Line Continuations
Backslash normalization : Proper spacing around backslash continuations
: Proper spacing around backslash continuations Smart joining: Consolidates simple continuations while preserving complex structures
.PHONY Declarations
Grouping : Consolidates multiple .PHONY declarations
: Consolidates multiple declarations Auto-insertion : Automatically detects and inserts .PHONY declarations when missing (opt-in)
: Automatically detects and inserts declarations when missing (opt-in) Dynamic enhancement : Enhances existing .PHONY declarations with additional detected phony targets
: Enhances existing declarations with additional detected phony targets Rule-based analysis : Uses command analysis to determine if targets are phony
: Uses command analysis to determine if targets are phony Minimal changes: Only modifies .PHONY lines, preserves file structure
Installation
PyPI (Recommended)
pip install mbake
VSCode Extension
Open VSCode Go to Extensions (Ctrl+Shift+X) Search for "mbake Makefile Formatter" Click Install
From Source
git clone https://github.com/ebodshojaei/bake.git cd mbake pip install -e .
Development Installation
git clone https://github.com/ebodshojaei/bake.git cd mbake pip install -e " .[dev] "
Usage
mbake uses a subcommand-based CLI. All commands support both bake and mbake aliases.
Quick Start
# Check version bake --version # Initialize configuration (optional) bake init # Format a Makefile bake format Makefile # Validate Makefile syntax bake validate Makefile
Configuration Management
# Initialize configuration file with defaults bake init # Initialize with custom path or force overwrite bake init --config /path/to/config.toml --force # Show current configuration bake config # Show configuration file path bake config --path # Use custom configuration file bake config --config /path/to/config.toml
Formatting Files
# Format a single Makefile bake format Makefile # Format multiple files bake format Makefile src/Makefile tests/ * .mk # Check if files need formatting (CI/CD mode) bake format --check Makefile # Show diff of changes without modifying files bake format --diff Makefile # Format with verbose output bake format --verbose Makefile # Create backup before formatting bake format --backup Makefile # Validate syntax after formatting bake format --validate Makefile # Use custom configuration bake format --config /path/to/config.toml Makefile
Syntax Validation
# Validate single file bake validate Makefile # Validate multiple files bake validate Makefile src/Makefile tests/ * .mk # Validate with verbose output bake validate --verbose Makefile # Use custom configuration bake validate --config /path/to/config.toml Makefile
Version Management
# Check current version and for updates bake --version # Check for updates only (without updating) bake update --check # Update to latest version bake update # Update with confirmation prompt bypass bake update --yes # Force update even if already up to date bake update --force
Shell Completion
# Install completion for current shell bake --install-completion # Show completion script (for manual installation) bake --show-completion
Configuration
mbake works with sensible defaults. Generate a configuration file with:
bake init
Sample Configuration
[ formatter ] # Indentation settings use_tabs = true tab_width = 4 # Spacing settings space_around_assignment = true space_before_colon = false space_after_colon = true # Line continuation settings normalize_line_continuations = true max_line_length = 120 # PHONY settings group_phony_declarations = true phony_at_top = true auto_insert_phony_declarations = false # General settings remove_trailing_whitespace = true ensure_final_newline = true normalize_empty_lines = true max_consecutive_empty_lines = 2 # Global settings debug = false verbose = false
Smart .PHONY Detection
mbake includes intelligent .PHONY detection that automatically identifies and manages phony targets.
How It Works
Detection uses dynamic analysis of recipe commands rather than hardcoded target names:
Command Analysis : Examines what each target's recipe actually does
: Examines what each target's recipe actually does File Creation Detection : Identifies if commands create files with the target name
: Identifies if commands create files with the target name Pattern Recognition: Understands compilation patterns, redirections, and common tools
Examples
Docker/Container Targets
# These are detected as phony because they manage containers, not files up : docker compose up -d down : docker compose down -v logs : docker compose logs -f
Build/Development Targets
# These are detected as phony because they don't create files with their names test : npm test lint : eslint src/ deploy : ssh user@server 'systemctl restart myapp'
File vs Phony Target Detection
# NOT phony - creates myapp.o file myapp.o : myapp.c gcc -c myapp.c -o myapp.o # Phony - removes files, doesn't create "clean" clean : rm -f *.o myapp
Configuration
Enable auto-insertion in your ~/.bake.toml :
[ formatter ] auto_insert_phony_declarations = true
Behavior Modes
Default (Conservative):
Groups existing .PHONY declarations
declarations No automatic insertion or enhancement
Backwards compatible
Enhanced (auto_insert_phony_declarations = true):
Automatically inserts .PHONY when missing
when missing Enhances existing .PHONY with detected targets
with detected targets Uses dynamic analysis for accurate detection
Before and After
Input (no .PHONY ):
setup : docker compose up -d npm install test : npm test clean : docker compose down -v rm -rf node_modules
Output (with auto-insertion enabled):
.PHONY : clean setup test setup : docker compose up -d npm install test : npm test clean : docker compose down -v rm -rf node_modules
Examples
Basic Formatting
Before:
# Inconsistent spacing and indentation CC: =gcc CFLAGS = -Wall -g SOURCES =main.c \ utils.c \ helper.c .PHONY : clean all : $( TARGET ) $(CC) $(CFLAGS) -o $@ $^ .PHONY : install clean : rm -f *.o
After:
# Clean, consistent formatting CC := gcc CFLAGS = -Wall -g SOURCES = main.c utils.c helper.c .PHONY : all clean install all : $( TARGET ) $(CC) $(CFLAGS) -o $@ $^ clean : rm -f *.o
Auto-Insertion Example
Before (with auto_insert_phony_declarations = true ):
# Docker development workflow setup : docker compose down -v docker compose up -d @echo "Services ready!" build : docker compose build --no-cache test : docker compose exec app npm test clean : docker compose down -v docker system prune -af
After:
# Docker development workflow .PHONY : build clean setup test setup : docker compose down -v docker compose up -d @echo "Services ready!" build : docker compose build --no-cache test : docker compose exec app npm test clean : docker compose down -v docker system prune -af
CI/CD Integration
Use mbake in continuous integration:
# GitHub Actions example - name : Check Makefile formatting run : | pip install mbake bake format --check Makefile
Exit codes:
0 - No formatting needed or formatting successful
- No formatting needed or formatting successful 1 - Files need formatting (--check mode) or validation failed
- Files need formatting (--check mode) or validation failed 2 - Error occurred
Development
Setup
git clone https://github.com/ebodshojaei/bake.git cd mbake pip install -e " .[dev] " pre-commit install
Running Tests
# Run all tests pytest # Run with coverage pytest --cov=bake --cov-report=html # Run specific test file pytest tests/test_formatter.py -v
Code Quality
# Format code black bake tests # Lint code ruff check bake tests # Type checking mypy bake
Architecture
mbake follows a modular, plugin-based architecture:
bake/ βββ __init__.py # Package initialization βββ cli.py # Command-line interface with subcommands βββ config.py # Configuration management βββ core/ β βββ formatter.py # Main formatting engine β βββ rules/ # Individual formatting rules β βββ tabs.py # Tab/indentation handling β βββ spacing.py # Spacing normalization β βββ continuation.py # Line continuation formatting β βββ phony.py # .PHONY declaration management βββ plugins/ βββ base.py # Plugin interface
Adding Custom Rules
Extend the FormatterPlugin base class:
from bake . plugins . base import FormatterPlugin , FormatResult class MyCustomRule ( FormatterPlugin ): def __init__ ( self ): super (). __init__ ( "my_rule" , priority = 50 ) def format ( self , lines : List [ str ], config : dict ) -> FormatResult : # Your formatting logic here return FormatResult ( lines = modified_lines , changed = True , errors = [], warnings = [] )
Contributing
Contributions are welcome! Read the Contributing Guide for details on development process, submitting pull requests, and reporting issues.
Quick Start for Contributors
Fork the repository Create a feature branch ( git checkout -b feature/amazing-feature ) Make your changes Add tests for new functionality Run the test suite ( pytest ) Commit your changes ( git commit -m 'Add amazing feature' ) Push to the branch ( git push origin feature/amazing-feature ) Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Design Philosophy