From 120d967b8e9e5479d4b9f12bda47854a795c2d5a Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Tue, 13 Oct 2026 16:11:58 +1000 Subject: [PATCH] Skip-existing: only count packages built for this distribution ROS 2 packages share their names across distributions (ros2-*), and the skip check matched name and build number only, so a channel holding several distributions (e.g. one test upload channel) made lyrical skip plotjuggler because rolling's build 30 was there. A record counts only if its mutex dependency (or, for the mutex, its build string) names the distribution; records without a mutex dependency as before. Co-Authored-By: Claude Opus 5.5 --- vinca/generate_gha.py | 2 ++ vinca/main.py | 5 ++++- vinca/test_skip_existing.py | 29 +++++++++++++++++++++++++++++ vinca/utils.py | 16 ++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 vinca/test_skip_existing.py diff --git a/vinca/generate_gha.py b/vinca/generate_gha.py index b0d3fe1..e8e64c0 100644 --- a/vinca/generate_gha.py +++ b/vinca/generate_gha.py @@ -18,6 +18,7 @@ NoAliasDumper, add_test_requirements, build_requirement_graph, + built_for_distro, extract_dependency_names, ) from vinca.utils import literal_unicode as lu @@ -119,6 +120,7 @@ def add_additional_recipes(args): pkg["name"] == name and pkg["version"] == version and pkg["build_number"] == bnumber + and built_for_distro(pkg, vinca_conf["ros_distro"]) ): skip = True print(f"{name}=={version}=={bnumber} already exists. Skipping.") diff --git a/vinca/main.py b/vinca/main.py index 96d210a..ae4997c 100644 --- a/vinca/main.py +++ b/vinca/main.py @@ -12,7 +12,7 @@ import ruamel.yaml from vinca import __version__, config -from vinca.utils import get_pkg_build_number, get_repodata +from vinca.utils import built_for_distro, get_pkg_build_number, get_repodata from .configuration import read_snapshot as read_snapshot from .configuration import read_vinca_yaml as load_vinca_yaml @@ -751,6 +751,9 @@ def main(): all_pkgs.update(repodata.get("packages.conda", {})) for _, pkg in all_pkgs.items(): is_built = False + if not built_for_distro(pkg, vinca_conf["ros_distro"]): + # e.g. a channel shared by several distributions + continue if selected_bn is not None: pkg_build_number = expected_build_number( selected_bn, pkg["name"], vinca_conf diff --git a/vinca/test_skip_existing.py b/vinca/test_skip_existing.py new file mode 100644 index 0000000..9c2f402 --- /dev/null +++ b/vinca/test_skip_existing.py @@ -0,0 +1,29 @@ +from vinca.utils import built_for_distro + + +def test_built_for_distro_by_mutex_dependency(): + lyrical = { + "name": "ros2-plotjuggler", + "build": "np2py314h1_30", + "depends": ["python", "ros2-distro-mutex 0.22.* lyrical_*"], + } + rolling = { + "name": "ros2-plotjuggler", + "build": "np2py314h2_30", + "depends": ["python", "ros2-distro-mutex 0.22.* rolling_*"], + } + assert built_for_distro(lyrical, "lyrical") + assert not built_for_distro(rolling, "lyrical") + + +def test_built_for_distro_mutex_and_unmarked_packages(): + assert built_for_distro( + {"name": "ros2-distro-mutex", "build": "lyrical_28"}, "lyrical" + ) + assert not built_for_distro( + {"name": "ros2-distro-mutex", "build": "rolling_28"}, "lyrical" + ) + # no mutex dependency (e.g. older or non-ROS records): as before + assert built_for_distro( + {"name": "ros-lyrical-foo", "build": "h0_1", "depends": ["ros2-foo"]}, "lyrical" + ) diff --git a/vinca/utils.py b/vinca/utils.py index 740b5ac..49a4be4 100644 --- a/vinca/utils.py +++ b/vinca/utils.py @@ -251,3 +251,19 @@ def is_dummy_metapackage(pkg_name, vinca_conf): return True else: return False + + +def built_for_distro(pkg: dict, ros_distro: str) -> bool: + """Whether a repodata record was built for *ros_distro*: ROS 2 packages share their + names across distributions (ros2-*), so a channel holding several of them must not + make one distribution skip a package because another one has it. The distribution + shows in the mutex dependency (``ros2-distro-mutex 0.22.* lyrical_*``) or, for the + mutex itself, in its build string; records without either are kept.""" + if pkg.get("name", "").endswith("distro-mutex"): + return pkg.get("build", "").startswith(f"{ros_distro}_") + mutexes = [ + d for d in pkg.get("depends", []) if d.split(" ")[0].endswith("distro-mutex") + ] + if not mutexes: + return True + return any(f" {ros_distro}_" in d or d.endswith(f" {ros_distro}") for d in mutexes)