🍞 mbake
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
- Suppress formatting with special comments
Formatting Rules
Indentation & Spacing
- Tabs for recipes: Recipe lines use tabs instead of spaces
- Assignment operators: Normalized spacing around
:=
, =
, +=
, ?=
- Target colons: Consistent spacing around target dependency colons
- Trailing whitespace: Removes unnecessary trailing spaces
Line Continuations
- Backslash normalization: Proper spacing around backslash continuations
- Smart joining: Consolidates simple continuations while preserving complex structures
.PHONY Declarations
- Grouping: Consolidates multiple
.PHONY
declarations
- Auto-insertion: Automatically detects and inserts
.PHONY
declarations when missing (opt-in)
- Dynamic enhancement: Enhances existing
.PHONY
declarations with additional detected phony targets
- Rule-based analysis: 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]"
Package Manager Installation
For system package managers and AUR packagers, mbake supports configurable command names to avoid namespace conflicts.
For AUR packagers: The default behavior already avoids conflicts with ruby-bake
- no additional configuration needed!
Migration to v1.3.x
⚠️ Breaking Change: Version 1.3.0 introduces a module rename from bake
to mbake
for consistency.
What Changed
- Python module:
bake
→ mbake
(for consistency with command name)
- Command name: Still
mbake
(unchanged)
- Configuration: Still
~/.bake.toml
(unchanged)
Migration Steps
-
Update the package:
pip install --upgrade mbake
-
If you have shell aliases, they will continue working:
alias bake='mbake'
bake --version
-
If you have Python scripts that import from bake
, update them:
from bake.config import Config
from mbake.config import Config
-
If you have CI/CD scripts, update import statements:
python -c "from bake.cli import main; main()"
python -c "from mbake.cli import main; main()"
Backward Compatibility
- CLI commands: All commands work exactly the same
- Configuration files: No changes needed
- Shell aliases: Continue working without modification
- Python imports: Require updating to use
mbake
module
Usage
mbake uses a subcommand-based CLI. All commands support both bake
and mbake
aliases.
Quick Start
mbake --version
mbake setup-command mbake
mbake init
mbake format Makefile
mbake validate Makefile
Configuration Management
bake init
bake init --config /path/to/config.toml --force
bake config
bake config --path
bake config --config /path/to/config.toml
Formatting Files
bake format Makefile
bake format Makefile src/Makefile tests/*.mk
bake format --check Makefile
bake format --diff Makefile
bake format --verbose Makefile
bake format --backup Makefile
bake format --validate Makefile
bake format --config /path/to/config.toml Makefile
Syntax Validation
bake validate Makefile
bake validate Makefile src/Makefile tests/*.mk
bake validate --verbose Makefile
bake validate --config /path/to/config.toml Makefile
validate vs format --check
bake validate
: Checks if Makefile will execute correctly using GNU make
(syntax validation)
bake format --check
: Checks if Makefile follows formatting rules (style validation)
Both are useful! Use validate
for syntax errors, format --check
for style issues.
Version Management
bake --version
bake update --check
bake update
bake update --yes
bake update --force
Shell Completion
bake --install-completion
bake --show-completion
Configuration
mbake works with sensible defaults. Generate a configuration file with:
bake init
Sample Configuration
[formatter]
use_tabs = true
tab_width = 4
space_around_assignment = true
space_before_colon = false
space_after_colon = true
normalize_line_continuations = true
max_line_length = 120
group_phony_declarations = true
phony_at_top = true
auto_insert_phony_declarations = false
remove_trailing_whitespace = true
ensure_final_newline = true
normalize_empty_lines = true
max_consecutive_empty_lines = 2
fix_missing_recipe_tabs = true
debug = false
verbose = false
gnu_error_format = true
wrap_error_messages = 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
- File Creation Detection: Identifies if commands create files with the target name
- Pattern Recognition: Understands compilation patterns, redirections, and common tools
Examples
Docker/Container Targets
up:
docker compose up -d
down:
docker compose down -v
logs:
docker compose logs -f
Build/Development Targets
test:
npm test
lint:
eslint src/
deploy:
ssh user@server 'systemctl restart myapp'
File vs Phony Target Detection
myapp.o: myapp.c
gcc -c myapp.c -o myapp.o
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
- No automatic insertion or enhancement
- Backwards compatible
Enhanced (auto_insert_phony_declarations = true):
- Automatically inserts
.PHONY
when missing
- Enhances existing
.PHONY
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):
setup:
docker compose up -d
npm install
test:
npm test
clean:
docker compose down -v
rm -rf node_modules
Examples
Basic Formatting
Before:
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:
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
Auto-Insertion Example
Before (with auto_insert_phony_declarations = true
):
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:
.PHONY: 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
Disable Formatting Example
Disable formatting within a region using special comments that switch formatting in a delimited range.
Use # bake-format off
to disable formatting for the lines until the next #bake-format on
, which re-enables formatting.
NO_FORMAT_1= \
1 \
45678 \
NO_FORMAT_2= \
1 \
45678 \
CI/CD Integration
Use mbake in continuous integration:
- name: Check Makefile formatting
run: |
pip install mbake
bake format --check Makefile
Exit codes:
0
- No formatting needed or formatting successful
1
- 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]"
Running Tests
pytest
pytest --cov=bake --cov-report=html
pytest tests/test_formatter.py -v
Code Quality
black bake tests
ruff check bake tests
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:
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
- Minimal changes: Only modify what needs to be fixed, preserve file structure
- Predictable behavior: Consistent formatting rules across all Makefiles
- Fast execution: Efficient processing of large Makefiles
- Reliable validation: Ensure formatted Makefiles have correct syntax
- Developer-friendly: Rich CLI with helpful error messages and progress indicators