diff --git a/extensions/desktop/common/desktop-exports b/extensions/desktop/common/desktop-exports index bbe0d5addf..78802e4805 100644 --- a/extensions/desktop/common/desktop-exports +++ b/extensions/desktop/common/desktop-exports @@ -170,7 +170,7 @@ needs_xdg_update=false needs_xdg_reload=false needs_xdg_links=false -if [ "$HOME" != "$SNAP_USER_DATA" ] && ! is_subpath "$XDG_CONFIG_HOME" "$HOME"; then +if [ "$HOME" != "$SNAP_REAL_HOME" ] && [ "$HOME" != "$SNAP_USER_DATA" ] && ! is_subpath "$XDG_CONFIG_HOME" "$HOME"; then for f in user-dirs.dirs user-dirs.locale; do if [ -f "$HOME/.config/$f" ]; then mv "$HOME/.config/$f" "$XDG_CONFIG_HOME" @@ -181,18 +181,20 @@ fi if can_open_file "$REALHOME/.config/user-dirs.dirs"; then # shellcheck disable=SC2154 - if [ "$needs_update" = true ] || [ "$needs_xdg_reload" = true ]; then - sed "/^#/!s#\$HOME#${REALHOME}#g" "$REALHOME/.config/user-dirs.dirs" > "$XDG_CONFIG_HOME/user-dirs.dirs" - md5sum < "$REALHOME/.config/user-dirs.dirs" > "$XDG_CONFIG_HOME/user-dirs.dirs.md5sum" - # It's possible user-dirs.dirs exists when user-dirs.locale doesn't. This - # simply means the user opted to never ask to translate their user dirs - if can_open_file "$REALHOME/.config/user-dirs.locale"; then - cp -a "$REALHOME/.config/user-dirs.locale" "$XDG_CONFIG_HOME" - md5sum < "$REALHOME/.config/user-dirs.locale" > "$XDG_CONFIG_HOME/user-dirs.locale.md5sum" - elif [ -f "$XDG_CONFIG_HOME/user-dirs.locale.md5sum" ]; then - rm "$XDG_CONFIG_HOME/user-dirs.locale.md5sum" + if [ "$XDG_CONFIG_HOME" != "$SNAP_REAL_HOME/.config" ]; then + if [ "$needs_update" = true ] || [ "$needs_xdg_reload" = true ]; then + sed "/^#/!s#\$HOME#${SNAP_REAL_HOME}#g" "$SNAP_REAL_HOME/.config/user-dirs.dirs" > "$XDG_CONFIG_HOME/user-dirs.dirs" + md5sum < "$SNAP_REAL_HOME/.config/user-dirs.dirs" > "$XDG_CONFIG_HOME/user-dirs.dirs.md5sum" + # It's possible user-dirs.dirs exists when user-dirs.locale doesn't. This + # simply means the user opted to never ask to translate their user dirs + if can_open_file "$SNAP_REAL_HOME/.config/user-dirs.locale"; then + cp -a "$SNAP_REAL_HOME/.config/user-dirs.locale" "$XDG_CONFIG_HOME" + md5sum < "$SNAP_REAL_HOME/.config/user-dirs.locale" > "$XDG_CONFIG_HOME/user-dirs.locale.md5sum" + elif [ -f "$XDG_CONFIG_HOME/user-dirs.locale.md5sum" ]; then + rm "$XDG_CONFIG_HOME/user-dirs.locale.md5sum" + fi + needs_xdg_reload=true fi - needs_xdg_reload=true fi else needs_xdg_update=true @@ -221,13 +223,15 @@ fi # Create links for user-dirs.dirs if [ "$needs_xdg_links" = true ]; then - for ((i = 0; i < ${#XDG_SPECIAL_DIRS_PATHS[@]}; i++)); do - b="$(realpath "${XDG_SPECIAL_DIRS_PATHS[$i]}" --relative-to="$HOME" 2>/dev/null)" - if [[ -n "$b" && "$b" != "." && -e "$REALHOME/$b" ]]; then - [ -d "$HOME/$b" ] && rmdir "$HOME/$b" 2> /dev/null - [ ! -e "$HOME/$b" ] && ln -s "$REALHOME/$b" "$HOME/$b" - fi - done + if [ "$HOME" != "$SNAP_REAL_HOME" ]; then + for ((i = 0; i < ${#XDG_SPECIAL_DIRS_PATHS[@]}; i++)); do + b="$(realpath "${XDG_SPECIAL_DIRS_PATHS[$i]}" --relative-to="$HOME" 2>/dev/null)" + if [[ -n "$b" && "$b" != "." && -e "$SNAP_REAL_HOME/$b" ]]; then + [ -d "$HOME/$b" ] && rmdir "$HOME/$b" 2> /dev/null + [ ! -e "$HOME/$b" ] && ln -s "$SNAP_REAL_HOME/$b" "$HOME/$b" + fi + done + fi else # If we aren't creating new links, check if we have content saved in old locations and move it for ((i = 0; i < ${#XDG_SPECIAL_DIRS[@]}; i++)); do diff --git a/extensions/desktop/kde-neon-6/Makefile b/extensions/desktop/kde-neon-6/Makefile new file mode 100644 index 0000000000..be0c279808 --- /dev/null +++ b/extensions/desktop/kde-neon-6/Makefile @@ -0,0 +1,34 @@ +#!/usr/bin/make -f + +SRC_DIR ?= . + +DATA_DIR := $(DESTDIR)/data-dir +BIN_DIR := $(DESTDIR)/snap/command-chain +LIB_DIR := $(DESTDIR)/lib +DEST_LAUNCHER := desktop-launch6 +LOCALE_GENERATOR := locale-gen +DEST_CONFIGURE_HOOK := hooks-configure-desktop + +build: $(DEST_LAUNCHER) $(DEST_CONFIGURE_HOOK) + +clean: + rm -f $(DEST_LAUNCHER) + rm -f $(DEST_CONFIGURE_HOOK) + rm -f $(BINDTEXTDOMAIN) + +$(DEST_LAUNCHER): + @cat $(SRC_DIR)/init > $(DEST_LAUNCHER) + # tail -n +2 to remove the shebang + @tail -n +2 $(SRC_DIR)/desktop-exports | sed -e "s/%PLATFORM_PLUG%/$${PLATFORM_PLUG:?}/" >> $(DEST_LAUNCHER) + @tail -n +2 $(SRC_DIR)/launcher-specific >> $(DEST_LAUNCHER) + @tail -n +2 $(SRC_DIR)/mark-and-exec >> $(DEST_LAUNCHER) + +$(DEST_CONFIGURE_HOOK): + @cat $(SRC_DIR)/fonts > $(DEST_CONFIGURE_HOOK) + +install: $(DEST_LAUNCHER) $(DEST_CONFIGURE_HOOK) + install -d $(DATA_DIR) + install -d $(DATA_DIR)/kf6 + install -D -m755 $(DEST_LAUNCHER) "$(BIN_DIR)"/$(DEST_LAUNCHER) + install -D -m755 $(LOCALE_GENERATOR) "$(BIN_DIR)"/$(LOCALE_GENERATOR) + install -D -m755 $(DEST_CONFIGURE_HOOK) "$(BIN_DIR)"/$(DEST_CONFIGURE_HOOK) diff --git a/extensions/desktop/kde-neon-6/desktop-exports b/extensions/desktop/kde-neon-6/desktop-exports new file mode 120000 index 0000000000..8013267817 --- /dev/null +++ b/extensions/desktop/kde-neon-6/desktop-exports @@ -0,0 +1 @@ +../common/desktop-exports \ No newline at end of file diff --git a/extensions/desktop/kde-neon-6/fonts b/extensions/desktop/kde-neon-6/fonts new file mode 120000 index 0000000000..3ef54f4a97 --- /dev/null +++ b/extensions/desktop/kde-neon-6/fonts @@ -0,0 +1 @@ +../common/fonts \ No newline at end of file diff --git a/extensions/desktop/kde-neon-6/init b/extensions/desktop/kde-neon-6/init new file mode 120000 index 0000000000..350b7be8e1 --- /dev/null +++ b/extensions/desktop/kde-neon-6/init @@ -0,0 +1 @@ +../common/init \ No newline at end of file diff --git a/extensions/desktop/kde-neon-6/launcher-specific b/extensions/desktop/kde-neon-6/launcher-specific new file mode 100644 index 0000000000..ffb6029e98 --- /dev/null +++ b/extensions/desktop/kde-neon-6/launcher-specific @@ -0,0 +1,69 @@ +#!/bin/bash +################################### +# KDE NEON launcher specific part # +################################### + +# Add paths for games +append_dir PATH "$SNAP/usr/games" +append_dir PATH "$SNAP_DESKTOP_RUNTIME/usr/games" + +# Qt Libs +prepend_dir LD_LIBRARY_PATH "$SNAP_DESKTOP_RUNTIME/usr/lib/$ARCH" + +# Add QT_PLUGIN_PATH (Qt Modules). +append_dir QT_PLUGIN_PATH "$SNAP/usr/lib/$ARCH/qt6/plugins" +append_dir QT_PLUGIN_PATH "$SNAP/usr/lib/$ARCH" +append_dir QT_PLUGIN_PATH "$SNAP_DESKTOP_RUNTIME/usr/lib/$ARCH/qt6/plugins" +append_dir QT_PLUGIN_PATH "$SNAP_DESKTOP_RUNTIME/usr/lib/$ARCH/" +# And QML2_IMPORT_PATH (Qt Modules). +append_dir QML2_IMPORT_PATH "$SNAP/usr/lib/$ARCH/qt6/qml" +append_dir QML2_IMPORT_PATH "$SNAP/lib/$ARCH" +append_dir QML2_IMPORT_PATH "$SNAP_DESKTOP_RUNTIME/usr/lib/$ARCH/qt6/qml" +append_dir QML2_IMPORT_PATH "$SNAP_DESKTOP_RUNTIME/lib/$ARCH" + +# Fix locating the QtWebEngineProcess executable +export QTWEBENGINEPROCESS_PATH="$SNAP_DESKTOP_RUNTIME/usr/lib/$ARCH/qt6/libexec/QtWebEngineProcess" + +# Removes Qt warning: Could not find a location +# of the system Compose files +export QTCOMPOSE="$SNAP_DESKTOP_RUNTIME/usr/share/X11/locale" +export QT_XKB_CONFIG_ROOT="/usr/share/X11/xkb" + +# KIO specific +# Directly fork slaves. +export KDE_FORK_SLAVES=1 +# Path to KIO slaves. +export KF6_LIBEXEC_DIR="$SNAP_DESKTOP_RUNTIME/usr/lib/$ARCH/libexec/kf6" + +# Add path to VLC plugins +export VLC_PLUGIN_PATH="$SNAP_DESKTOP_RUNTIME/usr/lib/$ARCH/vlc/plugins" + +# Ensure QtChooser behaves. +export QTCHOOSER_NO_GLOBAL_DIR=1 +export QT_SELECT=6 +# qtchooser hardcodes reference paths, we'll need to rewrite them properly +ensure_dir_exists "$XDG_CONFIG_HOME/qtchooser" +echo "$SNAP/usr/lib/qt6/bin" > "$XDG_CONFIG_HOME/qtchooser/6.conf" +echo "$SNAP/usr/lib/$ARCH" >> "$XDG_CONFIG_HOME/qtchooser/6.conf" +echo "$SNAP/usr/lib/qt6/bin" > "$XDG_CONFIG_HOME/qtchooser/default.conf" +echo "$SNAP/usr/lib/$ARCH" >> "$XDG_CONFIG_HOME/qtchooser/default.conf" + +# This relies on qtbase patch +# 0001-let-qlibraryinfo-fall-back-to-locate-qt.conf-via-XDG.patch +# to make QLibraryInfo look in XDG_* locations for qt.conf. The paths configured +# here are applied to everything that uses QLibraryInfo as final fallback and +# has no XDG_* fallback before that. Currently the most interesting offender +# is QtWebEngine which will not work unless the Data path is correctly set. +cat << EOF > "$XDG_CONFIG_HOME/qt.conf" +[Paths] +Data = $SNAP_DESKTOP_RUNTIME/usr/share/qt6/ +Translations = $SNAP_DESKTOP_RUNTIME/usr/share/qt6/translations +EOF + +if [ -e "$SNAP_DESKTOP_RUNTIME/usr/share/i18n" ]; then + export I18NPATH="$SNAP_DESKTOP_RUNTIME/usr/share/i18n" + locpath="$XDG_DATA_HOME/locale" + ensure_dir_exists "$locpath" + export LOCPATH="$locpath:/usr/lib/locale" + LC_ALL=C.UTF-8 async_exec "$SNAP/snap/command-chain/locale-gen" +fi diff --git a/extensions/desktop/kde-neon-6/locale-gen b/extensions/desktop/kde-neon-6/locale-gen new file mode 100755 index 0000000000..2c8b6050f0 --- /dev/null +++ b/extensions/desktop/kde-neon-6/locale-gen @@ -0,0 +1,62 @@ +#!/usr/bin/perl + +use strict; + +sub uniq +{ + my %seen; + grep !$seen{$_}++, @_; +} + +# TODO support for KDE's in-app language switch feature. +sub get_languages +{ + # Initialize with sane defaults. + my @found_languages = ($ENV{'LANG'} or 'C.UTF-8'); + + # Go through LC_. + foreach (sort keys %ENV) + { + if (substr($_, 0, length("LC_")) eq "LC_") + { + push(@found_languages, $ENV{$_}); + } + } + # And finally LANGUAGE, but normalize it. + if (my $language = $ENV{'LANGUAGE'}) + { + foreach (split(':', $language)) + { + push(@found_languages, "$_.UTF-8"); + } + } + + # Remove duplicates before returning. + @found_languages = uniq(@found_languages); + + return @found_languages +} + +my $env_locpath = $ENV{'LOCPATH'} or die "LOCPATH must be set"; +my @locpaths = split(/:/, $env_locpath); + +foreach my $lang (get_languages()) +{ + my $found = 0; + foreach my $locpath (@locpaths) + { + my $loc_target = "$locpath/$lang"; + if (-e $loc_target) + { + $found = 1; + last; + } + } + next if $found; + my $target = "@locpaths[0]/$lang"; + + # localedef will exit !0 for unknown reasons, even when everything was + # generated fine. + my ($locale, $encoding) = split(/\./, $lang); + system('localedef', '-i', $locale, '-f', $encoding, $target); +} diff --git a/extensions/desktop/kde-neon-6/mark-and-exec b/extensions/desktop/kde-neon-6/mark-and-exec new file mode 120000 index 0000000000..1bf8626763 --- /dev/null +++ b/extensions/desktop/kde-neon-6/mark-and-exec @@ -0,0 +1 @@ +../common/mark-and-exec \ No newline at end of file diff --git a/snapcraft/extensions/kde_neon_6.py b/snapcraft/extensions/kde_neon_6.py new file mode 100644 index 0000000000..5e41a8d29f --- /dev/null +++ b/snapcraft/extensions/kde_neon_6.py @@ -0,0 +1,298 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2022 Canonical Ltd. +# 2023-2024 Scarlett Moore +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Generic KDE NEON extension to support core22 and onwards.""" +import dataclasses +import functools +import re +from typing import Any, Dict, List, Optional, Tuple + +from overrides import overrides + +from .extension import Extension, get_extensions_data_dir, prepend_to_env + +_QT6_SDK_SNAP = {"core22": "kde-qt6-core22-sdk"} +_KF6_SDK_SNAP = {"core22": "kf6-core22-sdk"} + + +@dataclasses.dataclass +class KDESnaps6: + """A structure of KDE related snaps.""" + + qt6_sdk_snap: str + kf6_sdk_snap: str + content_qt6: str + content_kf6: str + qt6_builtin: bool = True + kf6_builtin: bool = True + + +class KDENeon6(Extension): + r"""The KDE Neon extension. + + This extension makes it easy to assemble KDE based applications + using the Neon stack. + + It configures each application with the following plugs: + + \b + - Common Icon Themes. + - Common Sound Themes. + - The Qt6 and KDE Frameworks 6 runtime libraries and utilities. + + For easier desktop integration, it also configures each application + entry with these additional plugs: + + \b + - desktop (https://snapcraft.io/docs/desktop-interface) + - desktop-legacy (https://snapcraft.io/docs/desktop-legacy-interface) + - opengl (https://snapcraft.io/docs/opengl-interface) + - wayland (https://snapcraft.io/docs/wayland-interface) + - x11 (https://snapcraft.io/docs/x11-interface) + - audio-playback (https://snapcraft.io/docs/audio-playback-interface) + - unity7 (https://snapcraft.io/docs/unity7-interface) + - network https://snapcraft.io/docs/network-interface) + - network-bind (https://snapcraft.io/docs/network-bind-interface) + """ + + @staticmethod + @overrides + def get_supported_bases() -> Tuple[str, ...]: + return ("core22",) + + @staticmethod + @overrides + def get_supported_confinement() -> Tuple[str, ...]: + return "strict", "devmode" + + @staticmethod + @overrides + def is_experimental(base: Optional[str]) -> bool: + return False + + @overrides + def get_app_snippet(self) -> Dict[str, Any]: + return { + "command-chain": ["snap/command-chain/desktop-launch6"], + "plugs": [ + "desktop", + "desktop-legacy", + "opengl", + "wayland", + "x11", + "audio-playback", + "unity7", + "network", + "network-bind", + ], + } + + @functools.cached_property + def kde_snaps(self) -> KDESnaps6: + """Return the KDE related snaps to use to construct the environment.""" + base = self.yaml_data["base"] + qt6_sdk_snap = _QT6_SDK_SNAP[base] + kf6_sdk_snap = _KF6_SDK_SNAP[base] + + build_snaps: List[str] = [] + for part in self.yaml_data["parts"].values(): + build_snaps.extend(part.get("build-snaps", [])) + + matcher = re.compile(r"kde-qt6-" + base + r"-sdk.*") + qt6_sdk_snap_candidates = [s for s in build_snaps if matcher.match(s)] + if qt6_sdk_snap_candidates: + qt6_sdk_snap = qt6_sdk_snap_candidates[0].split("/")[0] + qt6_builtin = False + else: + qt6_builtin = True + + matcher = re.compile(r"kf6-" + base + r"-sdk.*") + kf6_sdk_snap_candidates = [s for s in build_snaps if matcher.match(s)] + if kf6_sdk_snap_candidates: + kf6_sdk_snap = kf6_sdk_snap_candidates[0].split("/")[0] + kf6_builtin = False + else: + kf6_builtin = True + # The same except the trailing -sdk + content_qt6_snap = qt6_sdk_snap[:-4] + content_kf6_snap = kf6_sdk_snap[:-4] + + return KDESnaps6( + qt6_sdk_snap=qt6_sdk_snap, + content_qt6=content_qt6_snap, + qt6_builtin=qt6_builtin, + kf6_sdk_snap=kf6_sdk_snap, + content_kf6=content_kf6_snap, + kf6_builtin=kf6_builtin, + ) + + @overrides + def get_root_snippet(self) -> Dict[str, Any]: + platform_kf6_snap = self.kde_snaps.content_kf6 + content_kf6_snap = self.kde_snaps.content_kf6 + "-all" + + return { + "assumes": ["snapd2.58.3"], # for 'snapctl is-connected' + "compression": "lzo", + "plugs": { + "desktop": {"mount-host-font-cache": False}, + "icon-themes": { + "interface": "content", + "target": "$SNAP/data-dir/icons", + "default-provider": "gtk-common-themes", + }, + "sound-themes": { + "interface": "content", + "target": "$SNAP/data-dir/sounds", + "default-provider": "gtk-common-themes", + }, + platform_kf6_snap: { + "content": content_kf6_snap, + "interface": "content", + "default-provider": platform_kf6_snap, + "target": "$SNAP/kf6", + }, + }, + "environment": {"SNAP_DESKTOP_RUNTIME": "$SNAP/kf6"}, + "hooks": { + "configure": { + "plugs": ["desktop"], + "command-chain": ["snap/command-chain/hooks-configure-desktop"], + } + }, + "layout": {"/usr/share/X11": {"symlink": "$SNAP/kf6/usr/share/X11"}}, + } + + @overrides + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: + qt6_sdk_snap = self.kde_snaps.qt6_sdk_snap + kf6_sdk_snap = self.kde_snaps.kf6_sdk_snap + + return { + "build-environment": [ + { + "PATH": prepend_to_env( + "PATH", + [ + f"/snap/{qt6_sdk_snap}/current/usr/bin", + f"/snap/{kf6_sdk_snap}/current/usr/bin", + ], + ), + }, + { + "XDG_DATA_DIRS": prepend_to_env( + "XDG_DATA_DIRS", + [ + "$CRAFT_STAGE/usr/share", + f"/snap/{qt6_sdk_snap}/current/usr/share", + f"/snap/{kf6_sdk_snap}/current/usr/share", + "/usr/share", + ], + ), + }, + { + "XDG_CONFIG_HOME": prepend_to_env( + "XDG_CONFIG_HOME", + [ + "$CRAFT_STAGE/etc/xdg", + f"/snap/{qt6_sdk_snap}/current/etc/xdg", + f"/snap/{kf6_sdk_snap}/current/etc/xdg", + "/etc/xdg", + ], + ), + }, + { + "LD_LIBRARY_PATH": prepend_to_env( + "LD_LIBRARY_PATH", + [ + f"/snap/{qt6_sdk_snap}/current/usr/lib/" + f"${{CRAFT_ARCH_TRIPLET_BUILD_FOR}}", + f"/snap/{kf6_sdk_snap}/current/usr/lib/" + f"${{CRAFT_ARCH_TRIPLET_BUILD_FOR}}", + f"/snap/{qt6_sdk_snap}/current/usr/lib", + f"/snap/{kf6_sdk_snap}/current/usr/lib", + "$CRAFT_STAGE/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}", + "$CRAFT_STAGE/usr/lib", + "$CRAFT_STAGE/lib/", + ], + ), + }, + { + "CMAKE_PREFIX_PATH": prepend_to_env( + "CMAKE_PREFIX_PATH", + [ + "$CRAFT_STAGE", + f"/snap/{qt6_sdk_snap}/current", + f"/snap/{kf6_sdk_snap}/current", + "/usr", + ], + separator=";", + ), + }, + { + "CMAKE_FIND_ROOT_PATH": prepend_to_env( + "CMAKE_FIND_ROOT_PATH", + [ + "$CRAFT_STAGE", + f"/snap/{qt6_sdk_snap}/current", + f"/snap/{kf6_sdk_snap}/current", + "/usr", + ], + separator=";", + ), + }, + ], + } + + @overrides + def get_parts_snippet(self) -> Dict[str, Any]: + # We can change this to the lightweight command-chain when + # the content snap includes the desktop-launch from + # https://github.com/snapcore/snapcraft-desktop-integration + source = get_extensions_data_dir() / "desktop" / "kde-neon-6" + + if self.kde_snaps.kf6_builtin: + return { + "kde-neon-6/sdk": { + "source": str(source), + "plugin": "make", + "make-parameters": [f"PLATFORM_PLUG={self.kde_snaps.content_kf6}"], + "build-snaps": [ + self.kde_snaps.qt6_sdk_snap, + self.kde_snaps.kf6_sdk_snap, + ], + "build-packages": [ + "gettext", + "doxygen", + "graphviz", + "libxml2-utils", + "docbook-xml", + "docbook-xsl", + "libglx-dev", + "libgl-dev", + "libglvnd-dev", + ], + }, + } + + return { + "kde-neon-6/sdk": { + "source": str(source), + "plugin": "make", + "make-parameters": [f"PLATFORM_PLUG={self.kde_snaps.content_kf6}"], + }, + } diff --git a/snapcraft/extensions/registry.py b/snapcraft/extensions/registry.py index ae5d69f463..a641105c1e 100644 --- a/snapcraft/extensions/registry.py +++ b/snapcraft/extensions/registry.py @@ -22,6 +22,7 @@ from .gnome import GNOME from .kde_neon import KDENeon +from .kde_neon_6 import KDENeon6 from .ros2_humble import ROS2HumbleExtension from .ros2_humble_desktop import ROS2HumbleDesktopExtension from .ros2_humble_ros_base import ROS2HumbleRosBaseExtension @@ -39,6 +40,7 @@ "ros2-humble-ros-base": ROS2HumbleRosBaseExtension, "ros2-humble-desktop": ROS2HumbleDesktopExtension, "kde-neon": KDENeon, + "kde-neon-6": KDENeon6, } diff --git a/tests/spread/extensions/kde-neon-6/task.yaml b/tests/spread/extensions/kde-neon-6/task.yaml new file mode 100644 index 0000000000..812927981d --- /dev/null +++ b/tests/spread/extensions/kde-neon-6/task.yaml @@ -0,0 +1,52 @@ +summary: Build and run a basic kde snap using extensions + +# The content snap required for the test to succeed is only +# available on a subset of all the architectures this testbed +# can run on. +systems: + - ubuntu-22.04 + - ubuntu-22.04-64 + - ubuntu-22.04-amd64 + +environment: + SNAP_DIR: ../snaps/neon-hello-6 + +prepare: | + #shellcheck source=tests/spread/tools/snapcraft-yaml.sh + . "$TOOLS_DIR/snapcraft-yaml.sh" + set_base "$SNAP_DIR/snap/snapcraft.yaml" + +restore: | + cd "$SNAP_DIR" + snapcraft clean + rm -f ./*.snap + + #shellcheck source=tests/spread/tools/snapcraft-yaml.sh + . "$TOOLS_DIR/snapcraft-yaml.sh" + restore_yaml "snap/snapcraft.yaml" + +execute: | + cd "$SNAP_DIR" + output="$(snapcraft)" + snap install neon-hello-6_*.snap --dangerous + snap connect neon-hello-6:kf6-core22 kf6-core22:kf6-core22 + + [ "$(neon-hello-6)" = "hello world" ] + + # Verify that the extension command chain went through the proper setup procedure + snap_user_data="$HOME/snap/neon-hello-6/current" + [ -d "$snap_user_data/.config" ] + [ -d "$snap_user_data/.local" ] + [ -f "$snap_user_data/.last_revision" ] + [ "$(cat "$snap_user_data/.last_revision")" = "SNAP_DESKTOP_LAST_REVISION=x1" ] + + + # Verify content snap was installed for dependency checks. + snap list gtk-common-themes + snap list kf6-core22 + + # Verify all dependencies were found. + if echo "$output" | grep -q "part is missing libraries"; then + echo "failed to find content snaps' libraries" + exit 1 + fi diff --git a/tests/spread/extensions/snaps/neon-hello-6/CMakeLists.txt b/tests/spread/extensions/snaps/neon-hello-6/CMakeLists.txt new file mode 100644 index 0000000000..cef7cacf85 --- /dev/null +++ b/tests/spread/extensions/snaps/neon-hello-6/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.6) +project(hello) + +set (QT_MIN_VERSION "6.6.0") +find_package(Qt6 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core) + +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + +add_executable(hello hello.cpp) +target_include_directories(hello PRIVATE ${Qt6Core_INCLUDE_DIRS}) +target_link_libraries(hello PRIVATE Qt6::Core) + +install(TARGETS hello RUNTIME DESTINATION bin) diff --git a/tests/spread/extensions/snaps/neon-hello-6/hello.cpp b/tests/spread/extensions/snaps/neon-hello-6/hello.cpp new file mode 100644 index 0000000000..7011be0c9a --- /dev/null +++ b/tests/spread/extensions/snaps/neon-hello-6/hello.cpp @@ -0,0 +1,8 @@ +#include +#include + +int main() +{ + QString s("hello world"); + std::cout << s.toUtf8().constData() << std::endl; +} diff --git a/tests/spread/extensions/snaps/neon-hello-6/snap/snapcraft.yaml b/tests/spread/extensions/snaps/neon-hello-6/snap/snapcraft.yaml new file mode 100644 index 0000000000..da6e7e5681 --- /dev/null +++ b/tests/spread/extensions/snaps/neon-hello-6/snap/snapcraft.yaml @@ -0,0 +1,17 @@ +name: neon-hello-6 +version: "1.0" +summary: Test the kde-neon extension +description: It simply prints a hello world + +grade: devel +confinement: strict + +apps: + neon-hello-6: + command: usr/local/bin/hello + extensions: [kde-neon-6] + +parts: + hello: + plugin: cmake + source: . diff --git a/tests/unit/commands/test_list_extensions.py b/tests/unit/commands/test_list_extensions.py index f5460c5aa7..be0a03fe17 100644 --- a/tests/unit/commands/test_list_extensions.py +++ b/tests/unit/commands/test_list_extensions.py @@ -49,6 +49,7 @@ def test_command(emitter, command): gnome-3-34 core18 gnome-3-38 core20 kde-neon core18, core20, core22 + kde-neon-6 core22 ros1-noetic core20 ros1-noetic-desktop core20 ros1-noetic-perception core20 @@ -93,6 +94,7 @@ def test_command_extension_dups(emitter, command): gnome-3-34 core18 gnome-3-38 core20 kde-neon core18, core20, core22 + kde-neon-6 core22 ros1-noetic core20 ros1-noetic-desktop core20 ros1-noetic-perception core20 diff --git a/tests/unit/extensions/test_kde_neon_6.py b/tests/unit/extensions/test_kde_neon_6.py new file mode 100644 index 0000000000..e4f639c1bc --- /dev/null +++ b/tests/unit/extensions/test_kde_neon_6.py @@ -0,0 +1,357 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2022 Canonical Ltd. +# Copyright 2023-2024 Scarlett Moore +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import pytest + +from snapcraft.extensions import kde_neon_6 +from snapcraft.extensions.extension import get_extensions_data_dir + +############ +# Fixtures # +############ + + +@pytest.fixture +def kde_neon_6_extension(): + return kde_neon_6.KDENeon6( + yaml_data={"base": "core22", "parts": {}}, arch="amd64", target_arch="amd64" + ) + + +@pytest.fixture +def kde_neon_6_extension_with_build_snap(): + return kde_neon_6.KDENeon6( + yaml_data={ + "base": "core22", + "parts": { + "part1": { + "build-snaps": [ + "kde-qt6-core22-sdk/latest/stable", + "kf6-core22-sdk/latest/stable", + ] + } + }, + }, + arch="amd64", + target_arch="amd64", + ) + + +@pytest.fixture +def kde_neon_6_extension_with_default_build_snap_from_latest_edge(): + return kde_neon_6.KDENeon6( + yaml_data={ + "base": "core22", + "parts": { + "part1": { + "build-snaps": [ + "kde-qt6-core22-sdk/latest/edge", + "kf6-core22-sdk/latest/edge", + ] + } + }, + }, + arch="amd64", + target_arch="amd64", + ) + + +################### +# KDENeon6 Extension # +################### + + +def test_get_supported_bases(kde_neon_6_extension): + assert kde_neon_6_extension.get_supported_bases() == ("core22",) + + +def test_get_supported_confinement(kde_neon_6_extension): + assert kde_neon_6_extension.get_supported_confinement() == ("strict", "devmode") + + +def test_is_experimental(): + assert kde_neon_6.KDENeon6.is_experimental(base="core22") is False + + +def test_get_app_snippet(kde_neon_6_extension): + assert kde_neon_6_extension.get_app_snippet() == { + "command-chain": ["snap/command-chain/desktop-launch6"], + "plugs": [ + "desktop", + "desktop-legacy", + "opengl", + "wayland", + "x11", + "audio-playback", + "unity7", + "network", + "network-bind", + ], + } + + +def test_get_root_snippet(kde_neon_6_extension): + assert kde_neon_6_extension.get_root_snippet() == { + "assumes": ["snapd2.58.3"], + "compression": "lzo", + "environment": {"SNAP_DESKTOP_RUNTIME": "$SNAP/kf6"}, + "hooks": { + "configure": { + "plugs": ["desktop"], + "command-chain": ["snap/command-chain/hooks-configure-desktop"], + } + }, + "layout": {"/usr/share/X11": {"symlink": "$SNAP/kf6/usr/share/X11"}}, + "plugs": { + "desktop": {"mount-host-font-cache": False}, + "icon-themes": { + "interface": "content", + "target": "$SNAP/data-dir/icons", + "default-provider": "gtk-common-themes", + }, + "sound-themes": { + "interface": "content", + "target": "$SNAP/data-dir/sounds", + "default-provider": "gtk-common-themes", + }, + "kf6-core22": { + "content": "kf6-core22-all", + "interface": "content", + "default-provider": "kf6-core22", + "target": "$SNAP/kf6", + }, + }, + } + + +def test_get_root_snippet_with_external_sdk(kde_neon_6_extension_with_build_snap): + assert kde_neon_6_extension_with_build_snap.get_root_snippet() == { + "assumes": ["snapd2.58.3"], + "compression": "lzo", + "environment": {"SNAP_DESKTOP_RUNTIME": "$SNAP/kf6"}, + "hooks": { + "configure": { + "plugs": ["desktop"], + "command-chain": ["snap/command-chain/hooks-configure-desktop"], + } + }, + "layout": {"/usr/share/X11": {"symlink": "$SNAP/kf6/usr/share/X11"}}, + "plugs": { + "desktop": {"mount-host-font-cache": False}, + "icon-themes": { + "interface": "content", + "target": "$SNAP/data-dir/icons", + "default-provider": "gtk-common-themes", + }, + "sound-themes": { + "interface": "content", + "target": "$SNAP/data-dir/sounds", + "default-provider": "gtk-common-themes", + }, + "kf6-core22": { + "content": "kf6-core22-all", + "interface": "content", + "default-provider": "kf6-core22", + "target": "$SNAP/kf6", + }, + }, + } + + +class TestGetPartSnippet: + """Tests for KDENeon6.get_part_snippet when using the default sdk snap name.""" + + def test_get_part_snippet(self, kde_neon_6_extension): + self.assert_get_part_snippet(kde_neon_6_extension) + + def test_get_part_snippet_latest_edge( + self, kde_neon_6_extension_with_default_build_snap_from_latest_edge + ): + self.assert_get_part_snippet( + kde_neon_6_extension_with_default_build_snap_from_latest_edge + ) + + @staticmethod + def assert_get_part_snippet(kde_neon_6_instance): + assert kde_neon_6_instance.get_part_snippet(plugin_name="cmake") == { + "build-environment": [ + { + "PATH": ( + "/snap/kde-qt6-core22-sdk/current/usr/bin:" + "/snap/kf6-core22-sdk/current/usr/bin" + "${PATH:+:$PATH}" + ) + }, + { + "XDG_DATA_DIRS": ( + "$CRAFT_STAGE/usr/share:" + "/snap/kde-qt6-core22-sdk/current/usr/share:" + "/snap/kf6-core22-sdk/current/usr/share:" + "/usr/share${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}" + ) + }, + { + "XDG_CONFIG_HOME": ( + "$CRAFT_STAGE/etc/xdg:" + "/snap/kde-qt6-core22-sdk/current/etc/xdg:" + "/snap/kf6-core22-sdk/current/etc/xdg:" + "/etc/xdg${XDG_CONFIG_HOME:+:$XDG_CONFIG_HOME}" + ) + }, + { + "LD_LIBRARY_PATH": ( + "/snap/kde-qt6-core22-sdk/current/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:" + "/snap/kf6-core22-sdk/current/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:" + "/snap/kde-qt6-core22-sdk/current/usr/lib:" + "/snap/kf6-core22-sdk/current/usr/lib:" + "$CRAFT_STAGE/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:" + "$CRAFT_STAGE/usr/lib:" + "$CRAFT_STAGE/lib/" + "${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + ) + }, + { + "CMAKE_PREFIX_PATH": ( + "$CRAFT_STAGE;" + "/snap/kde-qt6-core22-sdk/current;" + "/snap/kf6-core22-sdk/current;" + "/usr" + "${CMAKE_PREFIX_PATH:+;$CMAKE_PREFIX_PATH}" + ) + }, + { + "CMAKE_FIND_ROOT_PATH": ( + "$CRAFT_STAGE;" + "/snap/kde-qt6-core22-sdk/current;" + "/snap/kf6-core22-sdk/current;" + "/usr" + "${CMAKE_FIND_ROOT_PATH:+;$CMAKE_FIND_ROOT_PATH}" + ) + }, + ] + } + + +def test_get_part_snippet_with_external_sdk(kde_neon_6_extension_with_build_snap): + assert kde_neon_6_extension_with_build_snap.get_part_snippet( + plugin_name="cmake" + ) == { + "build-environment": [ + { + "PATH": ( + "/snap/kde-qt6-core22-sdk/current/usr/bin:" + "/snap/kf6-core22-sdk/current/usr/bin" + "${PATH:+:$PATH}" + ) + }, + { + "XDG_DATA_DIRS": ( + "$CRAFT_STAGE/usr/share:" + "/snap/kde-qt6-core22-sdk/current/usr/share:" + "/snap/kf6-core22-sdk/current/usr/share:" + "/usr/share${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}" + ) + }, + { + "XDG_CONFIG_HOME": ( + "$CRAFT_STAGE/etc/xdg:" + "/snap/kde-qt6-core22-sdk/current/etc/xdg:" + "/snap/kf6-core22-sdk/current/etc/xdg:" + "/etc/xdg${XDG_CONFIG_HOME:+:$XDG_CONFIG_HOME}" + ) + }, + { + "LD_LIBRARY_PATH": ( + "/snap/kde-qt6-core22-sdk/current/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:" + "/snap/kf6-core22-sdk/current/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:" + "/snap/kde-qt6-core22-sdk/current/usr/lib:" + "/snap/kf6-core22-sdk/current/usr/lib:" + "$CRAFT_STAGE/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:" + "$CRAFT_STAGE/usr/lib:" + "$CRAFT_STAGE/lib/" + "${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + ), + }, + { + "CMAKE_PREFIX_PATH": ( + "$CRAFT_STAGE;" + "/snap/kde-qt6-core22-sdk/current;" + "/snap/kf6-core22-sdk/current;" + "/usr" + "${CMAKE_PREFIX_PATH:+;$CMAKE_PREFIX_PATH}" + ) + }, + { + "CMAKE_FIND_ROOT_PATH": ( + "$CRAFT_STAGE;" + "/snap/kde-qt6-core22-sdk/current;" + "/snap/kf6-core22-sdk/current;" + "/usr" + "${CMAKE_FIND_ROOT_PATH:+;$CMAKE_FIND_ROOT_PATH}" + ) + }, + ] + } + + +def test_get_parts_snippet(kde_neon_6_extension): + source = get_extensions_data_dir() / "desktop" / "kde-neon-6" + + assert kde_neon_6_extension.get_parts_snippet() == { + "kde-neon-6/sdk": { + "source": str(source), + "plugin": "make", + "make-parameters": ["PLATFORM_PLUG=kf6-core22"], + "build-snaps": ["kde-qt6-core22-sdk", "kf6-core22-sdk"], + "build-packages": [ + "gettext", + "doxygen", + "graphviz", + "libxml2-utils", + "docbook-xml", + "docbook-xsl", + "libglx-dev", + "libgl-dev", + "libglvnd-dev", + ], + } + } + + +def test_get_parts_snippet_with_external_sdk(kde_neon_6_extension_with_build_snap): + source = get_extensions_data_dir() / "desktop" / "kde-neon-6" + + assert kde_neon_6_extension_with_build_snap.get_parts_snippet() == { + "kde-neon-6/sdk": { + "source": str(source), + "plugin": "make", + "make-parameters": ["PLATFORM_PLUG=kf6-core22"], + } + } + + +def test_get_parts_snippet_with_external_sdk_different_channel( + kde_neon_6_extension_with_default_build_snap_from_latest_edge, +): + source = get_extensions_data_dir() / "desktop" / "kde-neon-6" + assert kde_neon_6_extension_with_default_build_snap_from_latest_edge.get_parts_snippet() == { + "kde-neon-6/sdk": { + "source": str(source), + "plugin": "make", + "make-parameters": ["PLATFORM_PLUG=kf6-core22"], + } + } diff --git a/tests/unit/extensions/test_registry.py b/tests/unit/extensions/test_registry.py index 1e3c5385db..1176aeb019 100644 --- a/tests/unit/extensions/test_registry.py +++ b/tests/unit/extensions/test_registry.py @@ -30,6 +30,7 @@ def test_get_extension_names(): "ros2-humble-ros-base", "ros2-humble-desktop", "kde-neon", + "kde-neon-6", "fake-extension-experimental", "fake-extension-extra", "fake-extension",