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..8c24222241
--- /dev/null
+++ b/snapcraft/extensions/env_injector.py
@@ -0,0 +1,125 @@
+# -*- 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"],
+ "override-build": f"""
+ rustup default stable
+ rustup target add {toolchain}
+
+ cargo build --target {toolchain} --release
+ mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain
+
+ 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..0797b64f1d
--- /dev/null
+++ b/tests/unit/extensions/test_env_injector.py
@@ -0,0 +1,112 @@
+# -*- 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"],
+ "override-build": f"""
+ rustup default stable
+ rustup target add {toolchain}
+
+ cargo build --target {toolchain} --release
+ mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain
+
+ 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",