#!/usr/bin/env python3 # Build the BeeRef appimage. Run from the git root directory. # On github actions: # ./tools/build_appimage --version=${{ github.ref_name }}\ # --jsonfile=tools/linux_libs.json # Locally: # ./tools/build_appimage --version=0.3.3-dev --jsonfile=tools/linux_libs.json # --skip-apt import argparse import glob import json import logging import os import shutil import subprocess from urllib.request import urlretrieve parser = argparse.ArgumentParser( description=('Create an appimage for BeeRef. ' 'Run from the git root directory.')) parser.add_argument( '-v', '--version', required=True, help='BeeRef version number/tag for output file') parser.add_argument( '-j', '--jsonfile', required=True, help='Json with lib files and packages as generated by find_linux_libs') parser.add_argument( '--redownload', default=False, action='store_true', help='Re-use downloaded files if present') parser.add_argument( '--skip-apt', default=False, action='store_true', help='Skip apt install step') parser.add_argument( '-l', '--loglevel', default='INFO', choices=list(logging._nameToLevel.keys()), help='log level for console output') args = parser.parse_args() BEEVERSION = args.version.removeprefix('v') APPIMAGE = 'python3.11.9-cp311-cp311-manylinux2014_x86_64.AppImage' # ^ Siehe: # https://python-appimage.readthedocs.io/en/latest/#alternative-site-packages-location PYVER = '3.11' logger = logging.getLogger(__name__) logging.basicConfig(level=getattr(logging, args.loglevel)) def run_command(*args, capture_output=False): logger.info(f'Running command: {args}') result = subprocess.run(args, capture_output=capture_output) assert result.returncode == 0, f'Failed with exit code {result.returncode}' def download_file(url, filename): if not args.redownload and os.path.exists(filename): logger.info(f'Found file: {filename}') else: logger.info(f'Downloading: {url}') logger.info(f'Saving as: {filename}') urlretrieve(url, filename=filename) os.chmod(filename, 0o755) url = ('https://github.com/niess/python-appimage/releases/download/' f'python{PYVER}/{APPIMAGE}') download_file(url, filename='python.appimage') try: shutil.rmtree('squashfs-root') except FileNotFoundError: pass run_command('./python.appimage', '--appimage-extract', capture_output=True) run_command('squashfs-root/usr/bin/pip', 'install', '.', f'--target=squashfs-root/opt/python{PYVER}/lib/python{PYVER}/') logger.info(f'Reading from: {args.jsonfile}') with open(args.jsonfile, 'r') as f: data = json.loads(f.read()) libs = data['libs'] packages = data['packages'] excludes = data['excludes'] paths = set() if not args.skip_apt: run_command('sudo', 'apt', 'install', *packages) logger.info('Copying .so files to appimage...') existing_files = [] for root, subdirs, files in os.walk('squashfs-root'): existing_files.extend(files) for lib in libs: if os.path.basename(lib) in existing_files: logger.debug(f'Skipping {lib} (already in appimage)') continue if os.path.basename(lib) in excludes: logger.debug(f'Skipping {lib} (excluded)') continue paths.add(os.path.dirname(lib)) if os.path.exists(lib): filename = lib else: filename, _ = os.path.splitext(lib) dest = f'squashfs-root{filename}' os.makedirs(os.path.dirname(dest), exist_ok=True) logger.debug(f'Copying {filename} to {dest}') shutil.copyfile(filename, f'squashfs-root{filename}') logger.info('Writing run script...') # Adapted from usr/bin/python3.x in the python appimage os.remove('squashfs-root/AppRun') # ^ This is only a symlink to usr/bin/python3.x paths = [ '/usr/lib', # The libs that come with the python appimage ar in /usr/lib ] + list(paths) ld_paths = ['${APPDIR}' + p for p in paths] + ['${LD_LIBRARY_PATH}'] ld_paths = ':'.join(ld_paths) logger.debug(f'LD_LIBRARY_PATH: {ld_paths}') content = ["""#! /bin/bash # If running from an extracted image, then export ARGV0 and APPDIR if [ -z "${APPIMAGE}" ]; then export ARGV0="$0" self=$(readlink -f -- "$0") # Protect spaces (issue 55) here="${self%/*}" tmp="${here%/*}" export APPDIR="${tmp%/*}" fi # Resolve the calling command (preserving symbolic links). export APPIMAGE_COMMAND=$(command -v -- "$ARGV0") # Export SSL certificate export SSL_CERT_FILE="${APPDIR}/opt/_internal/certs.pem" """] runbee = f'"$APPDIR/opt/python{PYVER}/bin/python{PYVER}" -I -m beeref "$@"' logfile = '/tmp/BeeRefAppimageLog.txt' content.extend([ f'export LD_LIBRARY_PATH="{ld_paths}"', f'{runbee} 2> >(tee {logfile} >&2)', 'if [ $? -ne 0 ]; then', # Workaround for: # https://bugreports.qt.io/browse/QTBUG-114635 # See also https://github.com/rbreu/beeref/issues/102 f' if grep -q wl_proxy_marshal_flags {logfile}; then', ' echo "Wayland version error; trying again without Wayland"', f' QT_QPA_PLATFORM=xcb {runbee}', 'fi; fi;']) with open('squashfs-root/AppRun', 'w') as f: f.write('\n'.join(content)) os.chmod('squashfs-root/AppRun', 0o755) logger.info('Copying appdata.xml...') for f in glob.glob('squashfs-root/usr/share/metainfo/*'): os.remove(f) filename = 'org.beeref.BeeRef.appdata.xml' shutil.copyfile(filename, f'squashfs-root/usr/share/metainfo/{filename}') logger.info('Writing .desktop...') for f in glob.glob('squashfs-root/usr/share/applications/*'): os.remove(f) for f in glob.glob('squashfs-root/*.desktop'): os.remove(f) content = f"""[Desktop Entry] Name=BeeRef GenericName=Image Viewer Comment=A simple reference image viewer Terminal=false Exec=BeeRef-{BEEVERSION} Type=Application Icon=logo MimeType=application/x-beeref; Categories=Qt;KDE;Graphics; X-KDE-NativeMimeType=application/x-beeref X-KDE-ExtraNativeMimeTypes= X-AppImage-Version={BEEVERSION} """ filename = 'squashfs-root/usr/share/applications/org.beeref.BeeRef.desktop' with open(filename, 'w') as f: f.write(content) os.symlink('usr/share/applications/org.beeref.BeeRef.desktop', 'squashfs-root/BeeRef.desktop') logger.info('Copying logos...') shutil.copyfile('./beeref/assets/logo.svg', 'squashfs-root/logo.svg') shutil.copyfile('./beeref/assets/logo.png', 'squashfs-root/.DirIcon') url = ('https://github.com/AppImage/AppImageKit/releases/download/' 'continuous/appimagetool-x86_64.AppImage') download_file(url, filename='appimagetool.appimage') run_command('./appimagetool.appimage', 'squashfs-root', f'BeeRef-{BEEVERSION}.appimage')