diff --git a/muto_composer/plugins/launch_plugin.py b/muto_composer/plugins/launch_plugin.py index 55ccccb..8687f73 100644 --- a/muto_composer/plugins/launch_plugin.py +++ b/muto_composer/plugins/launch_plugin.py @@ -126,8 +126,12 @@ def source_workspaces(self, current: StackManifest): self.get_logger().error("Failed to parse source data as JSON.") return - # Get stack name - check metadata.name first, then name, then default - stack_name = self._get_stack_name(current) + # Get stack name - handle both dict and StackManifest msg objects + if isinstance(current, dict): + stack_name = self._get_stack_name(current) + else: + # StackManifest msg: use .name attribute directly + stack_name = getattr(current, "name", None) or "default" workspace_dir = os.path.join(WORKSPACES_PATH, stack_name.replace(" ", "_")) def source_script(name: str, script_path: str) -> None: @@ -390,8 +394,14 @@ def _launch_via_ros2_launch(self, context: StackContext, full_path: str, launch_ launch_file: Original launch file name (for tracking) """ # Source the workspace before launching - working_dir = os.path.dirname(os.path.dirname(full_path)) - setup_bash = os.path.join(working_dir, "install", "setup.bash") + # Use context.workspace_path (the actual colcon workspace root) rather than + # deriving from the launch file path, which breaks for nested package structures. + workspace_dir = ( + context.workspace_path + if context and context.workspace_path + else os.path.dirname(os.path.dirname(full_path)) + ) + setup_bash = os.path.join(workspace_dir, "install", "setup.bash") if os.path.exists(setup_bash): self._source_workspace(setup_bash) diff --git a/test/test_launch_plugin.py b/test/test_launch_plugin.py index 13c5a99..236fc1a 100644 --- a/test/test_launch_plugin.py +++ b/test/test_launch_plugin.py @@ -160,6 +160,7 @@ def test_source_workspaces_no_current_stack(self, mock_environ_update, mock_subp @patch("os.environ.update") def test_source_workspace(self, mock_environ_update, mock_subprocess_run, mock_get_path): mock_current = MagicMock() + mock_current.name = "Test Stack" mock_current.source = json.dumps({"workspace_name": "/mock/file"}) # Mock the _get_stack_name method with patch.object(self.node, "_get_stack_name", return_value="Test Stack"), patch(