Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
persist-credentials: false
- uses: prefix-dev/setup-pixi@d3f436a425481402e6a95a1d1fc10331c708cd9e # v0.10.2
- run: pixi run lint
- run: pixi run test
- run: pixi run build-docs

dependabot:
Expand Down
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ RoboStack Website/
.pixi
*.egg-info

# Python artifacts
__pycache__/
*.py[cod]
*.pyo
*.pyd
*.pycache
*.pytest_cache
*.mypy_cache
*.coverage
coverage.xml
htmlcov/

# astro
node_modules
dist
Expand Down
50 changes: 50 additions & 0 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,13 @@ anaconda-client = ">=1.14.1,<2"
pyyaml = ">=6,<7"
py-rattler = ">=0.25,<0.26"
niquests = ">=3.21.0,<4"
pytest = ">=9.1.1,<10"

[feature.scripts.tasks]
test = {
cmd = "pytest scripts",
description = "Run the Python regression tests",
}
check-dependabot-merge = {
cmd = "python scripts/check_dependabot_merge.py",
description = "Check whether every Dependabot update qualifies for auto-merge",
Expand Down
32 changes: 25 additions & 7 deletions scripts/copy-to-distro-specific-channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import subprocess

import niquests
from rattler import MatchSpec
from urllib3.util.retry import Retry

# Configuration
Expand Down Expand Up @@ -88,6 +89,29 @@ def get_timestamp_with_workaround_for_timestamp_in_seconds_instead_of_millisecon
return timestamp


def belongs_to_distro(pkg_data: dict, distro: str) -> bool:
"""Identify renamed ROS packages by their distro-specific mutex build pin.

A version-only mutex pin is not enough: different distros can publish the
same mutex version. Require the literal distro name in the build selector.
"""
name = pkg_data["name"]
if name.startswith(f"ros-{distro}-"):
return True
if name in ("ros-distro-mutex", "ros2-distro-mutex"):
return pkg_data["build"].split("_", 1)[0] == distro
if not name.startswith("ros2-"):
return False

for dependency in pkg_data.get("depends", []):
if dependency.split(" ", 1)[0] != "ros2-distro-mutex":
continue
build = MatchSpec(dependency).build
if build and build.split("_", 1)[0] == distro:
return True
return False


def main() -> None:
# Parse command-line arguments
parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -135,16 +159,10 @@ def main() -> None:

# Filter packages that belong to the given distro
# and are newer then the specified cutoff date
prefix = "ros-" + distro
filtered_packages = {
pkg_name: pkg_data
for pkg_name, pkg_data in source_packages.items()
# This should cover both packages that start with 'ros-<distro>'
# '(ros|ros2)-<distro>-mutex' packages whose build string contains <distro>
if (
pkg_name.startswith(prefix)
or (pkg_data["name"].endswith("distro-mutex") and distro in pkg_data["build"])
)
if belongs_to_distro(pkg_data, distro)
and (
get_timestamp_with_workaround_for_timestamp_in_seconds_instead_of_milliseconds(
pkg_data
Expand Down
55 changes: 55 additions & 0 deletions scripts/test_copy_to_distro_specific_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import runpy
from pathlib import Path

import pytest

promotion = runpy.run_path(str(Path(__file__).with_name("copy-to-distro-specific-channel.py")))
belongs_to_distro = promotion["belongs_to_distro"]


@pytest.mark.parametrize(
("dependencies", "expected"),
[
(["ros2-distro-mutex 0.10.* humble_*"], True),
(
[
"ros2-distro-mutex >=0.10.0,<0.11.0a0",
"ros2-distro-mutex 0.10.* humble_*",
],
True,
),
(["ros2-distro-mutex 0.10.* jazzy_*"], False),
(["ros2-distro-mutex >=0.10.0,<0.11.0a0"], False),
(["ros2-distro-mutex 0.10.* *"], False),
(["ros2-distro-mutex 0.10.* humble-other_*"], False),
(["ros2-rcl 16.*"], False),
([], False),
],
)
def test_renamed_package_requires_distro_build_pin(dependencies: list[str], expected: bool) -> None:
artifact = {"name": "ros2-rclcpp", "depends": dependencies}
assert belongs_to_distro(artifact, "humble") is expected


def test_legacy_packages_and_shims_remain_distro_specific() -> None:
shim = {"name": "ros-humble-rclcpp", "depends": ["ros2-rclcpp ==16.0.19"]}
assert belongs_to_distro(shim, "humble")
assert not belongs_to_distro(shim, "jazzy")
assert belongs_to_distro({"name": "ros-noetic-roscpp"}, "noetic")
assert not belongs_to_distro({"name": "ros-humble-other-rclcpp"}, "hum")


@pytest.mark.parametrize(
("name", "build", "distro", "expected"),
[
("ros2-distro-mutex", "humble_20", "humble", True),
("ros2-distro-mutex", "humble", "humble", True),
("ros2-distro-mutex", "jazzy_20", "humble", False),
("ros2-distro-mutex", "not-humble_20", "humble", False),
("ros-distro-mutex", "noetic_1", "noetic", True),
],
)
def test_mutex_build_identifies_its_distro(
name: str, build: str, distro: str, expected: bool
) -> None:
assert belongs_to_distro({"name": name, "build": build}, distro) is expected
Loading