|
2 | 2 | # SPDX-License-Identifier: MPL-2.0 |
3 | 3 |
|
4 | 4 | import os |
| 5 | +import shutil |
5 | 6 |
|
6 | 7 | import yaml |
7 | 8 | from ament_index_python.packages import get_package_prefix, get_package_share_directory |
@@ -191,6 +192,36 @@ def _receives(destination_name): |
191 | 192 | ] |
192 | 193 |
|
193 | 194 |
|
| 195 | +def _stage_custom_config_files(params_file_path, custom_config_files): |
| 196 | + """Stage a demo's passthrough sink TOML(s) at the path `custom_config_files` names. |
| 197 | +
|
| 198 | + A `custom_config_files` entry (ADR-0003 passthrough, #471) is handed to `dc_bridge` |
| 199 | + as a plain filesystem path -- nothing installs a file there on its own. A |
| 200 | + containerized deploy solves this with a bind mount straight onto that path |
| 201 | + (deploy/robot/compose.isolated-network.yaml); a demo launched from an installed |
| 202 | + package has no such mount, so without this the referenced file is simply missing |
| 203 | + and `dc_bridge` runs without the sink (see the sim CI job this fixed: qrcodes_stdout |
| 204 | + reached every nav waypoint but read zero QR codes, because the passthrough console |
| 205 | + sink was never in place, and no `dc.*` route makes it to the Measurement pipeline |
| 206 | + without one). Recipes live in a `config/` directory `dc_demos/CMakeLists.txt` |
| 207 | + installs as a sibling of `params/`, so that sibling is where this looks for a |
| 208 | + same-named file to copy from. Never overwrites an existing destination file -- |
| 209 | + demos.md and elasticsearch.md both document hand-editing the staged copy to |
| 210 | + experiment, which a blind refresh on every launch would clobber. |
| 211 | + """ |
| 212 | + config_dir = os.path.join( |
| 213 | + os.path.dirname(os.path.dirname(os.path.abspath(params_file_path))), "config" |
| 214 | + ) |
| 215 | + for raw_path in custom_config_files: |
| 216 | + dest_path = os.path.expanduser(os.path.expandvars(raw_path)) |
| 217 | + if os.path.exists(dest_path): |
| 218 | + continue |
| 219 | + candidate = os.path.join(config_dir, os.path.basename(dest_path)) |
| 220 | + if os.path.isfile(candidate): |
| 221 | + os.makedirs(os.path.dirname(dest_path), exist_ok=True) |
| 222 | + shutil.copyfile(candidate, dest_path) |
| 223 | + |
| 224 | + |
194 | 225 | def build_bridge_and_mcap_actions(configured_params): |
195 | 226 | """Build the `dc_bridge` Node, plus `dc_mcap_writer` if the params file enables it. |
196 | 227 |
|
@@ -241,6 +272,11 @@ def _build(context, *args, **kwargs): |
241 | 272 | except OSError: |
242 | 273 | raw_params = {} |
243 | 274 |
|
| 275 | + dc_bridge_params_early = (raw_params.get("dc_bridge") or {}).get("ros__parameters") or {} |
| 276 | + _stage_custom_config_files( |
| 277 | + params_file_path, dc_bridge_params_early.get("custom_config_files", []) |
| 278 | + ) |
| 279 | + |
244 | 280 | mcap_params = (raw_params.get("dc_mcap_writer") or {}).get("ros__parameters") or {} |
245 | 281 | bridge_parameters = [configured_params] |
246 | 282 | extra_actions = [] |
|
0 commit comments