Files
unitforge/Makefile

465 lines
17 KiB
Makefile

# UnitForge Makefile
# Development workflow automation using uv package manager
.PHONY: help install install-dev clean test test-cov lint format type-check
.PHONY: server cli demo docker-build docker-dev docker-prod docker-test
.PHONY: docker-push docker-pull docker-tag docker-login registry-build registry-push
.PHONY: pre-commit setup-dev deps-update deps-check security-check
.PHONY: build package publish docs release validate-config
# Default target
.DEFAULT_GOAL := help
# Colors for output (load from centralized utility)
include scripts/colors.mk
# Configuration
PYTHON_VERSION := 3.8
PROJECT_NAME := unitforge
VENV_PATH := .venv
UV_INSTALLED := $(shell command -v uv 2> /dev/null)
help: ## Show this help message
$(call header,UnitForge Development Makefile)
@echo ""
@echo -e "$(GREEN)Available targets:$(NC)"
@awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_-]+:.*##/ { printf " $(YELLOW)%-20s$(NC) %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
@echo ""
@echo -e "$(GREEN)Quick start:$(NC)"
@echo " make setup-dev # Set up development environment"
@echo " make server # Start development server"
@echo " make test # Run tests"
@echo ""
check-uv: ## Check if uv is installed
ifndef UV_INSTALLED
$(call error,uv is not installed)
@echo "Install it with: curl -LsSf https://astral.sh/uv/install.sh | sh"
@exit 1
else
$(call success,uv is available)
endif
setup-dev: check-uv ## Set up development environment with uv
$(call info,Setting up development environment...)
@if [ -d "$(VENV_PATH)" ]; then \
echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) Removing existing virtual environment..."; \
rm -rf $(VENV_PATH); \
fi
uv venv --python python3
$(call success,Virtual environment created)
@$(MAKE) install-dev
@if [ ! -f ".env" ] && [ -f ".env.example" ]; then \
echo -e "$(BLUE)$(INFO_SYMBOL)$(NC) Creating .env file from template..."; \
cp .env.example .env; \
echo -e "$(GREEN)$(SUCCESS_SYMBOL)$(NC) .env file created. Please review and customize the settings."; \
fi
@if [ -f ".pre-commit-config.yaml" ]; then \
echo -e "$(BLUE)$(INFO_SYMBOL)$(NC) Installing pre-commit hooks..."; \
. $(VENV_PATH)/bin/activate && pre-commit install; \
echo -e "$(GREEN)$(SUCCESS_SYMBOL)$(NC) Pre-commit hooks installed"; \
fi
$(call success,Development environment ready!)
@echo ""
@echo -e "$(YELLOW)Next steps:$(NC)"
@echo " source $(VENV_PATH)/bin/activate # Activate environment"
@echo " make server # Start development server"
@echo " make test # Run tests"
install: check-uv ## Install production dependencies
$(call info,Installing production dependencies...)
uv pip install -e .
$(call success,Production dependencies installed)
install-dev: check-uv ## Install development dependencies
$(call info,Installing development dependencies...)
uv pip install -e ".[dev,web]"
$(call success,Development dependencies installed)
deps-update: check-uv ## Update all dependencies
$(call info,Updating dependencies...)
uv pip install --upgrade -e ".[dev,web]"
$(call success,Dependencies updated)
deps-check: ## Check for dependency vulnerabilities
$(call info,Checking dependencies for vulnerabilities...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && uv run pip-audit --desc || true; \
else \
echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) Virtual environment not found. Run 'make setup-dev' first."; \
fi
clean: ## Clean up cache files and build artifacts
$(call info,Cleaning up...)
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find . -type f -name "*.pyc" -delete 2>/dev/null || true
find . -type f -name "*.pyo" -delete 2>/dev/null || true
find . -type f -name "*.orig" -delete 2>/dev/null || true
rm -rf .pytest_cache/ htmlcov/ .coverage .mypy_cache/ dist/ build/
rm -rf *.egg-info/
rm -f *.service *.timer *.socket *.mount *.target *.path
$(call success,Cleanup complete)
test: ## Run tests
$(call info,Running tests...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && uv run pytest tests/ -v; \
else \
uv run pytest tests/ -v; \
fi
test-cov: ## Run tests with coverage
$(call info,Running tests with coverage...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && uv run pytest tests/ --cov=backend --cov-report=html --cov-report=term; \
else \
uv run pytest tests/ --cov=backend --cov-report=html --cov-report=term; \
fi
$(call success,Coverage report generated in htmlcov/)
test-watch: ## Run tests in watch mode
$(call info,Running tests in watch mode...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && uv run pytest tests/ -v --watch; \
else \
uv run pytest tests/ -v --watch; \
fi
lint: ## Run linters
$(call info,Running linters...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && \
uv run black --check backend/ tests/ && \
uv run isort --check-only backend/ tests/ && \
uv run flake8 backend/ tests/ --max-line-length=88 --extend-ignore=E203,W503 --exclude=.venv,__pycache__,build,dist; \
else \
uv run black --check backend/ tests/ && \
uv run isort --check-only backend/ tests/ && \
uv run flake8 backend/ tests/ --max-line-length=88 --extend-ignore=E203,W503 --exclude=.venv,__pycache__,build,dist; \
fi
$(call success,Linting passed)
format: ## Format code
$(call info,Formatting code...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && \
uv run black backend/ tests/ && \
uv run isort backend/ tests/; \
else \
uv run black backend/ tests/ && \
uv run isort backend/ tests/; \
fi
$(call success,Code formatted)
type-check: ## Run type checking
$(call info,Running type checks...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && PYTHONPATH=. uv run mypy backend/app/ backend/cli/ --ignore-missing-imports; \
else \
PYTHONPATH=. uv run mypy backend/app/ backend/cli/ --ignore-missing-imports; \
fi
$(call success,Type checking passed)
security-check: ## Run security checks
$(call info,Running security checks...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && uv run bandit -r backend/ -x tests/; \
else \
uv run bandit -r backend/ -x tests/; \
fi
$(call success,Security checks passed)
pre-commit: ## Run pre-commit hooks on all files
$(call info,Running pre-commit hooks...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && uv run pre-commit run --all-files; \
else \
uv run pre-commit run --all-files; \
fi
server: ## Start development server
$(call info,Starting development server...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && ./start-server.sh --log-level debug; \
else \
./start-server.sh --log-level debug; \
fi
server-prod: ## Start production server
$(call info,Starting production server...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && ./start-server.sh --no-reload --log-level info; \
else \
./start-server.sh --no-reload --log-level info; \
fi
cli: ## Run CLI with help
$(call header,UnitForge CLI)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && ./unitforge-cli --help; \
else \
./unitforge-cli --help; \
fi
demo: ## Run interactive demo
$(call header,UnitForge Demo)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && ./demo.sh; \
else \
./demo.sh; \
fi
validate-config: ## Validate environment configuration
$(call header,Validating Configuration)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && python scripts/validate_config.py; \
else \
python3 scripts/validate_config.py; \
fi
# Docker targets
DOCKER_PLATFORMS := linux/amd64,linux/arm64
docker-build: ## Build Docker images (docker-compose)
$(call info,Building Docker images with docker-compose...)
docker-compose build
$(call success,Docker images built)
docker-buildx-setup: ## Ensure docker buildx builder exists and is bootstrapped
$(call info,Setting up docker buildx builder...)
@docker buildx create --name unitforge-builder --use 2>/dev/null || true
@docker buildx inspect --bootstrap
$(call success,Buildx builder ready)
docker-buildx: docker-buildx-setup ## Build and push multi-arch image (linux/amd64, linux/arm64)
$(call info,Building multi-arch image with buildx...)
@if [ -f ".env" ]; then \
REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||'); \
CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2); \
docker buildx build --platform $(DOCKER_PLATFORMS) -t $${REGISTRY_URL}:$${CONTAINER_TAG} -t $${REGISTRY_URL}:latest --push -f Dockerfile .; \
else \
echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Set CONTAINER_REGISTRY_URL and CONTAINER_TAG to enable push."; \
fi
$(call success,Multi-arch image built and pushed)
docker-buildx-local: docker-buildx-setup ## Build for current arch locally (no push)
$(call info,Building local image for current architecture...)
docker buildx build --load -t unitforge:local -f Dockerfile .
$(call success,Local image built: unitforge:local)
# Container Registry targets
docker-tag: ## Tag Docker images with registry URL
$(call info,Tagging Docker images for registry...)
@if [ -f ".env" ]; then \
REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \
CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \
docker tag unitforge-unitforge-prod:latest $${REGISTRY_URL}:$${CONTAINER_TAG} && \
docker tag unitforge-unitforge-prod:latest $${REGISTRY_URL}:latest; \
else \
echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Using default values."; \
docker tag unitforge-unitforge-prod:latest http://gitea-http.taildb3494.ts.net/will/unitforge:latest; \
fi
$(call success,Docker images tagged for registry)
docker-push: docker-tag ## Push Docker images to registry
$(call info,Pushing Docker images to registry...)
@if [ -f ".env" ]; then \
REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \
CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \
docker push $${REGISTRY_URL}:$${CONTAINER_TAG} && \
docker push $${REGISTRY_URL}:latest; \
else \
echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Using default values."; \
docker push http://gitea-http.taildb3494.ts.net/will/unitforge:latest; \
fi
$(call success,Docker images pushed to registry)
docker-pull: ## Pull Docker images from registry
$(call info,Pulling Docker images from registry...)
@if [ -f ".env" ]; then \
REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \
CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \
docker pull $${REGISTRY_URL}:$${CONTAINER_TAG}; \
else \
echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Using default values."; \
docker pull http://gitea-http.taildb3494.ts.net/will/unitforge:latest; \
fi
$(call success,Docker images pulled from registry)
registry-build: docker-build docker-tag ## Build and tag images for registry
$(call success,Images built and tagged for registry)
registry-push: registry-build docker-push ## Build, tag, and push images to registry
$(call success,Images built, tagged, and pushed to registry)
docker-login: ## Login to container registry (interactive)
$(call info,Logging into container registry...)
@if [ -f ".env" ]; then \
REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \
echo "Logging into registry at: $${REGISTRY_URL}"; \
docker login $${REGISTRY_URL}; \
else \
echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Using default values."; \
docker login http://gitea-http.taildb3494.ts.net; \
fi
$(call success,Logged into container registry)
docker-dev: ## Start development environment with Docker
$(call info,Starting development environment with Docker...)
docker-compose up unitforge-dev
docker-prod: ## Start production environment with Docker
$(call info,Starting production environment with Docker...)
docker-compose up unitforge-prod
docker-cli: ## Run CLI in Docker
$(call info,Running CLI in Docker...)
docker-compose --profile cli run --rm unitforge-cli --help
docker-logs: ## Show logs from all Docker services
$(call info,Showing Docker logs...)
docker-compose logs -f
docker-shell: ## Open shell in development container
$(call info,Opening shell in development container...)
docker-compose exec unitforge-dev /bin/bash
docker-clean: ## Clean up Docker containers and volumes
$(call info,Cleaning up Docker resources...)
docker-compose down --remove-orphans --volumes
docker system prune -f
$(call success,Docker cleanup completed)
docker-clean-registry: ## Remove locally cached registry images
$(call info,Cleaning registry images from local cache...)
@if [ -f ".env" ]; then \
REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \
CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \
docker rmi $${REGISTRY_URL}:$${CONTAINER_TAG} 2>/dev/null || true && \
docker rmi $${REGISTRY_URL}:latest 2>/dev/null || true; \
else \
docker rmi http://gitea-http.taildb3494.ts.net/will/unitforge:latest 2>/dev/null || true; \
fi
$(call success,Registry images cleaned from local cache)
# Package and release targets
build: clean ## Build package
$(call info,Building package...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && uv run python -m build; \
else \
uv run python -m build; \
fi
$(call success,Package built in dist/)
package: build ## Create distribution packages
$(call info,Creating distribution packages...)
@ls -la dist/
$(call success,Distribution packages ready)
publish-test: package ## Publish to TestPyPI
$(call info,Publishing to TestPyPI...)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && uv run twine upload --repository testpypi dist/*; \
else \
uv run twine upload --repository testpypi dist/*; \
fi
publish: package ## Publish to PyPI
$(call info,Publishing to PyPI...)
$(call warning,This will publish to the real PyPI. Are you sure? [y/N])
@read -r confirm && [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ] || (echo "Aborted" && exit 1)
@if [ -f "$(VENV_PATH)/bin/activate" ]; then \
. $(VENV_PATH)/bin/activate && uv run twine upload dist/*; \
else \
uv run twine upload dist/*; \
fi
docs: ## Generate documentation
$(call info,Generating documentation...)
$(call warning,Documentation generation not yet implemented)
# Comprehensive quality check
check-all: lint security-check test ## Run all quality checks
$(call success,All quality checks passed)
# Release preparation
release-check: check-all build ## Prepare for release
$(call info,Performing release checks...)
$(call success,Release checks completed)
@echo ""
@echo -e "$(YELLOW)Release checklist:$(NC)"
@echo " □ Update version in pyproject.toml"
@echo " □ Update CHANGELOG.md"
@echo " □ Commit changes"
@echo " □ Create git tag"
@echo " □ Run 'make publish'"
# Quick development cycle
dev: format lint test ## Quick development cycle (format, lint, test)
$(call success,Development cycle complete)
# Initialize new development environment
init: setup-dev ## Initialize new development environment
$(call success,Development environment initialized)
@echo ""
@echo -e "$(BLUE)Try these commands:$(NC)"
@echo " make server # Start web server"
@echo " make cli # Try CLI tool"
@echo " make demo # Interactive demo"
@echo " make test # Run tests"
# Show project status
status: ## Show project status
$(call header,UnitForge Project Status)
@echo ""
@echo -e "$(YELLOW)Virtual Environment:$(NC)"
@if [ -d "$(VENV_PATH)" ]; then \
echo " ✓ Virtual environment exists at $(VENV_PATH)"; \
if [ -n "$$VIRTUAL_ENV" ]; then \
echo " ✓ Virtual environment is activated"; \
else \
echo " ⚠ Virtual environment not activated"; \
fi \
else \
echo " ✗ Virtual environment not found"; \
fi
@echo ""
@echo -e "$(YELLOW)Dependencies:$(NC)"
@if command -v uv >/dev/null 2>&1; then \
echo " ✓ uv package manager available"; \
else \
echo " ✗ uv package manager not found - REQUIRED"; \
fi
@if command -v docker >/dev/null 2>&1; then \
echo " ✓ Docker available"; \
else \
echo " ✗ Docker not found"; \
fi
@echo ""
@echo -e "$(YELLOW)Container Registry:$(NC)"
@if [ -f ".env" ]; then \
REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2) && \
CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \
echo " ✓ Registry URL: $${REGISTRY_URL}"; \
echo " ✓ Container Tag: $${CONTAINER_TAG}"; \
else \
echo " ⚠ .env file not found - using defaults"; \
fi
@echo ""
@echo -e "$(YELLOW)Project Files:$(NC)"
@if [ -f "pyproject.toml" ]; then echo " ✓ pyproject.toml"; else echo " ✗ pyproject.toml"; fi
@if [ -f "unitforge-cli" ]; then echo " ✓ CLI tool"; else echo " ✗ CLI tool"; fi
@if [ -f "start-server.sh" ]; then echo " ✓ Server script"; else echo " ✗ Server script"; fi
@if [ -d "backend" ]; then echo " ✓ Backend code"; else echo " ✗ Backend code"; fi
@if [ -d "frontend" ]; then echo " ✓ Frontend code"; else echo " ✗ Frontend code"; fi
@if [ -d "tests" ]; then echo " ✓ Tests"; else echo " ✗ Tests"; fi