# Define the path to uv UV_PATH := $(shell which uv 2>/dev/null || echo "") UV_LOCAL_PATH := $(HOME)/.local/bin/uv UV_CARGO_PATH := $(HOME)/.cargo/bin/uv # Check if uv is installed, if not set UV_INSTALL to 1 ifeq ($(UV_PATH),) ifeq ($(wildcard $(UV_LOCAL_PATH)),) ifeq ($(wildcard $(UV_CARGO_PATH)),) UV_INSTALL := 1 else UV_PATH := $(UV_CARGO_PATH) endif else UV_PATH := $(UV_LOCAL_PATH) endif endif # Define the virtual environment path VENV := .venv VENV_ACTIVATE := $(VENV)/bin/activate VENV_PYTHON := $(VENV)/bin/python VENV_UV := $(VENV)/bin/uv VENV_PIP := $(VENV)/bin/pip VENV_PRE_COMMIT := $(VENV)/bin/pre-commit VENV_RUFF := $(VENV)/bin/ruff .PHONY: all all: venv .PHONY: install-uv install-uv: ifdef UV_INSTALL @echo "Installing uv..." @curl -LsSf https://astral.sh/uv/install.sh | sh @echo "uv installed to $(HOME)/.local/bin/uv" $(eval UV_PATH := $(HOME)/.local/bin/uv) endif .PHONY: venv venv: install-uv @echo "Creating virtual environment..." @$(UV_PATH) venv $(VENV) @echo "Installing project dependencies from pyproject.toml..." @$(UV_PATH) pip install --python $(VENV_PYTHON) -e . --config-settings editable_mode=compat @echo "Removing conflicting console script to avoid PATH conflicts..." @rm -f $(VENV)/bin/qbit-manage 2>/dev/null || true @echo "Installing development dependencies..." @$(UV_PATH) pip install --python $(VENV_PYTHON) pre-commit ruff @echo "Virtual environment created and dependencies installed." @echo "✓ Virtual environment ready for development" @echo "To activate the virtual environment, run: source $(VENV_ACTIVATE)" .PHONY: sync sync: venv @echo "Syncing dependencies from pyproject.toml..." @$(UV_PATH) pip sync pyproject.toml .PHONY: test test: venv @echo "Running tests..." @. $(VENV_ACTIVATE) && $(VENV_PYTHON) -m pytest .PHONY: pre-commit pre-commit: venv @echo "Running pre-commit hooks..." @. $(VENV_ACTIVATE) && $(VENV_PRE_COMMIT) run --all-files .PHONY: install-hooks install-hooks: venv @echo "Installing pre-commit hooks..." @. $(VENV_ACTIVATE) && $(VENV_PRE_COMMIT) install -f --install-hooks .PHONY: clean clean: @echo "Cleaning up..." @find -name '*.pyc' -delete @find -name '__pycache__' -delete @rm -rf $(VENV) @rm -rf .pytest_cache @rm -rf .ruff_cache @rm -rf dist/ @rm -rf build/ @rm -rf *.egg-info/ @rm -rf web-ui/dist/ @rm -rf web-ui/build/ @rm -rf web-ui/node_modules/ @rm -rf desktop/tauri/src-tauri/target/ @rm -rf desktop/tauri/src-tauri/gen/ @rm -rf desktop/tauri/node_modules/ @echo "Cleanup complete." .PHONY: lint lint: venv @echo "Running linter..." @. $(VENV_ACTIVATE) && $(VENV_RUFF) check --fix . .PHONY: format format: venv @echo "Running formatter..." @. $(VENV_ACTIVATE) && $(VENV_RUFF) format . .PHONY: build build: venv @echo "Building package..." @$(UV_PATH) pip install --python $(VENV_PYTHON) build twine @. $(VENV_ACTIVATE) && $(VENV_PYTHON) -m build @echo "Package built successfully. Files in dist/" .PHONY: check-dist check-dist: build @echo "Checking distribution files..." @. $(VENV_ACTIVATE) && $(VENV_PYTHON) -m twine check dist/* .PHONY: setup-pypi setup-pypi: @echo "Setting up PyPI configuration..." @if [ -f ~/.pypirc ] && grep -q "password = pypi-" ~/.pypirc 2>/dev/null; then \ echo "✓ ~/.pypirc already exists with API tokens configured"; \ else \ $(MAKE) setup-pypi-interactive; \ fi .PHONY: setup-pypi-interactive setup-pypi-interactive: @echo "" @echo "This will set up your PyPI credentials for automatic uploads." @echo "You'll need API tokens from:" @echo " - Test PyPI: https://test.pypi.org/manage/account/token/" @echo " - Live PyPI: https://pypi.org/manage/account/token/" @echo "" @echo "Creating accounts (if needed):" @echo " - Test PyPI: https://test.pypi.org/account/register/" @echo " - Live PyPI: https://pypi.org/account/register/" @echo "" @printf "Press Enter to continue or Ctrl+C to cancel..." @read dummy @echo "" @printf "Please enter your Test PyPI API token (starts with 'pypi-'): " @read testpypi_token; \ echo ""; \ printf "Please enter your PyPI API token (starts with 'pypi-'): "; \ read pypi_token; \ echo ""; \ if [ -z "$$testpypi_token" ] || [ -z "$$pypi_token" ]; then \ echo "❌ Both tokens are required. Setup cancelled."; \ exit 1; \ fi; \ if ! echo "$$testpypi_token" | grep -q "^pypi-" || ! echo "$$pypi_token" | grep -q "^pypi-"; then \ echo "❌ Invalid token format. Tokens should start with 'pypi-'"; \ exit 1; \ fi; \ echo "Creating ~/.pypirc configuration file..."; \ echo "[distutils]" > ~/.pypirc; \ echo "index-servers =" >> ~/.pypirc; \ echo " pypi" >> ~/.pypirc; \ echo " testpypi" >> ~/.pypirc; \ echo "" >> ~/.pypirc; \ echo "[pypi]" >> ~/.pypirc; \ echo "repository = https://upload.pypi.org/legacy/" >> ~/.pypirc; \ echo "username = __token__" >> ~/.pypirc; \ echo "password = $$pypi_token" >> ~/.pypirc; \ echo "" >> ~/.pypirc; \ echo "[testpypi]" >> ~/.pypirc; \ echo "repository = https://test.pypi.org/legacy/" >> ~/.pypirc; \ echo "username = __token__" >> ~/.pypirc; \ echo "password = $$testpypi_token" >> ~/.pypirc; \ chmod 600 ~/.pypirc; \ echo "✓ PyPI configuration saved to ~/.pypirc"; \ echo "✓ You can now use 'make upload-test' and 'make upload-pypi' without entering tokens" .PHONY: upload-test upload-test: check-dist @echo "Uploading to Test PyPI..." @if [ -z "$$TWINE_PASSWORD_TESTPYPI" ] && ! grep -q "password = pypi-" ~/.pypirc 2>/dev/null; then \ echo ""; \ echo "No API token found. Please either:"; \ echo "1. Set environment variable: export TWINE_PASSWORD_TESTPYPI=your-test-pypi-token"; \ echo "2. Run 'make setup-pypi' and edit ~/.pypirc with your tokens"; \ echo "3. Get token from: https://test.pypi.org/manage/account/token/"; \ exit 1; \ fi @if [ -n "$$TWINE_PASSWORD_TESTPYPI" ]; then \ echo "Using environment variable for authentication"; \ . $(VENV_ACTIVATE) && TWINE_USERNAME=__token__ TWINE_PASSWORD=$$TWINE_PASSWORD_TESTPYPI $(VENV_PYTHON) -m twine upload --repository testpypi --verbose --skip-existing dist/*; \ else \ echo "Using ~/.pypirc for authentication"; \ . $(VENV_ACTIVATE) && $(VENV_PYTHON) -m twine upload --repository testpypi --verbose --skip-existing dist/*; \ fi @echo "Upload to Test PyPI complete!" @echo "Test installation with: pip install --index-url https://test.pypi.org/simple/ qbit-manage" .PHONY: upload-pypi upload-pypi: check-dist @echo "Uploading to PyPI..." @echo "WARNING: This will upload to the LIVE PyPI repository!" @if [ -z "$$TWINE_PASSWORD_PYPI" ] && ! grep -q "password = pypi-" ~/.pypirc 2>/dev/null; then \ echo ""; \ echo "No API token found. Please either:"; \ echo "1. Set environment variable: export TWINE_PASSWORD_PYPI=your-pypi-token"; \ echo "2. Run 'make setup-pypi' and edit ~/.pypirc with your tokens"; \ echo "3. Get token from: https://pypi.org/manage/account/token/"; \ exit 1; \ fi @read -p "Are you sure you want to continue? (y/N): " confirm && [ "$$confirm" = "y" ] @if [ -n "$$TWINE_PASSWORD_PYPI" ]; then \ echo "Using environment variable for authentication"; \ . $(VENV_ACTIVATE) && TWINE_USERNAME=__token__ TWINE_PASSWORD=$$TWINE_PASSWORD_PYPI $(VENV_PYTHON) -m twine upload --verbose --skip-existing dist/*; \ else \ echo "Using ~/.pypirc for authentication"; \ . $(VENV_ACTIVATE) && $(VENV_PYTHON) -m twine upload --verbose --skip-existing dist/*; \ fi @echo "Upload to PyPI complete!" @echo "Package is now available at: https://pypi.org/project/qbit-manage/" .PHONY: bump-version bump-version: @echo "Current version: $$(cat VERSION)" @echo "Bumping patch version for testing..." @current_version=$$(cat VERSION | cut -d'-' -f1); \ IFS='.' read -r major minor patch <<< "$$current_version"; \ new_patch=$$((patch + 1)); \ new_version="$$major.$$minor.$$new_patch"; \ echo "$$new_version-dev" > VERSION; \ echo "✓ Version bumped to: $$(cat VERSION)" @echo "Now you can run: make build && make upload-test" .PHONY: debug-upload debug-upload: check-dist @echo "Debugging upload configuration..." @echo "Current version: $$(cat VERSION 2>/dev/null || echo 'VERSION file not found')" @echo "" @echo "Checking ~/.pypirc configuration:" @if [ -f ~/.pypirc ]; then \ echo "✓ ~/.pypirc exists"; \ echo "Repositories configured:"; \ grep -E "^\[.*\]" ~/.pypirc || echo "No repositories found"; \ echo ""; \ echo "Test PyPI config:"; \ sed -n '/\[testpypi\]/,/^\[/p' ~/.pypirc | head -n -1 || echo "No testpypi section found"; \ else \ echo "❌ ~/.pypirc not found"; \ fi @echo "" @echo "Environment variables:" @echo "TWINE_USERNAME: $${TWINE_USERNAME:-not set}" @echo "TWINE_PASSWORD_TESTPYPI: $${TWINE_PASSWORD_TESTPYPI:+set (hidden)}" @echo "TWINE_PASSWORD_PYPI: $${TWINE_PASSWORD_PYPI:+set (hidden)}" @echo "" @echo "Package information:" @ls -la dist/ 2>/dev/null || echo "No dist/ directory found" @echo "" @echo "Common issues and solutions:" @echo " - File already exists: Run 'make bump-version' to create a new version" @echo " - Invalid token: Run 'make setup-pypi' to reconfigure" @echo " - Package name taken: Change name in pyproject.toml" # UV Tool Installation targets .PHONY: install install: @echo "Installing qbit-manage using uv tool..." @echo "Cleaning cache and build artifacts to ensure fresh install..." @rm -rf build/ dist/ *.egg-info/ 2>/dev/null || true @find . -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true @$(UV_PATH) cache clean >/dev/null 2>&1 || true @$(UV_PATH) tool install . --force @echo "✓ Installation complete!" @echo "Test with: qbit-manage --version" .PHONY: uninstall uninstall: @echo "Uninstalling qbit-manage..." @$(UV_PATH) tool uninstall qbit-manage || echo "qbit-manage was not installed" @echo "✓ Uninstall complete!" .PHONY: reinstall reinstall: uninstall install @echo "✓ Reinstall complete!" .PHONY: prep-release prep-release: @echo "Preparing release..." @# Step 1: Update uv lock and sync dependencies @echo "Updating uv lock and syncing dependencies..." @uv lock --upgrade @uv sync @echo "✓ Dependencies updated" @# Step 2: Strip '-develop*' suffix from VERSION @current_version=$$(cat VERSION); \ clean_version=$$(echo $$current_version | sed 's/-develop.*$$//'); \ echo "$$clean_version" > VERSION; \ echo "✓ VERSION updated to $$clean_version" @# Step 3: Check Tauri Rust project builds @echo "Running cargo check in desktop/tauri/src-tauri..." @cd desktop/tauri/src-tauri && cargo check @# Step 4: Prepare CHANGELOG skeleton and bump Full Changelog link @new_version=$$(cat VERSION); \ major=$$(echo "$$new_version" | cut -d. -f1); \ minor=$$(echo "$$new_version" | cut -d. -f2); \ patch=$$(echo "$$new_version" | cut -d. -f3); \ prev_patch=$$((patch - 1)); \ prev_version="$$major.$$minor.$$prev_patch"; \ updated_deps=$$(git diff master..HEAD -- pyproject.toml | grep '^+' | grep '==' | sed 's/^+//' | sed 's/^ *//' | sed 's/,$$//' | sed 's/^/- /'); \ echo "# Requirements Updated" > CHANGELOG; \ if [ -n "$$updated_deps" ]; then \ echo "$$updated_deps" >> CHANGELOG; \ fi; \ echo "" >> CHANGELOG; \ echo "# New Features" >> CHANGELOG; \ echo "" >> CHANGELOG; \ echo "" >> CHANGELOG; \ echo "# Improvements" >> CHANGELOG; \ echo "" >> CHANGELOG; \ echo "" >> CHANGELOG; \ echo "# Bug Fixes" >> CHANGELOG; \ echo "" >> CHANGELOG; \ echo "" >> CHANGELOG; \ echo "**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v$$prev_version...v$$new_version" >> CHANGELOG; \ echo "✓ CHANGELOG prepared for release $$new_version" @echo "" @echo "REMINDER: Update the CHANGELOG contents with actual improvements and bug fixes before making the release." .PHONY: help help: @echo "Available targets:" @echo " install - Install qbit-manage using uv tool (overwrites existing)" @echo " uninstall - Uninstall qbit-manage from uv tools" @echo " reinstall - Uninstall then install (clean reinstall)" @echo " venv - Create virtual environment and install dependencies" @echo " sync - Sync dependencies from pyproject.toml" @echo " test - Run tests" @echo " lint - Run linter with fixes" @echo " format - Run code formatter" @echo " pre-commit - Run pre-commit hooks" @echo " build - Build package for distribution" @echo " check-dist - Check distribution files" @echo " setup-pypi - Set up PyPI configuration (~/.pypirc)" @echo " bump-version - Bump patch version for testing uploads" @echo " prep-release - Strip '-develop*' from VERSION, cargo check, and template CHANGELOG" @echo " debug-upload - Debug PyPI upload configuration" @echo " upload-test - Upload to Test PyPI (uses env vars or ~/.pypirc)" @echo " upload-pypi - Upload to PyPI (LIVE) (uses env vars or ~/.pypirc)" @echo " clean - Clean up all generated files (venv, dist, build, cache)" @echo " help - Show this help message"