From b8a7fb903eb4bad8344f98b78fff8c6ce9791858 Mon Sep 17 00:00:00 2001 From: claudi Date: Fri, 20 Feb 2026 09:22:11 +0100 Subject: [PATCH] Add build scripts and configuration for Elytra PIM Client --- BUILD.md | 377 +++++++++++++++++++++++++++++++++++++++++ build_requirements.txt | 15 ++ build_wheel.ps1 | 188 ++++++++++++++++++++ build_wheel.py | 152 +++++++++++++++++ build_wheel.sh | 197 +++++++++++++++++++++ pyproject.toml | 3 +- setup.py | 10 ++ 7 files changed, 940 insertions(+), 2 deletions(-) create mode 100644 BUILD.md create mode 100644 build_requirements.txt create mode 100644 build_wheel.ps1 create mode 100644 build_wheel.py create mode 100644 build_wheel.sh create mode 100644 setup.py diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..e37f3d3 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,377 @@ +# Building and Distributing Elytra PIM Client + +This guide explains how to build wheel distributions and upload them to PyPI or other package repositories. + +## Project Version + +Current version: **0.1.0** (Development Release) + +The version is defined in `pyproject.toml` under `[project]` section. + +## Build Tools + +### Requirements + +- Python 3.9 or higher +- pip with setuptools and wheel + +### Build Dependencies + +Install build requirements: + +```bash +pip install -r build_requirements.txt +``` + +Or manually: + +```bash +pip install "setuptools>=65.0" "wheel>=0.38.0" "build>=0.10.0" +``` + +## Building Wheels + +### Option 1: Using Python Script (Recommended for All Platforms) + +```bash +# Activate virtual environment first +.venv\Scripts\activate # Windows +source .venv/bin/activate # macOS/Linux + +# Run the build script +python build_wheel.py +``` + +This script will: +- Clean previous build artifacts +- Build wheel (`.whl`) and source (`.tar.gz`) distributions +- Display the build results with file sizes +- Show installation instructions + +### Option 2: Using PowerShell Script (Windows) + +```powershell +# Activate virtual environment +.\.venv\Scripts\Activate.ps1 + +# Run the build script +.\build_wheel.ps1 + +# Clean and rebuild +.\build_wheel.ps1 -Clean + +# Build and upload to PyPI +.\build_wheel.ps1 -Upload +``` + +### Option 3: Using Shell Script (macOS/Linux) + +```bash +# Activate virtual environment +source .venv/bin/activate + +# Run the build script +chmod +x build_wheel.sh # Make executable (first time only) +./build_wheel.sh + +# Clean and rebuild +./build_wheel.sh --clean + +# Build and upload to PyPI +./build_wheel.sh --upload +``` + +### Option 4: Using Modern Build Tool Directly + +```bash +# Install build tool +pip install build + +# Build wheel and sdist in one command +python -m build + +# Build only wheel +python -m build --wheel + +# Build only source distribution +python -m build --sdist +``` + +### Option 5: Using setuptools Directly + +```bash +# Create wheel only +python setup.py bdist_wheel + +# Create both wheel and source distribution +python setup.py sdist bdist_wheel +``` + +## Build Output + +Distributions are created in the `dist/` directory: + +``` +dist/ +├── elytra_pim_client-0.1.0-py3-none-any.whl # Wheel distribution +└── elytra_pim_client-0.1.0.tar.gz # Source distribution +``` + +### Understanding the Wheel Filename + +`elytra_pim_client-0.1.0-py3-none-any.whl` + +- `elytra_pim_client` - Package name +- `0.1.0` - Version +- `py3` - Python version (3.x only) +- `none` - No C extensions (pure Python) +- `any` - Platform independent + +## Installation from Built Wheel + +### From Local File + +After building, install the wheel locally: + +```bash +pip install dist/elytra_pim_client-0.1.0-py3-none-any.whl +``` + +### Editable Install During Development + +During development, use editable install so changes are reflected immediately: + +```bash +pip install -e . +``` + +Or with dev dependencies: + +```bash +pip install -e ".[dev]" +``` + +## Uploading to PyPI + +### Prerequisites + +1. Create PyPI account at https://pypi.org/ +2. Create PyPI token: https://pypi.org/manage/account/tokens/ +3. Configure credentials + +### Using twine + +```bash +# Install twine +pip install twine + +# Upload to PyPI (after building) +twine upload dist/* + +# Or to TestPyPI for testing first +twine upload -r testpypi dist/* +``` + +### Authentication + +**Option 1: Using .pypirc file** + +Create `~/.pypirc`: + +```ini +[distutils] +index-servers = + pypi + testpypi + +[pypi] +repository = https://upload.pypi.org/legacy/ +username = __token__ +password = pypi_your_token_here + +[testpypi] +repository = https://test.pypi.org/legacy/ +username = __token__ +password = pypi_your_test_token_here +``` + +**Option 2: Interactive prompt** + +twine will prompt for username and password when uploading. + +### Upload to Test PyPI First + +Before uploading to production, test with TestPyPI: + +```bash +twine upload -r testpypi dist/* +``` + +Then test installation: + +```bash +pip install -i https://test.pypi.org/simple/ elytra-pim-client==0.1.0 +``` + +## Versioning + +### Version Format + +Follow PEP 440 versioning: + +``` +Major.Minor.Patch (e.g., 0.1.0) +Major.Minor.Patch.preN (e.g., 0.1.0.pre1) +Major.Minor.Patch.postN (e.g., 0.1.0.post1) +Major.Minor.Patch.devN (e.g., 0.1.0.dev1) +``` + +### Updating Version + +Edit `pyproject.toml`: + +```toml +[project] +name = "elytra-pim-client" +version = "0.2.0" # Update version here +``` + +## Automatic Build Validation + +Before building, scripts automatically: + +1. Remove old build artifacts +2. Clean `dist/`, `build/`, and `.egg-info` directories +3. Verify build dependencies +4. Validate Python version compatibility +5. Check that distributions were created + +## Continuous Integration + +### GitHub Actions Example + +```yaml +name: Build Distribution + +on: + release: + types: [created] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + - run: pip install -r build_requirements.txt + - run: python build_wheel.py + - uses: actions/upload-artifact@v3 + with: + name: dist + path: dist/ + + upload: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 +``` + +## Troubleshooting + +### Build Fails with "No module named 'build'" + +```bash +pip install build +python build_wheel.py +``` + +### "setuptools not found" + +```bash +pip install setuptools>=65.0 +``` + +### Permission Denied (Unix/Linux) + +Make build script executable: + +```bash +chmod +x build_wheel.sh +``` + +### "twine: command not found" + +```bash +pip install twine +python -m twine upload dist/* +``` + +### Wheel not in dist/ + +Check that `pyproject.toml` exists and is valid: + +```bash +python -m build --verbose +``` + +## File Structure + +``` +elytra_client/ +├── build_wheel.py # Python build script (all platforms) +├── build_wheel.ps1 # PowerShell build script (Windows) +├── build_wheel.sh # Shell build script (Unix/Linux) +├── build_requirements.txt # Build dependencies +├── setup.py # Setup configuration (legacy compatibility) +├── pyproject.toml # Modern build configuration (PEP 517/518) +├── MANIFEST.in # (Optional) File inclusion rules +└── dist/ # Output directory for distributions + ├── elytra_pim_client-0.1.0-py3-none-any.whl + └── elytra_pim_client-0.1.0.tar.gz +``` + +## Best Practices + +1. **Always test before releasing** + ```bash + twine upload -r testpypi dist/* + pip install -i https://test.pypi.org/simple/ elytra-pim-client==0.1.0 + ``` + +2. **Increment version for each release** + - Patch: Bug fixes (0.1.1) + - Minor: New features (0.2.0) + - Major: Breaking changes (1.0.0) + +3. **Clean before rebuilding** + ```bash + python build_wheel.py # Automatically cleans + # Or manually + rm -rf dist/ build/ *.egg-info/ + ``` + +4. **Keep dependencies minimal** + - Only required packages in `dependencies` + - Development tools in `[project.optional-dependencies]` + +5. **Document changes** + - Update CHANGELOG.md (if present) + - Update version in pyproject.toml + - Create git tag for release + +## Resources + +- [Python Packaging Guide](https://packaging.python.org/) +- [PEP 517 - Build System Interface](https://www.python.org/dev/peps/pep-0517/) +- [PEP 518 - pyproject.toml](https://www.python.org/dev/peps/pep-0518/) +- [PEP 440 - Version Identification](https://www.python.org/dev/peps/pep-0440/) +- [setuptools Documentation](https://setuptools.pypa.io/) +- [twine Documentation](https://twine.readthedocs.io/) +- [PyPI Help](https://pypi.org/help/) diff --git a/build_requirements.txt b/build_requirements.txt new file mode 100644 index 0000000..7695f20 --- /dev/null +++ b/build_requirements.txt @@ -0,0 +1,15 @@ +# Build Requirements for Elytra PIM Client + +# Core build tools +setuptools>=65.0 +wheel>=0.38.0 + +# Modern build frontend (PEP 517) +build>=0.10.0 + +# Distribution upload +twine>=4.0.0 + +# Optional: for building documentation +# sphinx>=5.0.0 +# sphinx-rtd-theme>=1.0.0 diff --git a/build_wheel.ps1 b/build_wheel.ps1 new file mode 100644 index 0000000..3720566 --- /dev/null +++ b/build_wheel.ps1 @@ -0,0 +1,188 @@ +# build_wheel.ps1 +# Build wheel distribution for Elytra PIM Client (PowerShell) +# +# Usage: +# .\build_wheel.ps1 +# +# Requires: +# - Python 3.9+ +# - setuptools>=65.0 +# - wheel +# - build (recommended) + +param( + [switch]$Clean = $false, + [switch]$Upload = $false +) + +# Define symbols as variables to avoid encoding issues +$symSuccess = "[OK]" +$symError = "[!]" +$symBullet = "[*]" + +# Color functions for output +function Write-Success { + Write-Host "$symSuccess $args" -ForegroundColor Green +} + +function Write-Error-Custom { + Write-Host "$symError $args" -ForegroundColor Red +} + +function Write-Header { + Write-Host ("=" * 70) + Write-Host $args + Write-Host ("=" * 70) +} + +function Write-SubHeader { + Write-Host ("-" * 70) + Write-Host $args + Write-Host ("-" * 70) +} + +# Main script +$projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +$distDir = Join-Path $projectRoot "dist" +$buildDir = Join-Path $projectRoot "build" +$eggInfoDir = Join-Path $projectRoot "elytra_pim_client.egg-info" + +Write-Header "Elytra PIM Client - Wheel Builder (PowerShell)" + +# Clean if requested +if ($Clean) { + Write-Host "" + Write-Host "Cleaning previous builds..." + + @($distDir, $buildDir, $eggInfoDir) | ForEach-Object { + if (Test-Path $_) { + Remove-Item $_ -Recurse -Force -ErrorAction SilentlyContinue + Write-Success "Removed $_" + } + } +} + +# Check if build directory exists from previous build +if (Test-Path $distDir) { + Write-Host "" + Write-Host "Cleaning dist directory..." + Remove-Item $distDir -Recurse -Force -ErrorAction SilentlyContinue + Write-Success "Removed old distributions" +} + +# Install build requirements if needed +Write-Host "" +Write-Host "Checking build dependencies..." +$buildCheck = python -m pip list 2>$null | Select-String "build" +if (-not $buildCheck) { + Write-Host "Installing build tools..." + python -m pip install -q build setuptools wheel + if ($LASTEXITCODE -eq 0) { + Write-Success "Build tools installed" + } else { + Write-Error-Custom "Failed to install build tools" + exit 1 + } +} + +# Build +Write-SubHeader "Building distributions..." +Write-Host "" + +Push-Location $projectRoot +try { + python -m build + if ($LASTEXITCODE -ne 0) { + Write-Error-Custom "Build failed" + exit 1 + } +} finally { + Pop-Location +} + +# Verify output +Write-SubHeader "Build Results" +Write-Host "" + +if (!(Test-Path $distDir)) { + Write-Error-Custom "dist directory not created" + exit 1 +} + +$wheels = @(Get-ChildItem -Path $distDir -Filter "*.whl" -ErrorAction SilentlyContinue) +$sdists = @(Get-ChildItem -Path $distDir -Filter "*.tar.gz" -ErrorAction SilentlyContinue) + +if ($wheels.Count -eq 0 -and $sdists.Count -eq 0) { + Write-Error-Custom "No distributions created" + exit 1 +} + +Write-Success "Build successful!" +Write-Host "" + +if ($wheels.Count -gt 0) { + Write-Host "Wheels:" + $wheels | ForEach-Object { + $sizeMB = [Math]::Round($_.Length / 1MB, 2) + Write-Host " $symBullet $($_.Name) ($sizeMB MB)" + } +} + +if ($sdists.Count -gt 0) { + Write-Host "" + Write-Host "Source Distributions:" + $sdists | ForEach-Object { + $sizeMB = [Math]::Round($_.Length / 1MB, 2) + Write-Host " $symBullet $($_.Name) ($sizeMB MB)" + } +} + +Write-Host "" +Write-Success "Distributions saved to: $distDir" + +# Installation instructions +Write-SubHeader "Installation Instructions" +Write-Host "" + +if ($wheels.Count -gt 0) { + $wheel = $wheels[0] + Write-Host "Install the wheel locally:" + Write-Host " pip install `"$($wheel.FullName)`"" + Write-Host "" + + Write-Host "Or upload to PyPI:" + Write-Host " pip install twine" + Write-Host " twine upload dist/*" + Write-Host "" +} + +# Optional upload +if ($Upload) { + Write-SubHeader "Uploading to PyPI..." + Write-Host "" + + $twineCheck = python -m pip list 2>$null | Select-String "twine" + if (-not $twineCheck) { + Write-Host "Installing twine..." + python -m pip install -q twine + } + + Write-Host "Running twine upload..." + Push-Location $projectRoot + try { + python -m twine upload dist/* + if ($LASTEXITCODE -eq 0) { + Write-Success "Upload complete!" + } else { + Write-Error-Custom "Upload failed" + exit 1 + } + } finally { + Pop-Location + } +} + +Write-Host "" +Write-Host "Done!" +Write-Host "" +exit 0 diff --git a/build_wheel.py b/build_wheel.py new file mode 100644 index 0000000..82435d8 --- /dev/null +++ b/build_wheel.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +"""Build wheel distribution for Elytra PIM Client. + +This script builds wheel and source distributions for the Elytra PIM Client package. +It requires setuptools and wheel to be installed. + +Usage: + python build_wheel.py + +Output: + - Wheels saved to dist/ directory + - Source distribution (sdist) also created for reference +""" + +import shutil +import subprocess +import sys +from pathlib import Path + + +def main() -> int: + """Build wheel distribution. + + Returns: + Exit code (0 for success, 1 for failure) + """ + project_root = Path(__file__).parent + dist_dir = project_root / "dist" + + print("=" * 70) + print("Elytra PIM Client - Wheel Builder") + print("=" * 70) + + # Clean previous builds + print("\nCleaning previous builds...") + if dist_dir.exists(): + try: + shutil.rmtree(dist_dir) + print(f"✓ Removed {dist_dir}") + except OSError as e: + print(f"✗ Failed to remove {dist_dir}: {e}") + return 1 + + build_dir = project_root / "build" + if build_dir.exists(): + try: + shutil.rmtree(build_dir) + print(f"✓ Removed {build_dir}") + except OSError as e: + print(f"✗ Failed to remove {build_dir}: {e}") + return 1 + + egg_info_dir = project_root / "elytra_pim_client.egg-info" + if egg_info_dir.exists(): + try: + shutil.rmtree(egg_info_dir) + print(f"✓ Removed {egg_info_dir}") + except OSError as e: + print(f"✗ Failed to remove {egg_info_dir}: {e}") + return 1 + + # Build wheel and sdist + print("\n" + "-" * 70) + print("Building distributions...") + print("-" * 70 + "\n") + + try: + # Use python -m build for modern approach + result = subprocess.run( + [sys.executable, "-m", "build"], + cwd=project_root, + check=False + ) + + if result.returncode != 0: + print("\n✗ Build failed") + print("\nTrying alternative build method...") + + # Fallback to direct setuptools invocation + result = subprocess.run( + [sys.executable, "setup.py", "sdist", "bdist_wheel"], + cwd=project_root, + check=False + ) + + if result.returncode != 0: + print("\n✗ Build failed with fallback method") + return 1 + except FileNotFoundError: + # If 'build' module not available, use setup.py + print("Using setuptools directly...\n") + result = subprocess.run( + [sys.executable, "setup.py", "sdist", "bdist_wheel"], + cwd=project_root, + check=False + ) + + if result.returncode != 0: + print("\n✗ Build failed") + return 1 + + # Verify build output + print("\n" + "-" * 70) + print("Build Results") + print("-" * 70 + "\n") + + if not dist_dir.exists(): + print("✗ dist directory not created") + return 1 + + wheels = list(dist_dir.glob("*.whl")) + sdists = list(dist_dir.glob("*.tar.gz")) + + if not wheels and not sdists: + print("✗ No distributions created") + return 1 + + print("✓ Build successful!\n") + + if wheels: + print("Wheels:") + for wheel in wheels: + size_mb = wheel.stat().st_size / (1024 * 1024) + print(f" • {wheel.name} ({size_mb:.2f} MB)") + + if sdists: + print("\nSource Distributions:") + for sdist in sdists: + size_mb = sdist.stat().st_size / (1024 * 1024) + print(f" • {sdist.name} ({size_mb:.2f} MB)") + + print(f"\n✓ Distributions saved to: {dist_dir}") + + # Installation instructions + print("\n" + "-" * 70) + print("Installation Instructions") + print("-" * 70 + "\n") + + if wheels: + wheel = wheels[0] + print(f"Install the wheel locally:\n") + print(f" pip install {dist_dir / wheel.name}\n") + + print(f"Or upload to PyPI:\n") + print(f" pip install twine") + print(f" twine upload dist/*\n") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/build_wheel.sh b/build_wheel.sh new file mode 100644 index 0000000..4b61897 --- /dev/null +++ b/build_wheel.sh @@ -0,0 +1,197 @@ +#!/bin/bash +# build_wheel.sh - Build wheel distribution for Elytra PIM Client +# +# Usage: +# ./build_wheel.sh [--clean] [--upload] +# +# Options: +# --clean Clean previous builds before building +# --upload Upload to PyPI after building +# +# Requires: +# - Python 3.9+ +# - setuptools>=65.0 +# - wheel +# - build (recommended) + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Functions +print_success() { + echo -e "${GREEN}✓ $1${NC}" +} + +print_error() { + echo -e "${RED}✗ $1${NC}" +} + +print_header() { + echo "========================================================================" + echo "$1" + echo "========================================================================" +} + +print_subheader() { + echo "------------------------------------------------------------------------" + echo "$1" + echo "------------------------------------------------------------------------" +} + +# Parse arguments +CLEAN=false +UPLOAD=false + +while [[ $# -gt 0 ]]; do + case $1 in + --clean) + CLEAN=true + shift + ;; + --upload) + UPLOAD=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--clean] [--upload]" + exit 1 + ;; + esac +done + +# Get project root +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DIST_DIR="$PROJECT_ROOT/dist" +BUILD_DIR="$PROJECT_ROOT/build" +EGG_INFO_DIR="$PROJECT_ROOT/elytra_pim_client.egg-info" + +print_header "Elytra PIM Client - Wheel Builder" + +# Clean if requested +if [ "$CLEAN" = true ]; then + echo "" + echo "Cleaning previous builds..." + for dir in "$DIST_DIR" "$BUILD_DIR" "$EGG_INFO_DIR"; do + if [ -d "$dir" ]; then + rm -rf "$dir" + print_success "Removed $dir" + fi + done +fi + +# Clean dist directory for fresh build +if [ -d "$DIST_DIR" ]; then + rm -rf "$DIST_DIR" + print_success "Removed old distributions" +fi + +# Check build dependencies +echo "" +echo "Checking build dependencies..." +if ! python3 -m pip list 2>/dev/null | grep -q "^build "; then + echo "Installing build tools..." + python3 -m pip install -q build setuptools wheel + if [ $? -eq 0 ]; then + print_success "Build tools installed" + else + print_error "Failed to install build tools" + exit 1 + fi +fi + +# Build +print_subheader "Building distributions..." +echo "" + +cd "$PROJECT_ROOT" +if ! python3 -m build; then + print_error "Build failed" + exit 1 +fi + +# Verify output +print_subheader "Build Results" +echo "" + +if [ ! -d "$DIST_DIR" ]; then + print_error "dist directory not created" + exit 1 +fi + +# Count wheels and sdists +WHEEL_COUNT=$(find "$DIST_DIR" -maxdepth 1 -name "*.whl" 2>/dev/null | wc -l) +SDIST_COUNT=$(find "$DIST_DIR" -maxdepth 1 -name "*.tar.gz" 2>/dev/null | wc -l) + +if [ $WHEEL_COUNT -eq 0 ] && [ $SDIST_COUNT -eq 0 ]; then + print_error "No distributions created" + exit 1 +fi + +print_success "Build successful!" +echo "" + +if [ $WHEEL_COUNT -gt 0 ]; then + echo "Wheels:" + for wheel in "$DIST_DIR"/*.whl; do + SIZE=$(du -h "$wheel" | cut -f1) + echo " • $(basename "$wheel") ($SIZE)" + done +fi + +if [ $SDIST_COUNT -gt 0 ]; then + echo "" + echo "Source Distributions:" + for sdist in "$DIST_DIR"/*.tar.gz; do + SIZE=$(du -h "$sdist" | cut -f1) + echo " • $(basename "$sdist") ($SIZE)" + done +fi + +echo "" +print_success "Distributions saved to: $DIST_DIR" + +# Installation instructions +print_subheader "Installation Instructions" +echo "" + +if [ $WHEEL_COUNT -gt 0 ]; then + WHEEL=$(ls "$DIST_DIR"/*.whl | head -1) + echo "Install the wheel locally:" + echo " pip install \"$WHEEL\"" + echo "" + echo "Or upload to PyPI:" + echo " pip install twine" + echo " twine upload dist/*" + echo "" +fi + +# Optional upload +if [ "$UPLOAD" = true ]; then + print_subheader "Uploading to PyPI..." + echo "" + + if ! python3 -m pip list 2>/dev/null | grep -q "^twine "; then + echo "Installing twine..." + python3 -m pip install -q twine + fi + + echo "Running twine upload..." + cd "$PROJECT_ROOT" + if python3 -m twine upload dist/*; then + print_success "Upload complete!" + else + print_error "Upload failed" + exit 1 + fi +fi + +echo "" +echo "Done!" +echo "" +exit 0 diff --git a/pyproject.toml b/pyproject.toml index ff38ba1..839336f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "0.1.0" description = "A Pythonic client for the Elytra PIM API" readme = "README.md" requires-python = ">=3.9" -license = {text = "MIT"} +license = "MIT" authors = [ {name = "Your Name", email = "your.email@example.com"} ] @@ -16,7 +16,6 @@ keywords = ["elytra", "pim", "api", "client"] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..85cce02 --- /dev/null +++ b/setup.py @@ -0,0 +1,10 @@ +"""Setup configuration for Elytra PIM Client. + +This setup.py file provides compatibility with legacy build tools. +The main configuration is in pyproject.toml (PEP 517/518). +""" + +from setuptools import setup + +if __name__ == "__main__": + setup()