Makefile Validator Overview Use this skill to validate Makefiles with a local-first, deterministic flow. Default validator entrypoint: Validation layers: 1. Dependency preflight ( , , ) 2. GNU make syntax check ( ) when is available 3. 4. 5. Custom security, best-practice, and optimization checks 6. Optional and checks when installed Trigger Guidance Use this skill when the request includes Makefile quality, linting, validation, hardening, or troubleshooting. Trigger Phrases - "Validate this Makefile" - "Lint my file" - "Find issues in this build Makefile" - "Check Makefile security problems"…

| while read file; do\n if ! mbake format --check \"$file\"; then\n echo \"Error: $file needs formatting\"\n echo \"Run: mbake format $file\"\n exit 1\n fi\ndone\n```\n\n### Automated Refactoring\n\n```bash\n# Refactor entire codebase\n#!/bin/bash\n\necho \"Refactoring all Makefiles...\"\n\nfind . -type f \\( -name \"Makefile\" -o -name \"*.mk\" \\) | while read file; do\n echo \"Processing: $file\"\n\n # Backup\n cp \"$file\" \"$file.backup\"\n\n # Format\n if mbake format \"$file\"; then\n echo \" ✓ Formatted\"\n else\n echo \" ✗ Format failed\"\n mv \"$file.backup\" \"$file\"\n continue\n fi\n\n # Validate\n if mbake validate \"$file\"; then\n echo \" ✓ Validated\"\n rm \"$file.backup\"\n else\n echo \" ✗ Validation failed - reverting\"\n mv \"$file.backup\" \"$file\"\n fi\ndone\n\necho \"Refactoring complete!\"\n```\n\n## Troubleshooting\n\n### Common Issues\n\n#### 1. mbake Command Not Found\n\n```bash\n# Problem: mbake not in PATH\n$ mbake format Makefile\nbash: mbake: command not found\n\n# Solution: Ensure pip install directory is in PATH\nexport PATH=\"$HOME/.local/bin:$PATH\"\n\n# Or use python -m\npython3 -m mbake format Makefile\n```\n\n#### 2. Syntax Errors After Formatting\n\n```bash\n# Problem: Validation fails after formatting\n$ mbake format --validate Makefile\nError: Syntax validation failed\n\n# Solution: Check format disable comments\n# Look for unclosed # bake-format off sections\ngrep -n \"bake-format\" Makefile\n\n# Or restore from backup\ncp Makefile.bak Makefile\n```\n\n#### 3. Configuration Not Applied\n\n```bash\n# Problem: Settings in .bake.toml ignored\n$ mbake format Makefile\n# Formatting doesn't match config\n\n# Solution: Verify config file location\nmbake config\n\n# Or specify config explicitly\nmbake format --config .bake.toml Makefile\n```\n\n#### 4. Permission Denied\n\n```bash\n# Problem: Cannot write to file\n$ mbake format Makefile\nError: Permission denied\n\n# Solution: Check file permissions\nls -l Makefile\nchmod u+w Makefile\n```\n\n#### 5. Python Version Incompatibility\n\n```bash\n# Problem: Wrong Python version\n$ pip install mbake\nERROR: mbake requires Python '>=3.9'\n\n# Solution: Use correct Python version\npython3.11 -m pip install mbake\n\n# Or use pyenv\npyenv install 3.11\npyenv local 3.11\npip install mbake\n```\n\n### Debug Mode\n\n```bash\n# Enable verbose output (if supported in future versions)\nMBAKE_DEBUG=1 mbake format Makefile\n\n# Check Python environment\npython3 -c \"import mbake; print(mbake.__version__)\"\n\n# Validate manually\nmake -f Makefile --dry-run\n```\n\n## Exit Codes\n\nmbake uses standard exit codes:\n\n| Code | Meaning | Commands |\n|------|---------|----------|\n| 0 | Success / No changes needed | All commands |\n| 1 | Formatting needed / Validation failed | `format --check`, `validate` |\n| 2 | Error occurred | All commands |\n\n**Usage in Scripts:**\n\n```bash\n# Check formatting\nif mbake format --check Makefile; then\n echo \"Formatting OK\"\nelse\n echo \"Needs formatting\"\n exit 1\nfi\n\n# Validate\nmbake validate Makefile || {\n echo \"Validation failed!\"\n exit 1\n}\n```\n\n## Best Practices\n\n1. **Always use --check in CI/CD** to prevent automatic modifications\n2. **Review diffs** with `--diff` before applying formatting\n3. **Create backups** with `--backup` for important files\n4. **Use configuration files** for consistent team formatting\n5. **Combine with validation** using `--validate` flag\n6. **Document exceptions** with `# bake-format off` comments\n7. **Run in pre-commit hooks** to catch issues early\n8. **Format incrementally** during refactoring, not all at once\n9. **Test after formatting** to ensure builds still work\n10. **Version control config** by committing `.bake.toml`\n\n## Alternative Tool: checkmake\n\n[checkmake](https://github.com/checkmake/checkmake) is a complementary linter that can be used alongside mbake for additional coverage.\n\n### Installation\n\n```bash\n# With Go (1.16+)\ngo install github.com/checkmake/checkmake/cmd/checkmake@latest\n\n# Docker\ndocker run --rm -v $(pwd):/data checkmake/checkmake Makefile\n```\n\n### Usage\n\n```bash\n# Basic linting\ncheckmake Makefile\n\n# List available rules\ncheckmake list-rules\n\n# JSON output\ncheckmake --output json Makefile\n\n# With config file\ncheckmake --config checkmake.ini Makefile\n```\n\n### What checkmake Checks\n\n- Missing required phony targets (all, test)\n- Targets that should be declared PHONY\n- Other configurable rules\n\n### Using Both Tools Together\n\n```makefile\n# Makefile validation target\n.PHONY: lint\nlint:\n\t@echo \"Running mbake...\"\n\tmbake format --check Makefile\n\tmbake validate Makefile\n\t@echo \"Running checkmake...\"\n\tcheckmake Makefile || true\n\t@echo \"Lint complete!\"\n```\n\n### CI/CD with Both Tools\n\n```yaml\n# GitHub Actions example\n- name: Lint Makefile\n run: |\n pip install mbake\n go install github.com/checkmake/checkmake/cmd/checkmake@latest\n mbake format --check Makefile\n mbake validate Makefile\n checkmake Makefile\n```\n\n## Resources\n\n- **mbake GitHub**: https://github.com/EbodShojaei/bake\n- **mbake PyPI**: https://pypi.org/project/mbake/\n- **mbake Issues**: https://github.com/EbodShojaei/bake/issues\n- **mbake VS Code Extension**: Search \"mbake\" in Extensions marketplace\n- **checkmake GitHub**: https://github.com/checkmake/checkmake\n\n## Version Compatibility\n\n- **mbake**: Latest stable version recommended\n- **Python**: 3.9+ required\n- **GNU Make**: Any version with `--dry-run` support\n- **OS**: Linux, macOS, Windows (with GNU Make installed)\n\n## License\n\nmbake is released under the MIT License.\n\n---\n\n**Note**: This documentation covers mbake as used by the makefile-validator skill. For the latest features and updates, visit the official GitHub repository.","content_type":"text/markdown; charset=utf-8","language":"markdown","size":21120,"content_sha256":"9d07ee5db1fcca86da872e8060f93066e9aebdfd37dd1600d9fcf005447bdfb1"},{"filename":"docs/best-practices.md","content":"# Makefile Best Practices\n\nComprehensive guide to writing professional, maintainable, and efficient Makefiles.\n\n## Table of Contents\n\n1. [Essential Special Targets](#essential-special-targets)\n2. [File Organization](#file-organization)\n3. [Target Declarations](#target-declarations)\n4. [Variable Management](#variable-management)\n5. [Recipe Best Practices](#recipe-best-practices)\n6. [Dependency Management](#dependency-management)\n7. [Performance Optimization](#performance-optimization)\n8. [Portability](#portability)\n9. [Documentation](#documentation)\n10. [Security](#security)\n11. [Advanced Patterns](#advanced-patterns)\n\n## Modern Makefile Header (Recommended)\n\nFor modern, robust Makefiles, start with this recommended preamble from [Jacob Davis-Hansson](https://tech.davis-hansson.com/p/make/):\n\n```makefile\n# Modern Makefile Header\nSHELL := bash\n.ONESHELL:\n.SHELLFLAGS := -eu -o pipefail -c\n.DELETE_ON_ERROR:\nMAKEFLAGS += --warn-undefined-variables\nMAKEFLAGS += --no-builtin-rules\n```\n\n**Explanation:**\n\n| Setting | Purpose |\n|---------|---------|\n| `SHELL := bash` | Use bash instead of /bin/sh for modern shell features |\n| `.ONESHELL:` | Run entire recipe in single shell (enables multi-line scripts) |\n| `.SHELLFLAGS := -eu -o pipefail -c` | Stop on errors (-e), undefined vars (-u), pipe failures |\n| `.DELETE_ON_ERROR:` | Delete target on recipe failure (prevents corrupt builds) |\n| `--warn-undefined-variables` | Alert on undefined Make variable references |\n| `--no-builtin-rules` | Disable built-in implicit rules for faster builds |\n\n**Note:** This preamble is for GNU Make 4.0+. For maximum portability, use a simpler header.\n\n## Essential Special Targets\n\nGNU Make provides several special targets that should be used in professional Makefiles.\n\n### .DELETE_ON_ERROR (Critical)\n\n**Always include `.DELETE_ON_ERROR:`** at the top of your Makefile. This ensures partially built targets are deleted when a recipe fails, preventing corrupt builds.\n\n```makefile\n# CRITICAL: Delete target on recipe failure\n.DELETE_ON_ERROR:\n\n# Rest of Makefile follows...\n```\n\n**Why it matters:**\n- Without this, a failed build leaves a partial/corrupt file\n- Next `make` run sees the file exists and skips rebuilding\n- Results in broken builds that are hard to debug\n\n**From GNU Make Manual:** *\"This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.\"*\n\n**Exception:** Use `.PRECIOUS` to protect specific targets that should be preserved even on error:\n\n```makefile\n.DELETE_ON_ERROR:\n.PRECIOUS: expensive-to-rebuild.dat\n```\n\n### .PHONY (Always Required)\n\nDeclare non-file targets as phony to avoid conflicts and improve performance:\n\n```makefile\n.PHONY: all build clean test install\n```\n\n### .ONESHELL (For Multi-line Recipes)\n\nRun entire recipe in a single shell invocation:\n\n```makefile\n.ONESHELL:\n\ndeploy:\n\tset -e\n\techo \"Deploying...\"\n\tcd /app\n\tgit pull\n\t./restart.sh\n```\n\n**Without `.ONESHELL`**, each line runs in a separate shell, so `cd` has no effect on subsequent lines.\n\n### .SUFFIXES (For Performance)\n\nClear built-in suffix rules to speed up builds:\n\n```makefile\n# Disable all built-in suffix rules\n.SUFFIXES:\n\n# Only keep rules you need (optional)\n.SUFFIXES: .c .o\n```\n\n**Why:** GNU Make has ~90 built-in implicit rules. Clearing them speeds up rule resolution.\n\n### Complete Special Targets Header\n\n```makefile\n# Modern Makefile Header\n.DELETE_ON_ERROR:\n.SUFFIXES:\n\n.PHONY: all build clean test install deploy\n\n# Your targets follow...\n```\n\n## File Organization\n\n### Directory Structure\n\n```makefile\n# Organized Makefile structure\n.PHONY: all clean test install\n\n# Variables section\nPROJECT := myapp\nVERSION := 1.0.0\nBUILD_DIR := build\nSRC_DIR := src\n\n# Include external makefiles\ninclude config.mk\ninclude rules/*.mk\n\n# Default target (should be first)\nall: build test\n\n# Build targets\nbuild: $(BUILD_DIR)/$(PROJECT)\n\n# ... more targets\n```\n\n### Modular Organization\n\nUse `include` for large projects:\n\n```makefile\n# Main Makefile\ninclude config/variables.mk\ninclude rules/build.mk\ninclude rules/test.mk\ninclude rules/deploy.mk\n\n.PHONY: all\nall: build test\n```\n\n### Namespace Targets\n\nUse `/` as delimiter for namespaced targets:\n\n```makefile\n# Good: Namespaced targets\n.PHONY: docker/build docker/push docker/clean\ndocker/build:\n\tdocker build -t $(IMAGE) .\n\ndocker/push:\n\tdocker push $(IMAGE)\n\ndocker/clean:\n\tdocker rmi $(IMAGE)\n\n# Avoid: Flat namespace\n.PHONY: docker-build docker-push docker-clean\n```\n\n## Target Declarations\n\n### Always Declare .PHONY\n\nDeclare targets that don't create files as phony:\n\n```makefile\n# GOOD: Proper .PHONY declarations\n.PHONY: all clean test install build deploy\n\nall: build test\n\nclean:\n\trm -rf $(BUILD_DIR)\n\ntest:\n\tgo test ./...\n\n# BAD: Missing .PHONY - causes issues if files named 'clean' or 'test' exist\nclean:\n\trm -rf build\n\ntest:\n\tgo test ./...\n```\n\n### Organize .PHONY Declarations\n\n```makefile\n# Group related phony targets\n.PHONY: all build clean\n.PHONY: test test-unit test-integration\n.PHONY: install uninstall\n.PHONY: docker/build docker/push docker/clean\n\n# Or use a single declaration (mbake can organize this)\n.PHONY: all build clean test test-unit test-integration install uninstall\n```\n\n### Default Target\n\nFirst target is the default (or use .DEFAULT_GOAL):\n\n```makefile\n# Method 1: First target is default\n.PHONY: all\nall: build test\n\n# Method 2: Explicit default goal\n.DEFAULT_GOAL := build\n\n.PHONY: build test\nbuild:\n\tgo build -o app\n\ntest:\n\tgo test ./...\n```\n\n## Variable Management\n\n### Variable Assignment Operators\n\nChoose the right operator for your use case:\n\n```makefile\n# Simple assignment (=) - Recursive expansion (evaluated when used)\nCFLAGS = -Wall $(OPTIMIZE)\nOPTIMIZE = -O2\n# CFLAGS will expand to: -Wall -O2 (recursive)\n\n# Immediate assignment (:=) - Expanded immediately (RECOMMENDED for most cases)\nBUILD_TIME := $(shell date +%Y%m%d-%H%M%S)\nVERSION := 1.0.0\n# Evaluated once, avoids repeated shell calls\n\n# Conditional assignment (?=) - Set only if not already defined\nCC ?= gcc\nPREFIX ?= /usr/local\n# Allows environment variable override\n\n# Append (+=) - Add to existing value\nCFLAGS := -Wall\nCFLAGS += -Wextra\nCFLAGS += -O2\n# CFLAGS = -Wall -Wextra -O2\n```\n\n### Use := for Most Variables\n\n```makefile\n# GOOD: Immediate expansion (predictable, faster)\nBUILD_DIR := build\nSRC_FILES := $(wildcard src/*.c)\nTIMESTAMP := $(shell date +%s)\n\n# AVOID: Recursive expansion (unpredictable, slower)\nBUILD_DIR = build\nSRC_FILES = $(wildcard src/*.c) # Re-evaluated every time!\nTIMESTAMP = $(shell date +%s) # Shell called multiple times!\n```\n\n### Sane Defaults with ?=\n\n```makefile\n# Allow user/environment override\nCC ?= gcc\nCXX ?= g++\nPREFIX ?= /usr/local\nDESTDIR ?=\nVERBOSE ?= 0\n\n# Usage:\n# make # Uses defaults\n# make CC=clang # Override CC\n# PREFIX=/opt make # Override via environment\n```\n\n### Variable Naming\n\n```makefile\n# GOOD: Clear, consistent naming\nPROJECT_NAME := myapp\nBUILD_DIR := build\nSOURCE_FILES := $(wildcard src/*.c)\nCOMPILER_FLAGS := -Wall -Wextra -O2\n\n# AVOID: Unclear abbreviations\nPROJ := myapp\nBDIR := build\nSRCS := $(wildcard src/*.c)\nFLAGS := -Wall\n```\n\n## Recipe Best Practices\n\n### Use Tabs, Not Spaces\n\n```makefile\n# GOOD: Tab character (required)\nbuild:\n\t@echo \"Building...\"\n\tgo build -o app\n\n# BAD: Spaces (will fail)\nbuild:\n @echo \"Building...\"\n go build -o app\n```\n\n**Note**: Makefiles require TAB characters for recipes. Configure your editor to use tabs for Makefiles.\n\n### Error Handling\n\n```makefile\n# Method 1: Prefix with @ to suppress echo, - to ignore errors\nclean:\n\t@echo \"Cleaning build artifacts...\"\n\t-rm -rf $(BUILD_DIR)\n\t@echo \"Done!\"\n\n# Method 2: Use || for conditional error handling\nbuild:\n\tmkdir -p $(BUILD_DIR) || exit 1\n\tgo build -o $(BUILD_DIR)/app || exit 1\n\n# Method 3: Use set -e for strict error handling\ntest:\n\t@set -e; \\\n\techo \"Running tests...\"; \\\n\tgo test ./...; \\\n\techo \"All tests passed!\"\n\n# Method 4: Check exit codes explicitly\ndeploy:\n\t@./scripts/deploy.sh\n\t@if [ $? -ne 0 ]; then \\\n\t\techo \"Deployment failed!\"; \\\n\t\texit 1; \\\n\tfi\n```\n\n### Multi-line Recipes\n\n```makefile\n# Use backslash for line continuation\nbuild: $(SOURCES)\n\t@echo \"Building $(PROJECT)...\"; \\\n\tmkdir -p $(BUILD_DIR); \\\n\t$(CC) $(CFLAGS) -o $(BUILD_DIR)/$(PROJECT) $(SOURCES); \\\n\techo \"Build complete!\"\n\n# Or use .ONESHELL for easier multi-line scripts\n.ONESHELL:\ntest:\n\techo \"Running tests...\"\n\tfor file in tests/*.sh; do\n\t\tbash $file\n\tdone\n\techo \"All tests passed!\"\n```\n\n### Silent vs Verbose Output\n\n```makefile\n# Use @ to suppress command echo\n.PHONY: build\nbuild:\n\t@echo \"Building...\"\n\t@$(CC) $(CFLAGS) -o app $(SOURCES)\n\n# Optional verbose mode\nVERBOSE ?= 0\nifeq ($(VERBOSE),1)\n\tQ :=\nelse\n\tQ := @\nendif\n\nbuild:\n\t$(Q)echo \"Building...\"\n\t$(Q)$(CC) $(CFLAGS) -o app $(SOURCES)\n\n# Usage:\n# make build # Silent\n# make build VERBOSE=1 # Verbose\n```\n\n## Dependency Management\n\n### Specify Dependencies Correctly\n\n```makefile\n# GOOD: Proper dependency chain\napp: $(OBJECTS)\n\t$(CC) -o $@ $^\n\n%.o: %.c %.h\n\t$(CC) $(CFLAGS) -c $\u003c -o $@\n\n# BAD: Missing dependencies - app won't rebuild when headers change\napp: $(OBJECTS)\n\t$(CC) -o $@ $^\n\n%.o: %.c\n\t$(CC) $(CFLAGS) -c $\u003c -o $@\n```\n\n### Auto-generate Dependencies (C/C++)\n\n```makefile\n# Automatic dependency generation\nDEPDIR := .deps\nDEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d\n\n%.o: %.c $(DEPDIR)/%.d | $(DEPDIR)\n\t$(CC) $(DEPFLAGS) $(CFLAGS) -c $\u003c -o $@\n\n$(DEPDIR):\n\t@mkdir -p $@\n\n# Include generated dependency files\n-include $(patsubst %,$(DEPDIR)/%.d,$(basename $(SOURCES)))\n```\n\n### Order-Only Prerequisites\n\nUse `|` for prerequisites that shouldn't trigger rebuilds:\n\n```makefile\n# Regular prerequisites trigger rebuild\n$(BUILD_DIR)/app: $(SOURCES)\n\t$(CC) -o $@ $^\n\n# Order-only prerequisites (directories) don't trigger rebuild\n$(BUILD_DIR)/app: $(SOURCES) | $(BUILD_DIR)\n\t$(CC) -o $@ $^\n\n$(BUILD_DIR):\n\tmkdir -p $@\n\n# Without |, updating BUILD_DIR timestamp would trigger app rebuild\n# With |, app only rebuilds when SOURCES change\n```\n\n### VPATH for Source Organization\n\n```makefile\n# Search for prerequisites in multiple directories\nVPATH = src:include:tests\n\n# Or use vpath for specific patterns\nvpath %.c src\nvpath %.h include\nvpath %.test tests\n\n# Now Make will find files in these directories\napp: main.o utils.o\n\t$(CC) -o $@ $^\n\n# Make will find src/main.c and src/utils.c automatically\n```\n\n## Performance Optimization\n\n### Use .PHONY for Performance\n\n```makefile\n# GOOD: Phony targets skip implicit rule search\n.PHONY: clean test install\n\nclean:\n\trm -rf $(BUILD_DIR)\n\n# BAD: Without .PHONY, Make checks for file existence\nclean:\n\trm -rf $(BUILD_DIR)\n```\n\n### Parallel Builds\n\n```makefile\n# Enable parallel builds (use -j flag)\n# make -j8 build # 8 parallel jobs\n\n# For sequential targets, use .NOTPARALLEL\n.NOTPARALLEL: deploy\n\ndeploy: build test\n\t./scripts/deploy.sh\n\n# Or use order-only prerequisites for partial ordering\nbuild-frontend: | build-backend\n\tnpm run build\n```\n\n### Intermediate File Cleanup\n\n```makefile\n# Mark intermediate files for auto-deletion\n.INTERMEDIATE: $(OBJECTS)\n\n# Or mark files to keep through one build\n.SECONDARY: $(OBJECTS)\n\n# Delete on error (recommended)\n.DELETE_ON_ERROR:\n\n# Example: .o files cleaned after linking\napp: main.o utils.o\n\t$(CC) -o $@ $^\n# main.o and utils.o auto-deleted after successful build\n```\n\n### Avoid Redundant Shell Calls\n\n```makefile\n# BAD: Shell called every time variable is used\nDATE = $(shell date +%Y%m%d)\nVERSION = $(shell git describe --tags)\n\ntarget1:\n\techo $(DATE) # Shell called here\n\ntarget2:\n\techo $(DATE) # Shell called again!\n\n# GOOD: Use := for one-time evaluation\nDATE := $(shell date +%Y%m%d)\nVERSION := $(shell git describe --tags)\n\ntarget1:\n\techo $(DATE) # Expands to cached value\n\ntarget2:\n\techo $(DATE) # Same cached value\n```\n\n## Portability\n\n### POSIX Shell Compatibility\n\n```makefile\n# GOOD: POSIX-compatible commands\n.PHONY: install\ninstall:\n\tmkdir -p $(DESTDIR)$(PREFIX)/bin\n\tcp -f app $(DESTDIR)$(PREFIX)/bin/\n\tchmod 755 $(DESTDIR)$(PREFIX)/bin/app\n\n# AVOID: Bashisms or GNU-specific features\ninstall:\n\tmkdir -p $(DESTDIR)$(PREFIX)/bin\n\tcp app $(DESTDIR)$(PREFIX)/bin/ # Missing -f for portability\n```\n\n### Cross-Platform Variables\n\n```makefile\n# Detect operating system\nUNAME_S := $(shell uname -s)\nifeq ($(UNAME_S),Linux)\n\tPLATFORM := linux\n\tEXE_EXT :=\nendif\nifeq ($(UNAME_S),Darwin)\n\tPLATFORM := macos\n\tEXE_EXT :=\nendif\nifeq ($(OS),Windows_NT)\n\tPLATFORM := windows\n\tEXE_EXT := .exe\nendif\n\n# Use platform-specific settings\nAPP := app$(EXE_EXT)\n```\n\n### Avoid Hard-Coded Paths\n\n```makefile\n# BAD: Hard-coded paths\ninstall:\n\tcp app /usr/local/bin/\n\tcp docs/app.1 /usr/share/man/man1/\n\n# GOOD: Use variables for paths\nPREFIX ?= /usr/local\nBINDIR ?= $(PREFIX)/bin\nMANDIR ?= $(PREFIX)/share/man\n\ninstall:\n\tinstall -d $(DESTDIR)$(BINDIR)\n\tinstall -m 755 app $(DESTDIR)$(BINDIR)/\n\tinstall -d $(DESTDIR)$(MANDIR)/man1\n\tinstall -m 644 docs/app.1 $(DESTDIR)$(MANDIR)/man1/\n```\n\n## Documentation\n\n### Comment Your Makefiles\n\n```makefile\n# Project: MyApp\n# Description: Build system for MyApp project\n# Author: Your Name\n# Version: 1.0.0\n\n# Configuration variables\nPROJECT := myapp\nVERSION := $(shell git describe --tags 2>/dev/null || echo \"dev\")\n\n# Build directories\nBUILD_DIR := build\nSRC_DIR := src\n\n# Compiler settings\nCC := gcc\nCFLAGS := -Wall -Wextra -O2\n\n# Default target: Build and test the application\n.PHONY: all\nall: build test\n\n# Build the main application binary\n.PHONY: build\nbuild: $(BUILD_DIR)/$(PROJECT)\n\t@echo \"Build complete: $(BUILD_DIR)/$(PROJECT)\"\n\n# Run all test suites\n.PHONY: test\ntest:\n\t@echo \"Running tests...\"\n\t@./scripts/run-tests.sh\n```\n\n### Help Target\n\n```makefile\n# Provide a help target\n.PHONY: help\nhelp:\n\t@echo \"Available targets:\"\n\t@echo \" make build - Build the application\"\n\t@echo \" make test - Run tests\"\n\t@echo \" make clean - Remove build artifacts\"\n\t@echo \" make install - Install to $(PREFIX)\"\n\t@echo \"\"\n\t@echo \"Variables:\"\n\t@echo \" PREFIX=$(PREFIX)\"\n\t@echo \" CC=$(CC)\"\n\n# Or auto-generate from comments\n.PHONY: help\nhelp:\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$' $(MAKEFILE_LIST) | \\\n\t\tawk 'BEGIN {FS = \":.*?## \"}; {printf \" %-20s %s\\n\", $1, $2}'\n\nbuild: ## Build the application\n\t@go build -o app\n\ntest: ## Run all tests\n\t@go test ./...\n\nclean: ## Remove build artifacts\n\t@rm -rf $(BUILD_DIR)\n```\n\n## Security\n\n### Avoid Hardcoded Credentials\n\n```makefile\n# BAD: Hardcoded secrets\ndeploy:\n\tcurl -H \"Authorization: Bearer sk-1234567890\" https://api.example.com/deploy\n\n# GOOD: Use environment variables\ndeploy:\n\t@if [ -z \"$API_TOKEN\" ]; then \\\n\t\techo \"Error: API_TOKEN not set\"; \\\n\t\texit 1; \\\n\tfi\n\tcurl -H \"Authorization: Bearer $API_TOKEN\" https://api.example.com/deploy\n```\n\n### Validate Input Variables\n\n```makefile\n# Validate critical variables\n.PHONY: deploy\ndeploy:\n\t@if [ -z \"$(ENV)\" ]; then \\\n\t\techo \"Error: ENV not specified (prod|staging|dev)\"; \\\n\t\texit 1; \\\n\tfi\n\t@if [ \"$(ENV)\" != \"prod\" ] && [ \"$(ENV)\" != \"staging\" ] && [ \"$(ENV)\" != \"dev\" ]; then \\\n\t\techo \"Error: Invalid ENV=$(ENV)\"; \\\n\t\texit 1; \\\n\tfi\n\t@echo \"Deploying to $(ENV)...\"\n\t./scripts/deploy.sh $(ENV)\n```\n\n### Safe Variable Expansion\n\n```makefile\n# BAD: Unsafe variable expansion\nclean:\n\trm -rf $(BUILD_DIR)/* # Dangerous if BUILD_DIR is empty or /\n\n# GOOD: Validate before dangerous operations\n.PHONY: clean\nclean:\n\t@if [ -z \"$(BUILD_DIR)\" ] || [ \"$(BUILD_DIR)\" = \"/\" ]; then \\\n\t\techo \"Error: Invalid BUILD_DIR\"; \\\n\t\texit 1; \\\n\tfi\n\trm -rf $(BUILD_DIR)/*\n\n# BETTER: Use safer patterns\nBUILD_DIR := build # Never empty\nclean:\n\t@test -d $(BUILD_DIR) && rm -rf $(BUILD_DIR)/* || true\n```\n\n## Advanced Patterns\n\n### Pattern Rules\n\n```makefile\n# Pattern rule for object files\n%.o: %.c\n\t$(CC) $(CFLAGS) -c $\u003c -o $@\n\n# Multiple pattern rules\n$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c\n\t@mkdir -p $(dir $@)\n\t$(CC) $(CFLAGS) -c $\u003c -o $@\n\n# Static pattern rules\n$(OBJECTS): %.o: %.c\n\t$(CC) $(CFLAGS) -c $\u003c -o $@\n```\n\n### Automatic Variables\n\n```makefile\n# $@ - Target name\n# $\u003c - First prerequisite\n# $^ - All prerequisites\n# $? - Prerequisites newer than target\n# $* - Stem of pattern rule\n\nbuild/%.o: src/%.c\n\t@mkdir -p $(dir $@) # Directory of target\n\t$(CC) -c $\u003c -o $@ # First prereq to target\n\t@echo \"Built $@\" # Target name\n\n# Example:\n# build/main.o: src/main.c\n# $@ = build/main.o\n# $\u003c = src/main.c\n# $* = main\n```\n\n### Functions\n\n```makefile\n# Built-in functions\nSOURCES := $(wildcard src/*.c)\nOBJECTS := $(patsubst src/%.c,build/%.o,$(SOURCES))\nHEADERS := $(shell find include -name '*.h')\n\n# String manipulation\nUPPERCASE := $(shell echo $(PROJECT) | tr '[:lower:]' '[:upper:]')\nVERSION_MAJOR := $(word 1,$(subst ., ,$(VERSION)))\n\n# Custom functions\ndefine compile_template\n$(1): $(2)\n\t$(CC) $(CFLAGS) -c $\u003c -o $@\nendef\n\n$(foreach src,$(SOURCES),$(eval $(call compile_template,$(patsubst %.c,%.o,$(src)),$(src))))\n```\n\n### Conditional Compilation\n\n```makefile\n# Debug vs Release builds\nDEBUG ?= 0\n\nifeq ($(DEBUG),1)\n\tCFLAGS := -g -O0 -DDEBUG\n\tBUILD_TYPE := debug\nelse\n\tCFLAGS := -O2 -DNDEBUG\n\tBUILD_TYPE := release\nendif\n\nbuild:\n\t@echo \"Building $(BUILD_TYPE) version...\"\n\t$(CC) $(CFLAGS) -o app $(SOURCES)\n```\n\n## Summary Checklist\n\n- [ ] **.DELETE_ON_ERROR:** declared at top (critical)\n- [ ] All non-file targets declared as .PHONY\n- [ ] Tabs used for recipe indentation (not spaces)\n- [ ] Variables use := for immediate expansion\n- [ ] Sane defaults with ?= for user override\n- [ ] Dependencies properly specified\n- [ ] Error handling in critical recipes\n- [ ] Default target documented and listed first\n- [ ] No hardcoded credentials or paths\n- [ ] Help target provided\n- [ ] Parallel build safety considered\n- [ ] Intermediate files managed (.INTERMEDIATE/.SECONDARY)\n- [ ] Comments explain complex logic\n- [ ] Portable commands used (POSIX compatible)\n- [ ] Variables validated before dangerous operations\n- [ ] .SUFFIXES: considered for disabling built-in rules\n\n## Additional Resources\n\n- [GNU Make Manual](https://www.gnu.org/software/make/manual/)\n- [Makefile Style Guide](https://clarkgrubb.com/makefile-style-guide)\n- [Advanced Makefile Tricks](https://www.gnu.org/software/make/manual/html_node/Quick-Reference.html)\n- [Recursive Make Considered Harmful](https://aegis.sourceforge.net/auug97.pdf)","content_type":"text/markdown; charset=utf-8","language":"markdown","size":18273,"content_sha256":"b75d29d9eebae0ebe40742eeeba1c4a93259b8d86a5deb6fe819d2bb063c8b83"},{"filename":"docs/common-mistakes.md","content":"# Common Makefile Mistakes\n\nA comprehensive guide to common mistakes in Makefiles, their consequences, and how to fix them.\n\n## Table of Contents\n\n1. [Critical Missing Declarations](#critical-missing-declarations)\n2. [Syntax Errors](#syntax-errors)\n3. [Indentation Issues](#indentation-issues)\n4. [Target and Dependency Problems](#target-and-dependency-problems)\n5. [Variable Issues](#variable-issues)\n6. [Security Vulnerabilities](#security-vulnerabilities)\n7. [Performance Problems](#performance-problems)\n8. [Portability Issues](#portability-issues)\n9. [Build Logic Errors](#build-logic-errors)\n\n## Critical Missing Declarations\n\n### 0. Missing .DELETE_ON_ERROR\n\n**Problem**: Not declaring `.DELETE_ON_ERROR` (most common critical mistake)\n\n```makefile\n# WRONG: Missing .DELETE_ON_ERROR\n.PHONY: all clean\n\nall: app.bin\n\napp.bin: app.c\n\t$(CC) -o $@ $\u003c\n\n# If compilation fails partway through, a partial/corrupt app.bin may exist\n# Next \"make\" sees the file and thinks target is up-to-date!\n```\n\n**Solution**: Always add `.DELETE_ON_ERROR:` at the top\n\n```makefile\n# CORRECT: Always include .DELETE_ON_ERROR\n.DELETE_ON_ERROR:\n\n.PHONY: all clean\n\nall: app.bin\n\napp.bin: app.c\n\t$(CC) -o $@ $\u003c\n\n# Now if build fails, the partial file is deleted\n# Next \"make\" will properly rebuild\n```\n\n**Impact**:\n- Corrupt/partial files left behind after failed builds\n- Subsequent builds silently use corrupt files\n- Very difficult to debug (\"it worked yesterday!\")\n\n**GNU Make Manual Quote**: *\"This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.\"*\n\n### 0b. Not Clearing .SUFFIXES\n\n**Problem**: Built-in suffix rules slow down large projects\n\n```makefile\n# Slow: Make checks ~90 built-in suffix rules\n%.o: %.c\n\t$(CC) -c $\u003c -o $@\n```\n\n**Solution**: Clear .SUFFIXES for faster builds\n\n```makefile\n# Fast: Disable built-in suffix rules\n.SUFFIXES:\n\n%.o: %.c\n\t$(CC) -c $\u003c -o $@\n```\n\n**Impact**: Up to 40% faster rule resolution on large projects\n\n## Syntax Errors\n\n### 1. Spaces Instead of Tabs\n\n**Problem**: Using spaces for recipe indentation\n\n```makefile\n# WRONG: Spaces (will fail!)\nbuild:\n echo \"Building...\" # 4 spaces\n go build -o app # 4 spaces\n\n# Error: Makefile:2: *** missing separator. Stop.\n```\n\n**Solution**: Use TAB characters\n\n```makefile\n# CORRECT: Tab characters\nbuild:\n\techo \"Building...\" # TAB\n\tgo build -o app # TAB\n```\n\n**Impact**: Build fails immediately with confusing error message\n\n**Detection**: mbake automatically detects and fixes this issue\n\n### 2. Missing Colon After Target\n\n**Problem**: Forgetting colon in target definition\n\n```makefile\n# WRONG\nbuild $(SOURCES)\n\t$(CC) -o app $^\n\n# Error: Makefile:1: *** missing separator. Stop.\n```\n\n**Solution**: Always include colon\n\n```makefile\n# CORRECT\nbuild: $(SOURCES)\n\t$(CC) -o app $^\n```\n\n### 3. Incorrect Line Continuation\n\n**Problem**: Missing backslash or space after backslash\n\n```makefile\n# WRONG: Missing backslash\nSOURCES = main.c\n utils.c\n config.c\n\n# WRONG: Space after backslash\nSOURCES = main.c \\\n utils.c \\\n config.c\n\n# Error: Unexpected token or incorrect variable value\n```\n\n**Solution**: Proper line continuation\n\n```makefile\n# CORRECT\nSOURCES = main.c \\\n utils.c \\\n config.c\n\n# Or use wildcards\nSOURCES := $(wildcard src/*.c)\n```\n\n### 4. Mismatched Quotes\n\n**Problem**: Unmatched or incorrect quotes\n\n```makefile\n# WRONG\nmessage:\n\techo \"Building project $(PROJECT)'\n\n# Error: Syntax error or unexpected behavior\n```\n\n**Solution**: Match quotes properly\n\n```makefile\n# CORRECT\nmessage:\n\techo \"Building project $(PROJECT)\"\n\n# Or use single quotes\nmessage:\n\techo 'Building project $(PROJECT)'\n```\n\n## Indentation Issues\n\n### 5. Mixed Tabs and Spaces\n\n**Problem**: Mixing tabs and spaces in recipes\n\n```makefile\n# WRONG: First line has tab, second has spaces\nbuild:\n\t@echo \"Starting...\"\n go build -o app # Spaces!\n\n# Error: Makefile:3: *** missing separator. Stop.\n```\n\n**Solution**: Use tabs consistently\n\n```makefile\n# CORRECT: All tabs\nbuild:\n\t@echo \"Starting...\"\n\tgo build -o app\n```\n\n**Editor Configuration**:\n```vim\n\" Vim: .vimrc\nautocmd FileType make setlocal noexpandtab\n\n# VS Code: settings.json\n\"[makefile]\": {\n \"editor.insertSpaces\": false,\n \"editor.detectIndentation\": false\n}\n```\n\n### 6. Tab Width Confusion\n\n**Problem**: Assuming tab width instead of using actual tabs\n\n```makefile\n# WRONG: Looks like tab but is 8 spaces\nbuild:\n echo \"Building...\" # 8 spaces, not a tab!\n```\n\n**Solution**: Configure editor to show whitespace and use real tabs\n\n```makefile\n# CORRECT: Actual tab character\nbuild:\n\techo \"Building...\" # TAB (shows as single character)\n```\n\n## Target and Dependency Problems\n\n### 7. Missing .PHONY Declarations\n\n**Problem**: Not declaring non-file targets as phony\n\n```makefile\n# WRONG: Missing .PHONY\nclean:\n\trm -rf build\n\ntest:\n\tgo test ./...\n\n# If files named 'clean' or 'test' exist, targets won't run!\n# $ touch clean # Create a file named 'clean'\n# $ make clean\n# make: 'clean' is up to date.\n```\n\n**Solution**: Always declare non-file targets\n\n```makefile\n# CORRECT: Declare .PHONY targets\n.PHONY: clean test all install\n\nclean:\n\trm -rf build\n\ntest:\n\tgo test ./...\n```\n\n**Impact**:\n- 35%+ of developers face issues due to missing .PHONY\n- Targets may not run if files with same names exist\n- Performance degradation (implicit rule search)\n\n### 8. Incorrect Dependency Specification\n\n**Problem**: Missing or incomplete dependencies\n\n```makefile\n# WRONG: Missing header dependencies\napp: main.o utils.o\n\t$(CC) -o $@ $^\n\n%.o: %.c\n\t$(CC) $(CFLAGS) -c $\u003c\n\n# If headers change, .o files won't rebuild!\n```\n\n**Solution**: Include all dependencies\n\n```makefile\n# CORRECT: Include header dependencies\napp: main.o utils.o\n\t$(CC) -o $@ $^\n\nmain.o: main.c main.h common.h\n\t$(CC) $(CFLAGS) -c main.c\n\nutils.o: utils.c utils.h common.h\n\t$(CC) $(CFLAGS) -c utils.c\n\n# BETTER: Auto-generate dependencies\nDEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d\n%.o: %.c\n\t$(CC) $(DEPFLAGS) $(CFLAGS) -c $\u003c\n\n-include $(DEPS)\n```\n\n**Impact**: Over 60% reduction in unnecessary recompilation with proper dependencies\n\n### 9. Circular Dependencies\n\n**Problem**: Targets depending on each other\n\n```makefile\n# WRONG: Circular dependency\nA: B\n\t@echo \"Target A\"\n\nB: A\n\t@echo \"Target B\"\n\n# Error: Makefile:1: *** Circular A \u003c- B dependency dropped.\n```\n\n**Solution**: Break the cycle\n\n```makefile\n# CORRECT: Proper dependency chain\nA: B\n\t@echo \"Target A depends on B\"\n\nB: C\n\t@echo \"Target B depends on C\"\n\nC:\n\t@echo \"Target C has no dependencies\"\n```\n\n### 10. Phony Target as Prerequisite of Real Target\n\n**Problem**: Using phony target as dependency of file target\n\n```makefile\n# WRONG: Phony prerequisite causes always-rebuild\n.PHONY: generate\n\napp.o: app.c generate\n\t$(CC) -c app.c -o app.o\n\ngenerate:\n\t./gen-config.sh\n\n# app.o rebuilds EVERY time because 'generate' is always out of date\n```\n\n**Solution**: Use real file dependencies\n\n```makefile\n# CORRECT: Depend on actual generated file\napp.o: app.c config.h\n\t$(CC) -c app.c -o app.o\n\nconfig.h:\n\t./gen-config.sh\n```\n\n## Variable Issues\n\n### 11. Using = Instead of :=\n\n**Problem**: Recursive expansion causing performance issues\n\n```makefile\n# WRONG: Recursive expansion (re-evaluated every time)\nBUILD_TIME = $(shell date +%Y%m%d-%H%M%S)\nGIT_HASH = $(shell git rev-parse HEAD)\n\ntarget1:\n\techo $(BUILD_TIME) # Shell called here\n\ntarget2:\n\techo $(BUILD_TIME) # Shell called AGAIN with different time!\n\techo $(GIT_HASH) # Shell called here\n\ntarget3:\n\techo $(GIT_HASH) # Shell called AGAIN!\n```\n\n**Solution**: Use := for immediate expansion\n\n```makefile\n# CORRECT: Immediate expansion (evaluated once)\nBUILD_TIME := $(shell date +%Y%m%d-%H%M%S)\nGIT_HASH := $(shell git rev-parse HEAD)\n\ntarget1:\n\techo $(BUILD_TIME) # Uses cached value\n\ntarget2:\n\techo $(BUILD_TIME) # Same cached value\n\techo $(GIT_HASH) # Cached value\n\ntarget3:\n\techo $(GIT_HASH) # Same cached value\n```\n\n**Impact**: Can cause significant slowdown and inconsistent builds\n\n### 12. Undefined Variables\n\n**Problem**: Using variables without defaults\n\n```makefile\n# WRONG: No default value\ninstall:\n\tcp app $(PREFIX)/bin/\n\n# If PREFIX is not set, installs to /bin/ (wrong!) or fails\n```\n\n**Solution**: Always provide defaults\n\n```makefile\n# CORRECT: Provide sensible defaults\nPREFIX ?= /usr/local\nBINDIR ?= $(PREFIX)/bin\n\ninstall:\n\tmkdir -p $(DESTDIR)$(BINDIR)\n\tcp app $(DESTDIR)$(BINDIR)/\n```\n\n### 13. Incorrect Variable Expansion\n\n**Problem**: Using wrong expansion syntax\n\n```makefile\n# WRONG: Shell variable vs Make variable confusion\nbuild:\n\tfor file in *.c; do \\\n\t\techo \"Compiling $file\"; \\\n\t\t$(CC) -c $file; \\\n\tdone\n\n# $file expands as Make variable (empty!), not shell variable\n# Output: Compiling (nothing)\n```\n\n**Solution**: Escape shell variables\n\n```makefile\n# CORRECT: Escape $ for shell variables\nbuild:\n\tfor file in *.c; do \\\n\t\techo \"Compiling $file\"; \\\n\t\t$(CC) -c $file; \\\n\tdone\n\n# Output: Compiling main.c, Compiling utils.c, etc.\n```\n\n### 14. Variable Naming Conflicts\n\n**Problem**: Overriding special Make variables\n\n```makefile\n# WRONG: Overriding built-in variable\nMAKEFLAGS = -j4 # This overrides Make's internal flags!\n\n# AVOID: Using reserved names\nMAKE = my-build-tool # Breaks recursive make\n```\n\n**Solution**: Use unique names\n\n```makefile\n# CORRECT: Use custom names for your variables\nBUILD_FLAGS := -j4\nMY_BUILD_TOOL := custom-builder\n\nbuild:\n\t$(MAKE) -f sub.mk $(BUILD_FLAGS)\n```\n\n## Security Vulnerabilities\n\n### 15. Hardcoded Credentials\n\n**Problem**: Secrets in Makefile\n\n```makefile\n# WRONG: Hardcoded secrets\nAPI_KEY = sk-1234567890abcdef\nDB_PASSWORD = super_secret_123\n\ndeploy:\n\tcurl -H \"Authorization: Bearer $(API_KEY)\" https://api.example.com/\n\tpsql -U admin -p $(DB_PASSWORD) -c \"SELECT version();\"\n```\n\n**Solution**: Use environment variables\n\n```makefile\n# CORRECT: Load from environment\ndeploy:\n\t@if [ -z \"$API_KEY\" ]; then \\\n\t\techo \"Error: API_KEY not set\"; \\\n\t\texit 1; \\\n\tfi\n\tcurl -H \"Authorization: Bearer $API_KEY\" https://api.example.com/\n\n# Or use a .env file (not committed)\ninclude .env\nexport\n```\n\n**Impact**: Credentials exposed in version control, logs, and process listings\n\n### 16. Unsafe Variable Expansion\n\n**Problem**: Unvalidated variables in dangerous commands\n\n```makefile\n# WRONG: Unsafe rm command\nBUILD_DIR = $(USER_INPUT)\n\nclean:\n\trm -rf $(BUILD_DIR)/*\n\n# If BUILD_DIR is empty or \"/\", this is catastrophic!\n# $ make clean BUILD_DIR=/\n# rm -rf /* # Disaster!\n```\n\n**Solution**: Validate before dangerous operations\n\n```makefile\n# CORRECT: Validate variables\nBUILD_DIR := build # Default value\n\nclean:\n\t@if [ -z \"$(BUILD_DIR)\" ] || [ \"$(BUILD_DIR)\" = \"/\" ]; then \\\n\t\techo \"Error: Invalid BUILD_DIR=$(BUILD_DIR)\"; \\\n\t\texit 1; \\\n\tfi\n\t@if [ -d \"$(BUILD_DIR)\" ]; then \\\n\t\trm -rf $(BUILD_DIR)/*; \\\n\tfi\n```\n\n### 17. Command Injection\n\n**Problem**: Unsanitized input in shell commands\n\n```makefile\n# WRONG: User input directly in command\ndeploy:\n\tssh user@$(SERVER) \"cd /app && git pull origin $(BRANCH)\"\n\n# Malicious input: BRANCH=\"; rm -rf /\"\n# Executes: git pull origin ; rm -rf /\n```\n\n**Solution**: Validate and quote input\n\n```makefile\n# CORRECT: Validate input\nALLOWED_BRANCHES := main develop staging\nBRANCH ?= main\n\ndeploy:\n\t@if ! echo \"$(ALLOWED_BRANCHES)\" | grep -wq \"$(BRANCH)\"; then \\\n\t\techo \"Error: Invalid branch $(BRANCH)\"; \\\n\t\texit 1; \\\n\tfi\n\tssh user@$(SERVER) \"cd /app && git pull origin '$(BRANCH)'\"\n```\n\n### 18. Logging Sensitive Information\n\n**Problem**: Echoing secrets in build output\n\n```makefile\n# WRONG: Secrets visible in logs\ndeploy:\n\techo \"Deploying with token: $(API_TOKEN)\"\n\tcurl -H \"Authorization: Bearer $(API_TOKEN)\" https://api.example.com/\n```\n\n**Solution**: Suppress sensitive output\n\n```makefile\n# CORRECT: Hide sensitive information\ndeploy:\n\t@echo \"Deploying to production...\"\n\t@curl -s -H \"Authorization: Bearer $API_TOKEN\" https://api.example.com/\n\t@echo \"Deployment complete\"\n\n# Or mask partial value\n\t@echo \"Using token: ${API_TOKEN:0:8}...\"\n```\n\n## Performance Problems\n\n### 19. Inefficient Wildcards\n\n**Problem**: Repeated wildcard evaluation\n\n```makefile\n# WRONG: wildcard called every time\nbuild:\n\t$(CC) -o app $(wildcard src/*.c)\n\ntest:\n\tfor file in $(wildcard tests/*.sh); do bash $file; done\n\n# wildcard searches filesystem every time these targets run\n```\n\n**Solution**: Evaluate once with :=\n\n```makefile\n# CORRECT: Evaluate wildcard once\nSOURCES := $(wildcard src/*.c)\nTESTS := $(wildcard tests/*.sh)\n\nbuild:\n\t$(CC) -o app $(SOURCES)\n\ntest:\n\tfor file in $(TESTS); do bash $file; done\n```\n\n**Impact**: Significant speedup for large projects (40%+ in some cases)\n\n### 20. Missing Incremental Build Support\n\n**Problem**: Always rebuilding everything\n\n```makefile\n# WRONG: No incremental build\nbuild:\n\trm -rf build\n\tmkdir -p build\n\t$(CC) -o build/app $(SOURCES)\n\n# Rebuilds from scratch every time!\n```\n\n**Solution**: Proper dependency tracking\n\n```makefile\n# CORRECT: Incremental build\nOBJECTS := $(patsubst src/%.c,build/%.o,$(SOURCES))\n\nbuild: build/app\n\nbuild/app: $(OBJECTS)\n\t$(CC) -o $@ $^\n\nbuild/%.o: src/%.c\n\t@mkdir -p $(dir $@)\n\t$(CC) $(CFLAGS) -c $\u003c -o $@\n\n# Only rebuilds changed files\n```\n\n**Impact**: Can reduce build times by up to 60% with proper dependencies\n\n### 21. Not Using Pattern Rules\n\n**Problem**: Duplicated rules for similar targets\n\n```makefile\n# WRONG: Repetitive rules\nmain.o: main.c\n\t$(CC) $(CFLAGS) -c main.c -o main.o\n\nutils.o: utils.c\n\t$(CC) $(CFLAGS) -c utils.c -o utils.o\n\nconfig.o: config.c\n\t$(CC) $(CFLAGS) -c config.c -o config.o\n\n# Lots of duplication!\n```\n\n**Solution**: Use pattern rules\n\n```makefile\n# CORRECT: Single pattern rule\n%.o: %.c\n\t$(CC) $(CFLAGS) -c $\u003c -o $@\n\n# Or with directories\nbuild/%.o: src/%.c\n\t@mkdir -p $(dir $@)\n\t$(CC) $(CFLAGS) -c $\u003c -o $@\n```\n\n## Portability Issues\n\n### 22. Assuming GNU Make\n\n**Problem**: Using GNU Make-specific features\n\n```makefile\n# WRONG: GNU Make specific\nSOURCES := $(shell find src -name '*.c')\n\nbuild: $(SOURCES:.c=.o)\n\t$(CC) -o app $^\n\n# Fails with BSD make or other Make implementations\n```\n\n**Solution**: Use portable constructs\n\n```makefile\n# CORRECT: More portable (though still uses shell)\nSOURCES != find src -name '*.c' || find src -name '*.c'\n\n# Or manually list sources for maximum portability\nSOURCES = src/main.c src/utils.c src/config.c\n```\n\n### 23. Hard-Coded Tools\n\n**Problem**: Assuming specific tool paths\n\n```makefile\n# WRONG: Hard-coded tool paths\nCC = /usr/bin/gcc\nPYTHON = /usr/bin/python3\n\nbuild:\n\t$(CC) -o app $(SOURCES)\n```\n\n**Solution**: Use which or allow override\n\n```makefile\n# CORRECT: Allow override with defaults\nCC ?= gcc\nPYTHON ?= python3\nINSTALL ?= install\n\n# Or detect at runtime\nCC := $(shell command -v gcc || command -v clang)\n```\n\n### 24. Platform-Specific Commands\n\n**Problem**: Using OS-specific commands\n\n```makefile\n# WRONG: Linux-specific\nclean:\n\trm -rf build\n\ncopy:\n\tcp -r src/* dest/\n\n# Fails on Windows\n```\n\n**Solution**: Detect platform or use portable commands\n\n```makefile\n# CORRECT: Platform detection\nUNAME_S := $(shell uname -s 2>/dev/null || echo Windows)\n\nifeq ($(UNAME_S),Windows)\n\tRM := del /Q /S\n\tMKDIR := mkdir\nelse\n\tRM := rm -rf\n\tMKDIR := mkdir -p\nendif\n\nclean:\n\t$(RM) build\n\n# Or use Go/Python for cross-platform scripts\nclean:\n\t@go run scripts/clean.go\n```\n\n## Build Logic Errors\n\n### 25. Silent Failures\n\n**Problem**: Not checking command exit codes\n\n```makefile\n# WRONG: Ignoring failures\ntest:\n\tgo test ./pkg1\n\tgo test ./pkg2\n\tgo test ./pkg3\n\t@echo \"All tests passed!\"\n\n# If pkg1 fails, Make continues to pkg2, pkg3, and prints \"passed\"\n```\n\n**Solution**: Use set -e or check exit codes\n\n```makefile\n# CORRECT: Stop on first failure\ntest:\n\t@set -e; \\\n\tgo test ./pkg1; \\\n\tgo test ./pkg2; \\\n\tgo test ./pkg3; \\\n\techo \"All tests passed!\"\n\n# Or check explicitly\ntest:\n\t@go test ./pkg1 || exit 1\n\t@go test ./pkg2 || exit 1\n\t@go test ./pkg3 || exit 1\n\t@echo \"All tests passed!\"\n```\n\n### 26. Race Conditions in Parallel Builds\n\n**Problem**: Unsafe parallel execution\n\n```makefile\n# WRONG: Race condition with parallel builds\nall: build-frontend build-backend\n\nbuild-frontend:\n\tnpm install # Both may write to node_modules!\n\tnpm run build\n\nbuild-backend:\n\tnpm install # Race condition!\n\tgo build\n\n# With make -j2, both run npm install simultaneously\n```\n\n**Solution**: Use order dependencies or .NOTPARALLEL\n\n```makefile\n# CORRECT: Sequential dependencies\nall: build-frontend build-backend\n\nbuild-frontend: node_modules\n\tnpm run build\n\nbuild-backend: node_modules\n\tgo build\n\nnode_modules: package.json\n\tnpm install\n\t@touch node_modules # Update timestamp\n\n# Or use .NOTPARALLEL for specific target\n.NOTPARALLEL: install\n```\n\n### 27. Assuming Build Order\n\n**Problem**: Relying on target order without dependencies\n\n```makefile\n# WRONG: Assuming build is run before test\nall: build test deploy\n\nbuild:\n\tgo build -o app\n\ntest:\n\t./scripts/test.sh # Assumes app exists!\n\ndeploy:\n\t./scripts/deploy.sh # Assumes tests passed!\n\n# Direct \"make test\" or \"make deploy\" fails!\n```\n\n**Solution**: Explicit dependencies\n\n```makefile\n# CORRECT: Explicit dependencies\nall: deploy\n\nbuild:\n\tgo build -o app\n\ntest: build\n\t./scripts/test.sh\n\ndeploy: test\n\t./scripts/deploy.sh\n\n# Now \"make deploy\" automatically runs build → test → deploy\n```\n\n## Quick Fix Checklist\n\nWhen you encounter Makefile issues, check:\n\n- [ ] **Is .DELETE_ON_ERROR: declared at top?** (Critical!)\n- [ ] Are you using TAB characters (not spaces) for recipes?\n- [ ] Are all non-file targets declared as .PHONY?\n- [ ] Is .SUFFIXES: declared to disable built-in rules?\n- [ ] Are dependencies complete and correct?\n- [ ] Are variables using := instead of = for expensive operations?\n- [ ] Are shell variables escaped with $?\n- [ ] Are dangerous operations (rm, sudo) validated?\n- [ ] Are secrets loaded from environment, not hardcoded?\n- [ ] Are wildcard results cached with :=?\n- [ ] Is error handling present in critical recipes?\n- [ ] Are tools and paths configurable (CC ?= gcc)?\n- [ ] Is parallel build safety considered?\n- [ ] Are pattern rules used instead of duplicate rules?\n\n## Impact Statistics\n\nAccording to research from build system studies (2024-2025):\n\n- **35%** of developers face issues with outdated targets due to improper dependencies\n- **40%** experience inaccurate profiling due to incorrect compiler flag usage\n- **60%** reduction in unnecessary recompilation possible with proper dependency tracking\n- **40%** faster incremental builds achievable with optimized Makefile patterns\n\n## Additional Resources\n\n- [GNU Make Manual - Common Mistakes](https://www.gnu.org/software/make/manual/)\n- [Makefile Tutorial - Pitfalls](https://makefiletutorial.com/)\n- [mbake - Automatic Formatting](https://github.com/EbodShojaei/bake)\n\n## Sources\n\n- [Common Mistakes in Makefile Incremental Builds](https://moldstud.com/articles/p-common-mistakes-in-makefile-incremental-builds-and-how-to-fix-them)\n- [Makefile Madness: Common Pitfalls](https://moldstud.com/articles/p-makefile-madness-common-pitfalls-and-how-to-avoid-them)\n- [Makefile Best Practices](https://danyspin97.org/blog/makefiles-best-practices/)","content_type":"text/markdown; charset=utf-8","language":"markdown","size":19121,"content_sha256":"cf71b3cf3bb30851558d3583cb424c72a1efd6264dfe0e50117612bec1107199"},{"filename":"scripts/test_validate.sh","content":"#!/usr/bin/env bash\n#\n# Regression test suite for validate_makefile.sh\n#\n# Runs the validator against fixture files and asserts expected exit codes and\n# output patterns. Each fixture file is validated once; all assertions on it\n# share that single run so mbake is only installed once per fixture.\n#\n# Exit 0 when all assertions pass; exit 1 on the first failure.\n#\n# Usage:\n# bash scripts/test_validate.sh # from makefile-validator/ directory\n# bash devops-skills-plugin/skills/makefile-validator/scripts/test_validate.sh\n#\n# Suites:\n# MAKEFILE_VALIDATOR_TEST_SUITE=offline # deterministic default (MBAKE_SKIP_INSTALL=1)\n# MAKEFILE_VALIDATOR_TEST_SUITE=integration # mbake install path checks\n# MAKEFILE_VALIDATOR_TEST_SUITE=all # run both suites\n\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nreadonly SCRIPT_DIR\nVALIDATOR=\"$SCRIPT_DIR/validate_makefile.sh\"\nEXAMPLES_DIR=\"$SCRIPT_DIR/../examples\"\nTEST_SUITE=\"${MAKEFILE_VALIDATOR_TEST_SUITE:-${TEST_SUITE:-offline}}\"\n\nPASS=0\nFAIL=0\nTMPFILES=()\nRUN_OFFLINE=0\nRUN_INTEGRATION=0\n\ncase \"$TEST_SUITE\" in\n offline)\n RUN_OFFLINE=1\n ;;\n integration)\n RUN_INTEGRATION=1\n ;;\n all)\n RUN_OFFLINE=1\n RUN_INTEGRATION=1\n ;;\n *)\n echo \"[ERROR] Unsupported test suite: $TEST_SUITE\" >&2\n echo \" Use one of: offline, integration, all\" >&2\n exit 2\n ;;\nesac\n\n# ─── cleanup ────────────────────────────────────────────────────────────────\n\ncleanup() {\n local f\n for f in \"${TMPFILES[@]:-}\"; do\n if [[ -d \"$f\" ]]; then\n rm -rf \"$f\" 2>/dev/null || true\n else\n rm -f \"$f\" 2>/dev/null || true\n fi\n done\n TMPFILES=()\n}\ntrap cleanup EXIT INT TERM\n\n# ─── helpers ────────────────────────────────────────────────────────────────\n\npass() { printf \" PASS %s\\n\" \"$1\"; PASS=$((PASS + 1)); }\nfail() { printf \" FAIL %s\\n\" \"$1\"; FAIL=$((FAIL + 1)); }\n\n# Run the validator once; populate OUTPUT and EXIT_CODE globals.\n# The || true prevents set -e from aborting on a non-zero validator exit.\n_run() {\n local file=\"$1\"\n local mbake_skip_install=\"${2:-1}\"\n EXIT_CODE=0\n OUTPUT=$(NO_COLOR=1 MBAKE_SKIP_INSTALL=\"$mbake_skip_install\" bash \"$VALIDATOR\" \"$file\" 2>&1) || EXIT_CODE=$?\n}\n\n# Assert the exit code stored in EXIT_CODE equals $1.\nassert_exit() {\n local label=\"$1\" expected=\"$2\"\n if [[ \"$EXIT_CODE\" -eq \"$expected\" ]]; then\n pass \"$label (exit $EXIT_CODE)\"\n else\n fail \"$label — expected exit $expected, got $EXIT_CODE\"\n printf '%s\\n' \"$OUTPUT\" | sed 's/^/ /'\n fi\n}\n\n# Assert that OUTPUT matches an ERE pattern.\nassert_contains() {\n local label=\"$1\" pattern=\"$2\"\n if printf '%s\\n' \"$OUTPUT\" | grep -qE \"$pattern\"; then\n pass \"$label\"\n else\n fail \"$label — pattern not found: $pattern\"\n printf '%s\\n' \"$OUTPUT\" | sed 's/^/ /'\n fi\n}\n\n# Assert that OUTPUT does NOT match an ERE pattern.\nassert_not_contains() {\n local label=\"$1\" pattern=\"$2\"\n if printf '%s\\n' \"$OUTPUT\" | grep -qE \"$pattern\"; then\n fail \"$label — unexpected pattern found: $pattern\"\n printf '%s\\n' \"$OUTPUT\" | grep -E \"$pattern\" | sed 's/^/ /'\n else\n pass \"$label\"\n fi\n}\n\n# Assert a path does not exist.\nassert_path_absent() {\n local label=\"$1\" path=\"$2\"\n if [[ ! -e \"$path\" ]]; then\n pass \"$label\"\n else\n fail \"$label — path still exists: $path\"\n fi\n}\n\n# Create a named temp fixture (.mk extension) and register it for cleanup.\n# macOS mktemp requires Xs to be at the very end of the template, so we create\n# a plain temp file and rename it with a .mk suffix.\nmktemp_mk() {\n local base f\n base=$(mktemp \"${TMPDIR:-/tmp}/test-mk-XXXXXX\")\n f=\"${base}.mk\"\n mv \"$base\" \"$f\"\n TMPFILES+=(\"$f\")\n echo \"$f\"\n}\n\n# ─── Test Groups ────────────────────────────────────────────────────────────\n\necho \"\"\necho \"Running validate_makefile.sh regression tests (suite: $TEST_SUITE)...\"\n\nif [[ \"$RUN_OFFLINE\" -eq 1 ]]; then\n # ── Harness: cleanup removes both files and directories ───────────────────\n echo \"\"\n echo \"── harness: cleanup removes files and directories ───────────────────\"\n cleanup_file=$(mktemp \"${TMPDIR:-/tmp}/test-clean-file-XXXXXX\")\n cleanup_dir=$(mktemp -d \"${TMPDIR:-/tmp}/test-clean-dir-XXXXXX\")\n TMPFILES+=(\"$cleanup_file\" \"$cleanup_dir\")\n cleanup\n assert_path_absent \"cleanup removes temp file\" \"$cleanup_file\"\n assert_path_absent \"cleanup removes temp directory\" \"$cleanup_dir\"\n\n # ── Offline mode: deterministic mbake skip ────────────────────────────────\n echo \"\"\n echo \"── offline: deterministic mbake skip mode ───────────────────────────\"\n FAKEOFFLINEBIN=$(mktemp -d \"${TMPDIR:-/tmp}/test-offlinebin-XXXXXX\")\n TMPFILES+=(\"$FAKEOFFLINEBIN\")\n printf '#!/usr/bin/env bash\\necho \"python3: blocked\" >&2; exit 127\\n' > \"$FAKEOFFLINEBIN/python3\"\n printf '#!/usr/bin/env bash\\necho \"pip3: blocked\" >&2; exit 127\\n' > \"$FAKEOFFLINEBIN/pip3\"\n chmod +x \"$FAKEOFFLINEBIN/python3\" \"$FAKEOFFLINEBIN/pip3\"\n\n PATH=\"$FAKEOFFLINEBIN:$PATH\" _run \"$EXAMPLES_DIR/good-makefile.mk\" 1\n assert_exit \"offline mode exits cleanly\" 0\n assert_contains \"offline mode banner printed\" \"MBAKE_SKIP_INSTALL=1 enabled\"\n assert_contains \"mbake stages skipped by deterministic mode\" \"Skipped.*MBAKE_SKIP_INSTALL=1\"\n assert_not_contains \"python3 warning suppressed in offline mode\" \"python3 not found\"\n assert_not_contains \"pip3 warning suppressed in offline mode\" \"pip3 not found\"\n assert_not_contains \"venv setup skipped in offline mode\" \"Creating temporary venv|Installing mbake\"\n\n # ── good-makefile.mk ─────────────────────────────────────────────────────\n echo \"\"\n echo \"── good-makefile.mk ─────────────────────────────────────────────────\"\n _run \"$EXAMPLES_DIR/good-makefile.mk\"\n assert_exit \"exits cleanly\" 0\n assert_not_contains \"no credential false positive\" \"hardcoded credentials\"\n assert_not_contains \"no tab false positive\" \"spaces instead of tabs\"\n assert_not_contains \"no validation-failed summary\" \"Validation FAILED\"\n assert_contains \"passes summary printed\" \"Validation PASSED\"\n\n # ── bad-makefile.mk ──────────────────────────────────────────────────────\n echo \"\"\n echo \"── bad-makefile.mk ──────────────────────────────────────────────────\"\n _run \"$EXAMPLES_DIR/bad-makefile.mk\"\n assert_exit \"exits with error code\" 2\n assert_contains \"catches spaces-as-tabs\" \"spaces instead of tabs\"\n assert_contains \"catches hardcoded credentials\" \"hardcoded credentials\"\n assert_contains \"catches missing .DELETE_ON_ERROR\" \"Missing .DELETE_ON_ERROR\"\n assert_contains \"catches missing .PHONY\" \"No .PHONY declarations\"\n assert_contains \"catches unsafe variable expansion\" \"without defaults\"\n assert_contains \"catches recursive = with shell\" \"Shell commands with recursive expansion\"\n assert_contains \"failed summary printed\" \"Validation FAILED\"\n\n # ── Edge: commented-out credentials must NOT be flagged ──────────────────\n echo \"\"\n echo \"── edge: commented credentials ──────────────────────────────────────\"\n F=$(mktemp_mk)\n cat > \"$F\" \u003c\u003c 'MKEOF'\nSHELL := bash\n.DELETE_ON_ERROR:\n.PHONY: deploy\n\n# Documentation example (not a real secret):\n# API_KEY = sk-example-1234567890abcdef\n# DB_PASSWORD = super_secret_password\n # github_token = ghp_example_token\n\ndeploy:\n\t@echo \"Deploying...\"\nMKEOF\n _run \"$F\"\n assert_not_contains \"commented creds not flagged\" \"hardcoded credentials\"\n\n # ── Edge: actual credentials after comment lines must still be caught ────\n echo \"\"\n echo \"── edge: real credentials are still caught ──────────────────────────\"\n F=$(mktemp_mk)\n cat > \"$F\" \u003c\u003c 'MKEOF'\nSHELL := bash\n.DELETE_ON_ERROR:\n.PHONY: deploy\n\n# This is a comment about the key below\nAPI_KEY = sk-1234567890abcdef\n\ndeploy:\n\t@echo \"Deploying...\"\nMKEOF\n _run \"$F\"\n assert_contains \"real creds after comments still caught\" \"hardcoded credentials\"\n\n # ── Edge: lowercase variable in dangerous command ─────────────────────────\n echo \"\"\n echo \"── edge: lowercase variable in rm -rf ───────────────────────────────\"\n F=$(mktemp_mk)\n cat > \"$F\" \u003c\u003c 'MKEOF'\nSHELL := bash\n.DELETE_ON_ERROR:\n.PHONY: clean\n\nclean:\n\trm -rf $(build_dir)\nMKEOF\n _run \"$F\"\n assert_contains \"lowercase unsafe var detected\" \"without defaults\"\n\n # ── Edge: bare make call at end of line (no args) ─────────────────────────\n echo \"\"\n echo \"── edge: bare make call (no args at end of line) ────────────────────\"\n F=$(mktemp_mk)\n cat > \"$F\" \u003c\u003c 'MKEOF'\nSHELL := bash\n.DELETE_ON_ERROR:\n.PHONY: recurse\n\nrecurse:\n\tmake\nMKEOF\n _run \"$F\"\n assert_contains \"bare make call detected\" \"Direct.*call\"\n\n # ── Edge: make with args is also caught ───────────────────────────────────\n echo \"\"\n echo \"── edge: make with args ─────────────────────────────────────────────\"\n F=$(mktemp_mk)\n cat > \"$F\" \u003c\u003c 'MKEOF'\nSHELL := bash\n.DELETE_ON_ERROR:\n.PHONY: recurse\n\nrecurse:\n\tmake build\nMKEOF\n _run \"$F\"\n assert_contains \"make-with-args detected\" \"Direct.*call\"\n\n # ── Edge: $(MAKE) must NOT be flagged as a bare make call ─────────────────\n echo \"\"\n echo \"── edge: \\$(MAKE) is not flagged ────────────────────────────────────\"\n F=$(mktemp_mk)\n cat > \"$F\" \u003c\u003c 'MKEOF'\nSHELL := bash\n.DELETE_ON_ERROR:\nMAKEFLAGS += --warn-undefined-variables\nMAKEFLAGS += --no-builtin-rules\n.PHONY: recurse\n\n## Default target\nrecurse:\n\t$(MAKE) build\nMKEOF\n _run \"$F\"\n assert_not_contains \"MAKE macro not flagged\" \"Direct.*call\"\n\n # ── Edge: syntax_check single make invocation (output present on error) ───\n echo \"\"\n echo \"── edge: syntax error output is shown ───────────────────────────────\"\n F=$(mktemp_mk)\n # Write a Makefile with a deliberate syntax error (spaces instead of tabs)\n printf 'build:\\n echo broken\\n' > \"$F\"\n _run \"$F\"\n assert_contains \"syntax error output shown\" \"missing separator|Syntax errors detected\"\n\n # ── Edge: graceful degradation when python3 is absent ─────────────────────\n # Shadow python3 with a stub that exits 127 so the validator cannot set up mbake.\n # The script should warn, skip mbake stages, still run custom/syntax checks,\n # and exit with warnings (1) rather than a hard error.\n echo \"\"\n echo \"── edge: graceful degradation (no python3) ───────────────────────────\"\n FAKEBINDIR=$(mktemp -d \"${TMPDIR:-/tmp}/test-fakebin-XXXXXX\")\n TMPFILES+=(\"$FAKEBINDIR\")\n printf '#!/usr/bin/env bash\\necho \"python3: not found\" >&2; exit 127\\n' > \"$FAKEBINDIR/python3\"\n chmod +x \"$FAKEBINDIR/python3\"\n\n F=$(mktemp_mk)\n cat > \"$F\" \u003c\u003c 'MKEOF'\nSHELL := bash\n.DELETE_ON_ERROR:\nMAKEFLAGS += --warn-undefined-variables\nMAKEFLAGS += --no-builtin-rules\n.PHONY: build\n\n## Default target\nbuild:\n\t@echo \"ok\"\nMKEOF\n\n PATH=\"$FAKEBINDIR:$PATH\" _run \"$F\" 0\n assert_exit \"degraded run exits with warnings\" 1\n assert_not_contains \"no hard exit on missing python3\" \"ERROR.*python3\"\n assert_contains \"warns about missing python3\" \"python3 not found|mbake.*skipped\"\n assert_contains \"mbake stages shown as skipped\" \"Skipped.*mbake\"\n assert_contains \"custom checks section printed\" \"\\\\[CUSTOM CHECKS\\\\]\"\n assert_contains \"custom checks executed\" \"No additional issues found|No \\\\.PHONY declarations found|Missing \\\\.DELETE_ON_ERROR declaration\"\n assert_contains \"degraded summary is warning mode\" \"Validation PASSED with warnings|Validation PASSED\"\nelse\n echo \"\"\n echo \"── offline suite skipped (set MAKEFILE_VALIDATOR_TEST_SUITE=offline|all) ──\"\nfi\n\nif [[ \"$RUN_INTEGRATION\" -eq 1 ]]; then\n # ── Integration: ensure mbake path executes when skip mode is disabled ────\n echo \"\"\n echo \"── integration: mbake path is exercised ──────────────────────────────\"\n _run \"$EXAMPLES_DIR/good-makefile.mk\" 0\n assert_not_contains \"integration run does not use skip mode\" \"MBAKE_SKIP_INSTALL=1\"\n assert_not_contains \"mbake stages are not skipped\" \"Skipped.*mbake not available|Skipped.*MBAKE_SKIP_INSTALL=1\"\n assert_contains \"venv setup is attempted\" \"ENVIRONMENT SETUP|Creating temporary venv|Installing mbake\"\n assert_contains \"mbake validation stage is present\" \"Running mbake validate|mbake validation passed|mbake validation failed\"\n\n # ── Edge: .PRECIOUS (non-list special target) not flagged by format check ─\n # When mbake reports \"Unknown special target '.PRECIOUS'\", it should be\n # treated as a known mbake false-positive (not a real error or warning).\n echo \"\"\n echo \"── integration: .PRECIOUS not flagged as mbake error ─────────────────\"\n F=$(mktemp_mk)\n cat > \"$F\" \u003c\u003c 'MKEOF'\nSHELL := bash\n.DELETE_ON_ERROR:\n.PRECIOUS: %.tar.gz\nMAKEFLAGS += --warn-undefined-variables\nMAKEFLAGS += --no-builtin-rules\n.PHONY: build\n\n## Default target\nbuild:\n\t@echo \"ok\"\nMKEOF\n _run \"$F\" 0\n # .PRECIOUS is valid GNU Make but mbake may not know it; should be info, not warning/error\n assert_not_contains \".PRECIOUS not counted as error\" \"Errors:[[:space:]]+[1-9]\"\nelse\n echo \"\"\n echo \"── integration suite skipped (set MAKEFILE_VALIDATOR_TEST_SUITE=integration|all) ──\"\nfi\n\n# ─── Summary ────────────────────────────────────────────────────────────────\necho \"\"\necho \"══════════════════════════════════════════════════════════════════════\"\nprintf \" Results: %d passed, %d failed\\n\" \"$PASS\" \"$FAIL\"\necho \"══════════════════════════════════════════════════════════════════════\"\necho \"\"\n\n[[ \"$FAIL\" -eq 0 ]]\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":16722,"content_sha256":"f9e714ee463187f6438c5bf740c50e6f970816f5e51e5c1043a318b81fe782e5"},{"filename":"scripts/validate_makefile.sh","content":"#!/usr/bin/env bash\n\n# Makefile Validator Script\n# Validates Makefile syntax, best practices, security, and optimization using mbake tool\n# Features: venv isolation, automatic cleanup via trap, comprehensive validation\n\nset -euo pipefail\n\n# Colors for output (supports NO_COLOR standard: https://no-color.org/)\nif [ -n \"${NO_COLOR:-}\" ]; then\n RED=''\n YELLOW=''\n GREEN=''\n BLUE=''\n NC=''\nelse\n RED='\\033[0;31m'\n YELLOW='\\033[1;33m'\n GREEN='\\033[0;32m'\n BLUE='\\033[0;34m'\n NC='\\033[0m' # No Color\nfi\n\n# Counters\nERRORS=0\nWARNINGS=0\nINFO=0\n\n# mbake availability flag — set to 1 by setup_venv when the venv+mbake are ready.\n# Stays 0 when python3/pip3 are absent or the venv/install step fails, allowing\n# the validator to fall back to GNU make + custom checks only.\nMBAKE_AVAILABLE=0\nMBAKE_SKIP_INSTALL_MODE=0\n\ncase \"${MBAKE_SKIP_INSTALL:-0}\" in\n 1|true|TRUE|yes|YES|on|ON)\n MBAKE_SKIP_INSTALL_MODE=1\n ;;\n 0|false|FALSE|no|NO|off|OFF|\"\")\n MBAKE_SKIP_INSTALL_MODE=0\n ;;\n *)\n echo -e \"${YELLOW}[WARNING]${NC} Unsupported MBAKE_SKIP_INSTALL='${MBAKE_SKIP_INSTALL}', treating as disabled\"\n ((WARNINGS+=1))\n ;;\nesac\n\n# Temporary venv directory (unique per invocation, respects TMPDIR)\nVENV_DIR=\"${TMPDIR:-/tmp}/makefile-validator-venv-$\"\nCLEANUP_DONE=0\n\n# Cleanup function - always runs on exit\ncleanup() {\n if [ \"$CLEANUP_DONE\" -eq 0 ]; then\n CLEANUP_DONE=1\n # Safety check: only remove if it's our temp venv (works with custom TMPDIR)\n if [ -d \"$VENV_DIR\" ] && [[ \"$VENV_DIR\" == */makefile-validator-venv-* ]]; then\n echo -e \"${BLUE}[CLEANUP]${NC} Removing temporary venv...\"\n rm -rf \"$VENV_DIR\"\n fi\n fi\n}\n\n# Register cleanup trap for all exit scenarios\ntrap cleanup EXIT INT TERM\n\n# Print error and exit\nerror_exit() {\n echo -e \"${RED}[ERROR]${NC} $1\" >&2\n exit 1\n}\n\n# Print section header\nprint_header() {\n echo \"\"\n echo \"========================================\"\n echo \"$1\"\n echo \"========================================\"\n}\n\n# Print sub-header\nprint_subheader() {\n echo -e \"\\n${BLUE}[$1]${NC}\"\n}\n\n# Check dependencies.\n# python3 and pip3 are needed only for the mbake stages; their absence degrades\n# coverage but does not abort — GNU make syntax and all custom checks still run.\ncheck_dependencies() {\n local mbake_prereqs_ok=1\n\n if [ \"$MBAKE_SKIP_INSTALL_MODE\" -eq 1 ]; then\n echo -e \"${BLUE}ℹ${NC} MBAKE_SKIP_INSTALL=1 enabled — deterministic mode will skip mbake stages\"\n ((INFO+=1))\n else\n if ! command -v python3 &> /dev/null; then\n echo -e \"${YELLOW}[WARNING]${NC} python3 not found — mbake stages will be skipped\"\n echo \" Install python3 to enable mbake validation and format-check coverage\"\n ((WARNINGS+=1))\n mbake_prereqs_ok=0\n fi\n\n if ! command -v pip3 &> /dev/null; then\n echo -e \"${YELLOW}[WARNING]${NC} pip3 not found — mbake stages will be skipped\"\n echo \" Install pip3 to enable mbake validation and format-check coverage\"\n ((WARNINGS+=1))\n mbake_prereqs_ok=0\n fi\n fi\n\n if ! command -v make &> /dev/null; then\n echo -e \"${YELLOW}[WARNING]${NC} GNU make not found — syntax validation will be limited\"\n ((WARNINGS+=1))\n fi\n\n # Signal to setup_venv (and main) that it is worth attempting venv setup\n if [ \"$MBAKE_SKIP_INSTALL_MODE\" -eq 0 ] && [ \"$mbake_prereqs_ok\" -eq 1 ]; then\n MBAKE_AVAILABLE=1\n fi\n}\n\n# Setup virtual environment and install mbake.\n# On failure (offline, proxy, bad python env) the function warns and clears\n# MBAKE_AVAILABLE so the caller skips all mbake-dependent stages.\nsetup_venv() {\n print_subheader \"ENVIRONMENT SETUP\"\n echo \"Creating temporary venv at: $VENV_DIR\"\n\n if ! python3 -m venv \"$VENV_DIR\" 2>&1; then\n echo -e \"${YELLOW}⚠${NC} Failed to create virtual environment — mbake stages will be skipped\"\n ((WARNINGS+=1))\n MBAKE_AVAILABLE=0\n return 0\n fi\n\n # Activate venv\n # shellcheck source=/dev/null\n source \"$VENV_DIR/bin/activate\"\n\n echo \"Installing mbake...\"\n if ! pip3 install --quiet mbake 2>&1; then\n echo -e \"${YELLOW}⚠${NC} Failed to install mbake — mbake stages will be skipped\"\n echo \" Ensure network access or an internal PyPI mirror is available, then rerun\"\n ((WARNINGS+=1))\n MBAKE_AVAILABLE=0\n return 0\n fi\n\n MBAKE_AVAILABLE=1\n echo -e \"${GREEN}✓${NC} Environment ready\"\n}\n\n# Validate Makefile exists and is readable\nvalidate_file() {\n local file=$1\n\n if [ ! -f \"$file\" ]; then\n error_exit \"File not found: $file\"\n fi\n\n if [ ! -r \"$file\" ]; then\n error_exit \"File not readable: $file\"\n fi\n}\n\n# Basic syntax check using GNU make\nsyntax_check() {\n local file=$1\n print_subheader \"SYNTAX CHECK (GNU make)\"\n\n if ! command -v make &> /dev/null; then\n echo -e \"${YELLOW}⚠${NC} Skipped - GNU make not installed\"\n return 0\n fi\n\n # Get absolute path to Makefile\n local abs_file\n abs_file=$(cd \"$(dirname \"$file\")\" && pwd)/$(basename \"$file\")\n local makefile_dir\n makefile_dir=$(dirname \"$abs_file\")\n local makefile_name\n makefile_name=$(basename \"$abs_file\")\n\n # Run make from the Makefile's directory to resolve relative paths correctly\n # Capture output and exit code in a single invocation (avoids running make twice)\n local make_output make_exit=0\n make_output=$(cd \"$makefile_dir\" && make -f \"$makefile_name\" -n 2>&1) || make_exit=$?\n\n if [ \"$make_exit\" -eq 0 ]; then\n echo -e \"${GREEN}✓${NC} No syntax errors found\"\n else\n echo -e \"${RED}✗${NC} Syntax errors detected:\"\n echo \"$make_output\"\n ((ERRORS+=1))\n return 1\n fi\n}\n\n# Run mbake validation\nmbake_validation() {\n local file=$1\n print_subheader \"MBAKE VALIDATION\"\n\n echo \"Running mbake validate...\"\n if mbake validate \"$file\" 2>&1; then\n echo -e \"${GREEN}✓${NC} mbake validation passed\"\n else\n echo -e \"${RED}✗${NC} mbake validation failed\"\n ((ERRORS+=1))\n fi\n}\n\n# Run mbake format check\nmbake_format_check() {\n local file=$1\n print_subheader \"MBAKE FORMAT CHECK\"\n\n echo \"Checking formatting consistency...\"\n # Capture output - mbake may wrap long lines\n local format_output\n format_output=$(mbake format --check \"$file\" 2>&1)\n local format_exit=$?\n\n # Join multi-line output for easier pattern matching (mbake wraps at ~80 chars)\n # Also normalize whitespace (collapse multiple spaces to single space)\n local format_oneline\n format_oneline=$(echo \"$format_output\" | tr '\\n' ' ' | tr -s ' ')\n\n # Check for known false positives (mbake limitation with GNU Make special targets).\n # mbake does not recognise many valid GNU Make special targets (.DELETE_ON_ERROR,\n # .SUFFIXES, .ONESHELL, .POSIX, .PRECIOUS, .NOTPARALLEL, .INTERMEDIATE, etc.).\n # The sed cleaning below already strips all of them; this flag just controls\n # whether the \"known mbake limitation\" info note is printed.\n local has_unknown_special_target=0\n if echo \"$format_oneline\" | grep -qE \"Unknown special target '\\.[A-Z_]+'\"; then\n has_unknown_special_target=1\n fi\n\n # Check if there are real formatting issues (\"Would reformat:\" indicates changes needed)\n local has_reformat=0\n if echo \"$format_oneline\" | grep -q \"Would reformat:\"; then\n has_reformat=1\n fi\n\n # Check for other errors (not related to unknown special targets)\n local has_other_errors=0\n # Remove known false positive patterns completely (including \"Error:\" before them)\n # Pattern matches: \"some_file.mk:0: Error: Unknown special target '.DELETE_ON_ERROR'\"\n local cleaned_output\n cleaned_output=$(echo \"$format_oneline\" | sed -E \"s/[^ ]*:[0-9]+: Error: Unknown special target '\\.[A-Z_]+'//g\")\n if echo \"$cleaned_output\" | grep -qE \"Error:|Fatal error\"; then\n has_other_errors=1\n fi\n\n # Decision logic\n if [ $format_exit -eq 0 ]; then\n echo -e \"${GREEN}✓${NC} Formatting is consistent\"\n elif [ $has_unknown_special_target -eq 1 ] && [ $has_reformat -eq 0 ] && [ $has_other_errors -eq 0 ]; then\n # Only false positive about unknown special targets - treat as success\n echo -e \"${GREEN}✓${NC} Formatting is consistent\"\n echo -e \"${BLUE}ℹ${NC} mbake reported unknown special targets (this is a known mbake limitation)\"\n elif [ $has_reformat -eq 1 ] || [ $has_other_errors -eq 1 ]; then\n # Real formatting issues or other errors exist\n echo \"$format_output\" | grep -v \"Unknown special target\" | grep -v \"^$\" || true\n echo -e \"${YELLOW}⚠${NC} Formatting issues found\"\n echo \"\"\n echo \"Run 'mbake format $file' to fix formatting issues\"\n echo \"Or run 'mbake format --diff $file' to preview changes\"\n ((WARNINGS+=1))\n else\n # Unknown case - show as info, don't warn\n echo -e \"${GREEN}✓${NC} Formatting is consistent\"\n if [ $has_unknown_special_target -eq 1 ]; then\n echo -e \"${BLUE}ℹ${NC} mbake reported unknown special targets (this is a known mbake limitation)\"\n fi\n fi\n}\n\n# Custom security and best practice checks\ncustom_checks() {\n local file=$1\n print_subheader \"CUSTOM CHECKS\"\n\n local found_issues=0\n\n # ============================================================\n # CRITICAL: Check for .DELETE_ON_ERROR (GNU Make best practice)\n # ============================================================\n if ! grep -q \"^\\.DELETE_ON_ERROR:\" \"$file\"; then\n echo -e \"${YELLOW}⚠${NC} Missing .DELETE_ON_ERROR declaration\"\n echo \" GNU Make recommends this to delete targets on recipe failure\"\n echo \" Add '.DELETE_ON_ERROR:' at the top of your Makefile\"\n echo \" See: https://www.gnu.org/software/make/manual/html_node/Special-Targets.html\"\n ((WARNINGS+=1))\n found_issues=1\n fi\n\n # ============================================================\n # Check for explicit SHELL setting (modern best practice)\n # ============================================================\n if ! grep -qE \"^SHELL\\s*:?=\\s*(bash|/bin/bash|/usr/bin/bash)\" \"$file\"; then\n echo -e \"${BLUE}ℹ${NC} No explicit SHELL setting\"\n echo \" Consider 'SHELL := bash' for predictable behavior\"\n echo \" See: https://tech.davis-hansson.com/p/make/\"\n ((INFO+=1))\n found_issues=1\n fi\n\n # ============================================================\n # Check for recommended MAKEFLAGS settings\n # ============================================================\n if ! grep -q \"MAKEFLAGS.*--warn-undefined-variables\" \"$file\"; then\n echo -e \"${BLUE}ℹ${NC} Consider 'MAKEFLAGS += --warn-undefined-variables'\"\n echo \" This alerts you to undefined Make variable references\"\n ((INFO+=1))\n found_issues=1\n fi\n if ! grep -q \"MAKEFLAGS.*--no-builtin-rules\" \"$file\"; then\n echo -e \"${BLUE}ℹ${NC} Consider 'MAKEFLAGS += --no-builtin-rules'\"\n echo \" This disables built-in implicit rules for faster builds\"\n ((INFO+=1))\n found_issues=1\n fi\n\n # Check for .PHONY declarations\n if ! grep -q \"^\\.PHONY:\" \"$file\"; then\n echo -e \"${YELLOW}⚠${NC} No .PHONY declarations found\"\n echo \" Consider adding .PHONY for targets that don't create files\"\n echo \" Example: .PHONY: clean test install\"\n ((WARNINGS+=1))\n found_issues=1\n fi\n\n # Check for tabs vs spaces in recipes (improved regex)\n # Catches 2, 4, or 8 space indentation that should be tabs\n local space_lines\n space_lines=$(grep -nE \"^( | | )[a-zA-Z@\\$\\(]\" \"$file\" 2>/dev/null | head -5) || true\n if [ -n \"$space_lines\" ]; then\n echo -e \"${RED}✗${NC} Potential spaces instead of tabs in recipes detected:\"\n echo \"$space_lines\"\n echo \" Makefiles require TAB characters for recipe indentation\"\n ((ERRORS+=1))\n found_issues=1\n fi\n\n # Check for hardcoded credentials (expanded pattern for common credential names)\n # Note: grep -n adds \"N:\" prefix, so filter must match \"N:# comment\" format, not \"# comment\"\n local cred_lines\n cred_lines=$(grep -niE '(password|secret|api[_-]?key|apikey|token|private[_-]?key|aws_access_key|aws_secret_access_key|github_token|auth_token|credentials|azure_client_secret|database_url|db_password|ssh_key|ssl_key|encryption_key)\\s*[:?]?=' \"$file\" 2>/dev/null | grep -vE \"^[0-9]+:[[:space:]]*#\" | head -3) || true\n if [ -n \"$cred_lines\" ]; then\n echo -e \"${RED}✗${NC} Potential hardcoded credentials detected:\"\n echo \"$cred_lines\"\n echo \" Use environment variables or secret management instead\"\n ((ERRORS+=1))\n found_issues=1\n fi\n\n # Check for TRULY unsafe variable expansion patterns\n # Only flag variables that are NOT defined with defaults in the same file\n # Look for rm/sudo/curl/wget with variables that could be empty or user-controlled\n # Handles both UPPERCASE and lowercase variable names\n local unsafe_vars=\"\"\n while IFS= read -r line; do\n # Extract variable name from $(VAR) or $(var) pattern (any case)\n var_name=$(echo \"$line\" | grep -oE '\\$\\([a-zA-Z_][a-zA-Z0-9_]*\\)' | head -1 | tr -d '$()')\n if [ -n \"$var_name\" ]; then\n # Check if variable has a default value with := or ?=\n if ! grep -qE \"^${var_name}\\s*[:?]=\" \"$file\"; then\n # Variable has no default, this is potentially unsafe\n unsafe_vars=\"${unsafe_vars}${line}\\n\"\n fi\n fi\n done \u003c \u003c(grep -E '\\$\\([a-zA-Z_][a-zA-Z0-9_]*\\)' \"$file\" | grep -E '(rm -rf|sudo|curl|wget)')\n\n if [ -n \"$unsafe_vars\" ]; then\n echo -e \"${YELLOW}⚠${NC} Variables without defaults used in dangerous commands:\"\n echo -e \"$unsafe_vars\" | head -3\n echo \" Consider adding default values (VAR := value) or validation\"\n ((WARNINGS+=1))\n found_issues=1\n fi\n\n # Check for missing error handling in recipes\n if grep -E \"^\\t[^@#-]\" \"$file\" | grep -vE \"set -e|pipefail|\\|\\||&&\" | grep -q .; then\n echo -e \"${BLUE}ℹ${NC} Some recipe commands may lack error handling\"\n echo \" Consider using 'set -e', '||', '&&' or '-' prefix for error control\"\n ((INFO+=1))\n found_issues=1\n fi\n\n # Check for missing .INTERMEDIATE or .SECONDARY for temporary files\n if grep -E \"\\.o|\\.tmp|\\.temp\" \"$file\" | grep -q \":\"; then\n if ! grep -qE \"^\\.(INTERMEDIATE|SECONDARY):\" \"$file\"; then\n echo -e \"${BLUE}ℹ${NC} Intermediate files detected (.o, .tmp, .temp)\"\n echo \" Consider using .INTERMEDIATE or .SECONDARY for automatic cleanup\"\n ((INFO+=1))\n found_issues=1\n fi\n fi\n\n # Check for missing default target documentation\n # Look for various patterns: \"Default target\", \"(default)\", \"Main target\", \"default:\" before all:\n if ! grep -qE \"^##?.*(([Dd]efault|[Mm]ain|[Ff]irst).*target|target.*(default|main)|^\\s*all:.*#.*default|\\(default\\))\" \"$file\"; then\n # Also check if there's a ## comment right before \"all:\" target\n if ! grep -B1 \"^all:\" \"$file\" | grep -qE \"^##\"; then\n echo -e \"${BLUE}ℹ${NC} No documentation for default target\"\n echo \" Consider adding a comment explaining the default target\"\n ((INFO+=1))\n found_issues=1\n fi\n fi\n\n # Check for recursive variable expansion with shell commands (performance issue)\n # Pattern uses single quotes so \\$ reaches grep as a literal-dollar matcher,\n # not as an end-of-line anchor (which double-quote expansion would produce).\n local shell_lines\n shell_lines=$(grep -nE '^\\s*[A-Z_]+\\s*=\\s*\\$\\(shell' \"$file\" 2>/dev/null | head -3) || true\n if [ -n \"$shell_lines\" ]; then\n echo -e \"${YELLOW}⚠${NC} Shell commands with recursive expansion '=' found:\"\n echo \"$shell_lines\"\n echo \" Use ':=' for immediate expansion to avoid repeated shell calls\"\n ((WARNINGS+=1))\n found_issues=1\n fi\n\n # Check for .SUFFIXES (optimization for disabling built-in rules)\n # This is informational - not all Makefiles need this\n if grep -qE \"^%\\.\" \"$file\" || grep -qE \"^\\.[a-z]+\\.[a-z]+:\" \"$file\"; then\n if ! grep -q \"^\\.SUFFIXES:\" \"$file\"; then\n echo -e \"${BLUE}ℹ${NC} Pattern/suffix rules found but no .SUFFIXES declaration\"\n echo \" Consider adding '.SUFFIXES:' to disable built-in rules for faster builds\"\n ((INFO+=1))\n found_issues=1\n fi\n fi\n\n # Check for using 'make' instead of '$(MAKE)' in recursive calls\n # Exclude: echo statements, comments, and string literals\n # Match 'make' followed by whitespace OR at end of line (bare 'make' with no args)\n local make_lines\n make_lines=$(grep -nE \"^\\t[^#@]*\\bmake(\\s|$)\" \"$file\" 2>/dev/null | grep -vE '(echo|printf|MAKE\\)|\".*make.*\"|'\"'\"'.*make.*'\"'\"')' | head -3) || true\n if [ -n \"$make_lines\" ]; then\n echo -e \"${YELLOW}⚠${NC} Direct 'make' call in recipe (should use \\$(MAKE)):\"\n echo \"$make_lines\"\n echo \" Use '\\$(MAKE)' for recursive make calls to preserve flags and options\"\n ((WARNINGS+=1))\n found_issues=1\n fi\n\n # Check for .SUFFIXES recommendation for large Makefiles\n local target_count\n target_count=$(grep -cE \"^[a-zA-Z_][a-zA-Z0-9_-]*:\" \"$file\" 2>/dev/null) || target_count=0\n if [ \"$target_count\" -gt 10 ] && ! grep -q \"^\\.SUFFIXES:\" \"$file\"; then\n echo -e \"${BLUE}ℹ${NC} Large Makefile ($target_count targets) without .SUFFIXES\"\n echo \" Consider adding '.SUFFIXES:' to disable built-in rules for faster builds\"\n ((INFO+=1))\n found_issues=1\n fi\n\n # ============================================================\n # Check for .ONESHELL without proper error handling\n # ============================================================\n if grep -q \"^\\.ONESHELL:\" \"$file\"; then\n # .ONESHELL is used - check if .SHELLFLAGS includes recommended flags\n local has_shellflags=0\n local has_e_flag=0\n local has_u_flag=0\n local has_pipefail=0\n\n if grep -qE \"^\\.?SHELLFLAGS\\s*:?=\" \"$file\"; then\n has_shellflags=1\n local shellflags_line\n shellflags_line=$(grep -E \"^\\.?SHELLFLAGS\\s*:?=\" \"$file\" | head -1)\n # Check for -e flag (can be standalone -e or combined like -eu, -euo, etc.)\n # Match: -e, -eu, -euo, -eux, etc. (e after dash, possibly with other letters)\n if [[ \"$shellflags_line\" =~ -[a-zA-Z]*e[a-zA-Z]* ]] || [[ \"$shellflags_line\" == *\"-e \"* ]] || [[ \"$shellflags_line\" == *\"-e\\\"\"* ]]; then\n has_e_flag=1\n fi\n # Check for -u flag (can be standalone -u or combined like -eu, -euo, etc.)\n if [[ \"$shellflags_line\" =~ -[a-zA-Z]*u[a-zA-Z]* ]] || [[ \"$shellflags_line\" == *\"-u \"* ]] || [[ \"$shellflags_line\" == *\"-u\\\"\"* ]]; then\n has_u_flag=1\n fi\n # Check for pipefail (always as -o pipefail)\n [[ \"$shellflags_line\" == *\"pipefail\"* ]] && has_pipefail=1\n fi\n\n if [ \"$has_shellflags\" -eq 0 ]; then\n # No SHELLFLAGS at all, check if recipes commonly use set -e\n local oneshell_recipes\n oneshell_recipes=$(grep -cE \"^\\t\" \"$file\" 2>/dev/null) || oneshell_recipes=0\n local set_e_count\n set_e_count=$(grep -cE \"^\\t.*set -e\" \"$file\" 2>/dev/null) || set_e_count=0\n\n # If less than 33% of recipe blocks have set -e, warn\n if [ \"$oneshell_recipes\" -gt 0 ] && [ \"$set_e_count\" -lt $((oneshell_recipes / 3)) ]; then\n echo -e \"${YELLOW}⚠${NC} .ONESHELL used without .SHELLFLAGS\"\n echo \" With .ONESHELL, recipe errors (except the last line) are silently ignored\"\n echo \" Fix: Add '.SHELLFLAGS := -eu -o pipefail -c'\"\n echo \" See: https://www.gnu.org/software/make/manual/html_node/One-Shell.html\"\n ((WARNINGS+=1))\n found_issues=1\n fi\n elif [ \"$has_e_flag\" -eq 0 ]; then\n echo -e \"${YELLOW}⚠${NC} .ONESHELL with SHELLFLAGS missing -e flag\"\n echo \" Without -e, errors in recipe commands are ignored\"\n echo \" Consider: .SHELLFLAGS := -eu -o pipefail -c\"\n ((WARNINGS+=1))\n found_issues=1\n else\n # Has -e, but recommend full flags as info\n if [ \"$has_u_flag\" -eq 0 ] || [ \"$has_pipefail\" -eq 0 ]; then\n echo -e \"${BLUE}ℹ${NC} .SHELLFLAGS could include additional safety flags\"\n echo \" Recommended: .SHELLFLAGS := -eu -o pipefail -c\"\n echo \" -u: error on undefined variables, -o pipefail: catch pipe failures\"\n ((INFO+=1))\n found_issues=1\n fi\n fi\n fi\n\n # ============================================================\n # Check for .EXPORT_ALL_VARIABLES (security concern)\n # ============================================================\n if grep -q \"^\\.EXPORT_ALL_VARIABLES:\" \"$file\"; then\n echo -e \"${YELLOW}⚠${NC} .EXPORT_ALL_VARIABLES used - security consideration\"\n echo \" This exports ALL Make variables to subprocesses, potentially leaking sensitive data\"\n echo \" Consider using 'export VAR' for specific variables instead\"\n ((WARNINGS+=1))\n found_issues=1\n fi\n\n # ============================================================\n # Check for order-only prerequisites (directory best practice)\n # ============================================================\n # If mkdir -p is used in recipes and order-only | syntax is not used\n if grep -qE \"^\\t.*mkdir.*-p\" \"$file\"; then\n if ! grep -qE '\\| \\$\\(' \"$file\"; then\n echo -e \"${BLUE}ℹ${NC} mkdir in recipes without order-only prerequisites\"\n echo \" Consider using order-only prerequisites for directories:\"\n echo \" Example: \\$(BUILD_DIR)/app: \\$(SOURCES) | \\$(BUILD_DIR)\"\n echo \" This prevents unnecessary rebuilds when only timestamps change\"\n ((INFO+=1))\n found_issues=1\n fi\n fi\n\n # ============================================================\n # Check for parallel-unsafe patterns without .NOTPARALLEL\n # ============================================================\n if grep -qE \"^\\t.*(docker build|npm install|pip install|yarn install|bundle install)\" \"$file\"; then\n if ! grep -q \"^\\.NOTPARALLEL:\" \"$file\"; then\n echo -e \"${BLUE}ℹ${NC} Parallel-sensitive commands detected (npm/docker/pip install)\"\n echo \" Consider using .NOTPARALLEL for targets with these commands\"\n echo \" Or add proper dependencies to prevent race conditions\"\n ((INFO+=1))\n found_issues=1\n fi\n fi\n\n if [ \"$found_issues\" -eq 0 ]; then\n echo -e \"${GREEN}✓${NC} No additional issues found\"\n fi\n}\n\n# Run checkmake if available\ncheckmake_validation() {\n local file=$1\n print_subheader \"CHECKMAKE VALIDATION (optional)\"\n\n if ! command -v checkmake &> /dev/null; then\n echo -e \"${BLUE}ℹ${NC} checkmake not installed - skipping additional linting\"\n echo \" Install with: go install github.com/checkmake/checkmake/cmd/checkmake@latest\"\n return 0\n fi\n\n echo \"Running checkmake...\"\n local checkmake_output\n checkmake_output=$(checkmake \"$file\" 2>&1) || true\n if [ -n \"$checkmake_output\" ]; then\n echo \"$checkmake_output\"\n # Count warnings from checkmake\n local cm_warnings\n cm_warnings=$(echo \"$checkmake_output\" | grep -c \"WARN\" 2>/dev/null) || cm_warnings=0\n if [ \"$cm_warnings\" -gt 0 ]; then\n ((WARNINGS+=cm_warnings))\n fi\n else\n echo -e \"${GREEN}✓${NC} checkmake validation passed\"\n fi\n}\n\n# Run unmake if available (for POSIX portability checks)\nunmake_validation() {\n local file=$1\n print_subheader \"UNMAKE VALIDATION (optional)\"\n\n if ! command -v unmake &> /dev/null; then\n echo -e \"${BLUE}ℹ${NC} unmake not installed - skipping POSIX portability checks\"\n echo \" See: https://github.com/mcandre/unmake\"\n return 0\n fi\n\n echo \"Running unmake for POSIX portability...\"\n local unmake_output\n unmake_output=$(unmake \"$file\" 2>&1) || true\n if [ -n \"$unmake_output\" ]; then\n echo \"$unmake_output\"\n # Count warnings from unmake\n local um_warnings\n um_warnings=$(echo \"$unmake_output\" | grep -cE \"(warning|Warning)\" 2>/dev/null) || um_warnings=0\n if [ \"$um_warnings\" -gt 0 ]; then\n ((WARNINGS+=um_warnings))\n fi\n else\n echo -e \"${GREEN}✓${NC} unmake validation passed (POSIX compatible)\"\n fi\n}\n\n# Print summary\nprint_summary() {\n local file=$1\n print_header \"VALIDATION SUMMARY\"\n echo \"File: $file\"\n echo \"\"\n echo -e \"${RED}Errors: ${NC} $ERRORS\"\n echo -e \"${YELLOW}Warnings:${NC} $WARNINGS\"\n echo -e \"${BLUE}Info: ${NC} $INFO\"\n echo \"\"\n\n if [ \"$ERRORS\" -gt 0 ]; then\n echo -e \"${RED}⚠ Validation FAILED - errors must be fixed${NC}\"\n return 2\n elif [ \"$WARNINGS\" -gt 0 ]; then\n echo -e \"${YELLOW}⚠ Validation PASSED with warnings${NC}\"\n return 1\n else\n echo -e \"${GREEN}✓ Validation PASSED${NC}\"\n return 0\n fi\n}\n\n# Main execution\nmain() {\n if [ $# -eq 0 ]; then\n echo \"Usage: $0 \u003cMakefile>\"\n echo \"\"\n echo \"Validates Makefile for syntax errors, best practices, security issues,\"\n echo \"and optimization opportunities using mbake and custom checks.\"\n echo \"\"\n echo \"Examples:\"\n echo \" $0 Makefile\"\n echo \" $0 path/to/Makefile\"\n echo \" $0 project.mk\"\n exit 1\n fi\n\n local makefile=$1\n\n print_header \"MAKEFILE VALIDATOR\"\n echo \"File: $makefile\"\n\n # Validation pipeline\n check_dependencies\n validate_file \"$makefile\"\n\n # Set up mbake venv only when python3+pip3 are present and skip mode is disabled.\n if [ \"$MBAKE_AVAILABLE\" -eq 1 ]; then\n setup_venv || true\n fi\n\n # Run syntax check — always runs (uses GNU make, not mbake)\n syntax_check \"$makefile\" || true\n\n # Run mbake stages only when the venv is ready\n if [ \"$MBAKE_AVAILABLE\" -eq 1 ]; then\n mbake_validation \"$makefile\" || true\n mbake_format_check \"$makefile\" || true\n else\n print_subheader \"MBAKE VALIDATION\"\n if [ \"$MBAKE_SKIP_INSTALL_MODE\" -eq 1 ]; then\n echo -e \"${BLUE}ℹ${NC} Skipped — MBAKE_SKIP_INSTALL=1 (deterministic mode)\"\n else\n echo -e \"${BLUE}ℹ${NC} Skipped — mbake not available (python3, pip3, and network required)\"\n fi\n print_subheader \"MBAKE FORMAT CHECK\"\n if [ \"$MBAKE_SKIP_INSTALL_MODE\" -eq 1 ]; then\n echo -e \"${BLUE}ℹ${NC} Skipped — MBAKE_SKIP_INSTALL=1 (deterministic mode)\"\n else\n echo -e \"${BLUE}ℹ${NC} Skipped — mbake not available (python3, pip3, and network required)\"\n fi\n ((INFO+=2))\n fi\n\n # Run custom checks — always runs (pure bash + grep, no external tools)\n custom_checks \"$makefile\" || true\n\n # Run checkmake if available (optional, continue even if it fails)\n checkmake_validation \"$makefile\" || true\n\n # Run unmake if available (optional, continue even if it fails)\n unmake_validation \"$makefile\" || true\n\n # Print summary and return appropriate exit code\n print_summary \"$makefile\"\n local exit_code=$?\n\n return $exit_code\n}\n\n# Run main with all arguments and exit with its return code\nmain \"$@\"\nexit $?\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":27998,"content_sha256":"cca591c6a1cf6c71967e8e202395fed5ef20c40aec412c1d445d11d583d3cd6b"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Makefile Validator","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Use this skill to validate Makefiles with a local-first, deterministic flow.","type":"text"}]},{"type":"paragraph","content":[{"text":"Default validator entrypoint:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/validate_makefile.sh \u003cmakefile-path>","type":"text"}]},{"type":"paragraph","content":[{"text":"Validation layers:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dependency preflight (","type":"text"},{"text":"python3","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pip3","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"make","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GNU make syntax check (","type":"text"},{"text":"make -n --dry-run","type":"text","marks":[{"type":"code_inline"}]},{"text":") when ","type":"text"},{"text":"make","type":"text","marks":[{"type":"code_inline"}]},{"text":" is available","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"mbake validate","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"mbake format --check","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Custom security, best-practice, and optimization checks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optional ","type":"text"},{"text":"checkmake","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"unmake","type":"text","marks":[{"type":"code_inline"}]},{"text":" checks when installed","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Trigger Guidance","type":"text"}]},{"type":"paragraph","content":[{"text":"Use this skill when the request includes Makefile quality, linting, validation, hardening, or troubleshooting.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Trigger Phrases","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Validate this Makefile\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Lint my ","type":"text"},{"text":".mk","type":"text","marks":[{"type":"code_inline"}]},{"text":" file\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Find issues in this build Makefile\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Check Makefile security problems\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Review this Makefile before CI\"","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Non-Trigger Examples","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Creating a brand new Makefile from scratch (use ","type":"text"},{"text":"makefile-generator","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Running build targets as part of delivery","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"General shell scripting work unrelated to Makefiles","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Deterministic Execution Model","type":"text"}]},{"type":"paragraph","content":[{"text":"Run from this skill directory for shortest commands:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd devops-skills-plugin/skills/makefile-validator","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: Preflight","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm target file exists and is readable.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm whether file edits are allowed (","type":"text"},{"text":"writable","type":"text","marks":[{"type":"code_inline"}]},{"text":") or only suggestions can be returned (","type":"text"},{"text":"read-only","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer the default validator path first; use fallbacks only when blocked by environment constraints.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: Baseline Validation (Default Path)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# From skill directory\nbash scripts/validate_makefile.sh \u003cmakefile-path>\n\n# From repository root\nbash devops-skills-plugin/skills/makefile-validator/scripts/validate_makefile.sh \u003cmakefile-path>","type":"text"}]},{"type":"paragraph","content":[{"text":"Always record:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"command executed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"exit code","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"summary counts (","type":"text"},{"text":"Errors","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Warnings","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Info","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"issue locations reported by tools","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Interpret Output and Exit Code","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exit code","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Meaning","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Expected summary line","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"no blocking findings","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validation PASSED","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"optional improvements only","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"warning-only result","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validation PASSED with warnings","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fix recommended; merge policy dependent","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"2","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"error result","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validation FAILED - errors must be fixed","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fix required, then rerun","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4: Progressive Reference Loading","type":"text"}]},{"type":"paragraph","content":[{"text":"Open only the docs required by the current findings.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Finding type","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reference doc","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":".PHONY","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".DELETE_ON_ERROR","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".ONESHELL","type":"text","marks":[{"type":"code_inline"}]},{"text":", variable usage, performance structure","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"docs/best-practices.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"tabs vs spaces, dependency mistakes, credential patterns, anti-patterns","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"docs/common-mistakes.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mbake","type":"text","marks":[{"type":"code_inline"}]},{"text":" behavior, formatter flags, known ","type":"text"},{"text":"mbake","type":"text","marks":[{"type":"code_inline"}]},{"text":" caveats","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"docs/bake-tool.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5: Fix + Rerun Loop","type":"text"}]},{"type":"paragraph","content":[{"text":"After applying fixes, rerun:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/validate_makefile.sh \u003cmakefile-path>","type":"text"}]},{"type":"paragraph","content":[{"text":"Loop rules:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stop only when no new errors are introduced.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If warnings remain intentionally, document why they are accepted.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always report the latest rerun exit code.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"How to Open Docs","type":"text"}]},{"type":"paragraph","content":[{"text":"Use explicit file-open commands so paths are unambiguous.","type":"text"}]},{"type":"paragraph","content":[{"text":"From repository root:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"sed -n '1,220p' devops-skills-plugin/skills/makefile-validator/docs/best-practices.md\nsed -n '1,220p' devops-skills-plugin/skills/makefile-validator/docs/common-mistakes.md\nsed -n '1,220p' devops-skills-plugin/skills/makefile-validator/docs/bake-tool.md\nrg -n \"PHONY|DELETE_ON_ERROR|ONESHELL|tab|credential|mbake\" devops-skills-plugin/skills/makefile-validator/docs/*.md","type":"text"}]},{"type":"paragraph","content":[{"text":"From ","type":"text"},{"text":"devops-skills-plugin/skills/makefile-validator","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"sed -n '1,220p' docs/best-practices.md\nsed -n '1,220p' docs/common-mistakes.md\nsed -n '1,220p' docs/bake-tool.md\nrg -n \"PHONY|DELETE_ON_ERROR|ONESHELL|tab|credential|mbake\" docs/*.md","type":"text"}]},{"type":"paragraph","content":[{"text":"If shell commands are unavailable, use the environment's file-open/read actions on the same paths.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Fallback Behavior","type":"text"}]},{"type":"paragraph","content":[{"text":"Use these only when the default validator path cannot run fully.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Constraint","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fallback action","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reporting requirement","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"python3","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"pip3","type":"text","marks":[{"type":"code_inline"}]},{"text":" unavailable","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run limited checks (","type":"text"},{"text":"make -f \u003cfile> -n --dry-run","type":"text","marks":[{"type":"code_inline"}]},{"text":" if ","type":"text"},{"text":"make","type":"text","marks":[{"type":"code_inline"}]},{"text":" exists, plus focused ","type":"text"},{"text":"grep","type":"text","marks":[{"type":"code_inline"}]},{"text":" checks)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"State that ","type":"text"},{"text":"mbake","type":"text","marks":[{"type":"code_inline"}]},{"text":" stages were skipped and coverage is reduced","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pip3 install mbake","type":"text","marks":[{"type":"code_inline"}]},{"text":" fails (offline/proxy/index issue)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Keep syntax/custom checks that still work; defer formatter/linter stages","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Report install failure and request rerun in a network-enabled environment","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"make","type":"text","marks":[{"type":"code_inline"}]},{"text":" unavailable","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Continue with non-syntax stages; script already downgrades syntax stage","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Explicitly note syntax coverage was skipped","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"checkmake","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"unmake","type":"text","marks":[{"type":"code_inline"}]},{"text":" unavailable","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Continue; these are optional stages","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Note optional lint/portability coverage not executed","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"target file is read-only","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Provide patch suggestions only","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mark response as advisory only","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"command execution unavailable","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Provide static review from file contents and docs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mark result as non-executed analysis","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Minimal fallback commands:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Syntax only (when make exists)\nmake -f \u003cmakefile-path> -n --dry-run\n\n# Focused quick checks\ngrep -n \"^\\\\.DELETE_ON_ERROR:\" \u003cmakefile-path>\ngrep -n \"^\\\\.PHONY:\" \u003cmakefile-path>\ngrep -nE \"^( | | )[a-zA-Z@\\\\$\\\\(]\" \u003cmakefile-path>","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Example Outcomes Mapped to Exit Codes","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Clean Result (","type":"text"},{"text":"exit 0","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"text"},"content":[{"text":"Errors: 0\nWarnings: 0\nInfo: 2\n✓ Validation PASSED","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Warning-Only Result (","type":"text"},{"text":"exit 1","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"text"},"content":[{"text":"Errors: 0\nWarnings: 3\nInfo: 1\n⚠ Validation PASSED with warnings","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Error Result (","type":"text"},{"text":"exit 2","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"text"},"content":[{"text":"Errors: 2\nWarnings: 1\nInfo: 0\n⚠ Validation FAILED - errors must be fixed","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Troubleshooting Quick Start","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify required tools:","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"command -v python3 pip3 make","type":"text"}]},{"type":"ordered_list","attrs":{"order":2,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Isolate GNU make syntax failures:","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"make -f \u003cmakefile-path> -n --dry-run","type":"text"}]},{"type":"ordered_list","attrs":{"order":3,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check likely tab-indentation violations:","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"grep -nE \"^( | | )[a-zA-Z@\\\\$\\\\(]\" \u003cmakefile-path>","type":"text"}]},{"type":"ordered_list","attrs":{"order":4,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rerun validator with plain output and capture log:","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"NO_COLOR=1 bash scripts/validate_makefile.sh \u003cmakefile-path> > /tmp/makefile-validator.log 2>&1\necho \"exit=$? log=/tmp/makefile-validator.log\"","type":"text"}]},{"type":"ordered_list","attrs":{"order":5,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If mbake install keeps failing, validate in a network-enabled shell or with an internal PyPI mirror, then rerun the full validator.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Skill Paths","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"text"},"content":[{"text":"makefile-validator/\n├── SKILL.md\n├── scripts/\n│ └── validate_makefile.sh\n├── docs/\n│ ├── best-practices.md\n│ ├── common-mistakes.md\n│ └── bake-tool.md\n└── examples/\n ├── good-makefile.mk\n └── bad-makefile.mk","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Done Criteria","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill update is complete when all are true:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Trigger guidance is explicit and easy to identify.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Execution flow is deterministic (preflight -> run -> interpret -> docs -> rerun).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"How to open docs\" instructions include exact commands and paths.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Example outcomes are explicitly tied to exit codes (","type":"text"},{"text":"0","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"1","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"2","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fallback behavior is documented for missing tools and constrained environments.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Troubleshooting quick-start is concise and runnable.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Frontmatter ","type":"text"},{"text":"name","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"description","type":"text","marks":[{"type":"code_inline"}]},{"text":" remain unchanged.","type":"text"}]}]}]}]},"metadata":{"date":"2026-11-02","name":"makefile-validator","author":"@skillopedia","source":{"stars":331,"repo_name":"cc-devops-skills","origin_url":"https://github.com/akin-ozer/cc-devops-skills/blob/HEAD/devops-skills-plugin/skills/makefile-validator/SKILL.md","repo_owner":"akin-ozer","body_sha256":"758d3db075a39e71f878dc843cffe4e7c775c90def3de9938880146abb4ecde5","cluster_key":"d7403966146440b7e038b12f3a0fb88417a6addc96a309ddbc6ed10273954e01","clean_bundle":{"format":"clean-skill-bundle-v1","source":"akin-ozer/cc-devops-skills/devops-skills-plugin/skills/makefile-validator/SKILL.md","text_bytes":103234,"source_commit":"276af751e659315aaf56d3ad13d7c26f4e72e28a","skills_sh_rank":16983,"attachment_count":5,"text_attachments":5,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"devops-skills-plugin/skills/makefile-validator/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"devops-infrastructure","category_label":"DevOps"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"devops-infrastructure","import_tag":"skills-sh-top20k-2026-11","description":"Validate, lint, audit, or check Makefiles and .mk files for errors."}},"renderedAt":1793665948002}

Makefile Validator Overview Use this skill to validate Makefiles with a local-first, deterministic flow. Default validator entrypoint: Validation layers: 1. Dependency preflight ( , , ) 2. GNU make syntax check ( ) when is available 3. 4. 5. Custom security, best-practice, and optimization checks 6. Optional and checks when installed Trigger Guidance Use this skill when the request includes Makefile quality, linting, validation, hardening, or troubleshooting. Trigger Phrases - "Validate this Makefile" - "Lint my file" - "Find issues in this build Makefile" - "Check Makefile security problems"…