Skip to content

Commit 46d2a50

Browse files
committed
Resolve merge conflicts
2 parents 824c078 + 56445c9 commit 46d2a50

137 files changed

Lines changed: 10163 additions & 549 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ contact_links:
77
url: https://docs.ros.org/
88
about: The ROS documentation is available at docs.ros.org
99
- name: 🔗 Want to discuss a ROS feature or get the latest ROS news?
10-
url: https://discourse.ros.org/
11-
about: ROS Discourse is our community discussion forum.
10+
url: https://discourse.openrobotics.org/c/ros/111
11+
about: Open Robotics Discourse is our community discussion forum.
1212
- name: 🔗 Do you need real time support from other ROS users?
1313
url: https://openrobotics.zulipchat.com/
1414
about: Visit our community Zulip server. ROS questions belong in "ROS General" channel.

.mergify.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pull_request_rules:
66
actions:
77
backport:
88
branches:
9+
- lyrical
910
- kilted
1011
- jazzy
1112
- humble
@@ -17,10 +18,20 @@ pull_request_rules:
1718
actions:
1819
backport:
1920
branches:
21+
- lyrical
2022
- kilted
2123
- jazzy
2224
- humble
2325

26+
- name: backport to lyrical at reviewers discretion
27+
conditions:
28+
- base=rolling
29+
- "label=backport-lyrical"
30+
actions:
31+
backport:
32+
branches:
33+
- lyrical
34+
2435
- name: backport to kilted at reviewers discretion
2536
conditions:
2637
- base=rolling

conf.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
# Intersphinx mapping
9595

9696
intersphinx_mapping = {
97+
'python': ('https://docs.python.org/3', None),
9798
'catkin_pkg': ('http://docs.ros.org/en/independent/api/catkin_pkg/html', None),
9899
'jenkins_tools': ('http://docs.ros.org/en/independent/api/jenkins_tools/html', None),
99100
'rosdep': ('http://docs.ros.org/en/independent/api/rosdep/html', None),
@@ -139,7 +140,7 @@
139140

140141
# smv_tag_whitelist = None
141142

142-
smv_branch_whitelist = r'^(rolling|kilted|jazzy|iron|humble|galactic|foxy|eloquent|dashing|crystal)$'
143+
smv_branch_whitelist = r'^(rolling|lyrical|kilted|jazzy|iron|humble|galactic|foxy|eloquent|dashing|crystal)$'
143144

144145

145146
smv_released_pattern = r'^refs/(heads|remotes/[^/]+)/(kilted|jazzy|iron|humble|galactic|foxy|eloquent|dashing|crystal).*$'
@@ -157,14 +158,39 @@
157158
'iron': 'Iron Irwini',
158159
'jazzy': 'Jazzy Jalisco',
159160
'kilted': 'Kilted Kaiju',
161+
'lyrical': 'Lyrical Luth',
160162
'rolling': 'Rolling Ridley',
161163
}
162164

165+
# Tier 1 Ubuntu platform for binary deb installs (see the release page for each distro)
166+
distro_ubuntu_deb_platform = {
167+
'crystal': 'Ubuntu Bionic (18.04)',
168+
'dashing': 'Ubuntu Bionic (18.04)',
169+
'eloquent': 'Ubuntu Bionic (18.04)',
170+
'foxy': 'Ubuntu Focal (20.04)',
171+
'galactic': 'Ubuntu Focal (20.04)',
172+
'humble': 'Ubuntu Jammy (22.04)',
173+
'iron': 'Ubuntu Jammy (22.04)',
174+
'jazzy': 'Ubuntu Noble (24.04)',
175+
'kilted': 'Ubuntu Noble (24.04)',
176+
'lyrical': 'Ubuntu Resolute Raccoon (26.04)',
177+
'rolling': 'Ubuntu Resolute Raccoon (26.04)',
178+
}
179+
180+
# ARM64 Ubuntu status page suffix on repo.ros2.org (ros_{distro}_{suffix}.html)
181+
distro_arm_status_suffix = {
182+
'humble': 'ujv8',
183+
'iron': 'ujv8',
184+
'lyrical': 'armv8',
185+
}
186+
163187
# These default values will be overridden when building multiversion
164188
macros = {
165189
'DISTRO': 'rolling',
166190
'DISTRO_TITLE': 'Rolling',
167191
'DISTRO_TITLE_FULL': 'Rolling Ridley',
192+
'DISTRO_UBUNTU_DEB_PLATFORM': distro_ubuntu_deb_platform['rolling'],
193+
'DISTRO_ARM_STATUS_SUFFIX': distro_arm_status_suffix.get('rolling', 'unv8'),
168194
'REPOS_FILE_BRANCH': 'rolling',
169195
}
170196

@@ -318,6 +344,10 @@ def smv_rewrite_configs(app, config):
318344
'DISTRO': distro,
319345
'DISTRO_TITLE': distro.title(),
320346
'DISTRO_TITLE_FULL': distro_full_names[distro],
347+
'DISTRO_UBUNTU_DEB_PLATFORM': distro_ubuntu_deb_platform.get(
348+
distro, 'Ubuntu Noble (24.04)'
349+
),
350+
'DISTRO_ARM_STATUS_SUFFIX': distro_arm_status_suffix.get(distro, 'unv8'),
321351
'REPOS_FILE_BRANCH' : distro,
322352
}
323353

plugins/adopters_schema.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@
2222
"""
2323

2424
import re
25+
import time
26+
import urllib.request
27+
import urllib.error
2528

2629
VALID_DOMAINS = [
2730
'Agriculture', # Farming, harvesting, crop monitoring, and precision agriculture
31+
'Aviation', # Aircraft systems, avionics, luggage handling, passenger service, and airport operations
2832
'Aerial/Drone', # UAVs, drones, aerial inspection, and survey systems
2933
'Automotive', # Self-driving cars, ADAS, and ground vehicle autonomy
3034
'Components', # Robot parts and peripherals (cameras, LIDAR, RADAR, SONAR, etc)
@@ -100,3 +104,85 @@ def validate_adopters(adopters):
100104
f'ISO 3166-1 alpha-2 code, got "{code}"'
101105
)
102106
return errors
107+
108+
109+
def validate_adopter_urls(adopters, timeout=10):
110+
"""Check URL availability for adopter entries. Returns list of warning strings.
111+
112+
Only flags URLs that are genuinely unreachable (connection failures
113+
or server-down HTTP status codes). Any HTTP response — even an
114+
error like 403 — proves the host is alive.
115+
"""
116+
warnings = []
117+
if not isinstance(adopters, list):
118+
return ["'adopters' must be a list"]
119+
120+
seen_urls = {} # cache results to avoid duplicate requests
121+
122+
for i, entry in enumerate(adopters):
123+
if not isinstance(entry, dict):
124+
continue
125+
prefix = (
126+
f'Entry {i + 1} '
127+
f'({entry.get("organization", "unknown")}/{entry.get("project", "unknown")})'
128+
)
129+
for url_field in ('organization_url', 'project_url'):
130+
url = entry.get(url_field)
131+
if not url:
132+
continue
133+
134+
# Use cached result if we already checked this URL.
135+
if url in seen_urls:
136+
if seen_urls[url] is not None:
137+
warnings.append(f'{prefix}: "{url_field}" {seen_urls[url]}')
138+
continue
139+
140+
error_msg = _check_url(url, timeout)
141+
seen_urls[url] = error_msg
142+
if error_msg:
143+
warnings.append(f'{prefix}: "{url_field}" {error_msg}')
144+
return warnings
145+
146+
147+
def _check_url(url, timeout, retries=3, backoff_factor=2):
148+
"""Return an error message string if the URL is unreachable, else None.
149+
150+
Only flags URLs that are genuinely dead — connection failures and
151+
HTTP status codes that explicitly indicate the server or origin is
152+
down. Any other HTTP response (even 403, 429, etc.) proves the host
153+
is alive, so those are treated as reachable. SSL/TLS errors also
154+
indicate the host responded at the TCP level, so those are not flagged.
155+
156+
Retries transient failures (timeouts, network errors, 5xx codes) up to
157+
``retries`` times with exponential backoff to avoid false alarms from
158+
flaky network conditions.
159+
"""
160+
# HTTP codes that mean the server/origin is down or unreachable.
161+
_UNREACHABLE_CODES = (502, 503, 504, 521, 522, 523, 525, 530)
162+
163+
last_error = None
164+
for attempt in range(retries):
165+
if attempt > 0:
166+
time.sleep(backoff_factor ** attempt)
167+
try:
168+
req = urllib.request.Request(url, method='HEAD')
169+
with urllib.request.urlopen(req, timeout=timeout) as resp:
170+
if resp.status in _UNREACHABLE_CODES:
171+
last_error = f'URL returned HTTP {resp.status}: {url}'
172+
continue
173+
return None
174+
except urllib.error.HTTPError as e:
175+
if e.code in _UNREACHABLE_CODES:
176+
last_error = f'URL returned HTTP {e.code}: {url}'
177+
continue
178+
return None # any other HTTP response means the server is alive
179+
except (urllib.error.URLError, OSError) as e:
180+
# SSL/TLS errors mean the TCP connection succeeded — host is alive.
181+
err_str = str(e)
182+
if 'SSL' in err_str or 'CERTIFICATE' in err_str:
183+
return None
184+
last_error = f'URL is not reachable: {url} ({e})'
185+
continue
186+
except ValueError as e:
187+
return f'invalid URL: {url} ({e})'
188+
return last_error

plugins/sphinx_adopters.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,26 @@
1414

1515
"""Sphinx extension to render and validate the ROS 2 adopters YAML file. """
1616

17+
import logging
1718
import os
1819

1920
import yaml
2021
from docutils import nodes
2122
from docutils.parsers.rst import Directive
2223
from sphinx.errors import ExtensionError
2324

24-
from adopters_schema import validate_adopters
25+
from adopters_schema import validate_adopters, validate_adopter_urls
26+
27+
# Use the stdlib logger (not sphinx.util.logging) so URL reachability
28+
# warnings are not promoted to errors when Sphinx is invoked with ``-W``
29+
# (warnings-as-errors). External URL availability is inherently flaky
30+
# and must not fail the CI workflow on transient outages.
31+
logger = logging.getLogger(__name__)
32+
if not logger.handlers:
33+
_handler = logging.StreamHandler()
34+
_handler.setFormatter(logging.Formatter('%(levelname)s: %(name)s: %(message)s'))
35+
logger.addHandler(_handler)
36+
logger.setLevel(logging.WARNING)
2537

2638

2739
def _escape(text):
@@ -58,7 +70,7 @@ def run(self):
5870
env = self.state.document.settings.env
5971
# Locate adopters.yaml relative to the source file containing the directive.
6072
source_dir = os.path.dirname(env.doc2path(env.docname))
61-
yaml_path = os.path.join(source_dir, 'adopters.yaml')
73+
yaml_path = os.path.join(source_dir, 'Adopters', 'adopters.yaml')
6274

6375
if not os.path.isfile(yaml_path):
6476
raise ExtensionError(
@@ -79,9 +91,16 @@ def run(self):
7991
'Adopters YAML validation failed:\n' + '\n'.join(f' - {e}' for e in errors)
8092
)
8193

82-
# Sort by date_added descending (newest first), then organization name (A-Z).
83-
adopters.sort(key=lambda a: a.get('organization', '').lower())
94+
# URL reachability is best-effort: external sites can be transiently
95+
# unavailable, so surface failures as warnings rather than failing the
96+
# build (and the CI workflow) on flaky network conditions.
97+
url_warnings = validate_adopter_urls(adopters)
98+
for w in url_warnings:
99+
logger.warning('Adopters YAML URL check: %s', w)
100+
101+
# Sort by organization name (A-Z), then date_added descending (newest first).
84102
adopters.sort(key=lambda a: a.get('date_added', ''), reverse=True)
103+
adopters.sort(key=lambda a: a.get('organization', '').lower())
85104

86105
# Collect unique values for filters.
87106
all_domains = sorted({d for a in adopters for d in a.get('domain', [])})

0 commit comments

Comments
 (0)