From 8a5a7bf11f3df65ae2d8b3df8881efa5e8f96408 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Tue, 26 Aug 2025 19:54:45 -0500 Subject: [PATCH] * Migrating primary build script to Python for maintainablity * Created individual spec files per distro * Created distro specific post install scripts (Debian/RHEL, Arch does not need one) --- .github/workflows/simple-release.yml | 44 +++- .gitignore | 4 +- scripts/build_package_linux.py | 207 ++++++++++++++++++ scripts/build_package_linux.sh | 8 +- .../{linux_postinst.sh => debian_postinst.sh} | 0 scripts/rhel_postinst.sh | 26 +++ .../android-file-handler-arch.spec | 47 ++++ ....spec => android-file-handler-debian.spec} | 2 +- .../android-file-handler-rhel.spec | 48 ++++ 9 files changed, 376 insertions(+), 10 deletions(-) create mode 100755 scripts/build_package_linux.py rename scripts/{linux_postinst.sh => debian_postinst.sh} (100%) create mode 100644 scripts/rhel_postinst.sh create mode 100644 scripts/spec_scripts/android-file-handler-arch.spec rename scripts/spec_scripts/{android-file-handler-linux.spec => android-file-handler-debian.spec} (94%) create mode 100644 scripts/spec_scripts/android-file-handler-rhel.spec diff --git a/.github/workflows/simple-release.yml b/.github/workflows/simple-release.yml index c9f0d1c..0003783 100644 --- a/.github/workflows/simple-release.yml +++ b/.github/workflows/simple-release.yml @@ -20,6 +20,7 @@ env: FPM_VERSION: "1.16.0" # Personal Access Token for HTTPS git operations (populate in repository secrets) CI_CD_PAT: ${{ secrets.CI_CD_PAT }} + CI_CD: true jobs: build-windows: @@ -61,7 +62,7 @@ jobs: with: name: windows-binary path: dist/android-file-handler.exe - + build-debian: runs-on: ubuntu-latest container: @@ -172,7 +173,7 @@ jobs: mkdir -p dist chmod +x dist/android-file-handler # explicit filename into dist/ - fpm -s dir -t pacman -n android-file-handler -v "$VERSION" --architecture x86_64 --prefix /usr/local/bin -p "dist/android-file-handler-${VERSION}-1-x86_64.pkg.tar.zst" dist/android-file-handler + fpm -s dir -t pacman -n android-file-handler -v "$VERSION" --architecture x86_64 --prefix /usr/bin -p "dist/android-file-handler-${VERSION}-1-x86_64.pkg.tar.zst" dist/android-file-handler - name: Upload Arch package uses: actions/upload-artifact@v4 @@ -219,4 +220,41 @@ jobs: draft: false prerelease: false env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + sync-wiki: + needs: create-release + runs-on: ubuntu-latest + steps: + - name: Checkout main repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Checkout wiki repo + run: | + git clone "https://github.com/${GITHUB_REPOSITORY}.wiki.git" wiki + + - name: Sync WIKI.md to Wiki/Home.md + run: | + set -e + SRC_FILE="WIKI.md" + DEST_FILE="wiki/Home.md" + + if [ ! -f "$SRC_FILE" ]; then + echo "No $SRC_FILE in main repo; skipping." + exit 0 + fi + + if ! cmp -s "$SRC_FILE" "$DEST_FILE"; then + echo "Changes found, updating wiki..." + cp "$SRC_FILE" "$DEST_FILE" + cd wiki + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Home.md + git commit -m "Sync WIKI.md from main repo [skip ci]" || echo "No changes to commit" + git push + else + echo "No changes in $SRC_FILE; wiki is up to date." + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore index 84cba60..a91cd38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ platform-tools** -dist/ +dist* build_temp/ build *.ini @@ -7,7 +7,7 @@ build .actrc bin/ test-act.yml -pkg_dist +pkg_dist* # Python cache files __pycache__/ diff --git a/scripts/build_package_linux.py b/scripts/build_package_linux.py new file mode 100755 index 0000000..c941dd0 --- /dev/null +++ b/scripts/build_package_linux.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import shutil +import sys +from pathlib import Path +from enum import Enum +from typing import List + + +class DistroType(Enum): + DEBIAN = "debian" + ARCH = "arch" + RHEL = "rhel" + + +def run_command(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess: + """Run command and handle errors.""" + print(f"Running: {' '.join(cmd)}") + try: + return subprocess.run(cmd, check=check, capture_output=False) + except subprocess.CalledProcessError as e: + print(f"Command failed with exit code {e.returncode}: {' '.join(cmd)}") + sys.exit(e.returncode) + + +def get_distro_config(distro_type: DistroType) -> dict: + """Get configuration for specific distro type.""" + configs = { + DistroType.DEBIAN: { + "name": "Debian", + "bin_path": "usr/local/bin", + "pkg_suffix": "debian", + "spec_file": "scripts/spec_scripts/android-file-handler-debian.spec" + }, + DistroType.ARCH: { + "name": "Arch", + "bin_path": "usr/bin", + "pkg_suffix": "arch", + "spec_file": "scripts/spec_scripts/android-file-handler-arch.spec" + }, + DistroType.RHEL: { + "name": "RHEL", + "bin_path": "usr/bin", + "pkg_suffix": "rhel", + "spec_file": "scripts/spec_scripts/android-file-handler-rhel.spec" + } + } + return configs[distro_type] + + +def prompt_distro_selection() -> List[DistroType]: + """Prompt user for distro selection.""" + print("Select distribution(s) to build for:") + print("1. Debian") + print("2. Arch") + print("3. RHEL") + print("4. All distributions") + + while True: + choice = input("Enter choice (1-4): ").strip() + if choice == "1": + return [DistroType.DEBIAN] + elif choice == "2": + return [DistroType.ARCH] + elif choice == "3": + return [DistroType.RHEL] + elif choice == "4": + return [DistroType.DEBIAN, DistroType.ARCH, DistroType.RHEL] + else: + print("Invalid choice. Please enter 1, 2, 3, or 4.") + + +def build_for_distro(distro_type: DistroType, version: str) -> None: + """Build package for specific distro type.""" + config = get_distro_config(distro_type) + print(f"\n=== Building for {config['name']} ===") + + # Build with distro-specific spec file + print(f"Building binary using {config['spec_file']}") + run_command([ + "poetry", "run", "pyinstaller", + config['spec_file'], + "--distpath", f"dist_{config['pkg_suffix']}" + ]) + + # Make binary executable + binary_path = Path(f"dist_{config['pkg_suffix']}/android-file-handler") + binary_path.chmod(0o755) + + # Package preparation + pkg_dir = Path(f"pkg_dist_{config['pkg_suffix']}") + + # Clean and create package structure + if pkg_dir.exists(): + shutil.rmtree(pkg_dir) + + directories = [ + config["bin_path"], + "usr/share/applications", + "usr/share/icons/hicolor/256x256/apps" + ] + + for directory in directories: + (pkg_dir / directory).mkdir(parents=True, exist_ok=True) + + # Copy built binary + binary_dst = pkg_dir / config["bin_path"] / "android-file-handler" + shutil.copy2(f"dist_{config['pkg_suffix']}/android-file-handler", binary_dst) + binary_dst.chmod(0o755) + + # Handle icon + icon_src = Path("icon_media/robot_files_256.png") + icon_dst = pkg_dir / "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" + icon_included = False + + if icon_src.exists(): + shutil.copy2(icon_src, icon_dst) + icon_dst.chmod(0o644) + icon_included = True + else: + print(f"Warning: icon not found at {icon_src}; packaging without icon") + + # Create .desktop file with correct exec path + exec_path = f"/{config['bin_path']}/android-file-handler" + desktop_content = f"""[Desktop Entry] +Type=Application +Name=Android File Handler +Comment=Manage Android device files via ADB +Exec={exec_path} %U +Icon=android-file-handler +Terminal=false +Categories=Utility;Development; +StartupNotify=true +""" + + desktop_path = pkg_dir / "usr/share/applications/android-file-handler.desktop" + desktop_path.write_text(desktop_content) + desktop_path.chmod(0o644) + + # 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") + + # Debug listing + print(f"Packaging the following items for {config['name']} (relative to {pkg_dir}):") + for item in pkg_items: + print(f" - {item}") + item_path = pkg_dir / item + if item_path.exists(): + stat = item_path.stat() + print(f" {oct(stat.st_mode)[-3:]} {stat.st_size:>8} {item_path}") + else: + print(f" (missing) {item_path}") + + +def main(): + # Check if running in CI/CD mode + is_ci_cd = os.environ.get("CI_CD", "false").lower() == "true" + + if is_ci_cd: + print("CI/CD mode detected - building for all distributions") + selected_distros = [DistroType.DEBIAN, DistroType.ARCH, DistroType.RHEL] + else: + selected_distros = prompt_distro_selection() + + # Poetry operations (only once) + print("=== Preparing Poetry environment ===") + if is_ci_cd: + print("CI/CD mode: skipping poetry lock") + run_command(["poetry", "install"]) + else: + print("Development mode: running poetry lock and install") + 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() + print(f"Version: {version}") + + # Build for selected distributions + for distro_type in selected_distros: + build_for_distro(distro_type, version) + + print(f"\n=== Build complete for: {', '.join([get_distro_config(d)['name'] for d in selected_distros])} ===") + + # Show output directories + print("\nOutput directories:") + for distro_type in selected_distros: + config = get_distro_config(distro_type) + print(f" {config['name']}: pkg_dist_{config['pkg_suffix']}/") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/build_package_linux.sh b/scripts/build_package_linux.sh index a8bd54c..d7fe3b7 100755 --- a/scripts/build_package_linux.sh +++ b/scripts/build_package_linux.sh @@ -7,16 +7,16 @@ poetry install # Build using the Linux spec so the build is reproducible and uses the project spec poetry run pyinstaller /home/jasonross/workspace/android_file_handler_adb/scripts/spec_scripts/android-file-handler-linux.spec --distpath dist -# Package into a Debian .deb using fpm +# Package into a Linux .deb using fpm export PATH="$HOME/.local/bin:/usr/local/bin:$PATH" VERSION="$(poetry version -s)" mkdir -p dist chmod +x dist/android-file-handler -# explicit filename into dist/ (Debian .deb) +# explicit filename into dist/ (Linux .(binary type)) # change directory into dist and package the binary named 'android-file-handler' # this ensures the package installs /usr/local/bin/android-file-handler (no extra dist/ prefix) -# Debian +# Linux # Prepare packaging layout in a separate directory to avoid clobbering build artifacts PKG_DIR="pkg_dist" rm -rf "$PKG_DIR" @@ -56,7 +56,7 @@ StartupNotify=true EOF chmod 644 "$PKG_DIR/usr/share/applications/android-file-handler.desktop" -# Build the Debian package and include postinst script +# Build the Linux package and include postinst script PKG_ITEMS=( "usr/local/bin/android-file-handler" "usr/share/applications/android-file-handler.desktop" ) if [ "$ICON_INCLUDED" = true ]; then PKG_ITEMS+=( "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" ) diff --git a/scripts/linux_postinst.sh b/scripts/debian_postinst.sh similarity index 100% rename from scripts/linux_postinst.sh rename to scripts/debian_postinst.sh diff --git a/scripts/rhel_postinst.sh b/scripts/rhel_postinst.sh new file mode 100644 index 0000000..19307d2 --- /dev/null +++ b/scripts/rhel_postinst.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +# Post-installation script for android-file-handler +# - update desktop database if available +# - update icon cache if available +# - ensure binary is executable + +# Update desktop database if the utility exists +if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database /usr/share/applications || true +fi + +# Update GTK icon cache for hicolor theme if utility exists +if command -v gtk-update-icon-cache >/dev/null 2>&1; then + if [ -d /usr/share/icons/hicolor ]; then + gtk-update-icon-cache -t -f /usr/share/icons/hicolor || true + fi +fi + +# Ensure installed binary is executable +if [ -f /usr/local/bin/android-file-handler ]; then + chmod 0755 /usr/bin/android-file-handler || true +fi + +exit 0 diff --git a/scripts/spec_scripts/android-file-handler-arch.spec b/scripts/spec_scripts/android-file-handler-arch.spec new file mode 100644 index 0000000..e23ade1 --- /dev/null +++ b/scripts/spec_scripts/android-file-handler-arch.spec @@ -0,0 +1,47 @@ +block_cipher = None +a = Analysis( + ['../../src/main.py'], + pathex=['src'], + binaries=[], + datas=[ + ('../../src/gui', 'gui') + ], + hiddenimports=[ + 'gui', + 'gui.file_browser', + 'gui.license_agreement', + 'gui.main_window', + 'gui.progress_handler', + 'adb_manager' + ], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='android-file-handler', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) \ No newline at end of file diff --git a/scripts/spec_scripts/android-file-handler-linux.spec b/scripts/spec_scripts/android-file-handler-debian.spec similarity index 94% rename from scripts/spec_scripts/android-file-handler-linux.spec rename to scripts/spec_scripts/android-file-handler-debian.spec index 91d6397..50eef07 100644 --- a/scripts/spec_scripts/android-file-handler-linux.spec +++ b/scripts/spec_scripts/android-file-handler-debian.spec @@ -6,7 +6,7 @@ a = Analysis( binaries=[], datas=[ ('../../src/gui', 'gui'), - ('../../scripts/linux_postinst.sh', 'scripts'), + ('../../scripts/debian_postinst.sh', 'scripts'), ], hiddenimports=[ 'gui', diff --git a/scripts/spec_scripts/android-file-handler-rhel.spec b/scripts/spec_scripts/android-file-handler-rhel.spec new file mode 100644 index 0000000..9bd562c --- /dev/null +++ b/scripts/spec_scripts/android-file-handler-rhel.spec @@ -0,0 +1,48 @@ +block_cipher = None +a = Analysis( + ['../../src/main.py'], + pathex=['src'], + binaries=[], + datas=[ + ('../../src/gui', 'gui'), + ('../../scripts/rhel_postinst.sh', 'scripts'), + ], + hiddenimports=[ + 'gui', + 'gui.file_browser', + 'gui.license_agreement', + 'gui.main_window', + 'gui.progress_handler', + 'adb_manager' + ], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='android-file-handler', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) \ No newline at end of file