Skip to content

Commit

Permalink
Merge branch 'main' into add-kde-neon-6-to-schema
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-cal authored Oct 18, 2024
2 parents 344bf66 + af8bc12 commit 2b64e81
Show file tree
Hide file tree
Showing 27 changed files with 432 additions and 25 deletions.
1 change: 1 addition & 0 deletions schema/snapcraft.json
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@
"uniqueItems": true,
"items": {
"enum": [
"env-injector",
"flutter-stable",
"flutter-beta",
"flutter-dev",
Expand Down
4 changes: 2 additions & 2 deletions snapcraft/extensions/_ros2_humble_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions snapcraft/extensions/_ros2_jazzy_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion snapcraft/extensions/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
125 changes: 125 additions & 0 deletions snapcraft/extensions/env_injector.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

"""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 <snap-name> env.<key>=<value>
- To set environment variables for a specific application **inside** the snap:
.. code-block:: shell
sudo snap set <snap-name> apps.<app-name>.env.<key>=<value>
- To set environment file inside the snap:
.. code-block:: shell
sudo snap set <snap-name> env-file=<path-to-env-file>
- To set environment file for a specific app:
.. code-block:: shell
sudo snap set <snap-name> apps.<app-name>.envfile=<path-to-env-file>
"""

@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)
7 changes: 5 additions & 2 deletions snapcraft/extensions/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
2 changes: 1 addition & 1 deletion snapcraft/extensions/gnome.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion snapcraft/extensions/kde_neon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
2 changes: 1 addition & 1 deletion snapcraft/extensions/kde_neon_6.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
2 changes: 2 additions & 0 deletions snapcraft/extensions/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,6 +39,7 @@
ExtensionType = Type[Extension]

_EXTENSIONS: Dict[str, "ExtensionType"] = {
"env-injector": EnvInjector,
"gnome": GNOME,
"ros2-humble": ROS2HumbleExtension,
"ros2-humble-ros-core": ROS2HumbleRosCoreExtension,
Expand Down
2 changes: 1 addition & 1 deletion snapcraft/extensions/ros2_humble.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion snapcraft/extensions/ros2_jazzy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
127 changes: 127 additions & 0 deletions tests/spread/extensions/env-injector/task.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#!/bin/bash
Loading

0 comments on commit 2b64e81

Please sign in to comment.