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 "