Browse Source

Add AppImage builder configs and scripts

Added the configuration file `AppImageBuilder.yml.jinja`, the Python script `create_appimage.py`, and the shell script
`entrypoint.sh.jinja` for AppImage building and packaging. These additions would enable the Cura application to be built
into a single, standalone, executables file for Linux users, improving software deployment and distribution.

Contributes CURA-10951
Jelle Spijker 1 year ago
parent
commit
b5fbcce33f

+ 90 - 0
packaging/AppImage-builder/AppImageBuilder.yml.jinja

@@ -0,0 +1,90 @@
+version: 1
+
+AppDir:
+  path: {{ app_dir }}
+  app_info:
+    id: com.ultimaker.cura
+    name: UltiMaker-Cura
+    icon: {{ icon }}
+    version: {{ version }}
+    exec: entrypoint.sh
+    exec_args: $@
+  apt:
+    arch:
+    - amd64
+    allow_unauthenticated: true
+    sources:
+    - sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy main restricted
+    - sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy-updates main restricted
+    - sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy universe
+    - sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy-updates universe
+    - sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy multiverse
+    - sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy-updates multiverse
+    - sourceline: deb http://nl.archive.ubuntu.com/ubuntu/ jammy-backports main restricted
+        universe multiverse
+    - sourceline: deb http://security.ubuntu.com/ubuntu jammy-security main restricted
+    - sourceline: deb http://security.ubuntu.com/ubuntu jammy-security universe
+    - sourceline: deb http://security.ubuntu.com/ubuntu jammy-security multiverse
+    - sourceline: deb https://releases.jfrog.io/artifactory/jfrog-debs xenial contrib
+    - sourceline: deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main
+    - sourceline: deb https://ppa.launchpadcontent.net/ubuntu-toolchain-r/test/ubuntu/
+        jammy main
+    - sourceline: deb https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu/ jammy
+        main
+    - sourceline: deb [arch=amd64] https://packages.microsoft.com/repos/ms-teams stable
+        main
+    - sourceline: deb https://ppa.launchpadcontent.net/ppa-verse/cling/ubuntu/ jammy
+        main
+    - sourceline: deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable
+        main
+    - sourceline: deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_14.x
+        jammy main
+    - sourceline: deb [arch=amd64 signed-by=/usr/share/keyrings/transip-stack.gpg]
+        https://mirror.transip.net/stack/software/deb/Ubuntu_22.04/ ./
+    - sourceline: deb http://repository.spotify.com stable non-free
+    - sourceline: deb [arch=amd64,arm64,armhf] http://packages.microsoft.com/repos/code
+        stable main
+    - sourceline: deb https://packagecloud.io/slacktechnologies/slack/debian/ jessie
+        main
+    include:
+    - libc6:amd64
+    - xdg-desktop-portal-kde:amd64
+  files:
+    include: []
+    exclude:
+    - usr/share/man
+    - usr/share/doc/*/README.*
+    - usr/share/doc/*/changelog.*
+    - usr/share/doc/*/NEWS.*
+    - usr/share/doc/*/TODO.*
+  runtime:
+    env:
+      PYTHONPATH: "$APPDIR"
+      QT_PLUGIN_PATH: "$APPDIR/qt/plugins"
+      QML2_IMPORT_PATH: "$APPDIR/qt/qml"
+      QT_QPA_PLATFORMTHEME: xdgdesktopportal
+  test:
+    fedora-30:
+      image: appimagecrafters/tests-env:fedora-30
+      command: ./AppRun
+      use_host_x: True
+    debian-stable:
+      image: appimagecrafters/tests-env:debian-stable
+      command: ./AppRun
+      use_host_x: True
+    archlinux-latest:
+      image: appimagecrafters/tests-env:archlinux-latest
+      command: ./AppRun
+      use_host_x: True
+    centos-7:
+      image: appimagecrafters/tests-env:centos-7
+      command: ./AppRun
+      use_host_x: True
+    ubuntu-xenial:
+      image: appimagecrafters/tests-env:ubuntu-xenial
+      command: ./AppRun
+      use_host_x: True
+AppImage:
+  arch: {{ arch }}
+  file_name: {{ file_name }}
+  update-information: guess

+ 99 - 0
packaging/AppImage-builder/create_appimage.py

@@ -0,0 +1,99 @@
+# Copyright (c) 2023 UltiMaker
+# Cura is released under the terms of the LGPLv3 or higher.
+
+
+import argparse  # Command line arguments parsing and help.
+from jinja2 import Template
+import os  # Finding installation directory.
+import os.path  # Finding files.
+import shutil  # Copying files.
+import stat  # For setting file permissions.
+import subprocess  # For calling system commands.
+
+def build_appimage(dist_path, version, appimage_filename):
+    """
+    Creates an AppImage file from the build artefacts created so far.
+    """
+    generate_appimage_builder_config(dist_path, version, appimage_filename)
+    generate_appimage_entrypoint(dist_path)
+    copy_files(dist_path)
+
+    try:
+        os.remove(os.path.join(dist_path, appimage_filename))  # Ensure any old file is removed, if it exists.
+    except FileNotFoundError:
+        pass  # If it didn't exist, that's even better.
+    create_appimage(dist_path, appimage_filename)
+    sign_appimage(dist_path, appimage_filename)
+
+
+def generate_appimage_builder_config(dist_path, version, appimage_filename):
+    with open(os.path.join(dist_path, "AppImageBuilder.yml.jinja"), "r") as appimage_builder_file:
+        appimage_builder = appimage_builder_file.read()
+
+    template = Template(appimage_builder)
+    appimage_builder = template.render(app_dir = dist_path,
+                                       icon = os.path.join("..", "icons", "cura-icon_256x256.png"),
+                                       version = version,
+                                       arch = "x86_64",
+                                       file_name = appimage_filename)
+
+    with open(os.path.join(dist_path, "AppImageBuilder.yml"), "w") as appimage_builder_file:
+        appimage_builder_file.write(appimage_builder)
+
+
+def generate_appimage_entrypoint(dist_path):
+    with open(os.path.join(dist_path, "entrypoint.sh.jinja"), "r") as entrypoint_file:
+        entrypoint = entrypoint_file.read()
+
+    template = Template(entrypoint)
+    entrypoint = template.render(executable = "UltiMaker-Cura")
+
+    with open(os.path.join(dist_path, "entrypoint.sh"), "w") as entrypoint_file:
+        entrypoint_file.write(entrypoint)
+
+def copy_files(dist_path):
+    """
+    Copy metadata files for the metadata of the AppImage.
+    """
+    copied_files = {
+        os.path.join("..", "icons", "cura-icon.svg"):         os.path.join("usr", "share", "icons", "hicolor", "scalable", "apps", "cura-icon.svg"),
+        os.path.join("..", "icons", "cura-icon_64x64.png"):   os.path.join("usr", "share", "icons", "hicolor", "64x64", "apps", "cura-icon.png"),
+        os.path.join("..", "icons", "cura-icon_128x128.png"): os.path.join("usr", "share", "icons", "hicolor", "128x128", "apps", "cura-icon.png"),
+        os.path.join("..", "icons", "cura-icon_256x256.png"): os.path.join("usr", "share", "icons", "hicolor", "256x256", "apps", "cura-icon.png"),
+        os.path.join("..", "icons", "cura-icon_256x256.png"): "cura-icon.png",
+        "entrypoint.sh": "entrypoint.sh"
+    }
+
+    # TODO: openssl.cnf ???
+
+    packaging_dir = os.path.dirname(__file__)
+    for source, dest in copied_files.items():
+        dest_file_path = os.path.join(dist_path, dest)
+        os.makedirs(os.path.dirname(dest_file_path), exist_ok=True)
+        shutil.copyfile(os.path.join(packaging_dir, source), dest_file_path)
+
+    # Ensure that entrypoint.sh has the proper permissions: 755 (user reads, writes and executes, group reads and executes, world reads and executes).
+    os.chmod(os.path.join(dist_path, "entrypoint.sh"), stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
+
+def create_appimage(dist_path, appimage_filename):
+    appimage_path = os.path.join(dist_path, "..", appimage_filename)
+    appimagetool = os.getenv("APPIMAGETOOL_LOCATION", "appimagetool")
+    command = [appimagetool, "--appimage-extract-and-run", f"{dist_path}/", appimage_path]
+    result = subprocess.call(command)
+    if result != 0:
+        raise RuntimeError(f"The AppImageTool command returned non-zero: {result}")
+
+def sign_appimage(dist_path, appimage_filename):
+    appimage_path = os.path.join(dist_path, "..", appimage_filename)
+    command = ["gpg", "--yes", "--armor", "--detach-sig", appimage_path]
+    result = subprocess.call(command)
+    if result != 0:
+        raise RuntimeError(f"The GPG command returned non-zero: {result}")
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description = "Create AppImages of Cura.")
+    parser.add_argument("dist_path", type=str, help="Path to where PyInstaller installed the distribution of Cura.")
+    parser.add_argument("version", type=str, help="Full version number of Cura (e.g. '5.1.0-beta')")
+    parser.add_argument("filename", type = str, help = "Filename of the AppImage (e.g. 'UltiMaker-Cura-5.1.0-beta-Linux-X64.AppImage')")
+    args = parser.parse_args()
+    build_appimage(args.dist_path, args.version, args.filename)

+ 8 - 0
packaging/AppImage-builder/entrypoint.sh.jinja

@@ -0,0 +1,8 @@
+#!/bin/bash
+
+BIN=`basename "$ARGV0" .AppImage`
+if [ -f ${APPDIR}/$BIN ]; then
+    ${APPDIR}/$BIN "$@"
+else
+    ${APPDIR}/{{ executable }} "$@"
+fi;