diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..065bf6e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,56 @@ +# Git +.git/ +.gitignore +.gitattributes + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +dist/ +build/ +*.egg +.pytest_cache/ +.mypy_cache/ +.coverage +htmlcov/ + +# Virtual environments +venv/ +ENV/ +env/ +.venv + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Build artifacts +dist_*/ +pkg_dist_*/ +*.deb +*.rpm +*.pkg.tar.zst +*.exe +*.asc +*.sha256 + +# Platform tools (downloaded at runtime) +src/platform-tools/ + +# Documentation +*.md +!CLAUDE.md + +# CI/CD +.github/ + +# Other +.DS_Store +*.log diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f1c294..451137c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,15 +32,7 @@ env: jobs: - status-checks: - permissions: - contents: read - packages: read - - uses: ./.github/workflows/status-checks.yml - build-windows: - needs: status-checks runs-on: windows-latest steps: - name: Checkout code @@ -126,7 +118,6 @@ jobs: dist/android-file-handler-windows.sha256 build-debian: - needs: status-checks permissions: contents: read packages: read @@ -144,50 +135,13 @@ jobs: with: ref: main - - name: Build executable + - name: Build and package Debian run: | # Container has Poetry and all dependencies pre-installed + # Python build script handles both PyInstaller build and fpm packaging poetry install --no-interaction poetry run python scripts/build_package_linux.py - - name: Package .deb (fpm) - shell: bash - run: | - set -euo pipefail - if ! VERSION="$(poetry version -s 2>&1)"; then - echo "::error::Failed to read version from pyproject.toml" - echo "::error::Poetry output: $VERSION" - exit 1 - fi - if [ -z "$VERSION" ]; then - echo "::error::Version is empty in pyproject.toml" - exit 1 - fi - # Validate semver format - if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?(\+[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$ ]]; then - echo "::error::Invalid version format in pyproject.toml: $VERSION" - echo "::error::Expected semantic version format (e.g., 1.2.3, 1.2.3-beta.1, 1.2.3+build.123)" - exit 1 - fi - echo "Using version: $VERSION" - PKG_DIR="pkg_dist_debian" - mkdir -p dist - echo "Packaging from $PKG_DIR" - ls -la "$PKG_DIR" || true - - ICON_PATH="$PKG_DIR/usr/share/icons/hicolor/256x256/apps/android-file-handler.png" - PKG_ITEMS=( "usr/local/bin/android-file-handler" "usr/share/applications/android-file-handler.desktop" ) - if [ -f "$ICON_PATH" ]; then - PKG_ITEMS+=( "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" ) - else - echo "Note: icon not present, packaging without icon" - fi - - fpm -s dir -t deb -n android-file-handler -v "$VERSION" \ - --architecture amd64 --deb-user root --deb-group root \ - --after-install scripts/debian_postinst.sh \ - -p "dist/android-file-handler_${VERSION}_amd64.deb" -C "$PKG_DIR" "${PKG_ITEMS[@]}" - - name: Import GPG key shell: bash run: | @@ -234,7 +188,6 @@ jobs: pkg_dist_debian/** build-arch: - needs: status-checks permissions: contents: read packages: read @@ -257,7 +210,7 @@ jobs: - name: Pull Docker image run: docker pull ghcr.io/jmr-dev/android-file-handler-arch-builder:latest - - name: Build executable inside container + - name: Build and package Arch run: | docker run --rm \ -v ${{ github.workspace }}:/workspace \ @@ -268,52 +221,6 @@ jobs: ghcr.io/jmr-dev/android-file-handler-arch-builder:latest \ sh -c "poetry install --no-interaction && poetry run python scripts/build_package_linux.py" - - name: Package pacman (fpm) inside container - run: | - docker run --rm \ - -v ${{ github.workspace }}:/workspace \ - -w /workspace \ - -e FPM_VERSION=${{ env.FPM_VERSION }} \ - ghcr.io/jmr-dev/android-file-handler-arch-builder:latest \ - sh -c 'set -euo pipefail && \ - if ! VERSION="$(poetry version -s 2>&1)"; then \ - echo "::error::Failed to read version from pyproject.toml" >&2 && \ - echo "::error::Poetry output: $VERSION" >&2 && \ - exit 1; \ - fi && \ - if [ -z "$VERSION" ]; then \ - echo "::error::Version is empty in pyproject.toml" >&2 && \ - exit 1; \ - fi && \ - if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?(\+[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$ ]]; then \ - echo "::error::Invalid version format in pyproject.toml: $VERSION" >&2 && \ - echo "::error::Expected semantic version format (e.g., 1.2.3, 1.2.3-beta.1, 1.2.3+build.123)" >&2 && \ - exit 1; \ - fi && \ - echo "Using version: $VERSION" && \ - PKG_DIR="pkg_dist_arch" && \ - mkdir -p dist && \ - echo "Packaging from $PKG_DIR" && \ - ls -la "$PKG_DIR" || true && \ - ICON_PATH="$PKG_DIR/usr/share/icons/hicolor/256x256/apps/android-file-handler.png" && \ - if [ -f "$ICON_PATH" ]; then \ - fpm -s dir -t pacman -n android-file-handler -v "$VERSION" \ - --architecture x86_64 \ - -p "dist/android-file-handler-${VERSION}-1-x86_64.pkg.tar.zst" \ - -C "$PKG_DIR" \ - "usr/bin/android-file-handler" \ - "usr/share/applications/android-file-handler.desktop" \ - "usr/share/icons/hicolor/256x256/apps/android-file-handler.png"; \ - else \ - echo "Note: icon not present, packaging without icon" && \ - fpm -s dir -t pacman -n android-file-handler -v "$VERSION" \ - --architecture x86_64 \ - -p "dist/android-file-handler-${VERSION}-1-x86_64.pkg.tar.zst" \ - -C "$PKG_DIR" \ - "usr/bin/android-file-handler" \ - "usr/share/applications/android-file-handler.desktop"; \ - fi' - - name: Import GPG key shell: bash run: | @@ -360,7 +267,6 @@ jobs: build-rhel: - needs: status-checks permissions: contents: read packages: read @@ -378,48 +284,13 @@ jobs: with: ref: main - - name: Build RHEL package + - name: Build and package RHEL run: | - set -euo pipefail # Container has Poetry and all dependencies pre-installed + # Python build script handles both PyInstaller build and fpm packaging poetry install --no-interaction poetry run python scripts/build_package_linux.py - - name: Package RHEL (fpm) - shell: bash - run: | - set -euo pipefail - if ! VERSION="$(poetry version -s 2>&1)"; then - echo "::error::Failed to read version from pyproject.toml" - echo "::error::Poetry output: $VERSION" - exit 1 - fi - if [ -z "$VERSION" ]; then - echo "::error::Version is empty in pyproject.toml" - exit 1 - fi - # Validate semver format - if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?(\+[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$ ]]; then - echo "::error::Invalid version format in pyproject.toml: $VERSION" - echo "::error::Expected semantic version format (e.g., 1.2.3, 1.2.3-beta.1, 1.2.3+build.123)" - exit 1 - fi - echo "Using version: $VERSION" - PKG_DIR="pkg_dist_rhel" - mkdir -p dist - echo "Packaging from $PKG_DIR (version=$VERSION)" - ls -la "$PKG_DIR" || true - - ICON_PATH="$PKG_DIR/usr/share/icons/hicolor/256x256/apps/android-file-handler.png" - PKG_ITEMS=( "usr/bin/android-file-handler" "usr/share/applications/android-file-handler.desktop" ) - if [ -f "$ICON_PATH" ]; then - PKG_ITEMS+=( "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" ) - else - echo "Note: icon not present, packaging without icon" - fi - - fpm -s dir -t rpm -n android-file-handler -v "$VERSION" --architecture x86_64 --prefix /usr/bin --after-install scripts/rhel_postinst.sh -p "dist/android-file-handler-${VERSION}.x86_64.rpm" -C "$PKG_DIR" "${PKG_ITEMS[@]}" - - name: Import GPG key shell: bash run: | @@ -468,7 +339,6 @@ jobs: do-release: needs: - - status-checks - build-windows - build-debian - build-arch diff --git a/.github/workflows/status-checks.yml b/.github/workflows/status-checks.yml index 9b34ffa..2a455c1 100644 --- a/.github/workflows/status-checks.yml +++ b/.github/workflows/status-checks.yml @@ -112,7 +112,7 @@ jobs: run: | poetry run pytest tests/ -v - build-windows: + build-and-package-windows: needs: [run-unit-tests-windows] runs-on: windows-latest steps: @@ -155,7 +155,7 @@ jobs: dist/**/android-file-handler*.exe dist/android-file-handler.exe - build-debian: + build-and-package-debian: needs: [run-unit-tests-debian, run-unit-tests-arch, run-unit-tests-rhel] permissions: contents: read @@ -172,35 +172,13 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Build executable + - name: Build and package Debian run: | # Container has Poetry and all dependencies pre-installed + # Python build script handles both PyInstaller build and fpm packaging poetry install --no-interaction poetry run python scripts/build_package_linux.py - - name: Package .deb (fpm) - shell: bash - run: | - set -euo pipefail - VERSION="$(poetry version -s)" - PKG_DIR="pkg_dist_debian" - mkdir -p dist - echo "Packaging from $PKG_DIR" - ls -la "$PKG_DIR" || true - - ICON_PATH="$PKG_DIR/usr/share/icons/hicolor/256x256/apps/android-file-handler.png" - PKG_ITEMS=( "usr/local/bin/android-file-handler" "usr/share/applications/android-file-handler.desktop" ) - if [ -f "$ICON_PATH" ]; then - PKG_ITEMS+=( "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" ) - else - echo "Note: icon not present, packaging without icon" - fi - - fpm -s dir -t deb -n android-file-handler -v "$VERSION" \ - --architecture amd64 --deb-user root --deb-group root \ - --after-install scripts/debian_postinst.sh \ - -p "dist/android-file-handler_${VERSION}_amd64.deb" -C "$PKG_DIR" "${PKG_ITEMS[@]}" - - name: Upload Debian .deb uses: actions/upload-artifact@v4 with: @@ -209,7 +187,7 @@ jobs: dist/android-file-handler_*.deb pkg_dist_debian/** - build-arch: + build-and-package-arch: needs: [run-unit-tests-debian, run-unit-tests-arch, run-unit-tests-rhel] permissions: contents: read @@ -219,20 +197,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Log in to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} - password: ${{ secrets.CI_CD_PAT }} # Personal Access Token with read:packages - + password: ${{ secrets.CI_CD_PAT }} - name: Pull Docker image run: docker pull ghcr.io/jmr-dev/android-file-handler-arch-builder:latest - - name: Build executable inside container + - name: Build and package Arch run: | docker run --rm \ -v ${{ github.workspace }}:/workspace \ @@ -243,38 +220,6 @@ jobs: ghcr.io/jmr-dev/android-file-handler-arch-builder:latest \ sh -c "poetry install --no-interaction && poetry run python scripts/build_package_linux.py" - - name: Package pacman (fpm) inside container - run: | - docker run --rm \ - -v ${{ github.workspace }}:/workspace \ - -w /workspace \ - -e FPM_VERSION=${{ env.FPM_VERSION }} \ - ghcr.io/jmr-dev/android-file-handler-arch-builder:latest \ - sh -c 'set -euo pipefail && \ - VERSION="$(poetry version -s)" && \ - PKG_DIR="pkg_dist_arch" && \ - mkdir -p dist && \ - echo "Packaging from $PKG_DIR" && \ - ls -la "$PKG_DIR" || true && \ - ICON_PATH="$PKG_DIR/usr/share/icons/hicolor/256x256/apps/android-file-handler.png" && \ - if [ -f "$ICON_PATH" ]; then \ - fpm -s dir -t pacman -n android-file-handler -v "$VERSION" \ - --architecture x86_64 \ - -p "dist/android-file-handler-${VERSION}-1-x86_64.pkg.tar.zst" \ - -C "$PKG_DIR" \ - "usr/bin/android-file-handler" \ - "usr/share/applications/android-file-handler.desktop" \ - "usr/share/icons/hicolor/256x256/apps/android-file-handler.png"; \ - else \ - echo "Note: icon not present, packaging without icon" && \ - fpm -s dir -t pacman -n android-file-handler -v "$VERSION" \ - --architecture x86_64 \ - -p "dist/android-file-handler-${VERSION}-1-x86_64.pkg.tar.zst" \ - -C "$PKG_DIR" \ - "usr/bin/android-file-handler" \ - "usr/share/applications/android-file-handler.desktop"; \ - fi' - - name: Upload Arch package uses: actions/upload-artifact@v4 with: @@ -283,7 +228,7 @@ jobs: dist/*.pkg.tar.* pkg_dist_arch/** - build-rhel: + build-and-package-rhel: needs: [run-unit-tests-debian, run-unit-tests-arch, run-unit-tests-rhel] permissions: contents: read @@ -300,33 +245,13 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Build RHEL package + - name: Build and package RHEL run: | - set -euo pipefail # Container has Poetry and all dependencies pre-installed + # Python build script handles both PyInstaller build and fpm packaging poetry install --no-interaction poetry run python scripts/build_package_linux.py - - name: Package RHEL (fpm) - shell: bash - run: | - set -euo pipefail - VERSION="$(poetry version -s)" - PKG_DIR="pkg_dist_rhel" - mkdir -p dist - echo "Packaging from $PKG_DIR (version=$VERSION)" - ls -la "$PKG_DIR" || true - - ICON_PATH="$PKG_DIR/usr/share/icons/hicolor/256x256/apps/android-file-handler.png" - PKG_ITEMS=( "usr/bin/android-file-handler" "usr/share/applications/android-file-handler.desktop" ) - if [ -f "$ICON_PATH" ]; then - PKG_ITEMS+=( "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" ) - else - echo "Note: icon not present, packaging without icon" - fi - - fpm -s dir -t rpm -n android-file-handler -v "$VERSION" --architecture x86_64 --prefix /usr/bin --after-install scripts/rhel_postinst.sh -p "dist/android-file-handler-${VERSION}.x86_64.rpm" -C "$PKG_DIR" "${PKG_ITEMS[@]}" - - name: Upload RHEL artifacts uses: actions/upload-artifact@v4 with: diff --git a/CLAUDE.md b/CLAUDE.md index daaf2ce..3553978 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,9 +54,29 @@ poetry run mypy src/ #### Local Development Build (Linux) ```sh +# Interactive build (prompts for distro selection) poetry run python scripts/build_package_linux.py ``` +#### Docker Compose Build (Recommended for Linux) +```sh +# Build all distributions (Debian, Arch, RHEL) +docker compose up --build + +# Build specific distribution +docker compose up --build debian +docker compose up --build arch +docker compose up --build rhel + +# Build all in parallel +docker compose up --build --parallel + +# Clean build artifacts +docker compose down -v && rm -rf dist pkg_dist_* dist_* +``` + +See [scripts/docker/README.md](scripts/docker/README.md) for detailed Docker build documentation. + #### Platform-Specific Builds ```sh # Windows executable (PyInstaller) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e6484c7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,72 @@ +# Docker Compose configuration for local multi-platform builds +# Matches the exact images and configurations from .github/workflows/release.yml +# +# Usage: +# Build all distributions: docker-compose up --build +# Build specific distro: docker-compose up --build debian +# +# Each service builds a distribution package and outputs to: +# - dist/ - Final packaged files (.deb, .rpm, .pkg.tar.zst) +# - pkg_dist_{distro}/ - Staging directory for package contents +# - dist_{distro}/ - PyInstaller build output + +services: + debian: + image: ghcr.io/jmr-dev/android-file-handler-debian-builder:debian13-trixie + build: + context: . + dockerfile: scripts/docker/Dockerfile.debian + args: + FPM_VERSION: "1.16.0" + volumes: + - .:/workspace + # Exclude host .venv to prevent conflicts with container Python + - /workspace/.venv + working_dir: /workspace + environment: + - CI_CD=true + - DISTRO_TYPE=debian + - FPM_VERSION=1.16.0 + - POETRY_VIRTUALENVS_IN_PROJECT=false + - POETRY_VIRTUALENVS_PATH=/tmp/poetry-cache + command: sh -c "poetry install --no-interaction && poetry run python scripts/build_package_linux.py" + + arch: + image: ghcr.io/jmr-dev/android-file-handler-arch-builder:latest + build: + context: . + dockerfile: scripts/docker/Dockerfile.arch + args: + FPM_VERSION: "1.16.0" + volumes: + - .:/workspace + # Exclude host .venv to prevent conflicts with container Python + - /workspace/.venv + working_dir: /workspace + environment: + - CI_CD=true + - DISTRO_TYPE=arch + - FPM_VERSION=1.16.0 + - POETRY_VIRTUALENVS_IN_PROJECT=false + - POETRY_VIRTUALENVS_PATH=/tmp/poetry-cache + command: sh -c "poetry install --no-interaction && poetry run python scripts/build_package_linux.py" + + rhel: + image: ghcr.io/jmr-dev/android-file-handler-rhel-builder:fedora42 + build: + context: . + dockerfile: scripts/docker/Dockerfile.rhel + args: + FPM_VERSION: "1.16.0" + volumes: + - .:/workspace + # Exclude host .venv to prevent conflicts with container Python + - /workspace/.venv + working_dir: /workspace + environment: + - CI_CD=true + - DISTRO_TYPE=rhel + - FPM_VERSION=1.16.0 + - POETRY_VIRTUALENVS_IN_PROJECT=false + - POETRY_VIRTUALENVS_PATH=/tmp/poetry-cache + command: sh -c "poetry install --no-interaction && poetry run python scripts/build_package_linux.py" diff --git a/scripts/build_package_linux.py b/scripts/build_package_linux.py index 9cbab0b..4f6198c 100755 --- a/scripts/build_package_linux.py +++ b/scripts/build_package_linux.py @@ -4,6 +4,7 @@ import subprocess import shutil import sys +import re from pathlib import Path from enum import Enum from typing import List @@ -35,24 +36,130 @@ def get_distro_config(distro_type: DistroType) -> dict: "name": "Debian", "bin_path": "usr/local/bin", "pkg_suffix": "debian", - "spec_file": "scripts/spec_scripts/android-file-handler-debian.spec" + "spec_file": "scripts/spec_scripts/android-file-handler-debian.spec", + "pkg_type": "deb", + "architecture": "amd64", + "postinstall": "scripts/debian_postinst.sh" }, DistroType.ARCH: { "name": "Arch", "bin_path": "usr/bin", "pkg_suffix": "arch", - "spec_file": "scripts/spec_scripts/android-file-handler-arch.spec" + "spec_file": "scripts/spec_scripts/android-file-handler-arch.spec", + "pkg_type": "pacman", + "architecture": "x86_64", + "postinstall": None }, DistroType.RHEL: { "name": "RHEL", - "bin_path": "usr/bin", + "bin_path": "usr/bin", "pkg_suffix": "rhel", - "spec_file": "scripts/spec_scripts/android-file-handler-rhel.spec" + "spec_file": "scripts/spec_scripts/android-file-handler-rhel.spec", + "pkg_type": "rpm", + "architecture": "x86_64", + "postinstall": "scripts/rhel_postinst.sh" } } return configs[distro_type] +def validate_version(version: str) -> bool: + """Validate semantic version format.""" + semver_pattern = r'^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?(\+[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$' + return bool(re.match(semver_pattern, version)) + + +def get_version() -> str: + """Get version from Poetry and validate it.""" + result = subprocess.run( + ["poetry", "version", "-s"], + capture_output=True, + text=True, + check=True + ) + version = result.stdout.strip() + + if not version: + print("ERROR: Version is empty in pyproject.toml") + sys.exit(1) + + if not validate_version(version): + print(f"ERROR: Invalid version format in pyproject.toml: {version}") + print("Expected semantic version format (e.g., 1.2.3, 1.2.3-beta.1, 1.2.3+build.123)") + sys.exit(1) + + return version + + +def package_with_fpm(distro_type: DistroType, version: str, project_root: Path) -> None: + """Package the built application using fpm.""" + config = get_distro_config(distro_type) + pkg_dir = project_root / f"pkg_dist_{config['pkg_suffix']}" + dist_dir = project_root / "dist" + + print(f"\n=== Packaging {config['name']} with fpm ===") + + # Ensure dist directory exists + dist_dir.mkdir(exist_ok=True) + + # Check if icon exists + icon_path = pkg_dir / "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" + icon_included = icon_path.exists() + + # Build package items list + pkg_items = [ + f"{config['bin_path']}/android-file-handler", + "usr/share/applications/android-file-handler.desktop" + ] + + if icon_included: + pkg_items.append("usr/share/icons/hicolor/256x256/apps/android-file-handler.png") + else: + print("Note: icon not present, packaging without icon") + + # Build fpm command based on distro type + fpm_cmd = [ + "fpm", + "-s", "dir", + "-t", config["pkg_type"], + "-n", "android-file-handler", + "-v", version, + "--architecture", config["architecture"], + "-C", str(pkg_dir) + ] + + # Add distro-specific options + if distro_type == DistroType.DEBIAN: + output_file = dist_dir / f"android-file-handler_{version}_{config['architecture']}.deb" + fpm_cmd.extend([ + "--deb-user", "root", + "--deb-group", "root", + "--after-install", config["postinstall"], + "-p", str(output_file) + ]) + elif distro_type == DistroType.ARCH: + output_file = dist_dir / f"android-file-handler-{version}-1-{config['architecture']}.pkg.tar.zst" + fpm_cmd.extend([ + "-p", str(output_file) + ]) + elif distro_type == DistroType.RHEL: + output_file = dist_dir / f"android-file-handler-{version}.{config['architecture']}.rpm" + fpm_cmd.extend([ + "--prefix", "/usr/bin", + "--after-install", config["postinstall"], + "-p", str(output_file) + ]) + + # Add package items + fpm_cmd.extend(pkg_items) + + # Run fpm + print(f"Creating package: {output_file}") + run_command(fpm_cmd, working_dir=str(project_root)) + + print(f"Package created successfully: {output_file}") + + def prompt_distro_selection() -> List[DistroType]: """Prompt user for distro selection.""" print("Select distribution(s) to build for:") @@ -60,7 +167,7 @@ def prompt_distro_selection() -> List[DistroType]: print("2. Arch") print("3. RHEL") print("4. All distributions") - + while True: choice = input("Enter choice (1-4): ").strip() if choice == "1": @@ -161,7 +268,7 @@ def build_for_distro(distro_type: DistroType, version: str, project_root: Path) pkg_items.append("usr/share/icons/hicolor/256x256/apps/android-file-handler.png") # Debug listing - print(f"Packaging the following items for {config['name']} (relative to {pkg_dir}):") + print(f"Prepared items for {config['name']} (relative to {pkg_dir}):") for item in pkg_items: print(f" - {item}") item_path = pkg_dir / item @@ -171,6 +278,9 @@ def build_for_distro(distro_type: DistroType, version: str, project_root: Path) else: print(f" (missing) {item_path}") + # Package with fpm + package_with_fpm(distro_type, version, project_root) + def main() -> None: # Get project root (parent of scripts directory) @@ -211,14 +321,8 @@ def main() -> None: run_command(["poetry", "lock"]) run_command(["poetry", "install"]) - # Get version from Poetry - result = subprocess.run( - ["poetry", "version", "-s"], - capture_output=True, - text=True, - check=True - ) - version = result.stdout.strip() + # Get and validate version from Poetry + version = get_version() print(f"Version: {version}") # Build for selected distributions diff --git a/scripts/docker/Dockerfile.rhel b/scripts/docker/Dockerfile.rhel index e9cbb51..3a19c7a 100644 --- a/scripts/docker/Dockerfile.rhel +++ b/scripts/docker/Dockerfile.rhel @@ -48,7 +48,7 @@ ENV PATH="$PYENV_ROOT/bin:$PATH" RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv -# Install Python 3.12 via pyenv with tkinter support +# Install Python 3.13 via pyenv with tkinter support # The tk8-devel package must be installed before this step for _tkinter to be compiled RUN eval "$(pyenv init -)" && \ LDFLAGS="-L/usr/lib64" \ diff --git a/scripts/docker/README.md b/scripts/docker/README.md new file mode 100644 index 0000000..3133c8f --- /dev/null +++ b/scripts/docker/README.md @@ -0,0 +1,121 @@ +# Docker Build Environment + +This directory contains Dockerfiles for building the Android File Handler on different Linux distributions. These images match exactly the images used in the CI/CD pipeline. + +## Quick Start + +### Using Docker Compose (Recommended) + +Build for all distributions: +```bash +docker-compose up --build +``` + +Build for a specific distribution: +```bash +docker-compose up --build debian +docker-compose up --build arch +docker-compose up --build rhel +``` + +Build all distributions in parallel: +```bash +docker-compose up --build --parallel +``` + +### Manual Docker Build + +Build the image: +```bash +# Debian +docker build -f scripts/docker/Dockerfile.debian -t android-file-handler-debian-builder . + +# Arch +docker build -f scripts/docker/Dockerfile.arch -t android-file-handler-arch-builder . + +# RHEL/Fedora +docker build -f scripts/docker/Dockerfile.rhel -t android-file-handler-rhel-builder . +``` + +Run the build: +```bash +# Debian +docker run --rm -v $(pwd):/workspace -w /workspace android-file-handler-debian-builder + +# Arch +docker run --rm -v $(pwd):/workspace -w /workspace android-file-handler-arch-builder + +# RHEL/Fedora +docker run --rm -v $(pwd):/workspace -w /workspace android-file-handler-rhel-builder +``` + +## Output + +After building, you'll find: + +- `dist/` - Final packaged files (.deb, .rpm, .pkg.tar.zst) +- `pkg_dist_{distro}/` - Staging directories for package contents +- `dist_{distro}/` - PyInstaller build outputs + +## Images + +### Debian Builder +- **Image**: `ghcr.io/jmr-dev/android-file-handler-debian-builder:debian13-trixie` +- **Base**: `debian:13` +- **Python**: 3.13 (via pyenv) +- **Tools**: Poetry, fpm, PyInstaller + +### Arch Builder +- **Image**: `ghcr.io/jmr-dev/android-file-handler-arch-builder:latest` +- **Base**: `archlinux:latest` +- **Python**: 3.13 (via pyenv) +- **Tools**: Poetry, fpm, PyInstaller + +### RHEL Builder +- **Image**: `ghcr.io/jmr-dev/android-file-handler-rhel-builder:fedora42` +- **Base**: `fedora:42` +- **Python**: 3.13 (via pyenv) +- **Tools**: Poetry, fpm, PyInstaller + +## Troubleshooting + +### Virtualenv Conflicts + +The Docker Compose configuration automatically excludes the host's `.venv` directory to prevent conflicts between the host Python environment and the container Python environment. Each container creates its own virtualenv in `/tmp/poetry-cache`. + +If you encounter virtualenv-related errors, ensure you're using the latest docker-compose.yml configuration. + +## Cleaning Up + +Remove build artifacts: +```bash +rm -rf dist pkg_dist_* dist_* +``` + +Remove Docker volumes and containers: +```bash +docker compose down -v +``` + +## Customization + +### Override FPM Version + +Build with a specific fpm version: +```bash +docker-compose build --build-arg FPM_VERSION=1.15.0 debian +``` + +### Environment Variables + +All builds use these environment variables: +- `CI_CD=true` - Runs in CI/CD mode +- `DISTRO_TYPE` - Set to `debian`, `arch`, or `rhel` +- `FPM_VERSION` - Version of fpm to use (default: 1.16.0) + +## Notes + +- All images use Python 3.13 built from source with tkinter support +- The builds are identical to what runs in GitHub Actions +- Poetry and fpm are pre-installed in all images +- The Python build script handles both PyInstaller and fpm packaging