465 lines
17 KiB
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
|