From 7c8b364ed38e844b69beab6624b902a2fe92e829 Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Thu, 8 Oct 2026 10:17:45 +1000 Subject: [PATCH 1/2] Resolve ros_distro selectors in package tests Selectors that test ros_distro (e.g. if: ros_distro in ["humble", "jazzy"]) are resolved when a test file is put into a recipe, so one test file serves several distributions; platform selectors stay for rattler-build. Co-Authored-By: Claude Opus 5.5 --- README.md | 18 +++++++++++++++ vinca/main.py | 5 ++++- vinca/template.py | 5 ++++- vinca/test_distro_selectors.py | 32 ++++++++++++++++++++++++++ vinca/v1_selectors.py | 41 ++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 vinca/test_distro_selectors.py diff --git a/README.md b/README.md index c785d8d..c3461e1 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,24 @@ A base configuration can itself extend another one. Paths (`patch_dir`, `rosdistro_snapshot`, `rosdistro_additional_recipes`, `conda_index` files) are relative to the file that sets them. +## Package tests + +`tests/.yaml` (next to `vinca.yaml`, or next to a configuration it extends) +becomes the `tests` section of the package's recipe. Selectors that test `ros_distro` +are resolved by vinca, so one test file can serve several distributions; all other +selectors are left to rattler-build: + +```yaml +tests: + - script: + - if: ros_distro in ["humble", "jazzy"] + then: + - if: osx + then: export ROS_LOCALHOST_ONLY=1 && launch_test smoke_test.py + else: + - launch_test smoke_test.py +``` + ## Package naming The optional `package_name_mode` setting controls the transition from legacy distro-qualified names such as `ros-humble-rclcpp` to ROS-major-version names such as `ros2-rclcpp`: diff --git a/vinca/main.py b/vinca/main.py index 96d210a..a6f7914 100644 --- a/vinca/main.py +++ b/vinca/main.py @@ -51,6 +51,7 @@ source_reference, ) from .template import write_recipe, write_recipe_package +from .v1_selectors import evaluate_distro_selectors unsatisfied_deps = set() distro = None @@ -500,7 +501,9 @@ def parse_package(pkg, distro, vinca_conf, path): if test := vinca_conf.get("_tests", {}).get(final_name): # parse as yaml text = test.read_text() - test_content = ruamel.yaml.safe_load(text) + test_content = evaluate_distro_selectors( + ruamel.yaml.safe_load(text), ros_distro=config.ros_distro + ) recipe["test"] = test_content for p in pkg["authors"]: diff --git a/vinca/template.py b/vinca/template.py index 6aa5c1e..84eea86 100644 --- a/vinca/template.py +++ b/vinca/template.py @@ -16,6 +16,7 @@ get_pkg_additional_info, get_pkg_build_number, ) +from vinca.v1_selectors import evaluate_distro_selectors from vinca.variants import VariantsMode, get_recipe_variants, get_variants_mode TEMPLATE = """\ @@ -147,7 +148,9 @@ def write_recipe(source, outputs, vinca_conf, distro, single_file=True): ): print("Using test: ", test) text = test.read_text() - test_content = yaml.safe_load(text) + test_content = evaluate_distro_selectors( + yaml.safe_load(text), ros_distro=config.ros_distro + ) meta["tests"] = test_content["tests"] recipe_dir = (Path("recipes") / package_name).absolute() diff --git a/vinca/test_distro_selectors.py b/vinca/test_distro_selectors.py new file mode 100644 index 0000000..7ebc0c9 --- /dev/null +++ b/vinca/test_distro_selectors.py @@ -0,0 +1,32 @@ +from vinca.v1_selectors import evaluate_distro_selectors + +TEST = { + "tests": [ + { + "script": [ + { + "if": 'ros_distro in ["humble", "jazzy"]', + "then": [{"if": "osx", "then": "old-osx"}], + "else": [{"if": "osx", "then": "new-osx"}], + }, + {"if": 'ros_distro == "humble"', "then": ["humble-only"]}, + {"if": "linux", "then": "linux-cmd"}, + ] + } + ] +} + + +def test_distro_selectors_are_resolved_platform_ones_kept(): + humble = evaluate_distro_selectors(TEST, ros_distro="humble") + assert humble["tests"][0]["script"] == [ + {"if": "osx", "then": "old-osx"}, + "humble-only", + {"if": "linux", "then": "linux-cmd"}, + ] + + rolling = evaluate_distro_selectors(TEST, ros_distro="rolling") + assert rolling["tests"][0]["script"] == [ + {"if": "osx", "then": "new-osx"}, + {"if": "linux", "then": "linux-cmd"}, + ] diff --git a/vinca/v1_selectors.py b/vinca/v1_selectors.py index 49720c7..b9da3c8 100644 --- a/vinca/v1_selectors.py +++ b/vinca/v1_selectors.py @@ -168,3 +168,44 @@ def evaluate_selectors( ctx = _platform_flags(target_platform) return _process_node(copy.deepcopy(data), ctx) + + +def _process_distro_node(node: Any, ctx: Mapping[str, Any]) -> Any | None: + """Like _process_node, but only for selectors that mention ``ros_distro``; + other selectors (e.g. platform ones) are left for rattler-build.""" + if ( + isinstance(node, Mapping) + and "if" in node + and "then" in node + and "ros_distro" in str(node["if"]) + ): + branch = "then" if _eval_condition(str(node["if"]), ctx) else "else" + if branch in node: + return _process_distro_node(node[branch], ctx) + return None + if isinstance(node, MutableMapping): + mapping_out: dict[str, Any] = {} + for k, v in node.items(): + new = _process_distro_node(v, ctx) + if new is not None: + mapping_out[k] = new + return mapping_out + if isinstance(node, Sequence) and not isinstance(node, (str, bytes)): + sequence_out: list[Any] = [] + for item in node: + new = _process_distro_node(item, ctx) + if new is None: + continue + if isinstance(new, list): + sequence_out.extend(new) + else: + sequence_out.append(new) + return sequence_out + return node + + +def evaluate_distro_selectors(data: Any, *, ros_distro: str | None) -> Any: + """Return *data* with the selectors that test ``ros_distro`` evaluated, e.g. + ``- if: ros_distro in ["humble", "jazzy"]``, so one test file can serve several + distributions. Selectors without ``ros_distro`` are kept unchanged.""" + return _process_distro_node(copy.deepcopy(data), {"ros_distro": ros_distro}) From 102428a2727a1657b1d3ae79b8d2eef8b178ffca Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Thu, 8 Oct 2026 13:45:28 +1000 Subject: [PATCH 2/2] Read package tests as UTF-8 Co-Authored-By: Claude Opus 5.5 --- vinca/main.py | 2 +- vinca/template.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vinca/main.py b/vinca/main.py index a6f7914..dd06b2c 100644 --- a/vinca/main.py +++ b/vinca/main.py @@ -500,7 +500,7 @@ def parse_package(pkg, distro, vinca_conf, path): if test := vinca_conf.get("_tests", {}).get(final_name): # parse as yaml - text = test.read_text() + text = test.read_text(encoding="utf-8") test_content = evaluate_distro_selectors( ruamel.yaml.safe_load(text), ros_distro=config.ros_distro ) diff --git a/vinca/template.py b/vinca/template.py index 84eea86..1a13046 100644 --- a/vinca/template.py +++ b/vinca/template.py @@ -147,7 +147,7 @@ def write_recipe(source, outputs, vinca_conf, distro, single_file=True): test := vinca_conf["_tests"].get(package_name) ): print("Using test: ", test) - text = test.read_text() + text = test.read_text(encoding="utf-8") test_content = evaluate_distro_selectors( yaml.safe_load(text), ros_distro=config.ros_distro )