Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QT extension #4294

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extensions/desktop/command-chain/desktop-launch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash

set -- "${SNAP}/gnome-platform/command-chain/desktop-launch" "$@"
set -- "${SNAP_DESKTOP_RUNTIME}/command-chain/desktop-launch" "$@"
# shellcheck source=/dev/null
source "${SNAP}/snap/command-chain/run"
2 changes: 1 addition & 1 deletion extensions/desktop/command-chain/hooks-configure-fonts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash

set -- "${SNAP}/gnome-platform/command-chain/hooks-configure-fonts" "$@"
set -- "${SNAP_DESKTOP_RUNTIME}/command-chain/hooks-configure-desktop" "$@"
# shellcheck source=/dev/null
source "${SNAP}/snap/command-chain/run"
1 change: 1 addition & 0 deletions schema/snapcraft.json
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,7 @@
"gnome-3-38",
"kde-neon",
"kde-neon-6",
"qt6-8",
"ros1-noetic",
"ros1-noetic-desktop",
"ros1-noetic-perception",
Expand Down
5 changes: 4 additions & 1 deletion snapcraft/extensions/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ def apply_extensions(
for extension_name in sorted(declared_extensions.keys()):
extension_class = get_extension_class(extension_name)
extension = extension_class(
yaml_data=copy.deepcopy(yaml_data), arch=arch, target_arch=target_arch
name=extension_name,
yaml_data=copy.deepcopy(yaml_data),
arch=arch,
target_arch=target_arch,
)
extension.validate(extension_name=extension_name)
_apply_extension(yaml_data, declared_extensions[extension_name], extension)
Expand Down
3 changes: 2 additions & 1 deletion snapcraft/extensions/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ class Extension(abc.ABC):
"""

def __init__(
self, *, yaml_data: Dict[str, Any], arch: str, target_arch: str
self, *, name: str, yaml_data: Dict[str, Any], arch: str, target_arch: str
) -> None:
"""Create a new Extension."""
self.name = name
self.yaml_data = yaml_data
self.arch = arch
self.target_arch = target_arch
Expand Down
227 changes: 227 additions & 0 deletions snapcraft/extensions/qt_framework.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2023 Canonical Ltd.
#
# 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 <http://www.gnu.org/licenses/>.

"""Generic QT Framework extension to support core22 and onwards."""
import dataclasses
import functools
from typing import Any, Dict, List, Optional, Tuple

from overrides import overrides

from .extension import Extension, prepend_to_env

_CONTENT_SNAP = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who owns these snaps?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would be me.

"qt6-8": "qt-framework-6-8",
}


@dataclasses.dataclass
class ExtensionInfo:
"""Content/SDK build information."""

cmake_args: list


@dataclasses.dataclass
class QTSnaps:
"""A structure of QT related snaps."""

sdk: dict
content: str
builtin: bool = True


class QTFramework(Extension):
r"""The QT Framework extension.

This extension makes it easy to assemble QT based applications.

It configures each application with the following plugs:

\b
- Common Icon Themes.
- Common Sound Themes.
- The QT Frameworks 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)
"""

@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, *, app_name: str) -> Dict[str, Any]:
return {
"command-chain": ["snap/command-chain/desktop-launch"],
"plugs": ["desktop", "desktop-legacy", "opengl", "wayland", "x11"],
"environment": {
"QT_PLUGIN_PATH": "$SNAP/qt-framework/usr/plugins:$SNAP/usr/lib/plugins",
},
}

@functools.cached_property
def qt_snaps(self) -> QTSnaps:
"""Return the QT related snaps to use to construct the environment."""
base = self.yaml_data["base"]
sdk_snap = "qt-framework-sdk"
sdk_channel = f"{self.name[2:].replace('-','.')}/stable"

build_snaps: List[str] = []
for part in self.yaml_data["parts"].values():
build_snaps.extend(part.get("build-snaps", []))

builtin = True

for snap in build_snaps:
if sdk_snap == snap.split("/")[0]:
try:
sdk_channel = snap.split("/", 1)[1]
except IndexError:
pass
builtin = False
break

sdk = {"snap": sdk_snap, "channel": sdk_channel}

# The same except the trailing -sd
content = _CONTENT_SNAP[self.name]

return QTSnaps(sdk=sdk, content=content, builtin=builtin)

@functools.cached_property
def ext_info(self) -> ExtensionInfo:
"""Return the extension info cmake_args, provider, content, build_snaps."""
cmake_args = [
f"-DCMAKE_FIND_ROOT_PATH=/snap/{self.qt_snaps.sdk['snap']}/current",
f"-DCMAKE_PREFIX_PATH=/snap/{self.qt_snaps.sdk['snap']}/current/usr",
"-DZLIB_INCLUDE_DIR=/lib/x86_64-linux-gnu",
]

return ExtensionInfo(cmake_args=cmake_args)

@overrides
def get_root_snippet(self) -> Dict[str, Any]:
return {
"assumes": ["snapd2.43"], # 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",
},
self.qt_snaps.content: {
"interface": "content",
"default-provider": self.qt_snaps.content,
"target": "$SNAP/qt-framework",
},
},
"environment": {"SNAP_DESKTOP_RUNTIME": "$SNAP/qt-framework"},
"hooks": {
"configure": {
"plugs": ["desktop"],
"command-chain": ["snap/command-chain/hooks-configure-fonts"],
}
},
"layout": {
"/usr/share/X11": {"symlink": "$SNAP/qt-framework/usr/share/X11"}
},
}

@overrides
def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]:
sdk_snap = self.qt_snaps.sdk["snap"]
cmake_args = self.ext_info.cmake_args

return {
"build-environment": [
{
"PATH": prepend_to_env(
"PATH", [f"/snap/{sdk_snap}/current/usr/bin"]
),
},
{
"XDG_DATA_DIRS": prepend_to_env(
"XDG_DATA_DIRS",
[
f"$CRAFT_STAGE/usr/share:/snap/{sdk_snap}/current/usr/share",
"/usr/share",
],
),
},
{
"SNAPCRAFT_CMAKE_ARGS": prepend_to_env(
"SNAPCRAFT_CMAKE_ARGS", cmake_args
),
},
],
"build-packages": [
"libgl1-mesa-dev",
"libpcre2-16-0",
"libglib2.0-0",
"libdouble-conversion3",
"libb2-1",
],
"stage-packages": [
"libpcre2-16-0",
"libglib2.0-0",
"libdouble-conversion3",
"libb2-1",
],
}

@overrides
def get_parts_snippet(self) -> Dict[str, Any]:
sdk_snap = self.qt_snaps.sdk["snap"]
sdk_channel = self.qt_snaps.sdk["channel"]

if self.qt_snaps.builtin:
return {}

return {
f"{self.name}/sdk": {
"plugin": "nil",
"build-snaps": [
f"{sdk_snap}/{sdk_channel}",
],
},
}
2 changes: 2 additions & 0 deletions snapcraft/extensions/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .gnome import GNOME
from .kde_neon import KDENeon
from .kde_neon_6 import KDENeon6
from .qt_framework import QTFramework
from .ros2_humble import ROS2HumbleExtension
from .ros2_humble_desktop import ROS2HumbleDesktopExtension
from .ros2_humble_ros_base import ROS2HumbleRosBaseExtension
Expand Down Expand Up @@ -51,6 +52,7 @@
"ros2-jazzy-desktop": ROS2JazzyDesktopExtension,
"kde-neon": KDENeon,
"kde-neon-6": KDENeon6,
"qt6-8": QTFramework,
}


Expand Down
51 changes: 51 additions & 0 deletions tests/spread/extensions/qt6-8/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
summary: Build and run a basic qt 6.5 snap using extensions

systems:
- ubuntu-22.04
- ubuntu-22.04-64
- ubuntu-22.04-amd64

environment:
SNAP_DIR: ../snaps/qt6-8-hello

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 qt6-8-hello_*.snap --dangerous

[ "$(qt6-8-hello)" = "hello world" ]

# Verify that the extension command chain went through the proper setup procedure
snap_user_data="$HOME/snap/qt6-8-hello/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
if [[ "$SPREAD_SYSTEM" =~ ubuntu-22.04 ]]; then
snap list qt-framework-6-8-core22
fi


# 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
13 changes: 13 additions & 0 deletions tests/spread/extensions/snaps/qt6-8-hello/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 2.6)
project(hello)

set (QT_MIN_VERSION "6.8.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)
8 changes: 8 additions & 0 deletions tests/spread/extensions/snaps/qt6-8-hello/hello.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <QString>
#include <iostream>

int main()
{
QString s("hello world");
std::cout << s.toUtf8().constData() << std::endl;
}
23 changes: 23 additions & 0 deletions tests/spread/extensions/snaps/qt6-8-hello/snap/snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: qt6-8-hello
version: "1.0"
summary: Test the qt6-8 extension
description: It simply prints a hello world

base: core22
grade: devel
confinement: strict

apps:
qt6-6-hello:
command: usr/local/bin/hello
extensions: [qt6-8]

parts:
hello:
plugin: cmake
source: .
build-packages:
- libpcre2-16-0
- libglib2.0-0
- libdouble-conversion3
- libb2-1
Loading
Loading