diff --git a/packaging/AppImage-builder/AppImageBuilder.yml.jinja b/packaging/AppImage-builder/AppImageBuilder.yml.jinja new file mode 100644 index 0000000000..cb446af520 --- /dev/null +++ b/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 diff --git a/packaging/AppImage-builder/create_appimage.py b/packaging/AppImage-builder/create_appimage.py new file mode 100644 index 0000000000..33d13ee5ad --- /dev/null +++ b/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) diff --git a/packaging/AppImage-builder/entrypoint.sh.jinja b/packaging/AppImage-builder/entrypoint.sh.jinja new file mode 100644 index 0000000000..6ff63e6c63 --- /dev/null +++ b/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; \ No newline at end of file