From 11a534ca110ec15e941ceb5744031484e1df384c Mon Sep 17 00:00:00 2001 From: Astounds Date: Sun, 3 May 2026 17:29:35 -0500 Subject: Makefile: auto-bootstrap venv, fix clean targets - Add ensure-venv target that auto-creates venv and installs deps on first run of dev/test/lint/i18n targets - PYTHON/PIP now resolve to venv automatically when available - clean: add release artefacts (yt-local/, python/, zips, get-pip.py, vc15) - distclean: reserved for venv removal - Fix $$ escaping for shell variables in Make recipes - Sanitize LANG_CODE input against shell injection - Fix test-cov (check python module, not command), info (shell vs Make expansion) - lint/format use venv python instead of system command -v - i18n-workflow uses native Make dependencies instead of recursive make --- Makefile | 215 ++++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 129 insertions(+), 86 deletions(-) diff --git a/Makefile b/Makefile index cf4ca99..de17745 100644 --- a/Makefile +++ b/Makefile @@ -1,57 +1,88 @@ # yt-local Makefile # Automated tasks for development, translations, and maintenance -.PHONY: help install dev clean test i18n-extract i18n-init i18n-update i18n-compile i18n-stats i18n-clean setup-dev lint format backup restore +.PHONY: help install dev clean test i18n-extract i18n-init i18n-update \ + i18n-compile i18n-stats i18n-clean setup-dev lint format backup \ + restore distclean info check-deps run test-cov i18n-workflow \ + ensure-venv # Variables -PYTHON := python3 -PIP := pip3 -LANG_CODE ?= es -VENV_DIR := venv -PROJECT_NAME := yt-local +SYSTEM_PYTHON := python3 +LANG_CODE ?= es +VENV_DIR := venv +PROJECT_NAME := yt-local + +# Use venv python/pip when the venv exists, system otherwise +VENV_PYTHON := $(VENV_DIR)/bin/python +VENV_PIP := $(VENV_DIR)/bin/pip +PYTHON = $(if $(wildcard $(VENV_PYTHON)),$(VENV_PYTHON),$(SYSTEM_PYTHON)) +PIP = $(if $(wildcard $(VENV_PIP)),$(VENV_PIP),$(SYSTEM_PYTHON) -m pip) + +# Patterns for release artefacts (generate_release.py) +RELEASE_DIR := yt-local +RELEASE_GLOBS := yt-local-*.zip python-dist-*.zip +RELEASE_DOWNLOADS := get-pip.py vc15_*.7z +PYTHON_DIST_DIR := python + +# Validate LANG_CODE: only allow letters and underscore, 2-5 chars (e.g. es, pt_BR) +# Guards against shell injection via make i18n-init LANG_CODE="$(malicious)" +LANG_CODE_SAFE := $(shell echo '$(LANG_CODE)' | grep -xE '[a-zA-Z_]{2,5}' || echo '') + +## Help ----------------------------------------------------------------------- -## Help help: ## Show this help message @echo "$(PROJECT_NAME) - Available tasks:" @echo "" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' @echo "" @echo "Examples:" - @echo " make install # Install dependencies" - @echo " make dev # Run development server" - @echo " make i18n-extract # Extract strings for translation" + @echo " make install # Install dependencies" + @echo " make dev # Run development server" + @echo " make i18n-extract # Extract strings for translation" @echo " make i18n-init LANG_CODE=fr # Initialize French" - @echo " make lint # Check code style" + @echo " make lint # Check code style" + +## Venv bootstrap (internal) -------------------------------------------------- + +ensure-venv: + @if [ ! -f "$(VENV_PYTHON)" ]; then \ + echo "[INFO] Creating virtual environment in $(VENV_DIR)..."; \ + $(SYSTEM_PYTHON) -m venv $(VENV_DIR); \ + if [ -f requirements-dev.txt ]; then \ + echo "[INFO] Installing dependencies (dev)..."; \ + $(VENV_DIR)/bin/pip install -r requirements-dev.txt; \ + else \ + echo "[INFO] Installing dependencies..."; \ + $(VENV_DIR)/bin/pip install -r requirements.txt; \ + fi; \ + echo "[SUCCESS] Virtual environment ready"; \ + fi + +## Installation and Setup ----------------------------------------------------- -## Installation and Setup -install: ## Install project dependencies +install: ensure-venv ## Install/update project dependencies @echo "[INFO] Installing dependencies..." $(PIP) install -r requirements.txt @echo "[SUCCESS] Dependencies installed" -setup-dev: ## Complete development setup - @echo "[INFO] Setting up development environment..." - $(PYTHON) -m venv $(VENV_DIR) - ./$(VENV_DIR)/bin/pip install -r requirements.txt - @echo "[SUCCESS] Virtual environment created in $(VENV_DIR)" - @echo "[INFO] Activate with: source $(VENV_DIR)/bin/activate" +setup-dev: ensure-venv ## Install dev dependencies (tests, linting) + @echo "[INFO] Installing dev dependencies..." + $(PIP) install -r requirements-dev.txt + @echo "[SUCCESS] Dev dependencies installed" -requirements: ## Update and install requirements - @echo "[INFO] Installing/updating requirements..." - $(PIP) install --upgrade pip - $(PIP) install -r requirements.txt - @echo "[SUCCESS] Requirements installed" +## Development ---------------------------------------------------------------- -## Development -dev: ## Run development server +dev: ensure-venv ## Run development server @echo "[INFO] Starting development server..." @echo "[INFO] Server available at: http://localhost:9010" $(PYTHON) server.py run: dev ## Alias for dev -## Testing -test: ## Run tests +## Testing -------------------------------------------------------------------- + +test: ensure-venv ## Run tests @echo "[INFO] Running tests..." @if [ -d "tests" ]; then \ $(PYTHON) -m pytest -v; \ @@ -59,32 +90,35 @@ test: ## Run tests echo "[WARN] No tests directory found"; \ fi -test-cov: ## Run tests with coverage +test-cov: ensure-venv ## Run tests with coverage @echo "[INFO] Running tests with coverage..." - @if command -v pytest-cov >/dev/null 2>&1; then \ - $(PYTHON) -m pytest -v --cov=$(PROJECT_NAME) --cov-report=html; \ - else \ - echo "[WARN] pytest-cov not installed. Run: pip install pytest-cov"; \ - fi + @$(PYTHON) -c "import pytest_cov" 2>/dev/null && \ + $(PYTHON) -m pytest -v --cov=youtube --cov-report=html || \ + echo "[WARN] pytest-cov not installed. Run: make setup-dev" + +## Internationalization (i18n) ------------------------------------------------ -## Internationalization (i18n) -i18n-extract: ## Extract strings for translation +i18n-extract: ensure-venv ## Extract strings for translation @echo "[INFO] Extracting strings for translation..." $(PYTHON) manage_translations.py extract @echo "[SUCCESS] Strings extracted to translations/messages.pot" -i18n-init: ## Initialize new language (use LANG_CODE=xx) - @echo "[INFO] Initializing language: $(LANG_CODE)" - $(PYTHON) manage_translations.py init $(LANG_CODE) - @echo "[SUCCESS] Language $(LANG_CODE) initialized" - @echo "[INFO] Edit: translations/$(LANG_CODE)/LC_MESSAGES/messages.po" +i18n-init: ensure-venv ## Initialize new language (use LANG_CODE=xx) + @if [ -z "$(LANG_CODE_SAFE)" ]; then \ + echo "[ERROR] Invalid LANG_CODE='$(LANG_CODE)'. Use 2-5 letters (e.g. es, pt_BR)."; \ + exit 1; \ + fi + @echo "[INFO] Initializing language: $(LANG_CODE_SAFE)" + $(PYTHON) manage_translations.py init $(LANG_CODE_SAFE) + @echo "[SUCCESS] Language $(LANG_CODE_SAFE) initialized" + @echo "[INFO] Edit: translations/$(LANG_CODE_SAFE)/LC_MESSAGES/messages.po" -i18n-update: ## Update existing translations +i18n-update: ensure-venv ## Update existing translations @echo "[INFO] Updating existing translations..." $(PYTHON) manage_translations.py update @echo "[SUCCESS] Translations updated" -i18n-compile: ## Compile translations to binary .mo files +i18n-compile: ensure-venv ## Compile translations to binary .mo files @echo "[INFO] Compiling translations..." $(PYTHON) manage_translations.py compile @echo "[SUCCESS] Translations compiled" @@ -98,7 +132,7 @@ i18n-stats: ## Show translation statistics po_file="$$lang_dir/LC_MESSAGES/messages.po"; \ if [ -f "$$po_file" ]; then \ total=$$(grep -c "^msgid " "$$po_file" 2>/dev/null || echo "0"); \ - translated=$$(grep -c "^msgstr \"[^\"]\+\"" "$$po_file" 2>/dev/null || echo "0"); \ + translated=$$(grep -c '^msgstr "[^"]' "$$po_file" 2>/dev/null || echo "0"); \ fuzzy=$$(grep -c "^#, fuzzy" "$$po_file" 2>/dev/null || echo "0"); \ if [ "$$total" -gt 0 ]; then \ percent=$$((translated * 100 / total)); \ @@ -106,50 +140,44 @@ i18n-stats: ## Show translation statistics else \ echo " [STAT] $$lang: No translations yet"; \ fi; \ - fi \ - fi \ + fi; \ + fi; \ done @echo "" -i18n-clean: ## Clean compiled translation files +i18n-clean: ## Clean compiled translation files (.mo only) @echo "[INFO] Cleaning compiled .mo files..." - find translations/ -name "*.mo" -delete + find translations/ -name "*.mo" -delete 2>/dev/null || true @echo "[SUCCESS] .mo files removed" -i18n-workflow: ## Complete workflow: extract → update → compile - @echo "[INFO] Running complete translation workflow..." - @make i18n-extract - @make i18n-update - @make i18n-compile - @make i18n-stats +i18n-workflow: i18n-extract i18n-update i18n-compile i18n-stats ## Complete workflow: extract → update → compile @echo "[SUCCESS] Translation workflow completed" -## Code Quality -lint: ## Check code with flake8 +## Code Quality --------------------------------------------------------------- + +lint: ensure-venv ## Check code with flake8 @echo "[INFO] Checking code style..." - @if command -v flake8 >/dev/null 2>&1; then \ - flake8 youtube/ --max-line-length=120 --ignore=E501,W503,E402 --exclude=youtube/ytdlp_service.py,youtube/ytdlp_integration.py,youtube/ytdlp_proxy.py; \ - echo "[SUCCESS] Code style check passed"; \ - else \ - echo "[WARN] flake8 not installed (pip install flake8)"; \ - fi + @$(PYTHON) -c "import flake8" 2>/dev/null && \ + $(PYTHON) -m flake8 youtube/ --max-line-length=120 --ignore=E501,W503,E402 \ + --exclude=youtube/ytdlp_service.py,youtube/ytdlp_integration.py,youtube/ytdlp_proxy.py && \ + echo "[SUCCESS] Code style check passed" || \ + echo "[WARN] flake8 not installed. Run: make setup-dev" -format: ## Format code with black (if available) +format: ensure-venv ## Format code with black (if available) @echo "[INFO] Formatting code..." - @if command -v black >/dev/null 2>&1; then \ - black youtube/ --line-length=120 --exclude='ytdlp_.*\.py'; \ - echo "[SUCCESS] Code formatted"; \ - else \ - echo "[WARN] black not installed (pip install black)"; \ - fi + @$(PYTHON) -c "import black" 2>/dev/null && \ + $(PYTHON) -m black youtube/ --line-length=120 --exclude='ytdlp_.*\.py' && \ + echo "[SUCCESS] Code formatted" || \ + echo "[WARN] black not installed. Run: make setup-dev" -check-deps: ## Check installed dependencies +check-deps: ensure-venv ## Check installed dependencies @echo "[INFO] Checking dependencies..." @$(PYTHON) -c "import flask_babel; print('[OK] Flask-Babel:', flask_babel.__version__)" 2>/dev/null || echo "[ERROR] Flask-Babel not installed" @$(PYTHON) -c "import flask; print('[OK] Flask:', flask.__version__)" 2>/dev/null || echo "[ERROR] Flask not installed" @$(PYTHON) -c "import yt_dlp; print('[OK] yt-dlp:', yt_dlp.__version__)" 2>/dev/null || echo "[ERROR] yt-dlp not installed" -## Maintenance +## Maintenance ---------------------------------------------------------------- + backup: ## Create translations backup @echo "[INFO] Creating translations backup..." @timestamp=$$(date +%Y%m%d_%H%M%S); \ @@ -158,24 +186,39 @@ backup: ## Create translations backup echo "[SUCCESS] Backup created: translations_backup_$$timestamp.tar.gz"; \ fi -restore: ## Restore translations from backup +restore: ## Restore translations from latest backup @echo "[INFO] Restoring translations from backup..." - @if ls translations_backup_*.tar.gz 1>/dev/null 2>&1; then \ - latest_backup=$$(ls -t translations_backup_*.tar.gz | head -1); \ + @latest_backup=$$(find . -maxdepth 1 -name 'translations_backup_*.tar.gz' -print0 | \ + xargs -0 ls -t 2>/dev/null | head -1); \ + if [ -n "$$latest_backup" ]; then \ tar -xzf "$$latest_backup"; \ echo "[SUCCESS] Restored from: $$latest_backup"; \ else \ echo "[ERROR] No backup files found"; \ fi -clean: ## Clean temporary files and caches +## Cleanup -------------------------------------------------------------------- + +clean: ## Clean temporary files, caches, and release artefacts @echo "[INFO] Cleaning temporary files..." + # Python byte-code and caches find . -type f -name "*.pyc" -delete - find . -type d -name "__pycache__" -delete + find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true + # Compiled translation files (project-wide) find . -type f -name "*.mo" -delete - find . -type d -name ".pytest_cache" -delete + # Pytest cache and coverage data + find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true + find . -type d -name "htmlcov" -exec rm -rf {} + 2>/dev/null || true find . -type f -name ".coverage" -delete - find . -type d -name "htmlcov" -delete + # Misc temporary / backup artefacts + find . -type f \( -name "*.tmp" -o -name "*.bak" -o -name "*.log" \) -delete + find . -type d -name "*.cache" -exec rm -rf {} + 2>/dev/null || true + # Checksum files + find . -type f \( -name "*.sha512sum" -o -name "*.sha256sum" -o -name "*.sha1sum" \ + -o -name "*.md5sum" -o -name "*.b2sum" \) -delete + # Release artefacts (generate_release.py) + rm -rf $(RELEASE_DIR)/ $(PYTHON_DIST_DIR)/ + rm -f $(RELEASE_GLOBS) $(RELEASE_DOWNLOADS) @echo "[SUCCESS] Temporary files removed" distclean: clean ## Clean everything including venv @@ -183,20 +226,20 @@ distclean: clean ## Clean everything including venv rm -rf $(VENV_DIR) @echo "[SUCCESS] Complete cleanup done" -## Project Information -info: ## Show project information +## Project Information -------------------------------------------------------- + +info: ensure-venv ## Show project information @echo "[INFO] $(PROJECT_NAME) - Project information:" @echo "" @echo " [INFO] Directory: $$(pwd)" - @echo " [INFO] Python: $$($(PYTHON) --version)" - @echo " [INFO] Pip: $$($(PIP) --version | cut -d' ' -f1-2)" + @echo " [INFO] Python: $$($(PYTHON) --version 2>&1)" + @echo " [INFO] Pip: $$($(PIP) --version 2>&1 | cut -d' ' -f1-2)" @echo "" @echo " [INFO] Configured languages:" @for lang_dir in translations/*/; do \ if [ -d "$$lang_dir" ] && [ "$$lang_dir" != "translations/*/" ]; then \ - lang=$$(basename "$$lang_dir"); \ - echo " - $$lang"; \ - fi \ + echo " - $$(basename $$lang_dir)"; \ + fi; \ done @echo "" @echo " [INFO] Main files:" -- cgit v1.2.3