diff --git a/schema/snapcraft.json b/schema/snapcraft.json index 9da873f184..f0890b80f3 100644 --- a/schema/snapcraft.json +++ b/schema/snapcraft.json @@ -906,6 +906,7 @@ "uniqueItems": true, "items": { "enum": [ + "env-injector", "flutter-stable", "flutter-beta", "flutter-dev", diff --git a/snapcraft/extensions/_ros2_humble_meta.py b/snapcraft/extensions/_ros2_humble_meta.py index be3d3ecff8..3d23330d71 100644 --- a/snapcraft/extensions/_ros2_humble_meta.py +++ b/snapcraft/extensions/_ros2_humble_meta.py @@ -64,8 +64,8 @@ def get_root_snippet(self) -> Dict[str, Any]: return root_snippet @overrides - def get_app_snippet(self) -> Dict[str, Any]: - app_snippet = super().get_app_snippet() + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + app_snippet = super().get_app_snippet(app_name=app_name) python_paths = app_snippet["environment"]["PYTHONPATH"] new_python_paths = [ f"$SNAP/opt/ros/underlay_ws/opt/ros/{self.ROS_DISTRO}/lib/python3.10/site-packages", diff --git a/snapcraft/extensions/_ros2_jazzy_meta.py b/snapcraft/extensions/_ros2_jazzy_meta.py index 9dcf31ad0d..b230119661 100644 --- a/snapcraft/extensions/_ros2_jazzy_meta.py +++ b/snapcraft/extensions/_ros2_jazzy_meta.py @@ -64,8 +64,8 @@ def get_root_snippet(self) -> Dict[str, Any]: return root_snippet @overrides - def get_app_snippet(self) -> Dict[str, Any]: - app_snippet = super().get_app_snippet() + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + app_snippet = super().get_app_snippet(app_name=app_name) python_paths = app_snippet["environment"]["PYTHONPATH"] new_python_paths = [ f"$SNAP/opt/ros/underlay_ws/opt/ros/{self.ROS_DISTRO}/lib/python3.12/site-packages", diff --git a/snapcraft/extensions/_utils.py b/snapcraft/extensions/_utils.py index 3aa885be35..d382ca44db 100644 --- a/snapcraft/extensions/_utils.py +++ b/snapcraft/extensions/_utils.py @@ -79,8 +79,8 @@ def _apply_extension( ) # Apply the app-specific components of the extension (if any) - app_extension = extension.get_app_snippet() for app_name in app_names: + app_extension = extension.get_app_snippet(app_name=app_name) app_definition = yaml_data["apps"][app_name] for property_name, property_value in app_extension.items(): app_definition[property_name] = _apply_extension_property( diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py new file mode 100644 index 0000000000..32a1b34a36 --- /dev/null +++ b/snapcraft/extensions/env_injector.py @@ -0,0 +1,129 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 . + +"""Extension to automatically set environment variables on snaps.""" + +from typing import Any, Dict, Optional, Tuple + +from overrides import overrides + +from .extension import Extension + + +class EnvInjector(Extension): + """Extension to automatically set environment variables on snaps. + + This extension allows you to transform snap options into environment + variables + + It configures your application to run a command-chain that transforms the + snap options into environment variables automatically. + + - To set global environment variables for all applications **inside** the snap: + + .. code-block:: shell + sudo snap set env.= + + - To set environment variables for a specific application **inside** the snap: + + .. code-block:: shell + sudo snap set apps..env.= + + - To set environment file inside the snap: + + .. code-block:: shell + sudo snap set env-file= + - To set environment file for a specific app: + + .. code-block:: shell + sudo snap set apps..envfile= + + """ + + @staticmethod + @overrides + def get_supported_bases() -> Tuple[str, ...]: + return ("core24",) + + @staticmethod + @overrides + def get_supported_confinement() -> Tuple[str, ...]: + return ("strict", "devmode", "classic") + + @staticmethod + @overrides + def is_experimental(base: Optional[str]) -> bool: + return True + + @overrides + def get_root_snippet(self) -> Dict[str, Any]: + return {} + + @overrides + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + """Return the app snippet to apply.""" + return { + "command-chain": ["bin/command-chain/env-exporter"], + "environment": { + "env_alias": f"{app_name}", + }, + } + + @overrides + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: + return {} + + @overrides + def get_parts_snippet(self) -> Dict[str, Any]: + toolchain = self.get_toolchain(self.arch) + if toolchain is None: + raise ValueError( + f"Unsupported architecture for env-injector extension: {self.arch}" + ) + + return { + "env-injector/env-injector": { + "source": "https://github.com/canonical/snappy-env.git", + "source-tag": "v1.0.0-beta", + "plugin": "nil", + "build-snaps": ["rustup"], + "build-packages": ["upx-ucl"], # for binary compression + "override-build": f""" + rustup default stable + rustup target add {toolchain} + + cargo build --target {toolchain} --release + mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain + + # compress the binary + upx --best --lzma target/{toolchain}/release/env-exporter + + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain + """, + } + } + + def get_toolchain(self, arch: str): + """Get the Rust toolchain for the current architecture.""" + toolchain = { + "amd64": "x86_64-unknown-linux-gnu", + "arm64": "aarch64-unknown-linux-gnu", + # 'armhf': 'armv8-unknown-linux-gnueabihf', # Tier 2 toolchain + # 'riscv64': 'riscv64gc-unknown-linux-gnu', # Tier 2 toolchain + # 'ppc64el': 'powerpc64-unknown-linux-gnu', # Tier 2 toolchain + # 's390x': 's390x-unknown-linux-gnu', # Tier 2 toolchain + } + return toolchain.get(arch) diff --git a/snapcraft/extensions/extension.py b/snapcraft/extensions/extension.py index 52cdfd902e..bf3c4bb04d 100644 --- a/snapcraft/extensions/extension.py +++ b/snapcraft/extensions/extension.py @@ -66,8 +66,11 @@ def get_root_snippet(self) -> Dict[str, Any]: """Return the root snippet to apply.""" @abc.abstractmethod - def get_app_snippet(self) -> Dict[str, Any]: - """Return the app snippet to apply.""" + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + """Return the app snippet to apply. + + :param app_name: the name of the app where the snippet will be applied + """ @abc.abstractmethod def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: diff --git a/snapcraft/extensions/gnome.py b/snapcraft/extensions/gnome.py index 30e7eeeb23..0f0c3be7ab 100644 --- a/snapcraft/extensions/gnome.py +++ b/snapcraft/extensions/gnome.py @@ -85,7 +85,7 @@ def is_experimental(base: Optional[str]) -> bool: return False @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: command_chain = ["snap/command-chain/desktop-launch"] if self.yaml_data["base"] == "core24": command_chain.insert(0, "snap/command-chain/gpu-2404-wrapper") diff --git a/snapcraft/extensions/kde_neon.py b/snapcraft/extensions/kde_neon.py index 587418b055..e4f0f7b534 100644 --- a/snapcraft/extensions/kde_neon.py +++ b/snapcraft/extensions/kde_neon.py @@ -89,7 +89,7 @@ def is_experimental(base: Optional[str]) -> bool: return False @overrides - def get_app_snippet(self) -> Dict[str, Any]: + 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"], diff --git a/snapcraft/extensions/kde_neon_6.py b/snapcraft/extensions/kde_neon_6.py index ab4a362774..765b981762 100644 --- a/snapcraft/extensions/kde_neon_6.py +++ b/snapcraft/extensions/kde_neon_6.py @@ -86,7 +86,7 @@ def is_experimental(base: Optional[str]) -> bool: return False @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return { "command-chain": ["snap/command-chain/desktop-launch6"], "plugs": [ diff --git a/snapcraft/extensions/registry.py b/snapcraft/extensions/registry.py index 42c60af1d6..e3ed00d23d 100644 --- a/snapcraft/extensions/registry.py +++ b/snapcraft/extensions/registry.py @@ -20,6 +20,7 @@ from snapcraft import errors +from .env_injector import EnvInjector from .gnome import GNOME from .kde_neon import KDENeon from .kde_neon_6 import KDENeon6 @@ -38,6 +39,7 @@ ExtensionType = Type[Extension] _EXTENSIONS: Dict[str, "ExtensionType"] = { + "env-injector": EnvInjector, "gnome": GNOME, "ros2-humble": ROS2HumbleExtension, "ros2-humble-ros-core": ROS2HumbleRosCoreExtension, diff --git a/snapcraft/extensions/ros2_humble.py b/snapcraft/extensions/ros2_humble.py index 56f33f56dc..5467c3d884 100644 --- a/snapcraft/extensions/ros2_humble.py +++ b/snapcraft/extensions/ros2_humble.py @@ -87,7 +87,7 @@ def get_root_snippet(self) -> Dict[str, Any]: } @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: python_paths = [ f"$SNAP/opt/ros/{self.ROS_DISTRO}/lib/python3.10/site-packages", "$SNAP/usr/lib/python3/dist-packages", diff --git a/snapcraft/extensions/ros2_jazzy.py b/snapcraft/extensions/ros2_jazzy.py index 635d23fd2a..ae17efd5ba 100644 --- a/snapcraft/extensions/ros2_jazzy.py +++ b/snapcraft/extensions/ros2_jazzy.py @@ -85,7 +85,7 @@ def get_root_snippet(self) -> Dict[str, Any]: } @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: python_paths = [ f"$SNAP/opt/ros/{self.ROS_DISTRO}/lib/python3.12/site-packages", "$SNAP/usr/lib/python3/dist-packages", diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml new file mode 100644 index 0000000000..69731ef048 --- /dev/null +++ b/tests/spread/extensions/env-injector/task.yaml @@ -0,0 +1,127 @@ +summary: Build and run a basic hello-world snap using extensions + +systems: + - ubuntu-24.04* + +environment: + + SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1" + SNAP_DIR: ../snaps/env-injector-hello + SNAP: env-injector-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 /var/snap/"${SNAP}"/common/*.env + rm -f ./*.snap + rm -rf ./squashfs-root + #shellcheck source=tests/spread/tools/snapcraft-yaml.sh + . "$TOOLS_DIR/snapcraft-yaml.sh" + restore_yaml "snap/snapcraft.yaml" + +execute: | + + assert_env() { + local snap_app="$1" + local env_name="$2" + local exp_value="$3" + + local actual_value + + if ! eval "$snap_app" | grep -q "^${env_name}="; then + echo "Environment variable '$env_name' is not set." + return 1 + fi + + if [ -z "$env_name" ]; then + empty=$( "$snap_app" | grep "=${exp_value}") + [ -z "$empty" ] || return 1 + fi + + actual_value=$( "$snap_app" | grep "^${env_name}=" | cut -d'=' -f2-) + + if [ "$actual_value" != "$exp_value" ]; then + echo "Environment variable '$env_name' does not match the expected value." + echo "Expected: '$env_name=$exp_value', but got: '$env_name=$actual_value'" + return 1 + fi + + return 0 + } + + cd "$SNAP_DIR" + SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=1 snapcraft + + unsquashfs "${SNAP}"_1.0_*.snap + + # Check that the env-exporter program is present + [ -f squashfs-root/bin/command-chain/env-exporter ] + + # Check that the exec-env script is present + [ -f squashfs-root/usr/bin/exec-env ] + + snap install "${SNAP}"_1.0_*.snap --dangerous + + echo "[env-injector] Creating global envfile" + echo 'HELLO_WORLD="Hello World"' >> /var/snap/"${SNAP}"/common/global.env + + # Load global envfile + snap set env-injector-hello envfile=/var/snap/"${SNAP}"/common/global.env + echo "[TEST] - Check if the global envfile is loaded for all apps" + assert_env "env-injector-hello.hello1" "HELLO_WORLD" "Hello World" || exit 1 + assert_env "env-injector-hello.hello2" "HELLO_WORLD" "Hello World" || exit 1 + assert_env "env-injector-hello.hello-demo" "HELLO_WORLD" "Hello World" || exit 1 + + echo "[env-injector] Creating app-specific envfile" + echo 'SCOPED=Scoped' >> /var/snap/"${SNAP}"/common/appenv.env + + # Load app-specific envfile + snap set env-injector-hello apps.hello1.envfile=/var/snap/"${SNAP}"/common/appenv.env + echo "[TEST] - Check if the app-specific envfile is loaded for the app" + assert_env "env-injector-hello.hello1" "SCOPED" "Scoped" || exit 1 + + echo "[env-injector] Setting global env variable" + # Set env vars: Global + snap set env-injector-hello env.global="World" + echo "[TEST] - Check if the global env var is set for all apps" + assert_env "env-injector-hello.hello1" "GLOBAL" "World" || exit 1 + assert_env "env-injector-hello.hello2" "GLOBAL" "World" || exit 1 + assert_env "env-injector-hello.hello-demo" "GLOBAL" "World" || exit 1 + + echo "[env-injector] Setting app-specific env variable" + # Set env vars: specific to each app + snap set env-injector-hello apps.hello1.env.hello="Hello" + snap set env-injector-hello apps.hello2.env.specific="City" + + echo "[TEST] - Check if the app-specific env var IS SET for the app hello1" + assert_env "env-injector-hello.hello1" "HELLO" "Hello" || exit 1 + echo "[TEST] - Check if the app-specific env var IS NOT SET for the app hello2" + ! assert_env "env-injector-hello.hello2" "HELLO" "Hello" || exit 1 + + echo "[TEST] - Check if the app-specific env var IS SET for the app hello2" + assert_env "env-injector-hello.hello2" "SPECIFIC" "City" || exit 1 + echo "[TEST] - Check if the app-specific env var IS NOT SET for the app hello1" + ! assert_env "env-injector-hello.hello1" "SPECIFIC" "City" || exit 1 + + snap set env-injector-hello env.word.dot="wrong" + echo "[TEST] - Check if the key with dot was ignored" + ! assert_env "env-injector-hello.hello1" "" "wrong" || exit 1 + + echo "[env-injector] Testing order of env vars" + echo 'ORDER="From envfile"' >> /var/snap/"${SNAP}"/common/local.env + snap set env-injector-hello apps.hello1.env.order="from app-specific" + snap set env-injector-hello apps.hello1.envfile=/var/snap/"${SNAP}"/common/local.env + echo "[TEST] - Check if local overrites global" + assert_env "env-injector-hello.hello1" "ORDER" "from app-specific" || exit 1 + + echo "[env-injector] Run hello-demo app" + snap set env-injector-hello apps.myapp.env.specific="City" + echo "[TEST] Make sure that alias is NOT rewritten" + assert_env "env-injector-hello.hello-demo" "SPECIFIC" "City" || exit 1 diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure b/tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure new file mode 100755 index 0000000000..a9bf588e2f --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure @@ -0,0 +1 @@ +#!/bin/bash diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml new file mode 100644 index 0000000000..fc925f609f --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml @@ -0,0 +1,30 @@ +name: env-injector-hello +version: "1.0" +summary: test the env-injector extension +description: This is a basic snap for testing env-injector extension + +grade: devel +base: core24 +confinement: strict + +apps: + hello1: + command: usr/bin/exec-env + extensions: [ env-injector ] + + hello2: + command: usr/bin/exec-env + extensions: [ env-injector ] + + hello-demo: + command: usr/bin/exec-env + environment: + # user-defined alias + env_alias: myapp + extensions: [ env-injector ] + +parts: + env: + plugin: dump + source: . + diff --git a/tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env b/tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env new file mode 100755 index 0000000000..b802721fcb --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env @@ -0,0 +1,3 @@ +#!/usr/bin/bash + +env diff --git a/tests/unit/commands/test_list_extensions.py b/tests/unit/commands/test_list_extensions.py index 1900e1a4d7..07bc97c1ec 100644 --- a/tests/unit/commands/test_list_extensions.py +++ b/tests/unit/commands/test_list_extensions.py @@ -38,6 +38,7 @@ def test_command(emitter, command): """\ Extension name Supported bases ---------------------- ---------------------- + env-injector core24 fake-extension core22, core24 flutter-beta core18 flutter-dev core18 @@ -87,6 +88,7 @@ def test_command_extension_dups(emitter, command): """\ Extension name Supported bases ---------------------- ---------------------- + env-injector core24 flutter-beta core18 flutter-dev core18 flutter-master core18 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 285cddb152..6f1b79b3cf 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -97,7 +97,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {"grade": "fake-grade"} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -136,7 +136,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug", "fake-plug-extra"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -170,7 +170,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {"grade": "fake-grade"} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -206,7 +206,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -242,7 +242,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug", "fake-plug-extra"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py new file mode 100644 index 0000000000..6f4faeba89 --- /dev/null +++ b/tests/unit/extensions/test_env_injector.py @@ -0,0 +1,116 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 . + +import pytest + +from snapcraft.extensions import env_injector + +############ +# Fixtures # +############ + + +@pytest.fixture +def envinjector_extension(): + return env_injector.EnvInjector( + yaml_data={"base": "core24", "parts": {}}, arch="amd64", target_arch="amd64" + ) + + +######################### +# EnvInjector Extension # +######################### + + +def test_get_supported_bases(envinjector_extension): + assert envinjector_extension.get_supported_bases() == ("core24",) + + +def test_get_supported_confinement(envinjector_extension): + assert envinjector_extension.get_supported_confinement() == ( + "strict", + "devmode", + "classic", + ) + + +def test_is_experimental(): + assert env_injector.EnvInjector.is_experimental(base="core24") is True + + +def test_get_root_snippet(envinjector_extension): + assert envinjector_extension.get_root_snippet() == {} + + +def test_get_app_snippet(envinjector_extension): + assert envinjector_extension.get_app_snippet(app_name="test") == { + "command-chain": ["bin/command-chain/env-exporter"], + "environment": { + "env_alias": "test", + }, + } + + +def test_get_toolchain_amd64(envinjector_extension): + assert envinjector_extension.get_toolchain("amd64") == "x86_64-unknown-linux-gnu" + + +def test_get_toolchain_arm64(envinjector_extension): + assert envinjector_extension.get_toolchain("arm64") == "aarch64-unknown-linux-gnu" + + +class TestGetPartSnippet: + """Tests for EnvInjector.get_part_snippet when using the default sdk snap name.""" + + def test_get_part_snippet(self, envinjector_extension): + self.assert_get_part_snippet(envinjector_extension) + + @staticmethod + def assert_get_part_snippet(envinjector_extension): + assert envinjector_extension.get_part_snippet(plugin_name="nil") == {} + + @pytest.mark.parametrize( + "unsupported_arch", ["armhf", "riscv64", "ppc64el", "s390x"] + ) + def test_get_parts_snippet(self, envinjector_extension, unsupported_arch): + toolchain = "x86_64-unknown-linux-gnu" + assert envinjector_extension.get_parts_snippet() == { + "env-injector/env-injector": { + "source": "https://github.com/canonical/snappy-env.git", + "source-tag": "v1.0.0-beta", + "plugin": "nil", + "build-snaps": ["rustup"], + "build-packages": ["upx-ucl"], # for binary compression + "override-build": f""" + rustup default stable + rustup target add {toolchain} + + cargo build --target {toolchain} --release + mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain + + # compress the binary + upx --best --lzma target/{toolchain}/release/env-exporter + + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain + """, + } + } + + envinjector_extension.arch = unsupported_arch + with pytest.raises( + ValueError, match="Unsupported architecture for env-injector extension" + ): + envinjector_extension.get_parts_snippet() diff --git a/tests/unit/extensions/test_gnome.py b/tests/unit/extensions/test_gnome.py index 7276dbb402..217194a2ce 100644 --- a/tests/unit/extensions/test_gnome.py +++ b/tests/unit/extensions/test_gnome.py @@ -81,14 +81,14 @@ def test_is_experimental(base): def test_get_app_snippet(gnome_extension): - assert gnome_extension.get_app_snippet() == { + assert gnome_extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/desktop-launch"], "plugs": ["desktop", "desktop-legacy", "gsettings", "opengl", "wayland", "x11"], } def test_get_app_snippet_core24(gnome_extension_core24): - assert gnome_extension_core24.get_app_snippet() == { + assert gnome_extension_core24.get_app_snippet(app_name="test-app") == { "command-chain": [ "snap/command-chain/gpu-2404-wrapper", "snap/command-chain/desktop-launch", diff --git a/tests/unit/extensions/test_kde_neon.py b/tests/unit/extensions/test_kde_neon.py index dda264abb4..35387a2a62 100644 --- a/tests/unit/extensions/test_kde_neon.py +++ b/tests/unit/extensions/test_kde_neon.py @@ -81,7 +81,7 @@ def test_is_experimental(): def test_get_app_snippet(kde_neon_extension): - assert kde_neon_extension.get_app_snippet() == { + assert kde_neon_extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/desktop-launch"], "plugs": ["desktop", "desktop-legacy", "opengl", "wayland", "x11"], } diff --git a/tests/unit/extensions/test_kde_neon_6.py b/tests/unit/extensions/test_kde_neon_6.py index 47340f290f..b59d474d6e 100644 --- a/tests/unit/extensions/test_kde_neon_6.py +++ b/tests/unit/extensions/test_kde_neon_6.py @@ -88,7 +88,7 @@ def test_is_experimental(): def test_get_app_snippet(kde_neon_6_extension): - assert kde_neon_6_extension.get_app_snippet() == { + assert kde_neon_6_extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/desktop-launch6"], "plugs": [ "desktop", diff --git a/tests/unit/extensions/test_registry.py b/tests/unit/extensions/test_registry.py index c823c92c60..0fefd4048c 100644 --- a/tests/unit/extensions/test_registry.py +++ b/tests/unit/extensions/test_registry.py @@ -24,6 +24,7 @@ @pytest.mark.usefixtures("fake_extension_experimental") def test_get_extension_names(): assert extensions.get_extension_names() == [ + "env-injector", "gnome", "ros2-humble", "ros2-humble-ros-core", diff --git a/tests/unit/parts/extensions/test_ros2_humble.py b/tests/unit/parts/extensions/test_ros2_humble.py index 53ad5b921b..4bbd91d771 100644 --- a/tests/unit/parts/extensions/test_ros2_humble.py +++ b/tests/unit/parts/extensions/test_ros2_humble.py @@ -110,7 +110,7 @@ def test_get_app_snippet(self, setup_method_fixture): "${PYTHONPATH}", ] extension = setup_method_fixture() - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", diff --git a/tests/unit/parts/extensions/test_ros2_humble_meta.py b/tests/unit/parts/extensions/test_ros2_humble_meta.py index 67a439be02..1ec41625b2 100644 --- a/tests/unit/parts/extensions/test_ros2_humble_meta.py +++ b/tests/unit/parts/extensions/test_ros2_humble_meta.py @@ -142,7 +142,7 @@ def test_get_app_snippet(self, extension_name, extension_class, meta, meta_dev): "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages", ] extension = setup_method_fixture(extension_class) - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", diff --git a/tests/unit/parts/extensions/test_ros2_jazzy.py b/tests/unit/parts/extensions/test_ros2_jazzy.py index 664d52ad7b..8a33a13d51 100644 --- a/tests/unit/parts/extensions/test_ros2_jazzy.py +++ b/tests/unit/parts/extensions/test_ros2_jazzy.py @@ -110,7 +110,7 @@ def test_get_app_snippet(self, setup_method_fixture): "${PYTHONPATH}", ] extension = setup_method_fixture() - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", diff --git a/tests/unit/parts/extensions/test_ros2_jazzy_meta.py b/tests/unit/parts/extensions/test_ros2_jazzy_meta.py index 7f5b996220..a772858e2b 100644 --- a/tests/unit/parts/extensions/test_ros2_jazzy_meta.py +++ b/tests/unit/parts/extensions/test_ros2_jazzy_meta.py @@ -142,7 +142,7 @@ def test_get_app_snippet(self, extension_name, extension_class, meta, meta_dev): "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages", ] extension = setup_method_fixture(extension_class) - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2",