Author SHA1 Message Date
JMR-dev e472a9bb46 fix status checks 2025-10-18 00:39:25 -05:00
JMR-dev b6b3292407 set up docker compose setup 2025-10-18 00:39:25 -05:00
JMR-dev c14d3dd04a remove status checks to get release out the door for now 2025-10-17 14:18:26 -05:00
8 changed files with 404 additions and 236 deletions
+56
View File
@@ -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
+5 -135
View File
@@ -32,15 +32,7 @@ env:
jobs: jobs:
status-checks:
permissions:
contents: read
packages: read
uses: ./.github/workflows/status-checks.yml
build-windows: build-windows:
needs: status-checks
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -126,7 +118,6 @@ jobs:
dist/android-file-handler-windows.sha256 dist/android-file-handler-windows.sha256
build-debian: build-debian:
needs: status-checks
permissions: permissions:
contents: read contents: read
packages: read packages: read
@@ -144,50 +135,13 @@ jobs:
with: with:
ref: main ref: main
- name: Build executable - name: Build and package Debian
run: | run: |
# Container has Poetry and all dependencies pre-installed # Container has Poetry and all dependencies pre-installed
# Python build script handles both PyInstaller build and fpm packaging
poetry install --no-interaction poetry install --no-interaction
poetry run python scripts/build_package_linux.py 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 - name: Import GPG key
shell: bash shell: bash
run: | run: |
@@ -234,7 +188,6 @@ jobs:
pkg_dist_debian/** pkg_dist_debian/**
build-arch: build-arch:
needs: status-checks
permissions: permissions:
contents: read contents: read
packages: read packages: read
@@ -257,7 +210,7 @@ jobs:
- name: Pull Docker image - name: Pull Docker image
run: docker pull ghcr.io/jmr-dev/android-file-handler-arch-builder:latest run: docker pull ghcr.io/jmr-dev/android-file-handler-arch-builder:latest
- name: Build executable inside container - name: Build and package Arch
run: | run: |
docker run --rm \ docker run --rm \
-v ${{ github.workspace }}:/workspace \ -v ${{ github.workspace }}:/workspace \
@@ -268,52 +221,6 @@ jobs:
ghcr.io/jmr-dev/android-file-handler-arch-builder:latest \ ghcr.io/jmr-dev/android-file-handler-arch-builder:latest \
sh -c "poetry install --no-interaction && poetry run python scripts/build_package_linux.py" 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 - name: Import GPG key
shell: bash shell: bash
run: | run: |
@@ -360,7 +267,6 @@ jobs:
build-rhel: build-rhel:
needs: status-checks
permissions: permissions:
contents: read contents: read
packages: read packages: read
@@ -378,48 +284,13 @@ jobs:
with: with:
ref: main ref: main
- name: Build RHEL package - name: Build and package RHEL
run: | run: |
set -euo pipefail
# Container has Poetry and all dependencies pre-installed # Container has Poetry and all dependencies pre-installed
# Python build script handles both PyInstaller build and fpm packaging
poetry install --no-interaction poetry install --no-interaction
poetry run python scripts/build_package_linux.py 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 - name: Import GPG key
shell: bash shell: bash
run: | run: |
@@ -468,7 +339,6 @@ jobs:
do-release: do-release:
needs: needs:
- status-checks
- build-windows - build-windows
- build-debian - build-debian
- build-arch - build-arch
+11 -86
View File
@@ -112,7 +112,7 @@ jobs:
run: | run: |
poetry run pytest tests/ -v poetry run pytest tests/ -v
build-windows: build-and-package-windows:
needs: [run-unit-tests-windows] needs: [run-unit-tests-windows]
runs-on: windows-latest runs-on: windows-latest
steps: steps:
@@ -155,7 +155,7 @@ jobs:
dist/**/android-file-handler*.exe dist/**/android-file-handler*.exe
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] needs: [run-unit-tests-debian, run-unit-tests-arch, run-unit-tests-rhel]
permissions: permissions:
contents: read contents: read
@@ -172,35 +172,13 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Build executable - name: Build and package Debian
run: | run: |
# Container has Poetry and all dependencies pre-installed # Container has Poetry and all dependencies pre-installed
# Python build script handles both PyInstaller build and fpm packaging
poetry install --no-interaction poetry install --no-interaction
poetry run python scripts/build_package_linux.py 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 - name: Upload Debian .deb
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -209,7 +187,7 @@ jobs:
dist/android-file-handler_*.deb dist/android-file-handler_*.deb
pkg_dist_debian/** pkg_dist_debian/**
build-arch: build-and-package-arch:
needs: [run-unit-tests-debian, run-unit-tests-arch, run-unit-tests-rhel] needs: [run-unit-tests-debian, run-unit-tests-arch, run-unit-tests-rhel]
permissions: permissions:
contents: read contents: read
@@ -219,20 +197,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.CI_CD_PAT }} # Personal Access Token with read:packages password: ${{ secrets.CI_CD_PAT }}
- name: Pull Docker image - name: Pull Docker image
run: docker pull ghcr.io/jmr-dev/android-file-handler-arch-builder:latest run: docker pull ghcr.io/jmr-dev/android-file-handler-arch-builder:latest
- name: Build executable inside container - name: Build and package Arch
run: | run: |
docker run --rm \ docker run --rm \
-v ${{ github.workspace }}:/workspace \ -v ${{ github.workspace }}:/workspace \
@@ -243,38 +220,6 @@ jobs:
ghcr.io/jmr-dev/android-file-handler-arch-builder:latest \ ghcr.io/jmr-dev/android-file-handler-arch-builder:latest \
sh -c "poetry install --no-interaction && poetry run python scripts/build_package_linux.py" 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 - name: Upload Arch package
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -283,7 +228,7 @@ jobs:
dist/*.pkg.tar.* dist/*.pkg.tar.*
pkg_dist_arch/** pkg_dist_arch/**
build-rhel: build-and-package-rhel:
needs: [run-unit-tests-debian, run-unit-tests-arch, run-unit-tests-rhel] needs: [run-unit-tests-debian, run-unit-tests-arch, run-unit-tests-rhel]
permissions: permissions:
contents: read contents: read
@@ -300,33 +245,13 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Build RHEL package - name: Build and package RHEL
run: | run: |
set -euo pipefail
# Container has Poetry and all dependencies pre-installed # Container has Poetry and all dependencies pre-installed
# Python build script handles both PyInstaller build and fpm packaging
poetry install --no-interaction poetry install --no-interaction
poetry run python scripts/build_package_linux.py 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 - name: Upload RHEL artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
+20
View File
@@ -54,9 +54,29 @@ poetry run mypy src/
#### Local Development Build (Linux) #### Local Development Build (Linux)
```sh ```sh
# Interactive build (prompts for distro selection)
poetry run python scripts/build_package_linux.py 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 #### Platform-Specific Builds
```sh ```sh
# Windows executable (PyInstaller) # Windows executable (PyInstaller)
+72
View File
@@ -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"
+118 -14
View File
@@ -4,6 +4,7 @@ import os
import subprocess import subprocess
import shutil import shutil
import sys import sys
import re
from pathlib import Path from pathlib import Path
from enum import Enum from enum import Enum
from typing import List from typing import List
@@ -35,24 +36,130 @@ def get_distro_config(distro_type: DistroType) -> dict:
"name": "Debian", "name": "Debian",
"bin_path": "usr/local/bin", "bin_path": "usr/local/bin",
"pkg_suffix": "debian", "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: { DistroType.ARCH: {
"name": "Arch", "name": "Arch",
"bin_path": "usr/bin", "bin_path": "usr/bin",
"pkg_suffix": "arch", "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: { DistroType.RHEL: {
"name": "RHEL", "name": "RHEL",
"bin_path": "usr/bin", "bin_path": "usr/bin",
"pkg_suffix": "rhel", "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] 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]: def prompt_distro_selection() -> List[DistroType]:
"""Prompt user for distro selection.""" """Prompt user for distro selection."""
print("Select distribution(s) to build for:") print("Select distribution(s) to build for:")
@@ -60,7 +167,7 @@ def prompt_distro_selection() -> List[DistroType]:
print("2. Arch") print("2. Arch")
print("3. RHEL") print("3. RHEL")
print("4. All distributions") print("4. All distributions")
while True: while True:
choice = input("Enter choice (1-4): ").strip() choice = input("Enter choice (1-4): ").strip()
if choice == "1": if choice == "1":
@@ -161,7 +268,7 @@ StartupNotify=true
pkg_items.append("usr/share/icons/hicolor/256x256/apps/android-file-handler.png") pkg_items.append("usr/share/icons/hicolor/256x256/apps/android-file-handler.png")
# Debug listing # 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: for item in pkg_items:
print(f" - {item}") print(f" - {item}")
item_path = pkg_dir / item item_path = pkg_dir / item
@@ -171,6 +278,9 @@ StartupNotify=true
else: else:
print(f" (missing) {item_path}") print(f" (missing) {item_path}")
# Package with fpm
package_with_fpm(distro_type, version, project_root)
def main() -> None: def main() -> None:
# Get project root (parent of scripts directory) # Get project root (parent of scripts directory)
@@ -211,14 +321,8 @@ def main() -> None:
run_command(["poetry", "lock"]) run_command(["poetry", "lock"])
run_command(["poetry", "install"]) run_command(["poetry", "install"])
# Get version from Poetry # Get and validate version from Poetry
result = subprocess.run( version = get_version()
["poetry", "version", "-s"],
capture_output=True,
text=True,
check=True
)
version = result.stdout.strip()
print(f"Version: {version}") print(f"Version: {version}")
# Build for selected distributions # Build for selected distributions
+1 -1
View File
@@ -48,7 +48,7 @@ ENV PATH="$PYENV_ROOT/bin:$PATH"
RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv 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 # The tk8-devel package must be installed before this step for _tkinter to be compiled
RUN eval "$(pyenv init -)" && \ RUN eval "$(pyenv init -)" && \
LDFLAGS="-L/usr/lib64" \ LDFLAGS="-L/usr/lib64" \
+121
View File
@@ -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