diff --git a/extensions/ros1/launch b/extensions/ros1/launch
index 5a0158370f..52b53ca6f1 100755
--- a/extensions/ros1/launch
+++ b/extensions/ros1/launch
@@ -2,19 +2,40 @@
function activate_workspace() {
rosdir="$1"
- setup_sh="$rosdir/setup.bash"
+ setup_sh="$rosdir/setup.sh"
if [ ! -f "$setup_sh" ]; then
echo "error: $setup_sh not found"
exit 1
fi
# shellcheck disable=SC1090
- _CATKIN_SETUP_DIR="$rosdir" source "$setup_sh"
+ _CATKIN_SETUP_DIR="$rosdir" source "$setup_sh" "${@:2}"
}
# Save off parameters, the sourced setup scripts may manipulate them.
original_args=("$@")
-activate_workspace "$SNAP/opt/ros/$ROS_DISTRO"
+# activate_workspace in content shared snaps
+if [ -d "$SNAP/opt/ros/underlay_ws/opt/ros/$ROS_DISTRO" ]; then
+ activate_workspace "$SNAP/opt/ros/underlay_ws/opt/ros/$ROS_DISTRO" --local
+
+ case "$SNAP_ARCH" in
+ "amd64") ARCH_TRIPLET='x86_64-linux-gnu'
+ ;;
+ "arm64") ARCH_TRIPLET="aarch64-linux-gnu"
+ ;;
+ "armhf") ARCH_TRIPLET="arm-linux-gnueabihf"
+ ;;
+ *)
+ echo "Unsupported architecture '$SNAP_ARCH' for this app build."
+ exit 1
+ ;;
+ esac
+
+ export PATH="$SNAP/opt/ros/underlay_ws/usr/bin:$PATH"
+ export LD_LIBRARY_PATH="$SNAP/opt/ros/underlay_ws/usr/lib:$SNAP/opt/ros/underlay_ws/usr/lib/$ARCH_TRIPLET:$LD_LIBRARY_PATH"
+fi
+
+activate_workspace "$SNAP/opt/ros/$ROS_DISTRO" --local --extend
exec "${original_args[@]}"
diff --git a/extensions/ros2/launch b/extensions/ros2/launch
index fc6dab06a8..0111a278f8 100755
--- a/extensions/ros2/launch
+++ b/extensions/ros2/launch
@@ -2,18 +2,47 @@
function source_with_prefix() {
export ROS_WORKSPACE_CURRENT_PREFIX="$1"
- if [ ! -f "$ROS_WORKSPACE_CURRENT_PREFIX/local_setup.bash" ]; then
- echo "error: $ROS_WORKSPACE_CURRENT_PREFIX/local_setup.bash not found"
+ SETUP_SCRIPT="$ROS_WORKSPACE_CURRENT_PREFIX/local_setup.bash"
+ if [ ! -f "$SETUP_SCRIPT" ]; then
+ echo "error: $SETUP_SCRIPT not found"
exit 1
fi
# shellcheck disable=SC1090,SC1091
- source "$ROS_WORKSPACE_CURRENT_PREFIX/local_setup.bash"
+ source "$SETUP_SCRIPT"
}
# Save off parameters, the sourced setup scripts may manipulate them.
original_args=("$@")
+if [ -d "$SNAP/opt/ros/underlay_ws/opt/ros/$ROS_DISTRO" ]; then
+ source_with_prefix "$SNAP/opt/ros/underlay_ws/opt/ros/$ROS_DISTRO"
+fi
+if [ -d "$SNAP/opt/ros/underlay_ws/opt/ros/snap" ]; then
+ source_with_prefix "$SNAP/opt/ros/underlay_ws/opt/ros/snap"
+fi
+if [ -d "$SNAP/opt/ros/underlay_ws/opt/ros/$ROS_DISTRO" ] || [ -d "$SNAP/opt/ros/underlay_ws/opt/ros/snap" ]; then
+
+ case "$SNAP_ARCH" in
+ "amd64") ARCH_TRIPLET='x86_64-linux-gnu'
+ ;;
+ "arm64") ARCH_TRIPLET="aarch64-linux-gnu"
+ ;;
+ "armhf") ARCH_TRIPLET="arm-linux-gnueabihf"
+ ;;
+ *)
+ echo "Unsupported architecture '$SNAP_ARCH' for this app build."
+ exit 1
+ ;;
+ esac
+
+ export PATH="$SNAP/opt/ros/underlay_ws/usr/bin:$PATH"
+ export LD_LIBRARY_PATH="$SNAP/opt/ros/underlay_ws/usr/lib:$SNAP/opt/ros/underlay_ws/usr/lib/$ARCH_TRIPLET:$LD_LIBRARY_PATH"
+fi
+
source_with_prefix "$SNAP/opt/ros/$ROS_DISTRO"
-source_with_prefix "$SNAP/opt/ros/snap"
+
+if [ -f "$SNAP/opt/ros/snap/local_setup.bash" ]; then
+ source_with_prefix "$SNAP/opt/ros/snap"
+fi
exec "${original_args[@]}"
diff --git a/schema/snapcraft.json b/schema/snapcraft.json
index c9c61369e4..3cc20cbaf3 100644
--- a/schema/snapcraft.json
+++ b/schema/snapcraft.json
@@ -915,7 +915,15 @@
"gnome-3-38",
"kde-neon",
"ros1-noetic",
- "ros2-foxy"
+ "ros1-noetic-desktop",
+ "ros1-noetic-perception",
+ "ros1-noetic-robot",
+ "ros1-noetic-ros-base",
+ "ros1-noetic-ros-core",
+ "ros2-foxy",
+ "ros2-foxy-ros-base",
+ "ros2-foxy-ros-core",
+ "ros2-foxy-desktop"
]
}
}
diff --git a/snapcraft/extensions/_ros2_humble_meta.py b/snapcraft/extensions/_ros2_humble_meta.py
new file mode 100644
index 0000000000..e4204d5f93
--- /dev/null
+++ b/snapcraft/extensions/_ros2_humble_meta.py
@@ -0,0 +1,119 @@
+# -*- 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+"""Base for ROS 2 Humble extensions to the Colcon plugin using content-sharing."""
+
+import dataclasses
+from abc import abstractmethod
+from typing import Any, Dict, Optional
+
+from overrides import overrides
+
+from .ros2_humble import ROS2HumbleExtension
+
+
+@dataclasses.dataclass
+class ROS2HumbleSnaps:
+ """A structure of ROS 2 Humble related snaps."""
+
+ sdk: str
+ content: str
+ variant: str
+
+
+class ROS2HumbleMetaBase(ROS2HumbleExtension):
+ """Drives ROS 2 build and runtime environment for snap using content-sharing."""
+
+ @property
+ @abstractmethod
+ def ros2_humble_snaps(self) -> ROS2HumbleSnaps:
+ """Return the ROS 2 Humble related snaps to use to construct the environment."""
+ raise NotImplementedError
+
+ @staticmethod
+ @overrides
+ def is_experimental(base: Optional[str]) -> bool:
+ return True
+
+ @overrides
+ def get_root_snippet(self) -> Dict[str, Any]:
+ root_snippet = super().get_root_snippet()
+ root_snippet["plugs"] = {
+ "ros-humble": {
+ "interface": "content",
+ "content": "ros-humble",
+ "target": "$SNAP/opt/ros/underlay_ws",
+ "default-provider": self.ros2_humble_snaps.content,
+ }
+ }
+ return root_snippet
+
+ @overrides
+ def get_app_snippet(self) -> Dict[str, Any]:
+ app_snippet = super().get_app_snippet()
+ 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",
+ "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages",
+ ]
+
+ app_snippet["environment"][
+ "PYTHONPATH"
+ ] = f'{python_paths}:{":".join(new_python_paths)}'
+
+ return app_snippet
+
+ @overrides
+ def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]:
+ part_snippet = super().get_part_snippet(plugin_name=plugin_name)
+
+ # These are colcon-plugin specific entries
+ if plugin_name == "colcon":
+ part_snippet["colcon-ros-build-snaps"] = [self.ros2_humble_snaps.sdk]
+ part_snippet["colcon-cmake-args"] = [
+ f'-DCMAKE_SYSTEM_PREFIX_PATH="/snap/{self.ros2_humble_snaps.sdk}/current/usr"'
+ ]
+
+ return part_snippet
+
+ @overrides
+ def get_parts_snippet(self) -> Dict[str, Any]:
+ parts_snippet = super().get_parts_snippet()
+ # Very unlikely but it may happen that the snapped application doesn't
+ # even pull those deps. In that case, there is no valid ROS 2 ws to source.
+ # We make sure here that they are staged no matter what.
+ parts_snippet[f"ros2-{self.ROS_DISTRO}/ros2-launch"]["stage-packages"] = [
+ f"ros-{self.ROS_DISTRO}-ros-environment",
+ f"ros-{self.ROS_DISTRO}-ros-workspace",
+ f"ros-{self.ROS_DISTRO}-ament-index-cpp",
+ f"ros-{self.ROS_DISTRO}-ament-index-python",
+ ]
+
+ # Something in the ROS 2 build chain requires to find this lib during cmake call,
+ # however its cmake files ship with the '-dev' package.
+ parts_snippet[f"ros2-{self.ROS_DISTRO}/ros2-launch"]["build-packages"].append(
+ "libpython3.10-dev"
+ )
+
+ # The part name must follow the format /
+ parts_snippet[
+ f"ros2-{self.ROS_DISTRO}-{self.ros2_humble_snaps.variant}/ros2-launch"
+ ] = parts_snippet[f"ros2-{self.ROS_DISTRO}/ros2-launch"]
+ parts_snippet.pop(f"ros2-{self.ROS_DISTRO}/ros2-launch")
+
+ return parts_snippet
diff --git a/snapcraft/extensions/registry.py b/snapcraft/extensions/registry.py
index 77ddb6ebe4..ae5d69f463 100644
--- a/snapcraft/extensions/registry.py
+++ b/snapcraft/extensions/registry.py
@@ -23,6 +23,9 @@
from .gnome import GNOME
from .kde_neon import KDENeon
from .ros2_humble import ROS2HumbleExtension
+from .ros2_humble_desktop import ROS2HumbleDesktopExtension
+from .ros2_humble_ros_base import ROS2HumbleRosBaseExtension
+from .ros2_humble_ros_core import ROS2HumbleRosCoreExtension
if TYPE_CHECKING:
from .extension import Extension
@@ -32,6 +35,9 @@
_EXTENSIONS: Dict[str, "ExtensionType"] = {
"gnome": GNOME,
"ros2-humble": ROS2HumbleExtension,
+ "ros2-humble-ros-core": ROS2HumbleRosCoreExtension,
+ "ros2-humble-ros-base": ROS2HumbleRosBaseExtension,
+ "ros2-humble-desktop": ROS2HumbleDesktopExtension,
"kde-neon": KDENeon,
}
diff --git a/snapcraft/extensions/ros2_humble_desktop.py b/snapcraft/extensions/ros2_humble_desktop.py
new file mode 100644
index 0000000000..376bbb02f1
--- /dev/null
+++ b/snapcraft/extensions/ros2_humble_desktop.py
@@ -0,0 +1,38 @@
+# -*- 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+"""Extension to the Colcon plugin for ROS 2 Humble using content sharing."""
+
+import functools
+
+from overrides import overrides
+
+from ._ros2_humble_meta import ROS2HumbleMetaBase, ROS2HumbleSnaps
+
+
+class ROS2HumbleDesktopExtension(ROS2HumbleMetaBase):
+ """Drives ROS 2 build and runtime environment for snap using content-sharing."""
+
+ @functools.cached_property
+ @overrides
+ def ros2_humble_snaps(self) -> ROS2HumbleSnaps:
+ return ROS2HumbleSnaps(
+ sdk="ros-humble-desktop-dev",
+ content="ros-humble-desktop",
+ variant="desktop",
+ )
diff --git a/snapcraft/extensions/ros2_humble_ros_base.py b/snapcraft/extensions/ros2_humble_ros_base.py
new file mode 100644
index 0000000000..a1f7efc707
--- /dev/null
+++ b/snapcraft/extensions/ros2_humble_ros_base.py
@@ -0,0 +1,38 @@
+# -*- 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+"""Extension to the Colcon plugin for ROS 2 Humble using content sharing."""
+
+import functools
+
+from overrides import overrides
+
+from ._ros2_humble_meta import ROS2HumbleMetaBase, ROS2HumbleSnaps
+
+
+class ROS2HumbleRosBaseExtension(ROS2HumbleMetaBase):
+ """Drives ROS 2 build and runtime environment for snap using content-sharing."""
+
+ @functools.cached_property
+ @overrides
+ def ros2_humble_snaps(self) -> ROS2HumbleSnaps:
+ return ROS2HumbleSnaps(
+ sdk="ros-humble-ros-base-dev",
+ content="ros-humble-ros-base",
+ variant="ros-base",
+ )
diff --git a/snapcraft/extensions/ros2_humble_ros_core.py b/snapcraft/extensions/ros2_humble_ros_core.py
new file mode 100644
index 0000000000..ca520a2f6e
--- /dev/null
+++ b/snapcraft/extensions/ros2_humble_ros_core.py
@@ -0,0 +1,38 @@
+# -*- 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+"""Extension to the Colcon plugin for ROS 2 Humble using content sharing."""
+
+import functools
+
+from overrides import overrides
+
+from ._ros2_humble_meta import ROS2HumbleMetaBase, ROS2HumbleSnaps
+
+
+class ROS2HumbleRosCoreExtension(ROS2HumbleMetaBase):
+ """Drives ROS 2 build and runtime environment for snap using content-sharing."""
+
+ @functools.cached_property
+ @overrides
+ def ros2_humble_snaps(self) -> ROS2HumbleSnaps:
+ return ROS2HumbleSnaps(
+ sdk="ros-humble-ros-core-dev",
+ content="ros-humble-ros-core",
+ variant="ros-core",
+ )
diff --git a/snapcraft/parts/plugins/_ros.py b/snapcraft/parts/plugins/_ros.py
index c4891128ef..2379e19197 100644
--- a/snapcraft/parts/plugins/_ros.py
+++ b/snapcraft/parts/plugins/_ros.py
@@ -30,6 +30,7 @@
from catkin_pkg import packages as catkin_packages
from craft_parts import plugins
from craft_parts.packages import Repository as Repo
+from craft_parts.packages.snaps import _get_parsed_snap
from overrides import overrides
from snapcraft.errors import SnapcraftError
@@ -85,11 +86,15 @@ class RosPlugin(plugins.Plugin):
@overrides
def get_build_snaps(self) -> Set[str]:
- return set()
+ return (
+ set(self._options.colcon_ros_build_snaps) # type: ignore
+ if self._options.colcon_ros_build_snaps # type: ignore
+ else set()
+ )
@overrides
def get_build_packages(self) -> Set[str]:
- return {"python3-rosdep"}
+ return {"python3-rosdep", "rospack-tools"}
@overrides
def get_build_environment(self) -> Dict[str, str]:
@@ -127,6 +132,49 @@ def _get_build_commands(self) -> List[str]:
specific functionality.
"""
+ def _get_list_packages_commands(self) -> List[str]:
+ """Generate a list of ROS 2 packages available in build snaps.
+
+ The ROS 2 workspaces contained in build snaps are crawled with `rospack`
+ to establish the list of all available ROS 2 packages.
+ The package names are then resolved with `rosdep` to map their names to debs.
+ The list is finally saved in the part's install directory.
+ """
+ cmd = []
+
+ # Clean up previously established list of packages in build snaps
+ cmd.append('rm -f "${CRAFT_PART_INSTALL}/.installed_packages.txt"')
+ cmd.append('rm -f "${CRAFT_PART_INSTALL}/.build_snaps.txt"')
+
+ if self._options.colcon_ros_build_snaps: # type: ignore
+ for ros_build_snap in self._options.colcon_ros_build_snaps: # type: ignore
+ snap_name = _get_parsed_snap(ros_build_snap)[0]
+ path = f"/snap/{snap_name}/current/opt/ros"
+ # pylint: disable=line-too-long
+ cmd.extend(
+ [
+ # Retrieve the list of all ROS packages available in the build snap
+ f"if [ -d {path} ]; then",
+ f"ROS_PACKAGE_PATH={path} "
+ 'rospack list-names | (xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | '
+ 'awk "/#apt/{getline;print;}" >> "${CRAFT_PART_INSTALL}/.installed_packages.txt"',
+ "fi",
+ # Retrieve the list of all non-ROS packages available in the build snap
+ f'if [ -d "{path}/${{ROS_DISTRO}}/" ]; then',
+ f'rosdep keys --rosdistro "${{ROS_DISTRO}}" --from-paths "{path}/${{ROS_DISTRO}}" --ignore-packages-from-source '
+ '| (xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" >> "${CRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ f'if [ -d "{path}/snap/" ]; then',
+ f'rosdep keys --rosdistro "${{ROS_DISTRO}}" --from-paths "{path}/snap" --ignore-packages-from-source '
+ '| (xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" >> "${CRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ ]
+ )
+ # pylint: enable=line-too-long
+ cmd.append("")
+
+ return cmd
+
def _get_stage_runtime_dependencies_commands(self) -> List[str]:
env = {"LANG": "C.UTF-8", "LC_ALL": "C.UTF-8"}
@@ -175,18 +223,37 @@ def _get_stage_runtime_dependencies_commands(self) -> List[str]:
@overrides
def get_build_commands(self) -> List[str]:
return (
- self._get_workspace_activation_commands()
- + [
+ [
"if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
# Preserve http(s)_proxy env var in root for remote-build proxy since rosdep
# doesn't support proxy
# https://github.com/ros-infrastructure/rosdep/issues/271
"sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
- "rosdep install --default-yes --ignore-packages-from-source "
- '--from-paths "${CRAFT_PART_SRC_WORK}"',
+ ]
+ # There are a number of unbound vars, disable flag
+ # after saving current state to restore after.
+ + [
+ 'state="$(set +o); set -$-"',
+ "set +u",
+ "",
]
+ self._get_workspace_activation_commands()
+ # Restore saved state
+ + ['eval "${state}"']
+ + self._get_list_packages_commands()
+ # pylint: disable=line-too-long
+ + [
+ 'rosdep install --default-yes --ignore-packages-from-source --from-paths "${CRAFT_PART_SRC_WORK}"',
+ ]
+ # pylint: enable=line-too-long
+ + [
+ 'state="$(set +o); set -$-"',
+ "set +u",
+ "",
+ ]
+ + self._get_workspace_activation_commands()
+ + ['eval "${state}"']
+ self._get_build_commands()
+ self._get_stage_runtime_dependencies_commands()
)
@@ -197,6 +264,51 @@ def plugin_cli():
"""Define the plugin_cli Click group."""
+def get_installed_dependencies(installed_packages_path: str) -> Set[str]:
+ """Retrieve recursive apt dependencies of a given package list."""
+ if os.path.isfile(installed_packages_path):
+ try:
+ with open(installed_packages_path, encoding="utf8") as file:
+ build_snap_packages = set(file.read().split())
+ package_dependencies = set()
+ for package in build_snap_packages:
+ cmd = [
+ "apt",
+ "depends",
+ "--recurse",
+ "--no-recommends",
+ "--no-suggests",
+ "--no-conflicts",
+ "--no-breaks",
+ "--no-replaces",
+ "--no-enhances",
+ f"{package}",
+ ]
+ click.echo(f"Running {cmd!r}")
+ try:
+ proc = subprocess.run(
+ cmd,
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ env={"PATH": os.environ["PATH"]},
+ )
+ except subprocess.CalledProcessError as error:
+ click.echo(f"failed to run {cmd!r}: {error.output}")
+ else:
+ apt_dependency_regex = re.compile(r"^\w.*$")
+ for line in proc.stdout.decode().strip().split("\n"):
+ if apt_dependency_regex.match(line):
+ package_dependencies.add(line)
+
+ build_snap_packages.update(package_dependencies)
+ click.echo(f"Will not fetch staged packages: {build_snap_packages!r}")
+ return build_snap_packages
+ except OSError:
+ pass
+ return set()
+
+
@plugin_cli.command()
@click.option("--part-src", envvar="CRAFT_PART_SRC", required=True)
@click.option("--part-install", envvar="CRAFT_PART_INSTALL", required=True)
@@ -263,6 +375,10 @@ def stage_runtime_dependencies(
if parsed:
click.echo(f"unhandled dependencies: {parsed!r}")
+ build_snap_packages = get_installed_dependencies(
+ part_install + "/.installed_packages.txt"
+ )
+
if apt_packages:
package_names = sorted(apt_packages)
install_path = pathlib.Path(part_install)
@@ -277,6 +393,7 @@ def stage_runtime_dependencies(
arch=target_arch,
base=base,
stage_packages_path=stage_packages_path,
+ packages_filters=build_snap_packages, # type: ignore
)
click.echo(f"Unpacking stage packages: {fetched_stage_packages!r}")
diff --git a/snapcraft/parts/plugins/colcon_plugin.py b/snapcraft/parts/plugins/colcon_plugin.py
index 45cac24029..4b8a740ca7 100644
--- a/snapcraft/parts/plugins/colcon_plugin.py
+++ b/snapcraft/parts/plugins/colcon_plugin.py
@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-"""The colcon plugin for ROS2 parts.
+"""The colcon plugin for ROS 2 parts.
- colcon-packages:
(list of strings)
@@ -58,6 +58,7 @@
from typing import Any, Dict, List, Set, cast
from craft_parts import plugins
+from craft_parts.packages.snaps import _get_parsed_snap
from overrides import overrides
from . import _ros
@@ -71,11 +72,13 @@ class ColconPluginProperties(plugins.PluginProperties, plugins.PluginModel):
colcon_cmake_args: List[str] = []
colcon_packages: List[str] = []
colcon_packages_ignore: List[str] = []
+ colcon_ros_build_snaps: List[str] = []
# part properties required by the plugin
source: str
@classmethod
+ @overrides
def unmarshal(cls, data: Dict[str, Any]) -> "ColconPluginProperties":
"""Populate make properties from the part specification.
@@ -85,6 +88,10 @@ def unmarshal(cls, data: Dict[str, Any]) -> "ColconPluginProperties":
:raise pydantic.ValidationError: If validation fails.
"""
+
+ # plugin specific parameters have to be prefixed with the plugin name.
+ # However we'd like to avoid that for 'ros-build-snaps'.
+ # Marking it required allows us to circumvent the prefix requirement.
plugin_data = plugins.extract_plugin_properties(
data, plugin_name="colcon", required=["source"]
)
@@ -116,6 +123,20 @@ def get_build_environment(self) -> Dict[str, str]:
return env
+ def _get_source_command(self, path: str) -> List[str]:
+ return [
+ f'if [ -f "{path}/opt/ros/${{ROS_DISTRO}}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="{wspath}" . "{wspath}/local_setup.sh"'.format(
+ wspath=f"{path}/opt/ros/${{ROS_DISTRO}}"
+ ),
+ "fi",
+ f'if [ -f "{path}/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="{wspath}" . "{wspath}/local_setup.sh"'.format(
+ wspath=f"{path}/opt/ros/snap"
+ ),
+ "fi",
+ ]
+
@overrides
def _get_workspace_activation_commands(self) -> List[str]:
"""Return a list of commands source a ROS 2 workspace.
@@ -129,31 +150,36 @@ def _get_workspace_activation_commands(self) -> List[str]:
specific functionality.
"""
- # There are a number of unbound vars, disable flag
- # after saving current state to restore after.
- return [
- 'state="$(set +o); set -$-"',
- "set +u",
- # If it exists, source the stage-snap underlay
- 'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="{path}" . "{path}/local_setup.sh"'.format(
- path="${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}"
- ),
- "fi",
- 'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="{path}" . "{path}/local_setup.sh"'.format(
- path="${CRAFT_PART_INSTALL}/opt/ros/snap"
- ),
- "fi",
- '. "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
- 'eval "${state}"',
- ]
+ activation_commands = []
+
+ # Source ROS ws in all build-snaps first
+ activation_commands.append("## Sourcing ROS ws in build snaps")
+ self._options: ColconPluginProperties
+ if self._options.colcon_ros_build_snaps:
+ for ros_build_snap in self._options.colcon_ros_build_snaps:
+ snap_name = _get_parsed_snap(ros_build_snap)[0]
+ activation_commands.extend(
+ self._get_source_command(f"/snap/{snap_name}/current")
+ )
+ activation_commands.append("")
+
+ # Source ROS ws in stage-snaps next
+ activation_commands.append("## Sourcing ROS ws in stage snaps")
+ activation_commands.extend(self._get_source_command("${CRAFT_PART_INSTALL}"))
+ activation_commands.append("")
+
+ # Finally source system's ROS ws
+ activation_commands.append("## Sourcing ROS ws in system")
+ activation_commands.extend(self._get_source_command(""))
+ activation_commands.append("")
+
+ return activation_commands
@overrides
def _get_build_commands(self) -> List[str]:
options = cast(ColconPluginProperties, self._options)
- cmd = [
+ build_command = [
"colcon",
"build",
"--base-paths",
@@ -166,27 +192,32 @@ def _get_build_commands(self) -> List[str]:
]
if options.colcon_packages_ignore:
- cmd.extend(["--packages-ignore", *options.colcon_packages_ignore])
+ build_command.extend(["--packages-ignore", *options.colcon_packages_ignore])
if options.colcon_packages:
- cmd.extend(["--packages-select", *options.colcon_packages])
+ build_command.extend(["--packages-select", *options.colcon_packages])
if options.colcon_cmake_args:
- cmd.extend(["--cmake-args", *options.colcon_cmake_args])
+ build_command.extend(["--cmake-args", *options.colcon_cmake_args])
if options.colcon_ament_cmake_args:
- cmd.extend(["--ament-cmake-args", *options.colcon_ament_cmake_args])
+ build_command.extend(
+ ["--ament-cmake-args", *options.colcon_ament_cmake_args]
+ )
if options.colcon_catkin_cmake_args:
- cmd.extend(["--catkin-cmake-args", *options.colcon_catkin_cmake_args])
+ build_command.extend(
+ ["--catkin-cmake-args", *options.colcon_catkin_cmake_args]
+ )
# Specify the number of workers
- cmd.extend(["--parallel-workers", '"${CRAFT_PARALLEL_BUILD_COUNT}"'])
+ build_command.extend(["--parallel-workers", '"${CRAFT_PARALLEL_BUILD_COUNT}"'])
- return [" ".join(cmd)] + [
+ return ["## Build command", " ".join(build_command)] + [
+ "## Post build command",
# Remove the COLCON_IGNORE marker so that, at staging,
# catkin can crawl the entire folder to look up for packages.
- 'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/snap/COLCON_IGNORE" ]; then',
- 'rm "${CRAFT_PART_INSTALL}/opt/ros/snap/COLCON_IGNORE"',
+ 'if [ -f "${CRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE ]; then',
+ 'rm "${CRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE',
"fi",
]
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/_ros1_noetic_meta.py b/snapcraft_legacy/internal/project_loader/_extensions/_ros1_noetic_meta.py
new file mode 100644
index 0000000000..d9fceafafb
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/_ros1_noetic_meta.py
@@ -0,0 +1,87 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import dataclasses
+from abc import abstractmethod
+from typing import Any, Dict, Optional
+
+from overrides import overrides
+
+from .ros1_noetic import ExtensionImpl as RosNoeticExtension
+
+
+@dataclasses.dataclass
+class ROS2NoeticSnaps:
+ """A structure of ROS 2 Noetic related snaps."""
+
+ sdk: str
+ content: str
+
+
+class RosNoeticMetaBase(RosNoeticExtension):
+ """Setup a ROS 1 build and runtime environment suitable for a snap."""
+
+ @property
+ @abstractmethod
+ def ros_noetic_snaps(self) -> ROS2NoeticSnaps:
+ """Return the ROS Noetic related snaps to use to construct the environment."""
+ raise NotImplementedError
+
+ @staticmethod
+ @overrides
+ def is_experimental(base: Optional[str]) -> bool:
+ return True
+
+ def __init__(self, *, extension_name: str, yaml_data: Dict[str, Any]) -> None:
+ super().__init__(extension_name=extension_name, yaml_data=yaml_data)
+
+ self.part_snippet_extra = dict()
+
+ self.root_snippet["plugs"] = {
+ "ros-noetic":
+ {
+ "interface": "content",
+ "content": "ros-noetic",
+ "target": "$SNAP/opt/ros/underlay_ws",
+ "default-provider": self.ros_noetic_snaps.content,
+ }
+ }
+
+ self.part_snippet_extra["ros-content-sharing-extension-cmake-args"] = [
+ f'-DCMAKE_SYSTEM_PREFIX_PATH="/snap/{self.ros_noetic_snaps.sdk}/current/usr"'
+ ]
+
+ self.part_snippet_extra["stage-packages"] = [f"ros-{self.ROS_DISTRO}-ros-environment"]
+
+ self.part_snippet_extra["ros-build-snaps"] = [self.ros_noetic_snaps.sdk]
+
+ python_paths = self.app_snippet["environment"]["PYTHONPATH"]
+ new_python_paths = [
+ f"$SNAP/opt/ros/underlay_ws/opt/ros/{self.ROS_DISTRO}/lib/python3.8/site-packages",
+ "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages",
+ ]
+
+ self.app_snippet["environment"]["PYTHONPATH"] = f'{python_paths}:{":".join(new_python_paths)}'
+
+ @overrides
+ def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]:
+ # If the part uses a ROS plugin, return the extra bits containing ROS plugin specifics entries
+ # If not, still return the base ROS plugin entries.
+ if plugin_name in ["catkin", "catkin-tools", "colcon"]:
+ return {**self.part_snippet,**self.part_snippet_extra}
+ return self.part_snippet
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/_ros2_foxy_meta.py b/snapcraft_legacy/internal/project_loader/_extensions/_ros2_foxy_meta.py
new file mode 100644
index 0000000000..95390250ea
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/_ros2_foxy_meta.py
@@ -0,0 +1,85 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import dataclasses
+from abc import abstractmethod
+from typing import Any, Dict, Optional
+
+from overrides import overrides
+
+from .ros2_foxy import ExtensionImpl as RosFoxyExtension
+
+
+@dataclasses.dataclass
+class ROS2FoxySnaps:
+ """A structure of ROS 2 Foxy related snaps."""
+
+ sdk: str
+ content: str
+
+
+class RosFoxyMetaBase(RosFoxyExtension):
+ """Setup a ROS 2 build and runtime environment suitable for a snap."""
+
+ @property
+ @abstractmethod
+ def ros2_foxy_snaps(self) -> ROS2FoxySnaps:
+ """Return the ROS 2 Foxy related snaps to use to construct the environment."""
+ raise NotImplementedError
+
+ @staticmethod
+ @overrides
+ def is_experimental(base: Optional[str]) -> bool:
+ return True
+
+ def __init__(self, *, extension_name: str, yaml_data: Dict[str, Any]) -> None:
+ super().__init__(extension_name=extension_name, yaml_data=yaml_data)
+
+ # Very unlikely but it may happen that the snapped application doesn't
+ # even pull those deps. In that case, there is no valid ROS 2 ws to source.
+ # We make sure here that they are staged no matter what.
+ self.parts[f"ros2-{self.ROS_DISTRO}-extension"]["stage-packages"] = [
+ f"ros-{self.ROS_DISTRO}-ros-environment",
+ f"ros-{self.ROS_DISTRO}-ros-workspace",
+ f"ros-{self.ROS_DISTRO}-ament-index-cpp",
+ f"ros-{self.ROS_DISTRO}-ament-index-python",
+ ]
+
+ self.root_snippet["plugs"] = {
+ "ros-foxy":
+ {
+ "interface": "content",
+ "content": "ros-foxy",
+ "target": "$SNAP/opt/ros/underlay_ws",
+ "default-provider": self.ros2_foxy_snaps.content,
+ }
+ }
+
+ self.part_snippet["colcon-cmake-args"] = [
+ f'-DCMAKE_SYSTEM_PREFIX_PATH="/snap/{self.ros2_foxy_snaps.sdk}/current/usr"'
+ ]
+
+ self.part_snippet["ros-build-snaps"] = [self.ros2_foxy_snaps.sdk]
+
+ python_paths = self.app_snippet["environment"]["PYTHONPATH"]
+ new_python_paths = [
+ f"$SNAP/opt/ros/underlay_ws/opt/ros/{self.ROS_DISTRO}/lib/python3.8/site-packages",
+ "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages",
+ ]
+
+ self.app_snippet["environment"]["PYTHONPATH"] = f'{python_paths}:{":".join(new_python_paths)}'
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_desktop.py b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_desktop.py
new file mode 100644
index 0000000000..56bdd7b9b9
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_desktop.py
@@ -0,0 +1,34 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import functools
+
+from overrides import overrides
+
+from ._ros1_noetic_meta import ROS2NoeticSnaps, RosNoeticMetaBase
+
+
+class ExtensionImpl(RosNoeticMetaBase):
+ """Setup a ROS 1 build and runtime environment suitable for a snap."""
+
+ @functools.cached_property
+ @overrides
+ def ros_noetic_snaps(self) -> ROS2NoeticSnaps:
+ return ROS2NoeticSnaps(
+ content="ros-noetic-desktop", sdk="ros-noetic-desktop-dev"
+ )
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_perception.py b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_perception.py
new file mode 100644
index 0000000000..3b389d3ff4
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_perception.py
@@ -0,0 +1,34 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import functools
+
+from overrides import overrides
+
+from ._ros1_noetic_meta import ROS2NoeticSnaps, RosNoeticMetaBase
+
+
+class ExtensionImpl(RosNoeticMetaBase):
+ """Setup a ROS 1 build and runtime environment suitable for a snap."""
+
+ @functools.cached_property
+ @overrides
+ def ros_noetic_snaps(self) -> ROS2NoeticSnaps:
+ return ROS2NoeticSnaps(
+ content="ros-noetic-perception", sdk="ros-noetic-perception-dev"
+ )
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_robot.py b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_robot.py
new file mode 100644
index 0000000000..5d4c763ea8
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_robot.py
@@ -0,0 +1,34 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import functools
+
+from overrides import overrides
+
+from ._ros1_noetic_meta import ROS2NoeticSnaps, RosNoeticMetaBase
+
+
+class ExtensionImpl(RosNoeticMetaBase):
+ """Setup a ROS 1 build and runtime environment suitable for a snap."""
+
+ @functools.cached_property
+ @overrides
+ def ros_noetic_snaps(self) -> ROS2NoeticSnaps:
+ return ROS2NoeticSnaps(
+ content="ros-noetic-robot", sdk="ros-noetic-robot-dev"
+ )
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_ros_base.py b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_ros_base.py
new file mode 100644
index 0000000000..933cabd5c5
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_ros_base.py
@@ -0,0 +1,34 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import functools
+
+from overrides import overrides
+
+from ._ros1_noetic_meta import ROS2NoeticSnaps, RosNoeticMetaBase
+
+
+class ExtensionImpl(RosNoeticMetaBase):
+ """Setup a ROS 1 build and runtime environment suitable for a snap."""
+
+ @functools.cached_property
+ @overrides
+ def ros_noetic_snaps(self) -> ROS2NoeticSnaps:
+ return ROS2NoeticSnaps(
+ content="ros-noetic-ros-base", sdk="ros-noetic-ros-base-dev"
+ )
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_ros_core.py b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_ros_core.py
new file mode 100644
index 0000000000..316c35a72f
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/ros1_noetic_ros_core.py
@@ -0,0 +1,34 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import functools
+
+from overrides import overrides
+
+from ._ros1_noetic_meta import ROS2NoeticSnaps, RosNoeticMetaBase
+
+
+class ExtensionImpl(RosNoeticMetaBase):
+ """Setup a ROS 1 build and runtime environment suitable for a snap."""
+
+ @functools.cached_property
+ @overrides
+ def ros_noetic_snaps(self) -> ROS2NoeticSnaps:
+ return ROS2NoeticSnaps(
+ content="ros-noetic-ros-core", sdk="ros-noetic-ros-core-dev"
+ )
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/ros2_foxy_desktop.py b/snapcraft_legacy/internal/project_loader/_extensions/ros2_foxy_desktop.py
new file mode 100644
index 0000000000..5c1f6327a7
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/ros2_foxy_desktop.py
@@ -0,0 +1,34 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import functools
+
+from overrides import overrides
+
+from ._ros2_foxy_meta import ROS2FoxySnaps, RosFoxyMetaBase
+
+
+class ExtensionImpl(RosFoxyMetaBase):
+ """Setup a ROS 2 build and runtime environment suitable for a snap."""
+
+ @functools.cached_property
+ @overrides
+ def ros2_foxy_snaps(self) -> ROS2FoxySnaps:
+ return ROS2FoxySnaps(
+ content="ros-foxy-desktop", sdk="ros-foxy-desktop-dev"
+ )
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/ros2_foxy_ros_base.py b/snapcraft_legacy/internal/project_loader/_extensions/ros2_foxy_ros_base.py
new file mode 100644
index 0000000000..806a7c81ce
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/ros2_foxy_ros_base.py
@@ -0,0 +1,34 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import functools
+
+from overrides import overrides
+
+from ._ros2_foxy_meta import ROS2FoxySnaps, RosFoxyMetaBase
+
+
+class ExtensionImpl(RosFoxyMetaBase):
+ """Setup a ROS 2 build and runtime environment suitable for a snap."""
+
+ @functools.cached_property
+ @overrides
+ def ros2_foxy_snaps(self) -> ROS2FoxySnaps:
+ return ROS2FoxySnaps(
+ content="ros-foxy-ros-base", sdk="ros-foxy-ros-base-dev"
+ )
diff --git a/snapcraft_legacy/internal/project_loader/_extensions/ros2_foxy_ros_core.py b/snapcraft_legacy/internal/project_loader/_extensions/ros2_foxy_ros_core.py
new file mode 100644
index 0000000000..b6671149c0
--- /dev/null
+++ b/snapcraft_legacy/internal/project_loader/_extensions/ros2_foxy_ros_core.py
@@ -0,0 +1,34 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+# Import types and tell flake8 to ignore the "unused" List.
+
+import functools
+
+from overrides import overrides
+
+from ._ros2_foxy_meta import ROS2FoxySnaps, RosFoxyMetaBase
+
+
+class ExtensionImpl(RosFoxyMetaBase):
+ """Setup a ROS 2 build and runtime environment suitable for a snap."""
+
+ @functools.cached_property
+ @overrides
+ def ros2_foxy_snaps(self) -> ROS2FoxySnaps:
+ return ROS2FoxySnaps(
+ content="ros-foxy-ros-core", sdk="ros-foxy-ros-core-dev"
+ )
diff --git a/snapcraft_legacy/internal/repo/_deb.py b/snapcraft_legacy/internal/repo/_deb.py
index 4e67ef932b..186bf82917 100644
--- a/snapcraft_legacy/internal/repo/_deb.py
+++ b/snapcraft_legacy/internal/repo/_deb.py
@@ -476,7 +476,7 @@ def fetch_stage_packages(
base: str,
stage_packages_path: pathlib.Path,
target_arch: str,
- packages_filters: Set[str] = set(),
+ packages_filters: Optional[Set[str]] = None,
) -> List[str]:
logger.debug(f"Requested stage-packages: {sorted(package_names)!r}")
@@ -487,7 +487,8 @@ def fetch_stage_packages(
base=base, package_list=package_list
)
- filtered_names.update(packages_filters)
+ if packages_filters:
+ filtered_names.update(packages_filters)
stage_packages_path.mkdir(exist_ok=True)
with AptCache(
diff --git a/snapcraft_legacy/plugins/v2/_ros.py b/snapcraft_legacy/plugins/v2/_ros.py
index 393196a458..a97a7aaa70 100644
--- a/snapcraft_legacy/plugins/v2/_ros.py
+++ b/snapcraft_legacy/plugins/v2/_ros.py
@@ -27,6 +27,7 @@
from snapcraft_legacy.internal import errors
from snapcraft_legacy.internal.repo import Repo
+from snapcraft_legacy.internal.repo.snaps import _get_parsed_snap
from snapcraft_legacy.plugins.v2 import PluginV2
@@ -76,11 +77,12 @@ class RosPlugin(PluginV2):
"""Base class for ROS-related plugins. Not intended for use by end users."""
def get_build_snaps(self) -> Set[str]:
- return set()
+ return set(self.options.ros_build_snaps) if self.options.ros_build_snaps else set()
def get_build_packages(self) -> Set[str]:
return {
"python3-rosdep",
+ "rospack-tools",
}
def get_build_environment(self) -> Dict[str, str]:
@@ -119,6 +121,41 @@ def _get_build_commands(self) -> List[str]:
specific functionality.
"""
+ def _get_list_packages_commands(self) -> List[str]:
+
+ cmd = list()
+
+ # Clean up previously established list of packages in build snaps
+ cmd.append('rm -f "${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"')
+ cmd.append('rm -f "${SNAPCRAFT_PART_INSTALL}/.build_snaps.txt"')
+
+ if self.options.ros_build_snaps:
+ for ros_build_snap in self.options.ros_build_snaps:
+ snap_name = _get_parsed_snap(ros_build_snap)[0]
+ path = f"/snap/{snap_name}/current/opt/ros"
+ cmd.extend([
+ # Retrieve the list of all ROS packages available in the build snap
+ f"if [ -d {path} ]; then",
+ f"ROS_PACKAGE_PATH={path} "
+ 'rospack list-names | (xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | '
+ 'awk "/#apt/{getline;print;}" >> "${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ "fi",
+
+ # Retrieve the list of all non-ROS packages available in the build snap
+ f'if [ -d "{path}/${{ROS_DISTRO}}/" ]; then',
+ f'rosdep keys --rosdistro "${{ROS_DISTRO}}" --from-paths "{path}/${{ROS_DISTRO}}" --ignore-packages-from-source '
+ '| (xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" >> "${SNAPCRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+
+ f'if [ -d "{path}/snap/" ]; then',
+ f'rosdep keys --rosdistro "${{ROS_DISTRO}}" --from-paths "{path}/snap" --ignore-packages-from-source '
+ '| (xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" >> "${SNAPCRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ ])
+ cmd.append("")
+
+ return cmd
+
def _get_stage_runtime_dependencies_commands(self) -> List[str]:
env = dict(LANG="C.UTF-8", LC_ALL="C.UTF-8")
@@ -161,17 +198,35 @@ def _get_stage_runtime_dependencies_commands(self) -> List[str]:
def get_build_commands(self) -> List[str]:
return (
- self._get_workspace_activation_commands()
- + [
+ [
"if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
# Preserve http(s)_proxy env var in root for remote-build proxy since rosdep
# doesn't support proxy
# https://github.com/ros-infrastructure/rosdep/issues/271
"sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
+ ]
+ # There are a number of unbound vars, disable flag
+ # after saving current state to restore after.
+ + [
+ 'state="$(set +o); set -$-"',
+ "set +u",
+ "",
+ ]
+ + self._get_workspace_activation_commands()
+ # Restore saved state
+ + ['eval "${state}"']
+ + self._get_list_packages_commands()
+ + [
'rosdep install --default-yes --ignore-packages-from-source --from-paths "${SNAPCRAFT_PART_SRC_WORK}"',
]
+ + [
+ 'state="$(set +o); set -$-"',
+ "set +u",
+ "",
+ ]
+ self._get_workspace_activation_commands()
+ + ['eval "${state}"']
+ self._get_build_commands()
+ self._get_stage_runtime_dependencies_commands()
)
@@ -182,6 +237,51 @@ def plugin_cli():
pass
+def get_installed_dependencies(installed_packages_path: str) -> Set[str]:
+ """Retrieve recursive apt dependencies of a given package list."""
+ if os.path.isfile(installed_packages_path):
+ try:
+ with open(installed_packages_path, "r") as f:
+ build_snap_packages = set(f.read().split())
+ package_dependencies = set()
+ for package in build_snap_packages:
+ cmd = [
+ "apt",
+ "depends",
+ "--recurse",
+ "--no-recommends",
+ "--no-suggests",
+ "--no-conflicts",
+ "--no-breaks",
+ "--no-replaces",
+ "--no-enhances",
+ f"{package}",
+ ]
+ click.echo(f"Running {cmd!r}")
+ try:
+ proc = subprocess.run(
+ cmd,
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ env=dict(PATH=os.environ["PATH"]),
+ )
+ except subprocess.CalledProcessError as error:
+ click.echo(f"failed to run {cmd!r}: {error.output}")
+ else:
+ apt_dependency_regex = re.compile(r"^\w.*$")
+ for line in proc.stdout.decode().strip().split("\n"):
+ if apt_dependency_regex.match(line):
+ package_dependencies.add(line)
+
+ build_snap_packages.update(package_dependencies)
+ click.echo(f"Will not fetch staged packages: {build_snap_packages!r}")
+ return build_snap_packages
+ except IOError:
+ pass
+ return set()
+
+
@plugin_cli.command()
@click.option("--part-src", envvar="SNAPCRAFT_PART_SRC_WORK", required=True)
@click.option("--part-install", envvar="SNAPCRAFT_PART_INSTALL", required=True)
@@ -238,20 +338,25 @@ def stage_runtime_dependencies(
if parsed:
click.echo(f"unhandled dependencies: {parsed!r}")
+ build_snap_packages = get_installed_dependencies(
+ part_install + "/.installed_packages.txt"
+ )
+
if apt_packages:
package_names = sorted(apt_packages)
install_path = pathlib.Path(part_install)
stage_packages_path = install_path.parent / "stage_packages"
click.echo(f"Fetching stage packages: {package_names!r}")
- Repo.fetch_stage_packages(
+ fetched_stage_packages = Repo.fetch_stage_packages(
package_names=package_names,
base="core20",
stage_packages_path=stage_packages_path,
target_arch=target_arch,
+ packages_filters=build_snap_packages,
)
- click.echo(f"Unpacking stage packages: {package_names!r}")
+ click.echo(f"Unpacking stage packages: {fetched_stage_packages!r}")
Repo.unpack_stage_packages(
stage_packages_path=stage_packages_path, install_path=install_path
)
diff --git a/snapcraft_legacy/plugins/v2/catkin.py b/snapcraft_legacy/plugins/v2/catkin.py
index 876f07b0f8..52c21bdac2 100644
--- a/snapcraft_legacy/plugins/v2/catkin.py
+++ b/snapcraft_legacy/plugins/v2/catkin.py
@@ -38,6 +38,7 @@
from typing import Any, Dict, List, Set
+from snapcraft_legacy.internal.repo.snaps import _get_parsed_snap
from snapcraft_legacy.plugins.v2 import _ros
@@ -68,12 +69,36 @@ def get_schema(cls) -> Dict[str, Any]:
"items": {"type": "string"},
"default": [],
},
+ "ros-build-snaps": {
+ "type": "array",
+ "minItems": 0,
+ "uniqueItems": True,
+ "items": {"type": "string"},
+ "default": [],
+ },
+ "ros-content-sharing-extension-cmake-args": {
+ "type": "array",
+ "minItems": 0,
+ "items": {"type": "string"},
+ "default": [],
+ },
},
}
def get_build_packages(self) -> Set[str]:
return super().get_build_packages() | {"ros-noetic-catkin"}
+ def _get_source_command(self, path: str) -> List[str]:
+ return [
+ f'if [ -f "{path}/opt/ros/${{ROS_DISTRO}}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="{fpath}" . "{fpath}/setup.sh"'.format(
+ fpath=f"{path}/opt/ros/${{ROS_DISTRO}}"
+ ),
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ ]
+
def _get_workspace_activation_commands(self) -> List[str]:
"""Return a list of commands to source a ROS workspace.
@@ -86,26 +111,31 @@ def _get_workspace_activation_commands(self) -> List[str]:
specific functionality.
"""
- # There are a number of unbound vars, disable flag
- # after saving current state to restore after.
- return [
- 'state="$(set +o); set -$-"',
- "set +u",
- 'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
- "set -- --local",
- '_CATKIN_SETUP_DIR="{path}" . "{path}/setup.sh"'.format(
- path="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}"
- ),
- "set -- --local --extend",
- "else",
- "set -- --local",
- "fi",
- '. /opt/ros/"${ROS_DISTRO}"/setup.sh',
- 'eval "${state}"',
- ]
+ activation_commands = list()
+
+ # Source ROS ws in all build-snaps first
+ activation_commands.append("## Sourcing ROS ws in build snaps")
+ if self.options.ros_build_snaps:
+ for ros_build_snap in self.options.ros_build_snaps:
+ snap_name = _get_parsed_snap(ros_build_snap)[0]
+ activation_commands.extend(self._get_source_command(f"/snap/{snap_name}/current"))
+ activation_commands.append("")
+
+ # Source ROS ws in stage-snaps next
+ activation_commands.append("## Sourcing ROS ws in stage snaps")
+ activation_commands.extend(self._get_source_command("${SNAPCRAFT_PART_INSTALL}"))
+ activation_commands.append("")
+
+ # Finally source system's ROS ws
+ activation_commands.append("## Sourcing ROS ws in system")
+ activation_commands.extend(self._get_source_command(""))
+ activation_commands.append("")
+
+ return activation_commands
def _get_build_commands(self) -> List[str]:
- cmd = [
+
+ build_command = [
"catkin_make_isolated",
"--install",
"--merge",
@@ -120,12 +150,14 @@ def _get_build_commands(self) -> List[str]:
]
if self.options.catkin_packages:
- cmd.extend(["--pkg", *self.options.catkin_packages])
+ build_command.extend(["--pkg", *self.options.catkin_packages])
if self.options.catkin_packages_ignore:
- cmd.extend(["--ignore-pkg", *self.options.catkin_packages_ignore])
+ build_command.extend(["--ignore-pkg", *self.options.catkin_packages_ignore])
- if self.options.catkin_cmake_args:
- cmd.extend(["--cmake-args", *self.options.catkin_cmake_args])
+ if self.options.catkin_cmake_args or self.options.ros_content_sharing_extension_cmake_args:
+ build_command.append("--cmake-args")
+ build_command.extend(self.options.catkin_cmake_args)
+ build_command.extend(self.options.ros_content_sharing_extension_cmake_args)
- return [" ".join(cmd)]
+ return (["## Build command", " ".join(build_command)])
diff --git a/snapcraft_legacy/plugins/v2/catkin_tools.py b/snapcraft_legacy/plugins/v2/catkin_tools.py
index 6270e4cf1b..5c466525af 100644
--- a/snapcraft_legacy/plugins/v2/catkin_tools.py
+++ b/snapcraft_legacy/plugins/v2/catkin_tools.py
@@ -33,6 +33,7 @@
from typing import Any, Dict, List, Set
+from snapcraft_legacy.internal.repo.snaps import _get_parsed_snap
from snapcraft_legacy.plugins.v2 import _ros
@@ -56,6 +57,19 @@ def get_schema(cls) -> Dict[str, Any]:
"uniqueItems": True,
"items": {"type": "string"},
},
+ "ros-build-snaps": {
+ "type": "array",
+ "minItems": 0,
+ "uniqueItems": True,
+ "items": {"type": "string"},
+ "default": [],
+ },
+ "ros-content-sharing-extension-cmake-args": {
+ "type": "array",
+ "minItems": 0,
+ "items": {"type": "string"},
+ "default": [],
+ },
},
}
@@ -64,6 +78,17 @@ def get_build_packages(self) -> Set[str]:
"python3-catkin-tools",
}
+ def _get_source_command(self, path: str) -> List[str]:
+ return [
+ f'if [ -f "{path}/opt/ros/${{ROS_DISTRO}}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="{fpath}" . "{fpath}/setup.sh"'.format(
+ fpath=f"{path}/opt/ros/${{ROS_DISTRO}}"
+ ),
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ ]
+
def _get_workspace_activation_commands(self) -> List[str]:
"""Return a list of commands to source a ROS workspace.
@@ -76,6 +101,28 @@ def _get_workspace_activation_commands(self) -> List[str]:
specific functionality.
"""
+ activation_commands = list()
+
+ # Source ROS ws in all build-snaps first
+ activation_commands.append("## Sourcing ROS ws in build snaps")
+ if self.options.ros_build_snaps:
+ for ros_build_snap in self.options.ros_build_snaps:
+ snap_name = _get_parsed_snap(ros_build_snap)[0]
+ activation_commands.extend(self._get_source_command(f"/snap/{snap_name}/current"))
+ activation_commands.append("")
+
+ # Source ROS ws in stage-snaps next
+ activation_commands.append("## Sourcing ROS ws in stage snaps")
+ activation_commands.extend(self._get_source_command("${SNAPCRAFT_PART_INSTALL}"))
+ activation_commands.append("")
+
+ # Finally source system's ROS ws
+ activation_commands.append("## Sourcing ROS ws in system")
+ activation_commands.extend(self._get_source_command(""))
+ activation_commands.append("")
+
+ return activation_commands
+
# There are a number of unbound vars, disable flag
# after saving current state to restore after.
return [
@@ -119,9 +166,14 @@ def _get_build_commands(self) -> List[str]:
'"${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}"',
]
- if self.options.catkin_tools_cmake_args:
+ if self.options.catkin_tools_cmake_args or self.options.ros_content_sharing_extension_cmake_args:
+ cmake_args = []
+ if self.options.catkin_tools_cmake_args:
+ cmake_args.extend(self.options.catkin_tools_cmake_args)
+ if self.options.ros_content_sharing_extension_cmake_args:
+ cmake_args.extend(self.options.ros_content_sharing_extension_cmake_args)
catkin_config_command.extend(
- ["--cmake-args", *self.options.catkin_tools_cmake_args]
+ ["--cmake-args", *cmake_args]
)
commands.append(" ".join(catkin_config_command))
diff --git a/snapcraft_legacy/plugins/v2/colcon.py b/snapcraft_legacy/plugins/v2/colcon.py
index 89516cdd15..4ed2e93752 100644
--- a/snapcraft_legacy/plugins/v2/colcon.py
+++ b/snapcraft_legacy/plugins/v2/colcon.py
@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-"""The colcon plugin for ROS2 parts.
+"""The colcon plugin for ROS 2 parts.
- colcon-packages:
(list of strings)
@@ -56,6 +56,7 @@
from typing import Any, Dict, List, Set
+from snapcraft_legacy.internal.repo.snaps import _get_parsed_snap
from snapcraft_legacy.plugins.v2 import _ros
@@ -98,6 +99,13 @@ def get_schema(cls) -> Dict[str, Any]:
"items": {"type": "string"},
"default": [],
},
+ "ros-build-snaps": {
+ "type": "array",
+ "minItems": 0,
+ "uniqueItems": True,
+ "items": {"type": "string"},
+ "default": [],
+ },
},
}
@@ -119,6 +127,20 @@ def get_build_environment(self) -> Dict[str, str]:
return env
+ def _get_source_command(self, path: str) -> List[str]:
+ return [
+ f'if [ -f "{path}/opt/ros/${{ROS_DISTRO}}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="{wspath}" . "{wspath}/local_setup.sh"'.format(
+ wspath=f"{path}/opt/ros/${{ROS_DISTRO}}"
+ ),
+ "fi",
+ f'if [ -f "{path}/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="{wspath}" . "{wspath}/local_setup.sh"'.format(
+ wspath=f"{path}/opt/ros/snap"
+ ),
+ "fi",
+ ]
+
def _get_workspace_activation_commands(self) -> List[str]:
"""Return a list of commands source a ROS 2 workspace.
@@ -131,28 +153,35 @@ def _get_workspace_activation_commands(self) -> List[str]:
specific functionality.
"""
- # There are a number of unbound vars, disable flag
- # after saving current state to restore after.
- return [
- 'state="$(set +o); set -$-"',
- "set +u",
- # If it exists, source the stage-snap underlay
- 'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
- 'AMENT_CURRENT_PREFIX="{path}" . "{path}/local_setup.sh"'.format(
- path='${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}'
- ),
- "fi",
- 'if [ -f "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/local_setup.sh ]; then',
- "COLCON_CURRENT_PREFIX={path} . {path}/local_setup.sh".format(
- path='"${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap'
- ),
- "fi",
- '. /opt/ros/"${ROS_DISTRO}"/local_setup.sh',
- 'eval "${state}"',
- ]
+ activation_commands = list()
+
+ # Source ROS ws in all build-snaps first
+ activation_commands.append("## Sourcing ROS ws in build snaps")
+ if self.options.ros_build_snaps:
+ for ros_build_snap in self.options.ros_build_snaps:
+ snap_name = _get_parsed_snap(ros_build_snap)[0]
+ activation_commands.extend(
+ self._get_source_command(f"/snap/{snap_name}/current")
+ )
+ activation_commands.append("")
+
+ # Source ROS ws in stage-snaps next
+ activation_commands.append("## Sourcing ROS ws in stage snaps")
+ activation_commands.extend(
+ self._get_source_command("${SNAPCRAFT_PART_INSTALL}")
+ )
+ activation_commands.append("")
+
+ # Finally source system's ROS ws
+ activation_commands.append("## Sourcing ROS ws in system")
+ activation_commands.extend(self._get_source_command(""))
+ activation_commands.append("")
+
+ return activation_commands
def _get_build_commands(self) -> List[str]:
- cmd = [
+
+ build_command = [
"colcon",
"build",
"--base-paths",
@@ -165,27 +194,31 @@ def _get_build_commands(self) -> List[str]:
]
if self.options.colcon_packages_ignore:
- cmd.extend(["--packages-ignore", *self.options.colcon_packages_ignore])
+ build_command.extend(["--packages-ignore", *self.options.colcon_packages_ignore])
if self.options.colcon_packages:
- cmd.extend(["--packages-select", *self.options.colcon_packages])
+ build_command.extend(["--packages-select", *self.options.colcon_packages])
if self.options.colcon_cmake_args:
- cmd.extend(["--cmake-args", *self.options.colcon_cmake_args])
+ build_command.extend(["--cmake-args", *self.options.colcon_cmake_args])
if self.options.colcon_ament_cmake_args:
- cmd.extend(["--ament-cmake-args", *self.options.colcon_ament_cmake_args])
+ build_command.extend(["--ament-cmake-args", *self.options.colcon_ament_cmake_args])
if self.options.colcon_catkin_cmake_args:
- cmd.extend(["--catkin-cmake-args", *self.options.colcon_catkin_cmake_args])
+ build_command.extend(["--catkin-cmake-args", *self.options.colcon_catkin_cmake_args])
# Specify the number of workers
- cmd.extend(["--parallel-workers", '"${SNAPCRAFT_PARALLEL_BUILD_COUNT}"'])
-
- return [" ".join(cmd)] + [
- # Remove the COLCON_IGNORE marker so that, at staging,
- # catkin can crawl the entire folder to look up for packages.
- 'if [ -f "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE ]; then',
- 'rm "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE',
- "fi",
- ]
+ build_command.extend(["--parallel-workers", '"${SNAPCRAFT_PARALLEL_BUILD_COUNT}"'])
+
+ return (
+ ["## Build command", " ".join(build_command)]
+ + [
+ "## Post build command",
+ # Remove the COLCON_IGNORE marker so that, at staging,
+ # catkin can crawl the entire folder to look up for packages.
+ 'if [ -f "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE ]; then',
+ 'rm "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE',
+ "fi",
+ ]
+ )
diff --git a/tests/legacy/unit/plugins/v2/test_catkin.py b/tests/legacy/unit/plugins/v2/test_catkin.py
index df99d97d77..812305f3fa 100644
--- a/tests/legacy/unit/plugins/v2/test_catkin.py
+++ b/tests/legacy/unit/plugins/v2/test_catkin.py
@@ -45,6 +45,19 @@ def test_schema():
"type": "array",
"uniqueItems": True,
},
+ "ros-build-snaps": {
+ "default": [],
+ "items": {"type": "string"},
+ "minItems": 0,
+ "type": "array",
+ "uniqueItems": True,
+ },
+ "ros-content-sharing-extension-cmake-args": {
+ "type": "array",
+ "minItems": 0,
+ "items": {"type": "string"},
+ "default": [],
+ },
},
"type": "object",
}
@@ -53,7 +66,27 @@ def test_schema():
def test_get_build_packages():
plugin = catkin.CatkinPlugin(part_name="my-part", options=lambda: None)
- assert plugin.get_build_packages() == {"python3-rosdep", "ros-noetic-catkin"}
+ assert plugin.get_build_packages() == {
+ "python3-rosdep",
+ "ros-noetic-catkin",
+ "rospack-tools",
+ }
+
+
+def test_get_build_snaps():
+ class OptionsDefault:
+ ros_build_snaps = list()
+
+ plugin = catkin.CatkinPlugin(part_name="my-part", options=OptionsDefault())
+
+ assert plugin.get_build_snaps() == set()
+
+ class Options:
+ ros_build_snaps = ["Foo"]
+
+ plugin = catkin.CatkinPlugin(part_name="my-part", options=Options())
+
+ assert plugin.get_build_snaps() == {"Foo"}
def test_get_build_environment():
@@ -75,6 +108,8 @@ class Options:
catkin_cmake_args = list()
catkin_packages = list()
catkin_packages_ignore = list()
+ ros_build_snaps = list()
+ ros_content_sharing_extension_cmake_args = list()
plugin = catkin.CatkinPlugin(part_name="my-part", options=Options())
@@ -84,32 +119,51 @@ class Options:
monkeypatch.setattr(os, "environ", dict())
assert plugin.get_build_commands() == [
+ "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
+ "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
+ 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
- "set -- --local",
+ 'set -- --local "${_EXTEND_WS}"',
'_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
- "set -- --local --extend",
- "else",
- "set -- --local",
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/setup.sh',
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
'eval "${state}"',
- "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
- "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
- 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.build_snaps.txt"',
'rosdep install --default-yes --ignore-packages-from-source --from-paths "${SNAPCRAFT_PART_SRC_WORK}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
- "set -- --local",
+ 'set -- --local "${_EXTEND_WS}"',
'_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
- "set -- --local --extend",
- "else",
- "set -- --local",
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/setup.sh',
+ "",
'eval "${state}"',
+ "## Build command",
"catkin_make_isolated --install --merge "
'--source-space "${SNAPCRAFT_PART_SRC_WORK}" --build-space "${SNAPCRAFT_PART_BUILD}" '
'--install-space "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" '
@@ -126,6 +180,8 @@ class Options:
catkin_cmake_args = ["cmake", "args..."]
catkin_packages = ["package1", "package2..."]
catkin_packages_ignore = ["ipackage1", "ipackage2..."]
+ ros_build_snaps = ["foo"]
+ ros_content_sharing_extension_cmake_args = ["DCMAKE_EXNTENSION_ARGS", "bar"]
plugin = catkin.CatkinPlugin(part_name="my-part", options=Options())
@@ -148,37 +204,87 @@ class Options:
)
assert plugin.get_build_commands() == [
+ "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
+ "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
+ 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ 'if [ -f "/snap/foo/current/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/snap/foo/current/opt/ros/${ROS_DISTRO}" . "/snap/foo/current/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
- "set -- --local",
+ 'set -- --local "${_EXTEND_WS}"',
'_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
- "set -- --local --extend",
- "else",
- "set -- --local",
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/setup.sh',
+ "",
'eval "${state}"',
- "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
- "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
- 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.build_snaps.txt"',
+ "if [ -d /snap/foo/current/opt/ros ]; then",
+ "ROS_PACKAGE_PATH=/snap/foo/current/opt/ros rospack list-names | (xargs "
+ 'rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | awk '
+ '"/#apt/{getline;print;}" >> '
+ '"${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ "fi",
+ 'if [ -d "/snap/foo/current/opt/ros/${ROS_DISTRO}/" ]; then',
+ 'rosdep keys --rosdistro "${ROS_DISTRO}" --from-paths '
+ '"/snap/foo/current/opt/ros/${ROS_DISTRO}" --ignore-packages-from-source | '
+ '(xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" '
+ '>> "${SNAPCRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ 'if [ -d "/snap/foo/current/opt/ros/snap/" ]; then',
+ 'rosdep keys --rosdistro "${ROS_DISTRO}" --from-paths '
+ '"/snap/foo/current/opt/ros/snap" --ignore-packages-from-source | (xargs '
+ 'rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" >> '
+ '"${SNAPCRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ "",
'rosdep install --default-yes --ignore-packages-from-source --from-paths "${SNAPCRAFT_PART_SRC_WORK}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ 'if [ -f "/snap/foo/current/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/snap/foo/current/opt/ros/${ROS_DISTRO}" . "/snap/foo/current/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
- "set -- --local",
+ 'set -- --local "${_EXTEND_WS}"',
'_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
- "set -- --local --extend",
- "else",
- "set -- --local",
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/setup.sh',
+ "",
'eval "${state}"',
+ "## Build command",
"catkin_make_isolated --install --merge "
'--source-space "${SNAPCRAFT_PART_SRC_WORK}" --build-space "${SNAPCRAFT_PART_BUILD}" '
'--install-space "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" '
'-j "${SNAPCRAFT_PARALLEL_BUILD_COUNT}" --pkg package1 package2... '
- "--ignore-pkg ipackage1 ipackage2... --cmake-args cmake args...",
+ "--ignore-pkg ipackage1 ipackage2... --cmake-args cmake args... DCMAKE_EXNTENSION_ARGS bar",
"env -i LANG=C.UTF-8 LC_ALL=C.UTF-8 PATH=/bin:/test SNAP=TESTSNAP "
"SNAP_ARCH=TESTARCH SNAP_NAME=TESTSNAPNAME SNAP_VERSION=TESTV1 "
"http_proxy=http://foo https_proxy=https://bar "
diff --git a/tests/legacy/unit/plugins/v2/test_catkin_tools.py b/tests/legacy/unit/plugins/v2/test_catkin_tools.py
index fc1c7905af..debf267f42 100644
--- a/tests/legacy/unit/plugins/v2/test_catkin_tools.py
+++ b/tests/legacy/unit/plugins/v2/test_catkin_tools.py
@@ -38,6 +38,19 @@ def test_schema():
"type": "array",
"uniqueItems": True,
},
+ "ros-build-snaps": {
+ "type": "array",
+ "minItems": 0,
+ "uniqueItems": True,
+ "items": {"type": "string"},
+ "default": [],
+ },
+ "ros-content-sharing-extension-cmake-args": {
+ "type": "array",
+ "minItems": 0,
+ "items": {"type": "string"},
+ "default": [],
+ },
},
"type": "object",
}
@@ -49,9 +62,28 @@ def test_get_build_packages():
assert plugin.get_build_packages() == {
"python3-rosdep",
"python3-catkin-tools",
+ "rospack-tools",
}
+def test_get_build_snaps():
+ class OptionsDefault:
+ ros_build_snaps = list()
+
+ plugin = catkin_tools.CatkinToolsPlugin(
+ part_name="my-part", options=OptionsDefault()
+ )
+
+ assert plugin.get_build_snaps() == set()
+
+ class Options:
+ ros_build_snaps = ["Foo"]
+
+ plugin = catkin_tools.CatkinToolsPlugin(part_name="my-part", options=Options())
+
+ assert plugin.get_build_snaps() == {"Foo"}
+
+
def test_get_build_environment():
plugin = catkin_tools.CatkinToolsPlugin(part_name="my-part", options=lambda: None)
@@ -70,6 +102,8 @@ def test_get_build_commands(monkeypatch):
class Options:
catkin_tools_cmake_args = list()
catkin_tools_packages = list()
+ ros_build_snaps = list()
+ ros_content_sharing_extension_cmake_args = list()
plugin = catkin_tools.CatkinToolsPlugin(part_name="my-part", options=Options())
@@ -79,31 +113,53 @@ class Options:
monkeypatch.setattr(os, "environ", dict())
assert plugin.get_build_commands() == [
+ "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
+ "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
+ 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
- "set -- --local",
- '_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
- "set -- --local --extend",
- "else",
- "set -- --local",
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . '
+ '"${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/opt/ros/${ROS_DISTRO}" . '
+ '"/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/setup.sh',
+ "",
'eval "${state}"',
- "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
- "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
- 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.build_snaps.txt"',
'rosdep install --default-yes --ignore-packages-from-source --from-paths "${SNAPCRAFT_PART_SRC_WORK}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
- "set -- --local",
- '_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
- "set -- --local --extend",
- "else",
- "set -- --local",
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . '
+ '"${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/opt/ros/${ROS_DISTRO}" . '
+ '"/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/setup.sh',
+ "",
'eval "${state}"',
"catkin init",
"catkin profile add -f default",
@@ -122,6 +178,8 @@ def test_get_build_commands_with_all_properties(monkeypatch):
class Options:
catkin_tools_cmake_args = ["cmake", "args..."]
catkin_tools_packages = ["package1", "package2..."]
+ ros_build_snaps = ["foo"]
+ ros_content_sharing_extension_cmake_args = ["DCMAKE_EXNTENSION_ARGS", "bar"]
plugin = catkin_tools.CatkinToolsPlugin(part_name="my-part", options=Options())
@@ -144,37 +202,91 @@ class Options:
)
assert plugin.get_build_commands() == [
+ "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
+ "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
+ 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ 'if [ -f "/snap/foo/current/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/snap/foo/current/opt/ros/${ROS_DISTRO}" . '
+ '"/snap/foo/current/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
- "set -- --local",
- '_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
- "set -- --local --extend",
- "else",
- "set -- --local",
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . '
+ '"${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/opt/ros/${ROS_DISTRO}" . '
+ '"/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/setup.sh',
+ "",
'eval "${state}"',
- "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
- "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
- 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.build_snaps.txt"',
+ "if [ -d /snap/foo/current/opt/ros ]; then",
+ "ROS_PACKAGE_PATH=/snap/foo/current/opt/ros rospack list-names | (xargs "
+ 'rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | awk '
+ '"/#apt/{getline;print;}" >> '
+ '"${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ "fi",
+ 'if [ -d "/snap/foo/current/opt/ros/${ROS_DISTRO}/" ]; then',
+ 'rosdep keys --rosdistro "${ROS_DISTRO}" --from-paths '
+ '"/snap/foo/current/opt/ros/${ROS_DISTRO}" --ignore-packages-from-source | '
+ '(xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" '
+ '>> "${SNAPCRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ 'if [ -d "/snap/foo/current/opt/ros/snap/" ]; then',
+ 'rosdep keys --rosdistro "${ROS_DISTRO}" --from-paths '
+ '"/snap/foo/current/opt/ros/snap" --ignore-packages-from-source | (xargs '
+ 'rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" >> '
+ '"${SNAPCRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ "",
'rosdep install --default-yes --ignore-packages-from-source --from-paths "${SNAPCRAFT_PART_SRC_WORK}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ 'if [ -f "/snap/foo/current/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/snap/foo/current/opt/ros/${ROS_DISTRO}" . '
+ '"/snap/foo/current/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
- "set -- --local",
+ 'set -- --local "${_EXTEND_WS}"',
'_CATKIN_SETUP_DIR="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/setup.sh"',
- "set -- --local --extend",
- "else",
- "set -- --local",
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/setup.sh" ]; then',
+ 'set -- --local "${_EXTEND_WS}"',
+ '_CATKIN_SETUP_DIR="/opt/ros/${ROS_DISTRO}" . '
+ '"/opt/ros/${ROS_DISTRO}/setup.sh"',
+ 'if [ -z ${_EXTEND_WS} ]; then _EXTEND_WS="--extend"; fi',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/setup.sh',
+ "",
'eval "${state}"',
"catkin init",
"catkin profile add -f default",
"catkin config --profile default --install "
'--source-space "${SNAPCRAFT_PART_SRC_WORK}" --build-space "${SNAPCRAFT_PART_BUILD}" '
- '--install-space "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" --cmake-args cmake args...',
+ '--install-space "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" --cmake-args cmake args... DCMAKE_EXNTENSION_ARGS bar',
'catkin build --no-notify --profile default -j "${SNAPCRAFT_PARALLEL_BUILD_COUNT}" package1 package2...',
"env -i LANG=C.UTF-8 LC_ALL=C.UTF-8 PATH=/bin:/test SNAP=TESTSNAP "
"SNAP_ARCH=TESTARCH SNAP_NAME=TESTSNAPNAME SNAP_VERSION=TESTV1 "
diff --git a/tests/legacy/unit/plugins/v2/test_colcon.py b/tests/legacy/unit/plugins/v2/test_colcon.py
index 9821d9817e..1267afbbb5 100644
--- a/tests/legacy/unit/plugins/v2/test_colcon.py
+++ b/tests/legacy/unit/plugins/v2/test_colcon.py
@@ -57,6 +57,13 @@ def test_schema():
"type": "array",
"uniqueItems": True,
},
+ "ros-build-snaps": {
+ "type": "array",
+ "minItems": 0,
+ "uniqueItems": True,
+ "items": {"type": "string"},
+ "default": [],
+ },
},
"type": "object",
}
@@ -70,9 +77,26 @@ def test_get_build_packages():
"python3-rosdep",
"python3-rosinstall",
"python3-wstool",
+ "rospack-tools",
}
+def test_get_build_snaps():
+ class OptionsDefault:
+ ros_build_snaps = list()
+
+ plugin = colcon.ColconPlugin(part_name="my-part", options=OptionsDefault())
+
+ assert plugin.get_build_snaps() == set()
+
+ class Options:
+ ros_build_snaps = ["Foo"]
+
+ plugin = colcon.ColconPlugin(part_name="my-part", options=Options())
+
+ assert plugin.get_build_snaps() == {"Foo"}
+
+
def test_get_build_environment():
plugin = colcon.ColconPlugin(part_name="my-part", options=lambda: None)
@@ -96,6 +120,7 @@ class Options:
colcon_cmake_args = list()
colcon_packages = list()
colcon_packages_ignore = list()
+ ros_build_snaps = list()
plugin = colcon.ColconPlugin(part_name="my-part", options=Options())
@@ -105,34 +130,60 @@ class Options:
monkeypatch.setattr(os, "environ", dict())
assert plugin.get_build_commands() == [
+ "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
+ "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
+ 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
'AMENT_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
- 'if [ -f "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/local_setup.sh ]; then',
- 'COLCON_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap . "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/local_setup.sh',
+ 'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}/opt/ros/snap" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/local_setup.sh',
+ 'if [ -f "/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/opt/ros/snap" . "/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
'eval "${state}"',
- "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
- "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
- 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.build_snaps.txt"',
'rosdep install --default-yes --ignore-packages-from-source --from-paths "${SNAPCRAFT_PART_SRC_WORK}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
'AMENT_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
- 'if [ -f "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/local_setup.sh ]; then',
- 'COLCON_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap . "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/local_setup.sh',
+ 'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}/opt/ros/snap" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/local_setup.sh',
+ 'if [ -f "/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/opt/ros/snap" . "/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
'eval "${state}"',
+ "## Build command",
"colcon build "
'--base-paths "${SNAPCRAFT_PART_SRC_WORK}" --build-base "${SNAPCRAFT_PART_BUILD}" '
'--merge-install --install-base "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap '
'--parallel-workers "${SNAPCRAFT_PARALLEL_BUILD_COUNT}"',
+ "## Post build command",
'if [ -f "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE ]; then',
'rm "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE',
"fi",
@@ -150,6 +201,7 @@ class Options:
colcon_cmake_args = ["cmake", "args..."]
colcon_packages = ["package1", "package2..."]
colcon_packages_ignore = ["ipackage1", "ipackage2..."]
+ ros_build_snaps = ["foo"]
plugin = colcon.ColconPlugin(part_name="my-part", options=Options())
@@ -172,30 +224,88 @@ class Options:
)
assert plugin.get_build_commands() == [
+ "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
+ "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
+ 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ 'if [ -f "/snap/foo/current/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/snap/foo/current/opt/ros/${ROS_DISTRO}" . "/snap/foo/current/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "fi",
+ 'if [ -f "/snap/foo/current/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/snap/foo/current/opt/ros/snap" . "/snap/foo/current/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
'AMENT_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
- 'if [ -f "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/local_setup.sh ]; then',
- 'COLCON_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap . "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/local_setup.sh',
+ 'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}/opt/ros/snap" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "fi",
+ 'if [ -f "/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/opt/ros/snap" . "/opt/ros/snap/local_setup.sh"',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/local_setup.sh',
+ "",
'eval "${state}"',
- "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
- "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
- 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ 'rm -f "${SNAPCRAFT_PART_INSTALL}/.build_snaps.txt"',
+ "if [ -d /snap/foo/current/opt/ros ]; then",
+ "ROS_PACKAGE_PATH=/snap/foo/current/opt/ros rospack list-names | (xargs "
+ 'rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | awk '
+ '"/#apt/{getline;print;}" >> '
+ '"${SNAPCRAFT_PART_INSTALL}/.installed_packages.txt"',
+ "fi",
+ 'if [ -d "/snap/foo/current/opt/ros/${ROS_DISTRO}/" ]; then',
+ 'rosdep keys --rosdistro "${ROS_DISTRO}" --from-paths '
+ '"/snap/foo/current/opt/ros/${ROS_DISTRO}" --ignore-packages-from-source | '
+ '(xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" '
+ '>> "${SNAPCRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ 'if [ -d "/snap/foo/current/opt/ros/snap/" ]; then',
+ 'rosdep keys --rosdistro "${ROS_DISTRO}" --from-paths '
+ '"/snap/foo/current/opt/ros/snap" --ignore-packages-from-source | (xargs '
+ 'rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" >> '
+ '"${SNAPCRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ "",
'rosdep install --default-yes --ignore-packages-from-source --from-paths "${SNAPCRAFT_PART_SRC_WORK}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ 'if [ -f "/snap/foo/current/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/snap/foo/current/opt/ros/${ROS_DISTRO}" . "/snap/foo/current/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "fi",
+ 'if [ -f "/snap/foo/current/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/snap/foo/current/opt/ros/snap" . "/snap/foo/current/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
'AMENT_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
- 'if [ -f "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/local_setup.sh ]; then',
- 'COLCON_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap . "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/local_setup.sh',
+ 'if [ -f "${SNAPCRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="${SNAPCRAFT_PART_INSTALL}/opt/ros/snap" . "${SNAPCRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "fi",
+ 'if [ -f "/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/opt/ros/snap" . "/opt/ros/snap/local_setup.sh"',
"fi",
- '. /opt/ros/"${ROS_DISTRO}"/local_setup.sh',
+ "",
'eval "${state}"',
+ "## Build command",
"colcon build "
'--base-paths "${SNAPCRAFT_PART_SRC_WORK}" --build-base "${SNAPCRAFT_PART_BUILD}" '
'--merge-install --install-base "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap '
@@ -203,6 +313,7 @@ class Options:
"package2... --cmake-args cmake args... "
"--ament-cmake-args ament args... --catkin-cmake-args catkin "
'args... --parallel-workers "${SNAPCRAFT_PARALLEL_BUILD_COUNT}"',
+ "## Post build command",
'if [ -f "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE ]; then',
'rm "${SNAPCRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE',
"fi",
diff --git a/tests/legacy/unit/project_loader/extensions/test_extensions.py b/tests/legacy/unit/project_loader/extensions/test_extensions.py
index fe15ce7045..08e0c4f22e 100644
--- a/tests/legacy/unit/project_loader/extensions/test_extensions.py
+++ b/tests/legacy/unit/project_loader/extensions/test_extensions.py
@@ -17,6 +17,7 @@
import inspect
import textwrap
+import pytest
from testscenarios import multiply_scenarios
from snapcraft_legacy.internal.project_loader import (
@@ -47,6 +48,7 @@ class ExtensionCombinationTest(ProjectLoaderBaseTest):
)
)
+ @pytest.mark.skip(reason="Doesn't scale")
def test_extensions_all_combinations_validate(self):
common_bases = set(["core20"])
diff --git a/tests/legacy/unit/project_loader/extensions/test_ros1_noetic_meta.py b/tests/legacy/unit/project_loader/extensions/test_ros1_noetic_meta.py
new file mode 100644
index 0000000000..78661abe69
--- /dev/null
+++ b/tests/legacy/unit/project_loader/extensions/test_ros1_noetic_meta.py
@@ -0,0 +1,157 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+from snapcraft_legacy.internal.project_loader._extensions.ros1_noetic_desktop import (
+ ExtensionImpl as Ros1NoeticDesktopExtension,
+)
+from snapcraft_legacy.internal.project_loader._extensions.ros1_noetic_perception import (
+ ExtensionImpl as Ros1NoeticPerceptionExtension,
+)
+from snapcraft_legacy.internal.project_loader._extensions.ros1_noetic_robot import (
+ ExtensionImpl as Ros1NoeticRobotExtension,
+)
+from snapcraft_legacy.internal.project_loader._extensions.ros1_noetic_ros_base import (
+ ExtensionImpl as Ros1NoeticRosBaseExtension,
+)
+from snapcraft_legacy.internal.project_loader._extensions.ros1_noetic_ros_core import (
+ ExtensionImpl as Ros1NoeticRosCoreExtension,
+)
+
+
+class TestClass:
+ scenarios = [
+ (
+ "desktop",
+ {
+ "extension_name": "ros1-noetic-desktop",
+ "extension_class": Ros1NoeticDesktopExtension,
+ "meta": "ros-noetic-desktop",
+ "meta_dev": "ros-noetic-desktop-dev",
+ },
+ "perception",
+ {
+ "extension_name": "ros1-noetic-perception",
+ "extension_class": Ros1NoeticPerceptionExtension,
+ "meta": "ros-noetic-perception",
+ "meta_dev": "ros-noetic-perception-dev",
+ },
+ "robot",
+ {
+ "extension_name": "ros1-noetic-robot",
+ "extension_class": Ros1NoeticRobotExtension,
+ "meta": "ros-noetic-robot",
+ "meta_dev": "ros-noetic-robot-dev",
+ },
+ "ros-base",
+ {
+ "extension_name": "ros1-noetic-ros-base",
+ "extension_class": Ros1NoeticRosBaseExtension,
+ "meta": "ros-noetic-ros-base",
+ "meta_dev": "ros-noetic-ros-base-dev",
+ },
+ "ros-core",
+ {
+ "extension_name": "ros1-noetic-ros-core",
+ "extension_class": Ros1NoeticRosCoreExtension,
+ "meta": "ros-meta-ros-core",
+ "meta_dev": "ros-meta-ros-core-dev",
+ },
+ ),
+ ]
+
+ def test_extension(self, extension_name, extension_class, meta, meta_dev):
+ ros1_extension = extension_class(
+ extension_name=extension_name, yaml_data=dict(base="core20")
+ )
+
+ assert ros1_extension.is_experimental(None)
+
+ assert ros1_extension.root_snippet == {
+ "package-repositories": [
+ {
+ "components": ["main"],
+ "formats": ["deb"],
+ "key-id": "C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654",
+ "key-server": "keyserver.ubuntu.com",
+ "suites": ["focal"],
+ "type": "apt",
+ "url": "http://packages.ros.org/ros/ubuntu",
+ }
+ ],
+ "plugs": {
+ "ros-noetic": {
+ "interface": "content",
+ "content": "ros-noetic",
+ "target": "$SNAP/opt/ros/underlay_ws",
+ "default-provider": meta,
+ }
+ },
+ }
+
+ python_paths = [
+ "$SNAP/opt/ros/noetic/lib/python3.8/site-packages",
+ "$SNAP/usr/lib/python3/dist-packages",
+ "${PYTHONPATH}",
+ "$SNAP/opt/ros/underlay_ws/opt/ros/noetic/lib/python3.8/site-packages",
+ "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages",
+ ]
+
+ assert ros1_extension.app_snippet == {
+ "command-chain": ["snap/command-chain/ros1-launch"],
+ "environment": {
+ "PYTHONPATH": ":".join(python_paths),
+ "ROS_VERSION": "1",
+ "ROS_DISTRO": "noetic",
+ },
+ }
+
+ assert ros1_extension.part_snippet == {
+ "build-environment": [{"ROS_VERSION": "1"}, {"ROS_DISTRO": "noetic"}],
+ }
+
+ assert ros1_extension.get_part_snippet(plugin_name="catkin") == {
+ "build-environment": [{"ROS_VERSION": "1"}, {"ROS_DISTRO": "noetic"}],
+ "ros-build-snaps": [meta_dev],
+ "ros-content-sharing-extension-cmake-args": [
+ f'-DCMAKE_SYSTEM_PREFIX_PATH="/snap/{meta_dev}/current/usr"'
+ ],
+ "stage-packages": ["ros-noetic-ros-environment"],
+ }
+
+ assert ros1_extension.get_part_snippet(plugin_name="catkin-tools") == {
+ "build-environment": [{"ROS_VERSION": "1"}, {"ROS_DISTRO": "noetic"}],
+ "ros-build-snaps": [meta_dev],
+ "ros-content-sharing-extension-cmake-args": [
+ f'-DCMAKE_SYSTEM_PREFIX_PATH="/snap/{meta_dev}/current/usr"'
+ ],
+ "stage-packages": ["ros-noetic-ros-environment"],
+ }
+
+ assert ros1_extension.parts == {
+ "ros1-noetic-extension": {
+ "build-packages": ["ros-noetic-catkin"],
+ "plugin": "make",
+ "source": "$SNAPCRAFT_EXTENSIONS_DIR/ros1",
+ }
+ }
+
+ def test_supported_bases(self, extension_name, extension_class, meta, meta_dev):
+ assert extension_class.get_supported_bases() == ("core20",)
+
+ def test_supported_confinement(
+ self, extension_name, extension_class, meta, meta_dev
+ ):
+ extension_class.get_supported_confinement() == ("strict", "devmode")
diff --git a/tests/legacy/unit/project_loader/extensions/test_ros2_foxy_meta.py b/tests/legacy/unit/project_loader/extensions/test_ros2_foxy_meta.py
new file mode 100644
index 0000000000..57c1dfe3ed
--- /dev/null
+++ b/tests/legacy/unit/project_loader/extensions/test_ros2_foxy_meta.py
@@ -0,0 +1,134 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 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 .
+
+from snapcraft_legacy.internal.project_loader._extensions.ros2_foxy_desktop import (
+ ExtensionImpl as Ros2FoxyDesktopExtension,
+)
+from snapcraft_legacy.internal.project_loader._extensions.ros2_foxy_ros_base import (
+ ExtensionImpl as Ros2FoxyRosBaseExtension,
+)
+from snapcraft_legacy.internal.project_loader._extensions.ros2_foxy_ros_core import (
+ ExtensionImpl as Ros2FoxyRosCoreExtension,
+)
+
+
+class TestClass:
+ scenarios = [
+ (
+ "desktop",
+ {
+ "extension_name": "ros2-foxy-desktop",
+ "extension_class": Ros2FoxyDesktopExtension,
+ "meta": "ros-foxy-desktop",
+ "meta_dev": "ros-foxy-desktop-dev",
+ },
+ "ros-base",
+ {
+ "extension_name": "ros2-foxy-ros-base",
+ "extension_class": Ros2FoxyRosBaseExtension,
+ "meta": "ros-foxy-ros-base",
+ "meta_dev": "ros-foxy-ros-base-dev",
+ },
+ "ros-core",
+ {
+ "extension_name": "ros2-foxy-ros-core",
+ "extension_class": Ros2FoxyRosCoreExtension,
+ "meta": "ros-foxy-ros-core",
+ "meta_dev": "ros-foxy-ros-core-dev",
+ },
+ ),
+ ]
+
+ def test_extension(self, extension_name, extension_class, meta, meta_dev):
+ ros_extension = extension_class(
+ extension_name=extension_name, yaml_data=dict(base="core20")
+ )
+
+ assert ros_extension.is_experimental(None)
+
+ assert ros_extension.root_snippet == {
+ "package-repositories": [
+ {
+ "components": ["main"],
+ "formats": ["deb"],
+ "key-id": "C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654",
+ "key-server": "keyserver.ubuntu.com",
+ "suites": ["focal"],
+ "type": "apt",
+ "url": "http://repo.ros2.org/ubuntu/main",
+ }
+ ],
+ "plugs": {
+ "ros-foxy": {
+ "interface": "content",
+ "content": "ros-foxy",
+ "target": "$SNAP/opt/ros/underlay_ws",
+ "default-provider": meta,
+ }
+ },
+ }
+
+ python_paths = [
+ "$SNAP/opt/ros/foxy/lib/python3.8/site-packages",
+ "$SNAP/usr/lib/python3/dist-packages",
+ "${PYTHONPATH}",
+ "$SNAP/opt/ros/underlay_ws/opt/ros/foxy/lib/python3.8/site-packages",
+ "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages",
+ ]
+
+ assert ros_extension.app_snippet == {
+ "command-chain": ["snap/command-chain/ros2-launch"],
+ "environment": {
+ "PYTHONPATH": ":".join(python_paths),
+ "ROS_VERSION": "2",
+ "ROS_DISTRO": "foxy",
+ },
+ }
+
+ assert ros_extension.part_snippet == {
+ "build-environment": [{"ROS_VERSION": "2"}, {"ROS_DISTRO": "foxy"}],
+ "ros-build-snaps": [meta_dev],
+ "colcon-cmake-args": [
+ f'-DCMAKE_SYSTEM_PREFIX_PATH="/snap/{meta_dev}/current/usr"'
+ ],
+ }
+
+ assert ros_extension.parts == {
+ "ros2-foxy-extension": {
+ "build-packages": [
+ "ros-foxy-ros-environment",
+ "ros-foxy-ros-workspace",
+ "ros-foxy-ament-index-cpp",
+ "ros-foxy-ament-index-python",
+ ],
+ "stage-packages": [
+ "ros-foxy-ros-environment",
+ "ros-foxy-ros-workspace",
+ "ros-foxy-ament-index-cpp",
+ "ros-foxy-ament-index-python",
+ ],
+ "plugin": "make",
+ "source": "$SNAPCRAFT_EXTENSIONS_DIR/ros2",
+ }
+ }
+
+ def test_supported_bases(self, extension_name, extension_class, meta, meta_dev):
+ assert extension_class.get_supported_bases() == ("core20",)
+
+ def test_supported_confinement(
+ self, extension_name, extension_class, meta, meta_dev
+ ):
+ extension_class.get_supported_confinement() == ("strict", "devmode")
diff --git a/tests/spread/extensions/catkin-tools-noetic-meta-hello/task.yaml b/tests/spread/extensions/catkin-tools-noetic-meta-hello/task.yaml
new file mode 100644
index 0000000000..828112480d
--- /dev/null
+++ b/tests/spread/extensions/catkin-tools-noetic-meta-hello/task.yaml
@@ -0,0 +1,76 @@
+summary: Build and run a basic ROS snap using meta-ros extension
+
+kill-timeout: 180m
+
+environment:
+
+ SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1"
+
+ SNAP: catkin-tools-noetic-hello
+ SNAP_DIR: "../../plugins/v2/snaps/$SNAP"
+
+ INTERFACE: ros-noetic
+
+ META_SNAP/catkin_noetic_ros_core: ros-noetic-ros-core
+ EXTENSION/catkin_noetic_ros_core: ros1-noetic-ros-core
+
+ META_SNAP/catkin_noetic_ros_base: ros-noetic-ros-base
+ EXTENSION/catkin_noetic_ros_base: ros1-noetic-ros-base
+
+ META_SNAP/catkin_noetic_robot: ros-noetic-robot
+ EXTENSION/catkin_noetic_robot: ros1-noetic-robot
+
+ META_SNAP/catkin_noetic_desktop: ros-noetic-desktop
+ EXTENSION/catkin_noetic_desktop: ros1-noetic-desktop
+
+# 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-20.04
+ - ubuntu-20.04-64
+ - ubuntu-20.04-amd64
+ - ubuntu-20.04-arm64
+
+prepare: |
+ #shellcheck source=tests/spread/tools/snapcraft-yaml.sh
+ . "$TOOLS_DIR/snapcraft-yaml.sh"
+ set_base "$SNAP_DIR/snap/snapcraft.yaml"
+
+ # Overwrite the extension to test them all out of a single snap
+ sed -i "s|ros1-noetic|${EXTENSION}|" "$SNAP_DIR/snap/snapcraft.yaml"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ create_dpkg_restore_point
+
+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"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ dpkg_restore_point
+
+execute: |
+ cd "$SNAP_DIR"
+
+ # Build what we have and verify the snap runs as expected.
+ snapcraft
+ snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Check that the snap size is fairly small
+ # The non-content sharing snap is ~90M
+ SNAP_SIZE=$(find . -maxdepth 1 -mindepth 1 -name '*_1.0_*.snap' -exec ls -s {} + | cut -d " " -f1)
+ [ "100" -gt "$SNAP_SIZE" ]
+
+ # The default providing snap is installed automatically
+ # snap install "${META_SNAP}"
+
+ snap connect "${SNAP}:${INTERFACE}" "${META_SNAP}:${INTERFACE}"
+ [ "$($SNAP)" = "hello world" ]
diff --git a/tests/spread/extensions/ros-noetic-meta-hello/task.yaml b/tests/spread/extensions/ros-noetic-meta-hello/task.yaml
new file mode 100644
index 0000000000..fc1ce2d070
--- /dev/null
+++ b/tests/spread/extensions/ros-noetic-meta-hello/task.yaml
@@ -0,0 +1,76 @@
+summary: Build and run a basic ROS snap using meta-ros extension
+
+kill-timeout: 180m
+
+environment:
+
+ SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1"
+
+ SNAP: catkin-noetic-hello
+ SNAP_DIR: "../../plugins/v2/snaps/$SNAP"
+
+ INTERFACE: ros-noetic
+
+ META_SNAP/catkin_noetic_ros_core: ros-noetic-ros-core
+ EXTENSION/catkin_noetic_ros_core: ros1-noetic-ros-core
+
+ META_SNAP/catkin_noetic_ros_base: ros-noetic-ros-base
+ EXTENSION/catkin_noetic_ros_base: ros1-noetic-ros-base
+
+ META_SNAP/catkin_noetic_robot: ros-noetic-robot
+ EXTENSION/catkin_noetic_robot: ros1-noetic-robot
+
+ META_SNAP/catkin_noetic_desktop: ros-noetic-desktop
+ EXTENSION/catkin_noetic_desktop: ros1-noetic-desktop
+
+# 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-20.04
+ - ubuntu-20.04-64
+ - ubuntu-20.04-amd64
+ - ubuntu-20.04-arm64
+
+prepare: |
+ #shellcheck source=tests/spread/tools/snapcraft-yaml.sh
+ . "$TOOLS_DIR/snapcraft-yaml.sh"
+ set_base "$SNAP_DIR/snap/snapcraft.yaml"
+
+ # Overwrite the extension to test them all out of a single snap
+ sed -i "s|ros1-noetic|${EXTENSION}|" "$SNAP_DIR/snap/snapcraft.yaml"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ create_dpkg_restore_point
+
+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"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ dpkg_restore_point
+
+execute: |
+ cd "$SNAP_DIR"
+
+ # Build what we have and verify the snap runs as expected.
+ snapcraft
+ snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Check that the snap size is fairly small
+ # The non-content sharing snap is ~90M
+ SNAP_SIZE=$(find . -maxdepth 1 -mindepth 1 -name '*_1.0_*.snap' -exec ls -s {} + | cut -d " " -f1)
+ [ "100" -gt "$SNAP_SIZE" ]
+
+ # The default providing snap is installed automatically
+ # snap install "${META_SNAP}"
+
+ snap connect "${SNAP}:${INTERFACE}" "${META_SNAP}:${INTERFACE}"
+ [ "$($SNAP)" = "hello world" ]
diff --git a/tests/spread/extensions/ros-noetic-meta-talker-listener/task.yaml b/tests/spread/extensions/ros-noetic-meta-talker-listener/task.yaml
new file mode 100644
index 0000000000..d702e84cb5
--- /dev/null
+++ b/tests/spread/extensions/ros-noetic-meta-talker-listener/task.yaml
@@ -0,0 +1,85 @@
+summary: Build and run a basic ROS snap using meta-ros extension
+
+kill-timeout: 180m
+
+environment:
+
+ SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1"
+
+ SNAP: catkin-roslaunch-wrapper
+
+ SNAP_DIR: "../../plugins/v2/snaps/$SNAP"
+
+ INTERFACE: ros-noetic
+
+ META_SNAP/catkin_noetic_ros_core: ros-noetic-ros-core
+ EXTENSION/catkin_noetic_ros_core: ros1-noetic-ros-core
+
+ META_SNAP/catkin_noetic_ros_base: ros-noetic-ros-base
+ EXTENSION/catkin_noetic_ros_base: ros1-noetic-ros-base
+
+ META_SNAP/catkin_noetic_robot: ros-noetic-robot
+ EXTENSION/catkin_noetic_robot: ros1-noetic-robot
+
+ META_SNAP/catkin_noetic_desktop: ros-noetic-desktop
+ EXTENSION/catkin_noetic_desktop: ros1-noetic-desktop
+
+# 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-20.04
+ - ubuntu-20.04-64
+ - ubuntu-20.04-amd64
+ - ubuntu-20.04-arm64
+
+prepare: |
+ #shellcheck source=tests/spread/tools/snapcraft-yaml.sh
+ . "$TOOLS_DIR/snapcraft-yaml.sh"
+ set_base "$SNAP_DIR/snap/snapcraft.yaml"
+
+ # Overwrite the extension to test them all out of a single snap
+ sed -i "s|ros1-noetic|${EXTENSION}|" "$SNAP_DIR/snap/snapcraft.yaml"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ create_dpkg_restore_point
+
+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"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ dpkg_restore_point
+
+execute: |
+ cd "$SNAP_DIR"
+
+ # Build what we have and verify the snap runs as expected.
+ snapcraft
+ snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Check that the snap size is fairly small
+ # The non-content sharing snap is ~90M
+ SNAP_SIZE=$(find . -maxdepth 1 -mindepth 1 -name '*_1.0_*.snap' -exec ls -s {} + | cut -d " " -f1)
+ [ "100" -gt "$SNAP_SIZE" ]
+
+ # The default providing snap is installed automatically
+ # snap install "${META_SNAP}"
+
+ snap connect "${SNAP}:${INTERFACE}" "${META_SNAP}:${INTERFACE}"
+
+ # Regression test for LP: #1660852. Make sure --help actually gets passed to
+ # roslaunch instead of being eaten by setup.sh.
+ "$SNAP" --help | MATCH "Usage: roslaunch"
+
+ # Run the ROS system. By default this will never exit, but the snap supports
+ # an `exit_after_receive` parameter that, if true, will cause the system to
+ # shutdown after the listener has successfully received a message.
+ "$SNAP" exit_after_receive:=true | MATCH "I heard Hello world"
diff --git a/tests/spread/extensions/ros2-foxy-meta-hello/task.yaml b/tests/spread/extensions/ros2-foxy-meta-hello/task.yaml
new file mode 100644
index 0000000000..095005808f
--- /dev/null
+++ b/tests/spread/extensions/ros2-foxy-meta-hello/task.yaml
@@ -0,0 +1,77 @@
+summary: Build and run a basic ROS 2 snap using meta-ros extension
+
+kill-timeout: 180m
+
+environment:
+ SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1"
+
+ SNAP: colcon-ros2-wrapper
+ SNAP_DIR: "../../plugins/v2/snaps/$SNAP"
+
+ INTERFACE: ros-foxy
+
+ META_SNAP/colcon_foxy_ros_core: ros-foxy-ros-core
+ EXTENSION/colcon_foxy_ros_core: ros2-foxy-ros-core
+
+ META_SNAP/colcon_foxy_ros_base: ros-foxy-ros-base
+ EXTENSION/colcon_foxy_ros_base: ros2-foxy-ros-base
+
+ META_SNAP/colcon_foxy_desktop: ros-foxy-desktop
+ EXTENSION/colcon_foxy_desktop: ros2-foxy-desktop
+
+# 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-20.04
+ - ubuntu-20.04-64
+ - ubuntu-20.04-amd64
+ - ubuntu-20.04-arm64
+
+prepare: |
+ #shellcheck source=tests/spread/tools/snapcraft-yaml.sh
+ . "$TOOLS_DIR/snapcraft-yaml.sh"
+ set_base "$SNAP_DIR/snap/snapcraft.yaml"
+
+ # Overwrite the extension to test them all out of a single snap
+ sed -i "s|ros2-foxy|${EXTENSION}|" "$SNAP_DIR/snap/snapcraft.yaml"
+
+ # The snap stages ros2run which will be available through content-sharing
+ sed -i "\|stage-packages|d" "$SNAP_DIR/snap/snapcraft.yaml"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ create_dpkg_restore_point
+
+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"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ dpkg_restore_point
+
+ snap remove --purge "${META_SNAP}"
+
+execute: |
+ cd "$SNAP_DIR"
+
+ # Build what we have and verify the snap runs as expected.
+ snapcraft
+ snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Check that the snap size is fairly small
+ # The non-content sharing snap is ~90M
+ SNAP_SIZE=$(find . -maxdepth 1 -mindepth 1 -name '*_1.0_*.snap' -exec ls -s {} + | cut -d " " -f1)
+ [ "200" -gt "$SNAP_SIZE" ]
+
+ # The default providing snap is installed automatically
+ # snap install "${META_SNAP}"
+
+ snap connect "${SNAP}:${INTERFACE}" "${META_SNAP}:${INTERFACE}"
+ [ "$($SNAP)" = "hello world" ]
diff --git a/tests/spread/extensions/ros2-foxy-meta-talker-listener/task.yaml b/tests/spread/extensions/ros2-foxy-meta-talker-listener/task.yaml
new file mode 100644
index 0000000000..bba345e2b7
--- /dev/null
+++ b/tests/spread/extensions/ros2-foxy-meta-talker-listener/task.yaml
@@ -0,0 +1,78 @@
+summary: Build and run a basic ROS 2 snap using meta-ros extension
+
+kill-timeout: 180m
+
+environment:
+ SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1"
+
+ SNAP: colcon-ros2-talker-listener
+ SNAP_DIR: "../../plugins/v2/snaps/$SNAP"
+
+ INTERFACE: ros-foxy
+
+ META_SNAP/colcon_foxy_ros_core: ros-foxy-ros-core
+ EXTENSION/colcon_foxy_ros_core: ros2-foxy-ros-core
+
+ META_SNAP/colcon_foxy_ros_base: ros-foxy-ros-base
+ EXTENSION/colcon_foxy_ros_base: ros2-foxy-ros-base
+
+ META_SNAP/colcon_foxy_desktop: ros-foxy-desktop
+ EXTENSION/colcon_foxy_desktop: ros2-foxy-desktop
+
+# 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-20.04
+ - ubuntu-20.04-64
+ - ubuntu-20.04-amd64
+ - ubuntu-20.04-arm64
+
+prepare: |
+ #shellcheck source=tests/spread/tools/snapcraft-yaml.sh
+ . "$TOOLS_DIR/snapcraft-yaml.sh"
+ set_base "$SNAP_DIR/snap/snapcraft.yaml"
+
+ # Overwrite the extension to test them all out of a single snap
+ sed -i "s|ros2-foxy|${EXTENSION}|" "$SNAP_DIR/snap/snapcraft.yaml"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ create_dpkg_restore_point
+
+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"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ dpkg_restore_point
+
+ snap remove --purge "${META_SNAP}"
+
+execute: |
+ cd "$SNAP_DIR"
+
+ # Build what we have and verify the snap runs as expected.
+ snapcraft
+ snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Check that the snap size is fairly small
+ # The non-content sharing snap is ~90M
+ SNAP_SIZE=$(find . -maxdepth 1 -mindepth 1 -name '*_1.0_*.snap' -exec ls -s {} + | cut -d " " -f1)
+ [ "600" -gt "$SNAP_SIZE" ]
+
+ # The default providing snap is installed automatically
+ # snap install "${META_SNAP}"
+
+ snap connect "${SNAP}:${INTERFACE}" "${META_SNAP}:${INTERFACE}"
+
+ # Run the ROS system. By default this will never exit, but the snap supports
+ # an `exit_after_receive` parameter that, if true, will cause the system to
+ # shutdown after the listener has successfully received a message.
+ "$SNAP" exit_after_receive:=true | MATCH "I heard: 'Hello, world! 0'"
diff --git a/tests/spread/extensions/ros2-humble-meta-hello/task.yaml b/tests/spread/extensions/ros2-humble-meta-hello/task.yaml
new file mode 100644
index 0000000000..7a0af11f65
--- /dev/null
+++ b/tests/spread/extensions/ros2-humble-meta-hello/task.yaml
@@ -0,0 +1,78 @@
+summary: Build and run a basic ROS 2 snap using meta-ros extension
+
+kill-timeout: 180m
+
+environment:
+
+ SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1"
+
+ SNAP: colcon-ros2-wrapper
+ SNAP_DIR: "../../plugins/craft-parts/build-and-run-hello/$SNAP"
+
+ INTERFACE: ros-humble
+
+ META_SNAP/colcon_humble_ros_core: ros-humble-ros-core
+ EXTENSION/colcon_humble_ros_core: ros2-humble-ros-core
+
+ META_SNAP/colcon_humble_ros_base: ros-humble-ros-base
+ EXTENSION/colcon_humble_ros_base: ros2-humble-ros-base
+
+ META_SNAP/colcon_humble_desktop: ros-humble-desktop
+ EXTENSION/colcon_humble_desktop: ros2-humble-desktop
+
+# 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
+ - ubuntu-22.04-arm64
+
+prepare: |
+ #shellcheck source=tests/spread/tools/snapcraft-yaml.sh
+ . "$TOOLS_DIR/snapcraft-yaml.sh"
+ set_base "$SNAP_DIR/snap/snapcraft.yaml"
+
+ # Overwrite the extension to test them all out of a single snap
+ sed -i "s|ros2-humble|${EXTENSION}|" "$SNAP_DIR/snap/snapcraft.yaml"
+
+ # The snap stages ros2run which will be available through content-sharing
+ sed -i "\|stage-packages|d" "$SNAP_DIR/snap/snapcraft.yaml"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ create_dpkg_restore_point
+
+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"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ dpkg_restore_point
+
+ snap remove --purge "${META_SNAP}"
+
+execute: |
+ cd "$SNAP_DIR"
+
+ # Build what we have and verify the snap runs as expected.
+ snapcraft
+ snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Check that the snap size is fairly small
+ # The non-content sharing snap is ~90M
+ SNAP_SIZE=$(find . -maxdepth 1 -mindepth 1 -name '*_1.0_*.snap' -exec ls -s {} + | cut -d " " -f1)
+ [ "200" -gt "$SNAP_SIZE" ]
+
+ # The default providing snap is installed automatically
+ # snap install "${META_SNAP}"
+
+ snap connect "${SNAP}:${INTERFACE}" "${META_SNAP}:${INTERFACE}"
+ [ "$($SNAP)" = "hello world" ]
diff --git a/tests/spread/extensions/ros2-humble-meta-talker-listener/task.yaml b/tests/spread/extensions/ros2-humble-meta-talker-listener/task.yaml
new file mode 100644
index 0000000000..7c594ad2f8
--- /dev/null
+++ b/tests/spread/extensions/ros2-humble-meta-talker-listener/task.yaml
@@ -0,0 +1,78 @@
+summary: Build and run a basic ROS 2 snap using meta-ros extension
+
+kill-timeout: 180m
+
+environment:
+ SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1"
+
+ SNAP: colcon-talker-listener
+ SNAP_DIR: "../../plugins/craft-parts/$SNAP/$SNAP"
+
+ INTERFACE: ros-humble
+
+ META_SNAP/colcon_humble_ros_core: ros-humble-ros-core
+ EXTENSION/colcon_humble_ros_core: ros2-humble-ros-core
+
+ META_SNAP/colcon_humble_ros_base: ros-humble-ros-base
+ EXTENSION/colcon_humble_ros_base: ros2-humble-ros-base
+
+ META_SNAP/colcon_humble_desktop: ros-humble-desktop
+ EXTENSION/colcon_humble_desktop: ros2-humble-desktop
+
+# 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
+ - ubuntu-22.04-arm64
+
+prepare: |
+ #shellcheck source=tests/spread/tools/snapcraft-yaml.sh
+ . "$TOOLS_DIR/snapcraft-yaml.sh"
+ set_base "$SNAP_DIR/snap/snapcraft.yaml"
+
+ # Overwrite the extension to test them all out of a single snap
+ sed -i "s|ros2-humble|${EXTENSION}|" "$SNAP_DIR/snap/snapcraft.yaml"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ create_dpkg_restore_point
+
+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"
+
+ #shellcheck source=tests/spread/tools/package-utils.sh
+ . "$TOOLS_DIR/package-utils.sh"
+ dpkg_restore_point
+
+ snap remove --purge "${META_SNAP}"
+
+execute: |
+ cd "$SNAP_DIR"
+
+ # Build what we have and verify the snap runs as expected.
+ snapcraft
+ snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Check that the snap size is fairly small
+ # The non-content sharing snap is ~90M
+ SNAP_SIZE=$(find . -maxdepth 1 -mindepth 1 -name '*_1.0_*.snap' -exec ls -s {} + | cut -d " " -f1)
+ [ "800" -gt "$SNAP_SIZE" ]
+
+ # The default providing snap is installed automatically
+ # snap install "${META_SNAP}"
+
+ snap connect "${SNAP}:${INTERFACE}" "${META_SNAP}:${INTERFACE}"
+
+ # Run the ROS system. By default this will never exit, but the snap supports
+ # an `exit_after_receive` parameter that, if true, will cause the system to
+ # shutdown after the listener has successfully received a message.
+ "$SNAP" exit_after_receive:=true | MATCH "I heard: 'Hello, world! 0'"
diff --git a/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/CMakeLists.txt b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/CMakeLists.txt
new file mode 100644
index 0000000000..55b98b7cd4
--- /dev/null
+++ b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/CMakeLists.txt
@@ -0,0 +1,37 @@
+cmake_minimum_required(VERSION 3.5)
+project(colcon-ros2-talker-listener)
+
+# Default to C++14
+if(NOT CMAKE_CXX_STANDARD)
+ set(CMAKE_CXX_STANDARD 14)
+endif()
+
+if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ add_compile_options(-Wall -Wextra -Wpedantic)
+endif()
+
+# find dependencies
+find_package(ament_cmake REQUIRED)
+find_package(rclcpp REQUIRED)
+find_package(std_msgs REQUIRED)
+
+if(NOT WIN32)
+ ament_environment_hooks(
+ "${ament_cmake_package_templates_ENVIRONMENT_HOOK_LIBRARY_PATH}"
+ )
+endif()
+
+add_executable(talker src/publisher_member_function.cpp)
+ament_target_dependencies(talker rclcpp std_msgs)
+
+add_executable(listener src/subscriber_member_function.cpp)
+ament_target_dependencies(listener rclcpp std_msgs)
+
+install(
+ TARGETS talker listener
+ DESTINATION lib/${PROJECT_NAME}
+)
+
+install(DIRECTORY launch DESTINATION share/${PROJECT_NAME}/)
+
+ament_package()
diff --git a/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/launch/talker_listener.launch.py b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/launch/talker_listener.launch.py
new file mode 100644
index 0000000000..97685ac468
--- /dev/null
+++ b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/launch/talker_listener.launch.py
@@ -0,0 +1,28 @@
+import launch
+import launch_ros.actions
+from launch.actions import DeclareLaunchArgument
+from launch.substitutions import LaunchConfiguration
+
+
+def generate_launch_description():
+ return launch.LaunchDescription(
+ [
+ launch_ros.actions.Node(
+ package="colcon-ros2-talker-listener",
+ executable="talker",
+ name="talker",
+ output="screen",
+ ),
+ DeclareLaunchArgument("exit_after_receive", default_value="False"),
+ launch_ros.actions.Node(
+ package="colcon-ros2-talker-listener",
+ executable="listener",
+ name="listener",
+ output="screen",
+ parameters=[
+ {"exit_after_receive": LaunchConfiguration("exit_after_receive")}
+ ],
+ on_exit=launch.actions.Shutdown(),
+ ),
+ ]
+ )
diff --git a/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/package.xml b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/package.xml
new file mode 100644
index 0000000000..6b2b7e4be5
--- /dev/null
+++ b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/package.xml
@@ -0,0 +1,23 @@
+
+
+
+ colcon-ros2-talker-listener
+ 0.0.0
+ A talker listener demo
+ ubuntu-robotics
+ GPLv3
+
+ ament_cmake
+
+ rclcpp
+ std_msgs
+
+ ros2launch
+
+ ament_lint_auto
+ ament_lint_common
+
+
+ ament_cmake
+
+
diff --git a/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/snap/snapcraft.yaml b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/snap/snapcraft.yaml
new file mode 100644
index 0000000000..98fe315535
--- /dev/null
+++ b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/snap/snapcraft.yaml
@@ -0,0 +1,21 @@
+name: colcon-talker-listener
+version: "1.0"
+summary: hello world
+description: |
+ A ROS 2 talker-listener.
+
+grade: stable
+confinement: strict
+base: core22
+
+apps:
+ colcon-talker-listener:
+ command: ros2 launch colcon-ros2-talker-listener talker_listener.launch.py
+ plugs: [network, network-bind]
+ extensions: [ros2-humble]
+
+parts:
+ hello:
+ plugin: colcon
+ source: .
+ build-packages: [g++, make]
diff --git a/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/src/publisher_member_function.cpp b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/src/publisher_member_function.cpp
new file mode 100644
index 0000000000..c8d2ebfe00
--- /dev/null
+++ b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/src/publisher_member_function.cpp
@@ -0,0 +1,56 @@
+// Copyright 2016 Open Source Robotics Foundation, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+#include
+
+#include "rclcpp/rclcpp.hpp"
+#include "std_msgs/msg/string.hpp"
+
+using namespace std::chrono_literals;
+
+/* This example creates a subclass of Node and uses std::bind() to register a
+ * member function as a callback from the timer. */
+
+class MinimalPublisher : public rclcpp::Node
+{
+public:
+ MinimalPublisher()
+ : Node("minimal_publisher"), count_(0)
+ {
+ publisher_ = this->create_publisher("topic", 10);
+ timer_ = this->create_wall_timer(
+ 500ms, std::bind(&MinimalPublisher::timer_callback, this));
+ }
+
+private:
+ void timer_callback()
+ {
+ auto message = std_msgs::msg::String();
+ message.data = "Hello, world! " + std::to_string(count_++);
+ RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
+ publisher_->publish(message);
+ }
+ rclcpp::TimerBase::SharedPtr timer_;
+ rclcpp::Publisher::SharedPtr publisher_;
+ size_t count_;
+};
+
+int main(int argc, char * argv[])
+{
+ rclcpp::init(argc, argv);
+ rclcpp::spin(std::make_shared());
+ rclcpp::shutdown();
+ return 0;
+}
diff --git a/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/src/subscriber_member_function.cpp b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/src/subscriber_member_function.cpp
new file mode 100644
index 0000000000..655a4ffd57
--- /dev/null
+++ b/tests/spread/plugins/craft-parts/colcon-talker-listener/colcon-talker-listener/src/subscriber_member_function.cpp
@@ -0,0 +1,51 @@
+// Copyright 2016 Open Source Robotics Foundation, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+
+#include "rclcpp/rclcpp.hpp"
+#include "std_msgs/msg/string.hpp"
+using std::placeholders::_1;
+
+class MinimalSubscriber : public rclcpp::Node
+{
+public:
+ MinimalSubscriber()
+ : Node("minimal_subscriber")
+ {
+ subscription_ = this->create_subscription(
+ "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
+
+ this->declare_parameter("exit_after_receive", false);
+ }
+
+private:
+ void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
+ {
+ RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
+
+ if (this->get_parameter("exit_after_receive").as_bool()) {
+ rclcpp::shutdown();
+ }
+ }
+ rclcpp::Subscription::SharedPtr subscription_;
+};
+
+int main(int argc, char * argv[])
+{
+ rclcpp::init(argc, argv);
+ rclcpp::spin(std::make_shared());
+ rclcpp::shutdown();
+ return 0;
+}
diff --git a/tests/spread/plugins/craft-parts/colcon-talker-listener/task.yaml b/tests/spread/plugins/craft-parts/colcon-talker-listener/task.yaml
new file mode 100644
index 0000000000..16efdf61a3
--- /dev/null
+++ b/tests/spread/plugins/craft-parts/colcon-talker-listener/task.yaml
@@ -0,0 +1,38 @@
+summary: Build and run a basic colcon snap
+
+kill-timeout: 180m
+
+environment:
+ SNAP/catkin_ros2_run: colcon-talker-listener
+
+systems:
+ - ubuntu-22.04
+ - ubuntu-22.04-64
+ - ubuntu-22.04-amd64
+ - ubuntu-22.04-arm64
+
+prepare: |
+ #shellcheck source=tests/spread/tools/snapcraft-yaml.sh
+ . "$TOOLS_DIR/snapcraft-yaml.sh"
+ set_base "$SNAP/snap/snapcraft.yaml"
+
+restore: |
+ cd "$SNAP"
+ 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"
+
+ # Build what and install the snap
+ snapcraft
+ snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Run the ROS system. By default this will never exit, but the snap supports
+ # an `exit_after_receive` parameter that, if true, will cause the system to
+ # shutdown after the listener has successfully received a message.
+ "$SNAP" exit_after_receive:=true | MATCH "I heard: 'Hello, world! 0'"
diff --git a/tests/spread/plugins/v2/colcon-ros2-run/task.yaml b/tests/spread/plugins/v2/colcon-ros2-run/task.yaml
new file mode 100644
index 0000000000..4af02d594f
--- /dev/null
+++ b/tests/spread/plugins/v2/colcon-ros2-run/task.yaml
@@ -0,0 +1,38 @@
+summary: Build and run a basic colcon snap
+
+kill-timeout: 180m
+
+environment:
+ SNAP/catkin_ros2_run: colcon-ros2-talker-listener
+
+systems:
+ - ubuntu-20.04
+ - ubuntu-20.04-64
+ - ubuntu-20.04-amd64
+ - ubuntu-20.04-arm64
+
+prepare: |
+ #shellcheck source=tests/spread/tools/snapcraft-yaml.sh
+ . "$TOOLS_DIR/snapcraft-yaml.sh"
+ set_base "../snaps/$SNAP/snap/snapcraft.yaml"
+
+restore: |
+ cd "../snaps/$SNAP"
+ 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 "../snaps/$SNAP"
+
+ # Build what and install the snap
+ snapcraft
+ snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Run the ROS system. By default this will never exit, but the snap supports
+ # an `exit_after_receive` parameter that, if true, will cause the system to
+ # shutdown after the listener has successfully received a message.
+ "$SNAP" exit_after_receive:=true | MATCH "I heard: 'Hello, world! 0'"
diff --git a/tests/spread/plugins/v2/ros1-hello/task.yaml b/tests/spread/plugins/v2/ros1-hello/task.yaml
index 0150d966a1..64cc610f52 100644
--- a/tests/spread/plugins/v2/ros1-hello/task.yaml
+++ b/tests/spread/plugins/v2/ros1-hello/task.yaml
@@ -10,6 +10,9 @@ environment:
SNAP/catkin_tools_noetic_hello: catkin-tools-noetic-hello
SNAP/catkin_tools_noetic_subdir: catkin-tools-noetic-subdir
SNAP/catkin_rosrun_wrapper: catkin-rosrun-wrapper
+ SNAP/catkin_noetic_content_sharing: catkin-noetic-content-sharing
+
+ SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS/catkin_noetic_content_sharing: "1"
systems:
- ubuntu-20.04
@@ -40,6 +43,24 @@ execute: |
# Build what we have and verify the snap runs as expected.
snapcraft
snap install "${SNAP}"_1.0_*.snap --dangerous
+
+ # Check specific to content sharing SNAP
+ if [ "$SNAP" = "catkin-noetic-content-sharing" ]; then
+ # Make sure final snap doesn't contain roscpp or roslaunch pkg
+ for to_check in \
+ "/snap/$SNAP/current/opt/ros/noetic/share/roscpp" \
+ "/snap/$SNAP/current/opt/ros/noetic/share/roslaunch" \
+ "/snap/$SNAP/current/usr/lib/x86_64-linux-gnu/libboost_filesystem.so.1.71.0"
+ do
+ if [ -e "$to_check" ]; then
+ FATAL "The $SNAP is containing $to_check from the content-sharing snap"
+ fi
+ done
+
+ # Connect the content sharing snap
+ snap connect "${SNAP}":ros-noetic ros-noetic-ros-core
+ fi
+
[ "$($SNAP)" = "hello world" ]
# Clean the hello part, then build and run again.
diff --git a/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/snap/snapcraft.yaml b/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/snap/snapcraft.yaml
new file mode 100644
index 0000000000..c35dab0fca
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/snap/snapcraft.yaml
@@ -0,0 +1,21 @@
+name: catkin-noetic-content-sharing
+version: "1.0"
+summary: hello world
+description: |
+ A ROS 1 roscpp-based workspace.
+
+grade: stable
+confinement: strict
+base: core20
+
+apps:
+ catkin-noetic-content-sharing:
+ command: opt/ros/noetic/lib/snapcraft_hello/snapcraft_hello
+ plugs: [network, network-bind]
+ extensions: [ros1-noetic-ros-core]
+
+parts:
+ hello:
+ plugin: catkin
+ source: .
+ build-packages: [g++, make]
diff --git a/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/src/snapcraft_hello/CMakeLists.txt b/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/src/snapcraft_hello/CMakeLists.txt
new file mode 100644
index 0000000000..5102c8946f
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/src/snapcraft_hello/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.0.2)
+project(snapcraft_hello)
+
+find_package(catkin REQUIRED COMPONENTS
+ roscpp
+)
+
+find_package( Boost REQUIRED COMPONENTS
+ filesystem
+)
+
+catkin_package()
+
+include_directories(
+ ${catkin_INCLUDE_DIRS}
+ ${Boost_INCLUDE_DIRS}
+)
+
+add_executable(${PROJECT_NAME} src/hello.cpp)
+
+target_link_libraries(${PROJECT_NAME}
+ ${catkin_LIBRARIES}
+ ${Boost_LIBRARIES}
+)
+
+install(TARGETS ${PROJECT_NAME}
+ RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
+)
diff --git a/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/src/snapcraft_hello/package.xml b/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/src/snapcraft_hello/package.xml
new file mode 100644
index 0000000000..c4e3788aa9
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/src/snapcraft_hello/package.xml
@@ -0,0 +1,15 @@
+
+
+ snapcraft_hello
+ 0.0.1
+ snapcraft test for roscpp
+ me
+ GPLv3
+ catkin
+ roscpp
+ fake_package_that_does_not_exists
+ libboost-filesystem-dev
+ roscpp
+ roscpp
+ libboost-filesystem
+
diff --git a/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/src/snapcraft_hello/src/hello.cpp b/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/src/snapcraft_hello/src/hello.cpp
new file mode 100644
index 0000000000..dae269b001
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/catkin-noetic-content-sharing/src/snapcraft_hello/src/hello.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 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 .
+
+#include
+
+#include
+
+#include
+
+int main(int argc, char * argv[])
+{
+ ros::init(argc, argv, "snapcraft_hello");
+ if(! boost::filesystem::exists("FileDoesntExist"))
+ std::cout << "hello world" << std::endl;
+ ros::shutdown();
+ return 0;
+}
diff --git a/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/CMakeLists.txt b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/CMakeLists.txt
new file mode 100644
index 0000000000..55b98b7cd4
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/CMakeLists.txt
@@ -0,0 +1,37 @@
+cmake_minimum_required(VERSION 3.5)
+project(colcon-ros2-talker-listener)
+
+# Default to C++14
+if(NOT CMAKE_CXX_STANDARD)
+ set(CMAKE_CXX_STANDARD 14)
+endif()
+
+if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ add_compile_options(-Wall -Wextra -Wpedantic)
+endif()
+
+# find dependencies
+find_package(ament_cmake REQUIRED)
+find_package(rclcpp REQUIRED)
+find_package(std_msgs REQUIRED)
+
+if(NOT WIN32)
+ ament_environment_hooks(
+ "${ament_cmake_package_templates_ENVIRONMENT_HOOK_LIBRARY_PATH}"
+ )
+endif()
+
+add_executable(talker src/publisher_member_function.cpp)
+ament_target_dependencies(talker rclcpp std_msgs)
+
+add_executable(listener src/subscriber_member_function.cpp)
+ament_target_dependencies(listener rclcpp std_msgs)
+
+install(
+ TARGETS talker listener
+ DESTINATION lib/${PROJECT_NAME}
+)
+
+install(DIRECTORY launch DESTINATION share/${PROJECT_NAME}/)
+
+ament_package()
diff --git a/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/launch/talker_listener.launch.py b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/launch/talker_listener.launch.py
new file mode 100644
index 0000000000..97685ac468
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/launch/talker_listener.launch.py
@@ -0,0 +1,28 @@
+import launch
+import launch_ros.actions
+from launch.actions import DeclareLaunchArgument
+from launch.substitutions import LaunchConfiguration
+
+
+def generate_launch_description():
+ return launch.LaunchDescription(
+ [
+ launch_ros.actions.Node(
+ package="colcon-ros2-talker-listener",
+ executable="talker",
+ name="talker",
+ output="screen",
+ ),
+ DeclareLaunchArgument("exit_after_receive", default_value="False"),
+ launch_ros.actions.Node(
+ package="colcon-ros2-talker-listener",
+ executable="listener",
+ name="listener",
+ output="screen",
+ parameters=[
+ {"exit_after_receive": LaunchConfiguration("exit_after_receive")}
+ ],
+ on_exit=launch.actions.Shutdown(),
+ ),
+ ]
+ )
diff --git a/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/package.xml b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/package.xml
new file mode 100644
index 0000000000..6b2b7e4be5
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/package.xml
@@ -0,0 +1,23 @@
+
+
+
+ colcon-ros2-talker-listener
+ 0.0.0
+ A talker listener demo
+ ubuntu-robotics
+ GPLv3
+
+ ament_cmake
+
+ rclcpp
+ std_msgs
+
+ ros2launch
+
+ ament_lint_auto
+ ament_lint_common
+
+
+ ament_cmake
+
+
diff --git a/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/snap/snapcraft.yaml b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/snap/snapcraft.yaml
new file mode 100644
index 0000000000..e2476e8bfb
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/snap/snapcraft.yaml
@@ -0,0 +1,21 @@
+name: colcon-ros2-talker-listener
+version: "1.0"
+summary: hello world
+description: |
+ A ROS 2 talker-listener.
+
+grade: stable
+confinement: strict
+base: core20
+
+apps:
+ colcon-ros2-talker-listener:
+ command: ros2 launch colcon-ros2-talker-listener talker_listener.launch.py
+ plugs: [network, network-bind]
+ extensions: [ros2-foxy]
+
+parts:
+ hello:
+ plugin: colcon
+ source: .
+ build-packages: [g++, make]
diff --git a/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/src/publisher_member_function.cpp b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/src/publisher_member_function.cpp
new file mode 100644
index 0000000000..c8d2ebfe00
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/src/publisher_member_function.cpp
@@ -0,0 +1,56 @@
+// Copyright 2016 Open Source Robotics Foundation, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+#include
+
+#include "rclcpp/rclcpp.hpp"
+#include "std_msgs/msg/string.hpp"
+
+using namespace std::chrono_literals;
+
+/* This example creates a subclass of Node and uses std::bind() to register a
+ * member function as a callback from the timer. */
+
+class MinimalPublisher : public rclcpp::Node
+{
+public:
+ MinimalPublisher()
+ : Node("minimal_publisher"), count_(0)
+ {
+ publisher_ = this->create_publisher("topic", 10);
+ timer_ = this->create_wall_timer(
+ 500ms, std::bind(&MinimalPublisher::timer_callback, this));
+ }
+
+private:
+ void timer_callback()
+ {
+ auto message = std_msgs::msg::String();
+ message.data = "Hello, world! " + std::to_string(count_++);
+ RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
+ publisher_->publish(message);
+ }
+ rclcpp::TimerBase::SharedPtr timer_;
+ rclcpp::Publisher::SharedPtr publisher_;
+ size_t count_;
+};
+
+int main(int argc, char * argv[])
+{
+ rclcpp::init(argc, argv);
+ rclcpp::spin(std::make_shared());
+ rclcpp::shutdown();
+ return 0;
+}
diff --git a/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/src/subscriber_member_function.cpp b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/src/subscriber_member_function.cpp
new file mode 100644
index 0000000000..655a4ffd57
--- /dev/null
+++ b/tests/spread/plugins/v2/snaps/colcon-ros2-talker-listener/src/subscriber_member_function.cpp
@@ -0,0 +1,51 @@
+// Copyright 2016 Open Source Robotics Foundation, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+
+#include "rclcpp/rclcpp.hpp"
+#include "std_msgs/msg/string.hpp"
+using std::placeholders::_1;
+
+class MinimalSubscriber : public rclcpp::Node
+{
+public:
+ MinimalSubscriber()
+ : Node("minimal_subscriber")
+ {
+ subscription_ = this->create_subscription(
+ "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
+
+ this->declare_parameter("exit_after_receive", false);
+ }
+
+private:
+ void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
+ {
+ RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
+
+ if (this->get_parameter("exit_after_receive").as_bool()) {
+ rclcpp::shutdown();
+ }
+ }
+ rclcpp::Subscription::SharedPtr subscription_;
+};
+
+int main(int argc, char * argv[])
+{
+ rclcpp::init(argc, argv);
+ rclcpp::spin(std::make_shared());
+ rclcpp::shutdown();
+ return 0;
+}
diff --git a/tests/unit/commands/test_list_extensions.py b/tests/unit/commands/test_list_extensions.py
index 2fef48836d..3ea1536796 100644
--- a/tests/unit/commands/test_list_extensions.py
+++ b/tests/unit/commands/test_list_extensions.py
@@ -30,21 +30,32 @@ def test_command(emitter, command):
emitter.assert_message(
dedent(
"""\
- Extension name Supported bases
- ---------------- ----------------------
- fake-extension core22
- flutter-beta core18
- flutter-dev core18
- flutter-master core18
- flutter-stable core18
- gnome core22
- gnome-3-28 core18
- gnome-3-34 core18
- gnome-3-38 core20
- kde-neon core18, core20, core22
- ros1-noetic core20
- ros2-foxy core20
- ros2-humble core22"""
+ Extension name Supported bases
+ ---------------------- ----------------------
+ fake-extension core22
+ flutter-beta core18
+ flutter-dev core18
+ flutter-master core18
+ flutter-stable core18
+ gnome core22
+ gnome-3-28 core18
+ gnome-3-34 core18
+ gnome-3-38 core20
+ kde-neon core18, core20, core22
+ ros1-noetic core20
+ ros1-noetic-desktop core20
+ ros1-noetic-perception core20
+ ros1-noetic-robot core20
+ ros1-noetic-ros-base core20
+ ros1-noetic-ros-core core20
+ ros2-foxy core20
+ ros2-foxy-desktop core20
+ ros2-foxy-ros-base core20
+ ros2-foxy-ros-core core20
+ ros2-humble core22
+ ros2-humble-desktop core22
+ ros2-humble-ros-base core22
+ ros2-humble-ros-core core22"""
)
)
@@ -57,19 +68,30 @@ def test_command_extension_dups(emitter, command):
emitter.assert_message(
dedent(
"""\
- Extension name Supported bases
- ---------------- ----------------------
- flutter-beta core18
- flutter-dev core18
- flutter-master core18
- flutter-stable core18
- gnome core22
- gnome-3-28 core18
- gnome-3-34 core18
- gnome-3-38 core20
- kde-neon core18, core20, core22
- ros1-noetic core20
- ros2-foxy core20
- ros2-humble core22"""
+ Extension name Supported bases
+ ---------------------- ----------------------
+ flutter-beta core18
+ flutter-dev core18
+ flutter-master core18
+ flutter-stable core18
+ gnome core22
+ gnome-3-28 core18
+ gnome-3-34 core18
+ gnome-3-38 core20
+ kde-neon core18, core20, core22
+ ros1-noetic core20
+ ros1-noetic-desktop core20
+ ros1-noetic-perception core20
+ ros1-noetic-robot core20
+ ros1-noetic-ros-base core20
+ ros1-noetic-ros-core core20
+ ros2-foxy core20
+ ros2-foxy-desktop core20
+ ros2-foxy-ros-base core20
+ ros2-foxy-ros-core core20
+ ros2-humble core22
+ ros2-humble-desktop core22
+ ros2-humble-ros-base core22
+ ros2-humble-ros-core core22"""
)
)
diff --git a/tests/unit/extensions/test_registry.py b/tests/unit/extensions/test_registry.py
index 118e815171..1e3c5385db 100644
--- a/tests/unit/extensions/test_registry.py
+++ b/tests/unit/extensions/test_registry.py
@@ -26,6 +26,9 @@ def test_get_extension_names():
assert extensions.get_extension_names() == [
"gnome",
"ros2-humble",
+ "ros2-humble-ros-core",
+ "ros2-humble-ros-base",
+ "ros2-humble-desktop",
"kde-neon",
"fake-extension-experimental",
"fake-extension-extra",
diff --git a/tests/unit/parts/extensions/test_ros2_humble_meta.py b/tests/unit/parts/extensions/test_ros2_humble_meta.py
new file mode 100644
index 0000000000..39b5e4827e
--- /dev/null
+++ b/tests/unit/parts/extensions/test_ros2_humble_meta.py
@@ -0,0 +1,191 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 2020 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 import errors
+from snapcraft.extensions import registry
+from snapcraft.extensions.extension import get_extensions_data_dir
+from snapcraft.extensions.ros2_humble_desktop import ROS2HumbleDesktopExtension
+from snapcraft.extensions.ros2_humble_ros_base import ROS2HumbleRosBaseExtension
+from snapcraft.extensions.ros2_humble_ros_core import ROS2HumbleRosCoreExtension
+
+
+def setup_method_fixture(extension, yaml_data=None, arch=None, target_arch=None):
+ return extension(yaml_data=yaml_data, arch=arch, target_arch=target_arch)
+
+
+class TestExtensionROS2HumbleMetaExtensions:
+ """ROS 2 Humble meta extensions tests."""
+
+ fixture_variables = "extension_name,extension_class,meta,meta_dev"
+ fixture_values = [
+ (
+ "ros2-humble-desktop",
+ ROS2HumbleDesktopExtension,
+ "ros-humble-desktop",
+ "ros-humble-desktop-dev",
+ ),
+ (
+ "ros2-humble-ros-base",
+ ROS2HumbleRosBaseExtension,
+ "ros-humble-ros-base",
+ "ros-humble-ros-base-dev",
+ ),
+ (
+ "ros2-humble-ros-core",
+ ROS2HumbleRosCoreExtension,
+ "ros-humble-ros-core",
+ "ros-humble-ros-core-dev",
+ ),
+ ]
+
+ @pytest.mark.parametrize(fixture_variables, fixture_values)
+ def test_is_registered(self, extension_name, extension_class, meta, meta_dev):
+ assert extension_name in registry.get_extension_names()
+
+ try:
+ registry.get_extension_class(extension_name)
+ except errors.ExtensionError as exc:
+ raise AssertionError(f"Couldn't get extension '{extension_name}': {exc}")
+
+ @pytest.mark.parametrize(fixture_variables, fixture_values)
+ def test_experimental(self, extension_name, extension_class, meta, meta_dev):
+ extension = setup_method_fixture(extension_class)
+ assert extension.is_experimental(None)
+
+ @pytest.mark.parametrize(fixture_variables, fixture_values)
+ def test_ros_version(self, extension_name, extension_class, meta, meta_dev):
+ extension = setup_method_fixture(extension_class)
+ assert extension.ROS_VERSION == "2"
+
+ @pytest.mark.parametrize(fixture_variables, fixture_values)
+ def test_get_supported_bases(self, extension_name, extension_class, meta, meta_dev):
+ extension = setup_method_fixture(extension_class)
+ assert extension.get_supported_bases() == ("core22",)
+
+ @pytest.mark.parametrize(fixture_variables, fixture_values)
+ def test_get_supported_confinement(
+ self, extension_name, extension_class, meta, meta_dev
+ ):
+ extension = setup_method_fixture(extension_class)
+ assert extension.get_supported_confinement() == ("strict", "devmode")
+
+ @pytest.mark.parametrize(fixture_variables, fixture_values)
+ def test_get_root_snippet(self, extension_name, extension_class, meta, meta_dev):
+ extension = setup_method_fixture(extension_class)
+ assert extension.get_root_snippet() == {
+ "package-repositories": [
+ {
+ "type": "apt",
+ "url": "http://repo.ros2.org/ubuntu/main",
+ "components": ["main"],
+ "formats": ["deb"],
+ "key-id": "C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654",
+ "key-server": "keyserver.ubuntu.com",
+ "suites": ["jammy"],
+ }
+ ],
+ "lint": {
+ "ignore": [
+ {
+ "unused-library": [
+ "opt/ros/*",
+ "lib/*/libcrypt.so*",
+ "lib/*/libexpat.so*",
+ "lib/*/libtirpc.so*",
+ "lib/*/libz.so*",
+ "usr/lib/*libatomic.so*",
+ "usr/lib/*libconsole_bridge.so*",
+ "usr/lib/*libfmt.so*",
+ "usr/lib/*libicui18n.so*",
+ "usr/lib/*libicuio.so*",
+ "usr/lib/*libicutest.so*",
+ "usr/lib/*libicutu.so*",
+ "usr/lib/*libpython3.10.so*",
+ "usr/lib/*libspdlog.so*",
+ "usr/lib/*libtinyxml2.so*",
+ ]
+ }
+ ]
+ },
+ "plugs": {
+ "ros-humble": {
+ "content": "ros-humble",
+ "default-provider": meta,
+ "interface": "content",
+ "target": "$SNAP/opt/ros/underlay_ws",
+ }
+ },
+ }
+
+ @pytest.mark.parametrize(fixture_variables, fixture_values)
+ def test_get_app_snippet(self, extension_name, extension_class, meta, meta_dev):
+ python_paths = [
+ "$SNAP/opt/ros/humble/lib/python3.10/site-packages",
+ "$SNAP/usr/lib/python3/dist-packages",
+ "${PYTHONPATH}",
+ "$SNAP/opt/ros/underlay_ws/opt/ros/humble/lib/python3.10/site-packages",
+ "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages",
+ ]
+ extension = setup_method_fixture(extension_class)
+ assert extension.get_app_snippet() == {
+ "command-chain": ["snap/command-chain/ros2-launch"],
+ "environment": {
+ "ROS_VERSION": "2",
+ "ROS_DISTRO": "humble",
+ "PYTHONPATH": ":".join(python_paths),
+ "ROS_HOME": "$SNAP_USER_DATA/ros",
+ },
+ }
+
+ @pytest.mark.parametrize(fixture_variables, fixture_values)
+ def test_get_part_snippet(self, extension_name, extension_class, meta, meta_dev):
+ extension = setup_method_fixture(extension_class)
+ assert extension.get_part_snippet(plugin_name="colcon") == {
+ "build-environment": [{"ROS_VERSION": "2"}, {"ROS_DISTRO": "humble"}],
+ "colcon-ros-build-snaps": [meta_dev],
+ "colcon-cmake-args": [
+ f'-DCMAKE_SYSTEM_PREFIX_PATH="/snap/{meta_dev}/current/usr"'
+ ],
+ }
+
+ assert extension.get_part_snippet(plugin_name="cmake") == {
+ "build-environment": [{"ROS_VERSION": "2"}, {"ROS_DISTRO": "humble"}],
+ }
+
+ @pytest.mark.parametrize(fixture_variables, fixture_values)
+ def test_get_parts_snippet(self, extension_name, extension_class, meta, meta_dev):
+ extension = setup_method_fixture(extension_class)
+ assert extension.get_parts_snippet() == {
+ f"{extension_name}/ros2-launch": {
+ "source": f"{get_extensions_data_dir()}/ros2",
+ "plugin": "make",
+ "build-packages": [
+ "ros-humble-ros-environment",
+ "ros-humble-ros-workspace",
+ "ros-humble-ament-index-cpp",
+ "ros-humble-ament-index-python",
+ "libpython3.10-dev",
+ ],
+ "stage-packages": [
+ "ros-humble-ros-environment",
+ "ros-humble-ros-workspace",
+ "ros-humble-ament-index-cpp",
+ "ros-humble-ament-index-python",
+ ],
+ }
+ }
diff --git a/tests/unit/parts/plugins/test_colcon.py b/tests/unit/parts/plugins/test_colcon.py
index a37c025dc4..2e27a13c4c 100644
--- a/tests/unit/parts/plugins/test_colcon.py
+++ b/tests/unit/parts/plugins/test_colcon.py
@@ -74,6 +74,19 @@ def test_property_default(self):
except ValidationError as e:
raise AssertionError(f"{e}") from e
+ def test_property_unexpected(self):
+ try:
+ colcon.ColconPlugin.properties_class( # noqa F841
+ source="." # type: ignore
+ )
+ except ValidationError as e:
+ raise AssertionError(f"{e}") from e
+
+ with pytest.raises(ValidationError):
+ colcon.ColconPlugin.properties_class( # noqa F841
+ source=".", foo="bar" # type: ignore
+ )
+
def test_property_all(self):
try:
properties = colcon.ColconPlugin.properties_class.unmarshal(
@@ -84,6 +97,7 @@ def test_property_all(self):
"colcon-cmake-args": ["cmake", "args..."],
"colcon-packages": ["package1", "package2..."],
"colcon-packages-ignore": ["ipackage1", "ipackage2..."],
+ "colcon-ros-build-snaps": ["ros-core"],
}
)
except ValidationError as e:
@@ -95,6 +109,7 @@ def test_property_all(self):
assert properties.colcon_cmake_args == ["cmake", "args..."] # type: ignore
assert properties.colcon_packages == ["package1", "package2..."] # type: ignore
assert properties.colcon_packages_ignore == ["ipackage1", "ipackage2..."] # type: ignore
+ assert properties.colcon_ros_build_snaps == ["ros-core"] # type: ignore
def test_get_build_packages(self, setup_method_fixture, new_dir):
plugin = setup_method_fixture(new_dir)
@@ -104,8 +119,29 @@ def test_get_build_packages(self, setup_method_fixture, new_dir):
"python3-rosdep",
"python3-rosinstall",
"python3-wstool",
+ "rospack-tools",
}
+ def test_get_build_snaps(self, setup_method_fixture, new_dir):
+ plugin = setup_method_fixture(new_dir)
+
+ assert plugin.get_build_snaps() == set()
+
+ plugin = setup_method_fixture(
+ new_dir,
+ properties={
+ "source": ".",
+ "colcon-ament-cmake-args": [],
+ "colcon-catkin-cmake-args": [],
+ "colcon-cmake-args": [],
+ "colcon-packages": [],
+ "colcon-packages-ignore": [],
+ "colcon-ros-build-snaps": ["Foo"],
+ },
+ )
+
+ assert plugin.get_build_snaps() == {"Foo"}
+
def test_get_build_environment(self, setup_method_fixture, new_dir):
plugin = setup_method_fixture(new_dir)
@@ -119,6 +155,7 @@ def test_out_of_source_build_property(self):
assert colcon.ColconPlugin.get_out_of_source_build
def test_get_build_commands(self, setup_method_fixture, new_dir, monkeypatch):
+ # pylint: disable=line-too-long
plugin = setup_method_fixture(new_dir)
monkeypatch.setattr(sys, "path", ["", "/test"])
@@ -127,41 +164,62 @@ def test_get_build_commands(self, setup_method_fixture, new_dir, monkeypatch):
monkeypatch.setattr(os, "environ", {})
assert plugin.get_build_commands() == [
+ "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
+ "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
+ 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . '
- '"${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ 'AMENT_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/snap" . '
- '"${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/snap" . "${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
- '. "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ 'if [ -f "/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/opt/ros/snap" . "/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
'eval "${state}"',
- "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
- "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
- 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
- "rosdep install --default-yes --ignore-packages-from-source "
- '--from-paths "${CRAFT_PART_SRC_WORK}"',
+ 'rm -f "${CRAFT_PART_INSTALL}/.installed_packages.txt"',
+ 'rm -f "${CRAFT_PART_INSTALL}/.build_snaps.txt"',
+ 'rosdep install --default-yes --ignore-packages-from-source --from-paths "${CRAFT_PART_SRC_WORK}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . '
- '"${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ 'AMENT_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/snap" . '
- '"${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/snap" . "${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "fi",
+ 'if [ -f "/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/opt/ros/snap" . "/opt/ros/snap/local_setup.sh"',
"fi",
- '. "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "",
'eval "${state}"',
+ "## Build command",
"colcon build "
'--base-paths "${CRAFT_PART_SRC_WORK}" --build-base "${CRAFT_PART_BUILD}" '
'--merge-install --install-base "${CRAFT_PART_INSTALL}/opt/ros/snap" '
'--parallel-workers "${CRAFT_PARALLEL_BUILD_COUNT}"',
- 'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/snap/COLCON_IGNORE" ]; then',
- 'rm "${CRAFT_PART_INSTALL}/opt/ros/snap/COLCON_IGNORE"',
+ "## Post build command",
+ 'if [ -f "${CRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE ]; then',
+ 'rm "${CRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE',
"fi",
"env -i LANG=C.UTF-8 LC_ALL=C.UTF-8 /test/python3 -I "
"/test/_ros.py "
@@ -175,6 +233,7 @@ def test_get_build_commands(self, setup_method_fixture, new_dir, monkeypatch):
def test_get_build_commands_with_all_properties(
self, setup_method_fixture, new_dir, monkeypatch
):
+ # pylint: disable=line-too-long
plugin = setup_method_fixture(
new_dir,
properties={
@@ -184,6 +243,7 @@ def test_get_build_commands_with_all_properties(
"colcon-cmake-args": ["cmake", "args..."],
"colcon-packages": ["package1", "package2..."],
"colcon-packages-ignore": ["ipackage1", "ipackage2..."],
+ "colcon-ros-build-snaps": ["foo"],
},
)
@@ -206,35 +266,88 @@ def test_get_build_commands_with_all_properties(
)
assert plugin.get_build_commands() == [
+ "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
+ "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
+ 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ 'if [ -f "/snap/foo/current/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/snap/foo/current/opt/ros/${ROS_DISTRO}" . "/snap/foo/current/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "fi",
+ 'if [ -f "/snap/foo/current/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/snap/foo/current/opt/ros/snap" . "/snap/foo/current/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . '
- '"${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ 'AMENT_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/snap" . '
- '"${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/snap" . "${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
- '. "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ 'if [ -f "/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/opt/ros/snap" . "/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
'eval "${state}"',
- "if [ ! -f /etc/ros/rosdep/sources.list.d/20-default.list ]; then",
- "sudo --preserve-env=http_proxy,https_proxy rosdep init; fi",
- 'rosdep update --include-eol-distros --rosdistro "${ROS_DISTRO}"',
- "rosdep install --default-yes --ignore-packages-from-source "
- '--from-paths "${CRAFT_PART_SRC_WORK}"',
+ 'rm -f "${CRAFT_PART_INSTALL}/.installed_packages.txt"',
+ 'rm -f "${CRAFT_PART_INSTALL}/.build_snaps.txt"',
+ "if [ -d /snap/foo/current/opt/ros ]; then",
+ "ROS_PACKAGE_PATH=/snap/foo/current/opt/ros rospack list-names | (xargs "
+ 'rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | awk '
+ '"/#apt/{getline;print;}" >> '
+ '"${CRAFT_PART_INSTALL}/.installed_packages.txt"',
+ "fi",
+ 'if [ -d "/snap/foo/current/opt/ros/${ROS_DISTRO}/" ]; then',
+ 'rosdep keys --rosdistro "${ROS_DISTRO}" --from-paths '
+ '"/snap/foo/current/opt/ros/${ROS_DISTRO}" --ignore-packages-from-source | '
+ '(xargs rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" '
+ '>> "${CRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ 'if [ -d "/snap/foo/current/opt/ros/snap/" ]; then',
+ 'rosdep keys --rosdistro "${ROS_DISTRO}" --from-paths '
+ '"/snap/foo/current/opt/ros/snap" --ignore-packages-from-source | (xargs '
+ 'rosdep resolve --rosdistro "${ROS_DISTRO}" || echo "") | grep -v "#" >> '
+ '"${CRAFT_PART_INSTALL}"/.installed_packages.txt',
+ "fi",
+ "",
+ 'rosdep install --default-yes --ignore-packages-from-source --from-paths "${CRAFT_PART_SRC_WORK}"',
'state="$(set +o); set -$-"',
"set +u",
+ "",
+ "## Sourcing ROS ws in build snaps",
+ 'if [ -f "/snap/foo/current/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/snap/foo/current/opt/ros/${ROS_DISTRO}" . "/snap/foo/current/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "fi",
+ 'if [ -f "/snap/foo/current/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/snap/foo/current/opt/ros/snap" . "/snap/foo/current/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in stage snaps",
'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . '
- '"${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ 'AMENT_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}" . "${CRAFT_PART_INSTALL}/opt/ros/${ROS_DISTRO}/local_setup.sh"',
"fi",
'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh" ]; then',
- 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/snap" . '
- '"${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ 'COLCON_CURRENT_PREFIX="${CRAFT_PART_INSTALL}/opt/ros/snap" . "${CRAFT_PART_INSTALL}/opt/ros/snap/local_setup.sh"',
+ "fi",
+ "",
+ "## Sourcing ROS ws in system",
+ 'if [ -f "/opt/ros/${ROS_DISTRO}/local_setup.sh" ]; then',
+ 'AMENT_CURRENT_PREFIX="/opt/ros/${ROS_DISTRO}" . "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "fi",
+ 'if [ -f "/opt/ros/snap/local_setup.sh" ]; then',
+ 'COLCON_CURRENT_PREFIX="/opt/ros/snap" . "/opt/ros/snap/local_setup.sh"',
"fi",
- '. "/opt/ros/${ROS_DISTRO}/local_setup.sh"',
+ "",
'eval "${state}"',
+ "## Build command",
"colcon build "
'--base-paths "${CRAFT_PART_SRC_WORK}" --build-base "${CRAFT_PART_BUILD}" '
'--merge-install --install-base "${CRAFT_PART_INSTALL}/opt/ros/snap" '
@@ -242,8 +355,9 @@ def test_get_build_commands_with_all_properties(
"package2... --cmake-args cmake args... "
"--ament-cmake-args ament args... --catkin-cmake-args catkin "
'args... --parallel-workers "${CRAFT_PARALLEL_BUILD_COUNT}"',
- 'if [ -f "${CRAFT_PART_INSTALL}/opt/ros/snap/COLCON_IGNORE" ]; then',
- 'rm "${CRAFT_PART_INSTALL}/opt/ros/snap/COLCON_IGNORE"',
+ "## Post build command",
+ 'if [ -f "${CRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE ]; then',
+ 'rm "${CRAFT_PART_INSTALL}"/opt/ros/snap/COLCON_IGNORE',
"fi",
"env -i LANG=C.UTF-8 LC_ALL=C.UTF-8 PATH=/bin:/test SNAP=TESTSNAP "
"SNAP_ARCH=TESTARCH SNAP_NAME=TESTSNAPNAME SNAP_VERSION=TESTV1 "